diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index af3ad1281..000000000 --- a/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -/.yarn/** linguist-vendored -/.yarn/releases/* binary -/.yarn/plugins/**/* binary -/.pnp.* binary linguist-generated diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8848899f4..b812f6b7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,3 +40,48 @@ jobs: - run: pnpm test - run: pnpm check-hygiene + + production-consumer-smoke: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Check smoke API key + id: smoke-key + env: + STAKEKIT_SMOKE_API_KEY: ${{ secrets.STAKEKIT_SMOKE_API_KEY }} + run: | + if [ -n "$STAKEKIT_SMOKE_API_KEY" ]; then + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping production-consumer smoke test because STAKEKIT_SMOKE_API_KEY is unavailable." + fi + + # actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + if: steps.smoke-key.outputs.available == 'true' + + - name: Install mise + if: steps.smoke-key.outputs.available == 'true' + run: | + curl https://mise.run | MISE_VERSION=v2026.5.6 sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" + export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH" + mise install + + - if: steps.smoke-key.outputs.available == 'true' + run: pnpm install --frozen-lockfile + + - name: Install Chromium + if: steps.smoke-key.outputs.available == 'true' + run: pnpm -F @stakekit/widget exec playwright install --with-deps chromium + + - name: Run production-consumer smoke test + if: steps.smoke-key.outputs.available == 'true' + env: + VITE_API_KEY: ${{ secrets.STAKEKIT_SMOKE_API_KEY }} + NEXT_PUBLIC_API_KEY: ${{ secrets.STAKEKIT_SMOKE_API_KEY }} + run: pnpm test:smoke diff --git a/.gitignore b/.gitignore index 7eec523be..33b628965 100644 --- a/.gitignore +++ b/.gitignore @@ -1,268 +1,47 @@ -# production -/build - -# misc -.DS_Store -*.pem -.idea - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# local env files -.env*.local +# Dependencies and package-manager state +node_modules/ +.pnpm-store/ +*.tgz -# typescript +# Build output and caches +build/ +dist/ +.next/ +out/ +.turbo/ +.cache/ +coverage/ +.nyc_output/ +playwright-report/ +test-results/ +*.lcov *.tsbuildinfo next-env.d.ts +# Environment and local configuration +.env +.env.* +!.env.example +*.local -# compiled output -lib -**/lib - -# OS -.DS_Store - -# Tests -coverage -**/coverage -.nyc_output - -.data -ormconfig.json - -## Terraform -**/.terraform/**/ - -.terraform.lock.hcl -.terraform -# .tfstate files -*.tfstate -*.tfstate.* -*.tfvars - -# Logs -logs +# Logs and diagnostics +logs/ *.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) +*-debug.log* report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -*.lcov - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -# Swap the comments on the following lines if you don't wish to use zero-installs -# Documentation here: https://yarnpkg.com/features/zero-installs -!.yarn/cache -#.pnp.* - -# Yarn Not-Zero-Installs -# https://yarnpkg.com/getting-started/qa/\#which-files-should-be-gitignored -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions -.turbo - +# Editors and operating systems .DS_Store - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files +.idea/ .vscode/* !.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln +!.vscode/settings.json *.sw? - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - +# Credentials and certificates *.key *.pem *.p12 *.pfx *.crt -*.cer - -.pnpm-store \ No newline at end of file +*.cer \ No newline at end of file diff --git a/.repos/effect/.agents/AGENTS.md b/.repos/effect/.agents/AGENTS.md new file mode 100644 index 000000000..c7bb67c03 --- /dev/null +++ b/.repos/effect/.agents/AGENTS.md @@ -0,0 +1,163 @@ +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 --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 lint` | +| JSDoc example changes | `pnpm lint`; root `pnpm doctest --run ` | +| Docs-only changes | `pnpm lint-fix`; no tests required unless examples or code changed | + +Never run the whole test suite. A bare `pnpm test` or `pnpm doctest` runs every package in watch mode and will not +exit; always pass `--run` and the specific test files covering your change. CI runs the full suite +on push, so leave that to CI. + +## 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. + +- Run only the tests covering the files you changed. +- From the repository root, run an affected package with `pnpm --filter effect test --run` only when package-wide coverage is necessary. +- Prefer a single test file, using a path relative to the package: `pnpm --filter effect test --run test/Option.test.ts`. + Replace the package name and test path with those covering your changed files, and narrow further with `-t ""` when useful. +- Test files are located in `packages/*/test/`. +- Main Effect library tests are in `packages/effect/test/`. +- Use `it.effect` for Effect-returning tests. +- `it.effect` and `it.live` already provide and close a `Scope` for each test; do not wrap test bodies in `Effect.scoped`. +- 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 categories and example best practices, read `.patterns/jsdoc.md`. +- Mark runnable TypeScript examples with `````ts import.meta.vitest``. Leave examples that register Vitest tests or suites + as plain `````ts`` fences because the doctest collector executes runnable snippets inside tests; invoke registration + APIs directly to show their intended top-level usage. +- Prefer direct trailing value assertions such as `operation() // => Option.some(1)`. Keep bindings only for reuse or meaningful multi-step setup, separate later assertion blocks with a blank line, use dense expected arrays such as `[1, 2]`, and keep a call on one line when the complete line is at most 120 characters. +- Assert semantic values rather than console formatting. Preserve `import.meta.vitest` on type-level examples without adding tautological runtime assertions. +- Keep marked examples self-contained, deterministic, bounded, and free of external-service dependencies. Await asynchronous work. +- Run `pnpm doctest --run ` from the repository root to execute changed examples. + +## 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/.repos/effect/.agents/skills/jsdocs/SKILL.md b/.repos/effect/.agents/skills/jsdocs/SKILL.md new file mode 100644 index 000000000..a2972d8d8 --- /dev/null +++ b/.repos/effect/.agents/skills/jsdocs/SKILL.md @@ -0,0 +1,260 @@ +--- +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. +--- + +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 import.meta.vitest + * 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. + +## Example quality + +Examples are optional. They should demonstrate: + +- behavior or constraints that are not clear from the signature; +- meaningful composition with other public APIs; +- a realistic use case supported by repository tests or call sites; or +- useful type inference, narrowing, or overload behavior. + +A good example: + +- focuses on the documented API and includes only the context needed to + understand it; +- is a complete, self-contained TypeScript module without placeholders or + omitted setup; +- imports public APIs rather than internal modules or unrelated test helpers; +- uses stable, deterministic, bounded behavior and does not require network + access, external services, timing assumptions, randomness, or machine-specific + state; +- demonstrates the meaningful result, with a concise expected-value comment + when useful; and +- uses explanatory prose only when the code cannot communicate an important + choice or caveat on its own. + +### Executable examples + +- Mark runnable TypeScript fences with `import.meta.vitest`. Run changed examples from the repository root with `pnpm doctest --run `. +- Write each marked example as a complete isolated module. Import public APIs, define every runtime value, await asynchronous work, and keep execution deterministic and bounded. +- Prefer `operation() // => expected` over introducing a result binding used only by the assertion. Retain bindings for reuse, mutation, identity checks, or meaningful multi-step setup, and insert a blank line before a separate assertion block. +- Keep direct assertions on one line up to 120 characters. Use dense expected arrays such as `[1, 2]` and semantic Effect values such as `Option.some(1)` rather than console formatting. +- Preserve `import.meta.vitest` for type-level examples, but do not add tautological runtime assertions to them. +- Leave examples that register Vitest tests or suites as plain `````ts`` fences because the doctest collector executes + runnable snippets inside tests. Call the registration API directly to show its intended top-level usage. +- Keep documentation-only snippets as plain `````ts`` fences. + +When reviewing existing examples: + +1. Derive the example's use case and behavior from repository evidence. Inspect + the declaration, implementation, tests, call sites, and related APIs. Do not + invent a scenario merely to retain an example. +2. Keep a correct, clear, high-value example without gratuitous rewriting. +3. Fix or replace an example when repository evidence supports a concise, + valuable version. +4. Remove an example when it is trivial, misleading, contrived, or requires more + scaffolding than the insight justifies. Also remove it when a good replacement + would require guessing at a use case. + +Prefer concise trailing `// =>` assertions that keep the meaningful result visible; +public documentation should not look like a test suite. Type-level examples may demonstrate inference or assignability +without runtime assertions. For lazy APIs such as `Effect`, execute enough of the +program to demonstrate the behavior unless the example's value is specifically +type-level or construction-oriented. + +If an example review exposes a likely implementation or type-definition bug, +do not change runtime or API code as part of the documentation pass. Report the +finding and do not present the suspected behavior as recommended usage. + +## 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 runnable JSDoc example changes, run `pnpm doctest --run ` from the repository root. +- 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. diff --git a/.repos/effect/.agents/skills/scratchpad/SKILL.md b/.repos/effect/.agents/skills/scratchpad/SKILL.md new file mode 100644 index 000000000..b42bdc88f --- /dev/null +++ b/.repos/effect/.agents/skills/scratchpad/SKILL.md @@ -0,0 +1,44 @@ +--- +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/.repos/effect/.agents/skills/scratchpad/agents/openai.yaml b/.repos/effect/.agents/skills/scratchpad/agents/openai.yaml new file mode 100644 index 000000000..f83a2a4b0 --- /dev/null +++ b/.repos/effect/.agents/skills/scratchpad/agents/openai.yaml @@ -0,0 +1,7 @@ +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/.repos/effect/.agents/skills/scratchpad/scripts/extract-example.mjs b/.repos/effect/.agents/skills/scratchpad/scripts/extract-example.mjs new file mode 100644 index 000000000..e66812292 --- /dev/null +++ b/.repos/effect/.agents/skills/scratchpad/scripts/extract-example.mjs @@ -0,0 +1,260 @@ +#!/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/.repos/effect/.changeset/config.json b/.repos/effect/.changeset/config.json new file mode 100644 index 000000000..0a755e5f0 --- /dev/null +++ b/.repos/effect/.changeset/config.json @@ -0,0 +1,53 @@ +{ + "$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", + "updateInternalDependencies": "patch", + "ignore": [ + "scratchpad", + "scripts" + ], + "privatePackages": false, + "fixed": [ + [ + "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": { + "useCalculatedVersion": false, + "prereleaseTemplate": "{tag}-{commit}" + } +} diff --git a/.repos/effect/.changeset/pre.json b/.repos/effect/.changeset/pre.json new file mode 100644 index 000000000..d1deaa4cf --- /dev/null +++ b/.repos/effect/.changeset/pre.json @@ -0,0 +1,4 @@ +{ + "mode": "pre", + "tag": "rc" +} diff --git a/.repos/effect/.changeset/pre/add-adaptive-rate-limiter-store.md b/.repos/effect/.changeset/pre/add-adaptive-rate-limiter-store.md new file mode 100644 index 000000000..c191add81 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-adaptive-rate-limiter-store.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add adaptive consume and feedback operations to the unstable persistent RateLimiterStore API, including in-memory and Redis-backed bounded cooldown, learning, learned pacing, and expiry behavior for 429 Retry-After feedback. diff --git a/.repos/effect/.changeset/pre/add-atom-equality.md b/.repos/effect/.changeset/pre/add-atom-equality.md new file mode 100644 index 000000000..c2600bfa5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-bigdecimal-sumall-multiplyall.md b/.repos/effect/.changeset/pre/add-bigdecimal-sumall-multiplyall.md new file mode 100644 index 000000000..3d3a69563 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-bigdecimal-sumall-multiplyall.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Added `BigDecimal.sumAll` and `BigDecimal.multiplyAll` for feature parity with `Number` and `BigInt`, closes #1880. diff --git a/.repos/effect/.changeset/pre/add-chunk-schema.md b/.repos/effect/.changeset/pre/add-chunk-schema.md new file mode 100644 index 000000000..890e0fcbf --- /dev/null +++ b/.repos/effect/.changeset/pre/add-chunk-schema.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `Chunk` schema, closes #1585. diff --git a/.repos/effect/.changeset/pre/add-command-hidden.md b/.repos/effect/.changeset/pre/add-command-hidden.md new file mode 100644 index 000000000..34754c51b --- /dev/null +++ b/.repos/effect/.changeset/pre/add-command-hidden.md @@ -0,0 +1,19 @@ +--- +"effect": patch +--- + +Add `Command.withHidden` to hide subcommands from `--help` output, shell completions, and "did you mean?" suggestions, while keeping them fully invocable by exact name. + +Useful for experimental or internal subcommands that should be accepted but not advertised on the public CLI surface. + +```ts +import { Command } from "effect/unstable/cli" + +const experimental = Command.make("experimental").pipe( + Command.withHidden +) + +const root = Command.make("mycli").pipe( + Command.withSubcommands([experimental]) +) +``` diff --git a/.repos/effect/.changeset/pre/add-config-nested.md b/.repos/effect/.changeset/pre/add-config-nested.md new file mode 100644 index 000000000..22f53da81 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-config-nested.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Config.nested` combinator to scope a config under a named prefix, closes #1437. diff --git a/.repos/effect/.changeset/pre/add-deno-file-system.md b/.repos/effect/.changeset/pre/add-deno-file-system.md new file mode 100644 index 000000000..6bd6a73df --- /dev/null +++ b/.repos/effect/.changeset/pre/add-deno-file-system.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a Deno-backed FileSystem layer. diff --git a/.repos/effect/.changeset/pre/add-deno-http-client.md b/.repos/effect/.changeset/pre/add-deno-http-client.md new file mode 100644 index 000000000..2bf5753f8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-deno-multipart.md b/.repos/effect/.changeset/pre/add-deno-multipart.md new file mode 100644 index 000000000..b396bfd8c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-deno-socket-server.md b/.repos/effect/.changeset/pre/add-deno-socket-server.md new file mode 100644 index 000000000..d72662428 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-deno-socket.md b/.repos/effect/.changeset/pre/add-deno-socket.md new file mode 100644 index 000000000..da98c6486 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-flag-hidden.md b/.repos/effect/.changeset/pre/add-flag-hidden.md new file mode 100644 index 000000000..1c31e6cd8 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-flag-hidden.md @@ -0,0 +1,15 @@ +--- +"effect": patch +--- + +Add `Flag.withHidden` (and `Param.withHidden`) to hide flags from `--help` output and shell completions while keeping them fully parseable on the command line. + +Useful for experimental, internal, or deprecated flags that should be accepted but not advertised, e.g. `--experimental-foo`, debug toggles, or escape hatches that are not yet committed to the public CLI surface. + +```ts +import { Flag } from "effect/unstable/cli" + +const experimental = Flag.boolean("experimental-foo").pipe( + Flag.withHidden +) +``` diff --git a/.repos/effect/.changeset/pre/add-from-string-schemas.md b/.repos/effect/.changeset/pre/add-from-string-schemas.md new file mode 100644 index 000000000..5ee9f82fb --- /dev/null +++ b/.repos/effect/.changeset/pre/add-from-string-schemas.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `DateFromString`, `BigIntFromString`, `BigDecimalFromString`, `TimeZoneNamedFromString`, `TimeZoneFromString`, and `DateTimeZonedFromString` schemas, closes #1941. diff --git a/.repos/effect/.changeset/pre/add-headers-remove-many.md b/.repos/effect/.changeset/pre/add-headers-remove-many.md new file mode 100644 index 000000000..8d09bb516 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-headers-remove-many.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +unstable/http Headers: add `removeMany` combinator for removing multiple headers at once diff --git a/.repos/effect/.changeset/pre/add-http-client-request-update-headers.md b/.repos/effect/.changeset/pre/add-http-client-request-update-headers.md new file mode 100644 index 000000000..391894aa6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-http-client-tracer-header-filter.md b/.repos/effect/.changeset/pre/add-http-client-tracer-header-filter.md new file mode 100644 index 000000000..44d804467 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-httpapi-with-headers.md b/.repos/effect/.changeset/pre/add-httpapi-with-headers.md new file mode 100644 index 000000000..e91cccacc --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-indexeddb-kvs-layer.md b/.repos/effect/.changeset/pre/add-indexeddb-kvs-layer.md new file mode 100644 index 000000000..bb6671bc7 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-indexeddb-kvs-layer.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Adds an IndexedDB backed implementation of `KeyValueStore` as `BrowserKeyValueStore.layerIndexedDb`. This backend allows for non-blocking `KeyValueStore` operations, unlike the existing `Storage` api backed implementations. diff --git a/.repos/effect/.changeset/pre/add-json-schema-draft-04.md b/.repos/effect/.changeset/pre/add-json-schema-draft-04.md new file mode 100644 index 000000000..440b16628 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-make-msgpack.md b/.repos/effect/.changeset/pre/add-make-msgpack.md new file mode 100644 index 000000000..9abec1e9e --- /dev/null +++ b/.repos/effect/.changeset/pre/add-make-msgpack.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `RpcSerialization.makeMsgPack` for creating MessagePack serialization with custom msgpackr options. On Cloudflare Workers with `allow_eval_during_startup` (default for `compatibility_date >= 2025-06-01`), pass `{ useRecords: false }` to prevent msgpackr's JIT code generation via `new Function()`, which is blocked during request handling. Also fixes silent error swallowing in the `msgPack` decode path — non-incomplete errors are now rethrown instead of returning `[]`. diff --git a/.repos/effect/.changeset/pre/add-make-option.md b/.repos/effect/.changeset/pre/add-make-option.md new file mode 100644 index 000000000..4984b6319 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-make-option.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `SchemaParser.makeOption` and `Schema.makeOption` for constructing schema values as `Option`. diff --git a/.repos/effect/.changeset/pre/add-mcp-2025-11-25-protocol.md b/.repos/effect/.changeset/pre/add-mcp-2025-11-25-protocol.md new file mode 100644 index 000000000..fbc935c0b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-mcp-icons.md b/.repos/effect/.changeset/pre/add-mcp-icons.md new file mode 100644 index 000000000..8e2383a62 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-missing-tx-modules.md b/.repos/effect/.changeset/pre/add-missing-tx-modules.md new file mode 100644 index 000000000..aa7cc305c --- /dev/null +++ b/.repos/effect/.changeset/pre/add-missing-tx-modules.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Add transactional STM modules: TxDeferred, TxPriorityQueue, TxPubSub, TxReentrantLock, TxSubscriptionRef. + +Refactor transaction model: remove `Effect.atomic`/`Effect.atomicWith`. All Tx operations now return `Effect` requiring explicit `Effect.tx(...)` at boundaries. + +Expose `TxPubSub.acquireSubscriber`/`releaseSubscriber` for composable transaction boundaries. Fix `TxSubscriptionRef.changes` race condition ensuring current value is delivered first. + +Remove `TxRandom` module. diff --git a/.repos/effect/.changeset/pre/add-newtype-module.md b/.repos/effect/.changeset/pre/add-newtype-module.md new file mode 100644 index 000000000..c407aeb54 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-newtype-module.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Newtype` module. diff --git a/.repos/effect/.changeset/pre/add-otlp-manual-flush.md b/.repos/effect/.changeset/pre/add-otlp-manual-flush.md new file mode 100644 index 000000000..fbbd9b16a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-platform-deno.md b/.repos/effect/.changeset/pre/add-platform-deno.md new file mode 100644 index 000000000..9237eee68 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-scalar-show-operation-id.md b/.repos/effect/.changeset/pre/add-scalar-show-operation-id.md new file mode 100644 index 000000000..04d131e1d --- /dev/null +++ b/.repos/effect/.changeset/pre/add-scalar-show-operation-id.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `showOperationId` to `HttpApiScalar.ScalarConfig`. diff --git a/.repos/effect/.changeset/pre/add-schedule-tap.md b/.repos/effect/.changeset/pre/add-schedule-tap.md new file mode 100644 index 000000000..0f4ccc3c5 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schedule-tap.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Added `Schedule.tap`, which allows observing full schedule metadata without altering schedule inputs or outputs. diff --git a/.repos/effect/.changeset/pre/add-schema-annotate-encoded.md b/.repos/effect/.changeset/pre/add-schema-annotate-encoded.md new file mode 100644 index 000000000..0e5b7e115 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schema-annotate-encoded.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `annotateEncoded` function for annotating the encoded side of a schema. diff --git a/.repos/effect/.changeset/pre/add-schema-array-ensure.md b/.repos/effect/.changeset/pre/add-schema-array-ensure.md new file mode 100644 index 000000000..908baea88 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schema-array-ensure.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schema.ArrayEnsure`. diff --git a/.repos/effect/.changeset/pre/add-schema-bigdecimal.md b/.repos/effect/.changeset/pre/add-schema-bigdecimal.md new file mode 100644 index 000000000..76d278fdb --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schema-bigdecimal.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `BigDecimal` schema with comparison checks (`isGreaterThanBigDecimal`, `isGreaterThanOrEqualToBigDecimal`, `isLessThanBigDecimal`, `isLessThanOrEqualToBigDecimal`, `isBetweenBigDecimal`). diff --git a/.repos/effect/.changeset/pre/add-schema-datetime.md b/.repos/effect/.changeset/pre/add-schema-datetime.md new file mode 100644 index 000000000..c72f4608e --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schema-datetime.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `DateTimeZoned`, `TimeZoneOffset`, `TimeZoneNamed`, and `TimeZone` schemas. diff --git a/.repos/effect/.changeset/pre/add-schema-error-module.md b/.repos/effect/.changeset/pre/add-schema-error-module.md new file mode 100644 index 000000000..d28c2c29a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-schema-option-from-optional-nullor.md b/.repos/effect/.changeset/pre/add-schema-option-from-optional-nullor.md new file mode 100644 index 000000000..d634eea40 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schema-option-from-optional-nullor.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `OptionFromOptionalNullOr` schema, closes #1707. diff --git a/.repos/effect/.changeset/pre/add-schema-option-from-undefined-nullish.md b/.repos/effect/.changeset/pre/add-schema-option-from-undefined-nullish.md new file mode 100644 index 000000000..c2d569326 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schema-option-from-undefined-nullish.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `OptionFromUndefinedOr` and `OptionFromNullishOr` schemas. diff --git a/.repos/effect/.changeset/pre/add-schema-string-encoding.md b/.repos/effect/.changeset/pre/add-schema-string-encoding.md new file mode 100644 index 000000000..7910ae29f --- /dev/null +++ b/.repos/effect/.changeset/pre/add-schema-string-encoding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `StringFromBase64`, `StringFromBase64Url`, `StringFromHex`, and `StringFromUriComponent` schemas for decoding encoded strings into UTF-8 strings, closes #1995. diff --git a/.repos/effect/.changeset/pre/add-schema-tagged-union-discriminants.md b/.repos/effect/.changeset/pre/add-schema-tagged-union-discriminants.md new file mode 100644 index 000000000..5930ed631 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-semaphore-take-if-available.md b/.repos/effect/.changeset/pre/add-semaphore-take-if-available.md new file mode 100644 index 000000000..8cb5182cc --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-sql-pglite.md b/.repos/effect/.changeset/pre/add-sql-pglite.md new file mode 100644 index 000000000..e0eb64eae --- /dev/null +++ b/.repos/effect/.changeset/pre/add-sql-pglite.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pglite": minor +--- + +Add `@effect/sql-pglite` package, wrapping `@electric-sql/pglite` with the Effect SQL client (Postgres dialect, Effect-managed transactions via savepoints, listen/notify, dumpDataDir/refreshArrayTypes, and a Migrator). diff --git a/.repos/effect/.changeset/pre/add-standard-jsdoc-rule.md b/.repos/effect/.changeset/pre/add-standard-jsdoc-rule.md new file mode 100644 index 000000000..317fe356f --- /dev/null +++ b/.repos/effect/.changeset/pre/add-standard-jsdoc-rule.md @@ -0,0 +1,5 @@ +--- +"@effect/oxc": patch +--- + +Add an `effect/standard-jsdoc` oxlint rule for staged public API JSDoc standardization. diff --git a/.repos/effect/.changeset/pre/add-stream-broadcastn.md b/.repos/effect/.changeset/pre/add-stream-broadcastn.md new file mode 100644 index 000000000..ca1fa612f --- /dev/null +++ b/.repos/effect/.changeset/pre/add-stream-broadcastn.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add Stream.broadcastN for fixed-size stream broadcasts. diff --git a/.repos/effect/.changeset/pre/add-tool-set-needs-approval.md b/.repos/effect/.changeset/pre/add-tool-set-needs-approval.md new file mode 100644 index 000000000..959b43579 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-unstable-encoding-export.md b/.repos/effect/.changeset/pre/add-unstable-encoding-export.md new file mode 100644 index 000000000..99b107249 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-unstable-encoding-export.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `unstable/encoding` subpath export. diff --git a/.repos/effect/.changeset/pre/add-update-service-scoped.md b/.repos/effect/.changeset/pre/add-update-service-scoped.md new file mode 100644 index 000000000..95f49061a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/add-values-unprepared.md b/.repos/effect/.changeset/pre/add-values-unprepared.md new file mode 100644 index 000000000..d0ac10cb9 --- /dev/null +++ b/.repos/effect/.changeset/pre/add-values-unprepared.md @@ -0,0 +1,17 @@ +--- +"effect": patch +"@effect/sql-clickhouse": patch +"@effect/sql-d1": patch +"@effect/sql-libsql": patch +"@effect/sql-mssql": patch +"@effect/sql-mysql2": patch +"@effect/sql-pg": patch +"@effect/sql-pglite": patch +"@effect/sql-sqlite-bun": patch +"@effect/sql-sqlite-do": patch +"@effect/sql-sqlite-node": patch +"@effect/sql-sqlite-react-native": patch +"@effect/sql-sqlite-wasm": patch +--- + +Add `Statement.valuesUnprepared` for returning unprepared SQL statement rows as arrays. diff --git a/.repos/effect/.changeset/pre/afraid-cobras-like.md b/.repos/effect/.changeset/pre/afraid-cobras-like.md new file mode 100644 index 000000000..c471ffa35 --- /dev/null +++ b/.repos/effect/.changeset/pre/afraid-cobras-like.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +default to endOnDone: false in NodeStdio diff --git a/.repos/effect/.changeset/pre/ai-openai-config-field-leak.md b/.repos/effect/.changeset/pre/ai-openai-config-field-leak.md new file mode 100644 index 000000000..58df093aa --- /dev/null +++ b/.repos/effect/.changeset/pre/ai-openai-config-field-leak.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +--- + +Fix `OpenAiLanguageModel` leaking library-only config fields (`fileIdPrefixes`, `strictJsonSchema`) into request body, causing OpenAI 400 errors. diff --git a/.repos/effect/.changeset/pre/ai-openai-file-nullable-fields.md b/.repos/effect/.changeset/pre/ai-openai-file-nullable-fields.md new file mode 100644 index 000000000..b59718e49 --- /dev/null +++ b/.repos/effect/.changeset/pre/ai-openai-file-nullable-fields.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Fix `OpenAIFile` schema decode failure on responses where `expires_at` and `status_details` are returned as literal `null`. The OpenAI files endpoint returns `null` (not omitted) for these fields when no expiration / status detail applies (e.g. uploads with `purpose: "user_data"`), but the upstream OpenAPI spec marks them only as optional. Codegen patches widen both fields to allow `null`, which now decodes cleanly via `OpenAiClient.createFile`, `retrieveFile`, `listFiles`, and any other endpoint returning the `OpenAIFile` shape. diff --git a/.repos/effect/.changeset/pre/anthropic-4-6-structured-output.md b/.repos/effect/.changeset/pre/anthropic-4-6-structured-output.md new file mode 100644 index 000000000..3900a93f6 --- /dev/null +++ b/.repos/effect/.changeset/pre/anthropic-4-6-structured-output.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Mark the Claude 4-6 generation as supporting native structured output in `getModelCapabilities`. `claude-opus-4-6` and `claude-sonnet-4-6` support Anthropic's constrained-decoding structured output (verified against the live API), but were classified as `supportsStructuredOutput: false`, so `generateObject` fell back to a forced JSON tool instead of requesting `output_config.format` (`json_schema`). `claude-opus-4-7` / `claude-opus-4-8` are classified the same way for when the generated `Model` enum picks them up. diff --git a/.repos/effect/.changeset/pre/anthropic-claude-5-capabilities.md b/.repos/effect/.changeset/pre/anthropic-claude-5-capabilities.md new file mode 100644 index 000000000..785acd535 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/anthropic-open-model-enum.md b/.repos/effect/.changeset/pre/anthropic-open-model-enum.md new file mode 100644 index 000000000..b5ab0393d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/anthropic-stale-max-output-tokens.md b/.repos/effect/.changeset/pre/anthropic-stale-max-output-tokens.md new file mode 100644 index 000000000..f7e83ff6e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/apply-httpapi-endpoint-client-transform.md b/.repos/effect/.changeset/pre/apply-httpapi-endpoint-client-transform.md new file mode 100644 index 000000000..ce9d74cba --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/asyncresult-exhaustive.md b/.repos/effect/.changeset/pre/asyncresult-exhaustive.md new file mode 100644 index 000000000..d448fd604 --- /dev/null +++ b/.repos/effect/.changeset/pre/asyncresult-exhaustive.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add an exhaustive finalizer to the AsyncResult builder. diff --git a/.repos/effect/.changeset/pre/atom-stream-error-type.md b/.repos/effect/.changeset/pre/atom-stream-error-type.md new file mode 100644 index 000000000..a0809cae7 --- /dev/null +++ b/.repos/effect/.changeset/pre/atom-stream-error-type.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Expose `NoSuchElementError` in the error type of stream-based `Atom.make` overloads. diff --git a/.repos/effect/.changeset/pre/atomic-optional-rc-map.md b/.repos/effect/.changeset/pre/atomic-optional-rc-map.md new file mode 100644 index 000000000..2e6c8caa8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/authentication-error-description.md b/.repos/effect/.changeset/pre/authentication-error-description.md new file mode 100644 index 000000000..f5745934d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/autocomplete-jk-filter.md b/.repos/effect/.changeset/pre/autocomplete-jk-filter.md new file mode 100644 index 000000000..00c1ec668 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/batch-persistence-expiration-cleanup.md b/.repos/effect/.changeset/pre/batch-persistence-expiration-cleanup.md new file mode 100644 index 000000000..3409edfb6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/beige-goats-sin.md b/.repos/effect/.changeset/pre/beige-goats-sin.md new file mode 100644 index 000000000..22cef1f7f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/beige-paths-sort.md b/.repos/effect/.changeset/pre/beige-paths-sort.md new file mode 100644 index 000000000..0cfad27cc --- /dev/null +++ b/.repos/effect/.changeset/pre/beige-paths-sort.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +move ChildProcess apis into spawner service diff --git a/.repos/effect/.changeset/pre/better-apples-nail.md b/.repos/effect/.changeset/pre/better-apples-nail.md new file mode 100644 index 000000000..338b25b16 --- /dev/null +++ b/.repos/effect/.changeset/pre/better-apples-nail.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Effect.repeat now uses effect return value when using options diff --git a/.repos/effect/.changeset/pre/better-rocks-arrive.md b/.repos/effect/.changeset/pre/better-rocks-arrive.md new file mode 100644 index 000000000..c703f179d --- /dev/null +++ b/.repos/effect/.changeset/pre/better-rocks-arrive.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +add back openai reasoning types diff --git a/.repos/effect/.changeset/pre/better-shrimps-follow.md b/.repos/effect/.changeset/pre/better-shrimps-follow.md new file mode 100644 index 000000000..95cb59640 --- /dev/null +++ b/.repos/effect/.changeset/pre/better-shrimps-follow.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +prefix opentelemetry modules to avoid collisions diff --git a/.repos/effect/.changeset/pre/big-masks-care.md b/.repos/effect/.changeset/pre/big-masks-care.md new file mode 100644 index 000000000..8b3a17254 --- /dev/null +++ b/.repos/effect/.changeset/pre/big-masks-care.md @@ -0,0 +1,5 @@ +--- +"@effect/doctest": patch +--- + +Support `.mdx` files diff --git a/.repos/effect/.changeset/pre/big-pans-look.md b/.repos/effect/.changeset/pre/big-pans-look.md new file mode 100644 index 000000000..0e4fd594f --- /dev/null +++ b/.repos/effect/.changeset/pre/big-pans-look.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Rpc.custom diff --git a/.repos/effect/.changeset/pre/blue-dingos-greet.md b/.repos/effect/.changeset/pre/blue-dingos-greet.md new file mode 100644 index 000000000..01e918bc6 --- /dev/null +++ b/.repos/effect/.changeset/pre/blue-dingos-greet.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.filter` support for synchronous `Filter.Filter` overloads and correctly handle non-effect `Result` return values at runtime. diff --git a/.repos/effect/.changeset/pre/blue-ligers-cheat.md b/.repos/effect/.changeset/pre/blue-ligers-cheat.md new file mode 100644 index 000000000..37a66d750 --- /dev/null +++ b/.repos/effect/.changeset/pre/blue-ligers-cheat.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update unstable schema variant helpers to use array-based arguments for `FieldOnly`, `FieldExcept`, and `Union`, aligning `VariantSchema` and `Model` with other v4 API shapes. diff --git a/.repos/effect/.changeset/pre/blue-onions-smile.md b/.repos/effect/.changeset/pre/blue-onions-smile.md new file mode 100644 index 000000000..2ef4e625a --- /dev/null +++ b/.repos/effect/.changeset/pre/blue-onions-smile.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Preserve streamed OpenAI compat tool call ids and names across fragmented chat completion chunks. diff --git a/.repos/effect/.changeset/pre/blue-pigs-push.md b/.repos/effect/.changeset/pre/blue-pigs-push.md new file mode 100644 index 000000000..7cb74e13f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/blue-ravens-type.md b/.repos/effect/.changeset/pre/blue-ravens-type.md new file mode 100644 index 000000000..0919ee483 --- /dev/null +++ b/.repos/effect/.changeset/pre/blue-ravens-type.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure streamed tool results are emitted before the finish part so chat history includes tool outputs before stream termination. diff --git a/.repos/effect/.changeset/pre/blue-trams-kiss.md b/.repos/effect/.changeset/pre/blue-trams-kiss.md new file mode 100644 index 000000000..27fa6f36b --- /dev/null +++ b/.repos/effect/.changeset/pre/blue-trams-kiss.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix consolePretty ignoring explicit colors option in non-TTY environments. + +When colors is explicitly set to true, prettyLoggerTty was still gating it with processStdoutIsTTY check, making it impossible to enable colors in non-TTY environments like Vite dev server. diff --git a/.repos/effect/.changeset/pre/bold-chairs-yawn.md b/.repos/effect/.changeset/pre/bold-chairs-yawn.md new file mode 100644 index 000000000..1b14a10a8 --- /dev/null +++ b/.repos/effect/.changeset/pre/bold-chairs-yawn.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add HttpApiTest module diff --git a/.repos/effect/.changeset/pre/bold-planets-shout.md b/.repos/effect/.changeset/pre/bold-planets-shout.md new file mode 100644 index 000000000..001dbd01f --- /dev/null +++ b/.repos/effect/.changeset/pre/bold-planets-shout.md @@ -0,0 +1,30 @@ +--- +"effect": patch +--- + +add support for merging external events into `Prompt.custom` render loops via an optional `events` dequeue and `receive` handler. + +The prompt races user input against events from the dequeue, allowing background events to trigger re-renders without waiting for a keypress: + +```ts +const eventQueue = yield * Queue.make() + +const prompt = Prompt.custom( + { count: 0 }, + Queue.asDequeue(eventQueue), // <-- provide the event queue as a dequeue to the prompt + { + render: (state) => Effect.succeed(`Count: ${state.count}`), + process: (input, state) => + Effect.succeed( + Match.value(input).pipe( + // handle user input + Match.tag("Input", () => Action.Submit({ value: state.count })), + // handle external events from the queue + Match.tag("Event", (input) => Action.NextFrame({ state: { count: state.count + input.value } })), + Match.exhaustive + ) + ), + clear: () => Effect.succeed("") + } +) +``` diff --git a/.repos/effect/.changeset/pre/brave-keys-commit.md b/.repos/effect/.changeset/pre/brave-keys-commit.md new file mode 100644 index 000000000..f9358594f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/brave-rings-update.md b/.repos/effect/.changeset/pre/brave-rings-update.md new file mode 100644 index 000000000..21931bcb5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/breezy-meals-see.md b/.repos/effect/.changeset/pre/breezy-meals-see.md new file mode 100644 index 000000000..c4a8359c5 --- /dev/null +++ b/.repos/effect/.changeset/pre/breezy-meals-see.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +support tag unions in Effect.catchTag/Reason diff --git a/.repos/effect/.changeset/pre/bright-bugs-bow.md b/.repos/effect/.changeset/pre/bright-bugs-bow.md new file mode 100644 index 000000000..ca6cb86e9 --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-bugs-bow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Run request resolver batch fibers with request services by using `Effect.runForkWith`, so resolver delay effects and `runAll` execution see the request service map. diff --git a/.repos/effect/.changeset/pre/bright-canyons-clean.md b/.repos/effect/.changeset/pre/bright-canyons-clean.md new file mode 100644 index 000000000..3bddee36c --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-canyons-clean.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add support for deferred responses in rpc diff --git a/.repos/effect/.changeset/pre/bright-clocks-count.md b/.repos/effect/.changeset/pre/bright-clocks-count.md new file mode 100644 index 000000000..8f3df8ee0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/bright-docs-teach.md b/.repos/effect/.changeset/pre/bright-docs-teach.md new file mode 100644 index 000000000..3e26d3391 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/bright-dogs-fail.md b/.repos/effect/.changeset/pre/bright-dogs-fail.md new file mode 100644 index 000000000..f29db9418 --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-dogs-fail.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Model.BooleanSqlite`, a model field schema that uses `0 | 1` encoding for database variants and plain `boolean` encoding for JSON variants. diff --git a/.repos/effect/.changeset/pre/bright-flags-stand.md b/.repos/effect/.changeset/pre/bright-flags-stand.md new file mode 100644 index 000000000..131bcf434 --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-flags-stand.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix CLI parsing so command-local flags can override globals without breaking global flags before subcommands. diff --git a/.repos/effect/.changeset/pre/bright-graphs-calculate.md b/.repos/effect/.changeset/pre/bright-graphs-calculate.md new file mode 100644 index 000000000..86935a995 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/bright-graphs-trace.md b/.repos/effect/.changeset/pre/bright-graphs-trace.md new file mode 100644 index 000000000..1f08e6a7f --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-graphs-trace.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include traversed edge indexes in graph shortest-path results. diff --git a/.repos/effect/.changeset/pre/bright-journals-commit.md b/.repos/effect/.changeset/pre/bright-journals-commit.md new file mode 100644 index 000000000..5c312a18f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/bright-laws-teach.md b/.repos/effect/.changeset/pre/bright-laws-teach.md new file mode 100644 index 000000000..12df791c7 --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-laws-teach.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Config.Success` type utility, closes #1783. diff --git a/.repos/effect/.changeset/pre/bright-lemons-dance.md b/.repos/effect/.changeset/pre/bright-lemons-dance.md new file mode 100644 index 000000000..0bae9332d --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-lemons-dance.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow `Effect.acquireRelease` release finalizers to depend on the surrounding environment. diff --git a/.repos/effect/.changeset/pre/bright-planes-smash.md b/.repos/effect/.changeset/pre/bright-planes-smash.md new file mode 100644 index 000000000..edf82b8b2 --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-planes-smash.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `responseText` to `AiError.StructuredOutputError` and populate it from `LanguageModel.generateObject` so failed structured output decodes include the full LLM text. diff --git a/.repos/effect/.changeset/pre/bright-rats-attend.md b/.repos/effect/.changeset/pre/bright-rats-attend.md new file mode 100644 index 000000000..071f37890 --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-rats-attend.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +make fiber keepAlive setInterval evaluation lazy diff --git a/.repos/effect/.changeset/pre/bright-tags-recognize.md b/.repos/effect/.changeset/pre/bright-tags-recognize.md new file mode 100644 index 000000000..edead0f68 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/bright-toes-rush.md b/.repos/effect/.changeset/pre/bright-toes-rush.md new file mode 100644 index 000000000..34bda7c43 --- /dev/null +++ b/.repos/effect/.changeset/pre/bright-toes-rush.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Persist MCP HTTP session and protocol headers after initialize so follow-up JSON-RPC requests include `MCP-Protocol-Version`. diff --git a/.repos/effect/.changeset/pre/brown-glasses-thank.md b/.repos/effect/.changeset/pre/brown-glasses-thank.md new file mode 100644 index 000000000..0445b02de --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/brown-peas-enter.md b/.repos/effect/.changeset/pre/brown-peas-enter.md new file mode 100644 index 000000000..2779fa9ef --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/bumpy-boxes-teach.md b/.repos/effect/.changeset/pre/bumpy-boxes-teach.md new file mode 100644 index 000000000..31205424e --- /dev/null +++ b/.repos/effect/.changeset/pre/bumpy-boxes-teach.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename Atom's `Context` type to `AtomContext` diff --git a/.repos/effect/.changeset/pre/bun-websocket-outgoing-compression.md b/.repos/effect/.changeset/pre/bun-websocket-outgoing-compression.md new file mode 100644 index 000000000..9be7aeaf6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/busy-lions-sneeze.md b/.repos/effect/.changeset/pre/busy-lions-sneeze.md new file mode 100644 index 000000000..de2167c4a --- /dev/null +++ b/.repos/effect/.changeset/pre/busy-lions-sneeze.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Fix HTTP Referer header name in the `OpenRouterClient` diff --git a/.repos/effect/.changeset/pre/busy-maps-attend.md b/.repos/effect/.changeset/pre/busy-maps-attend.md new file mode 100644 index 000000000..395698754 --- /dev/null +++ b/.repos/effect/.changeset/pre/busy-maps-attend.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/platform-bun": patch +"@effect/platform-node": patch +--- + +add rows to Terminal diff --git a/.repos/effect/.changeset/pre/calm-atoms-relax.md b/.repos/effect/.changeset/pre/calm-atoms-relax.md new file mode 100644 index 000000000..a7464d543 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-atoms-relax.md @@ -0,0 +1,5 @@ +--- +"@effect/atom-react": patch +--- + +Relax react peer dependency range diff --git a/.repos/effect/.changeset/pre/calm-bash-completions.md b/.repos/effect/.changeset/pre/calm-bash-completions.md new file mode 100644 index 000000000..ca77256ce --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-bash-completions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Select Bash completions for the active positional argument. diff --git a/.repos/effect/.changeset/pre/calm-buckets-own.md b/.repos/effect/.changeset/pre/calm-buckets-own.md new file mode 100644 index 000000000..0610fbdba --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-buckets-own.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +improve openai socket errors diff --git a/.repos/effect/.changeset/pre/calm-buses-smile.md b/.repos/effect/.changeset/pre/calm-buses-smile.md new file mode 100644 index 000000000..e7fe600ce --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-carrots-march.md b/.repos/effect/.changeset/pre/calm-carrots-march.md new file mode 100644 index 000000000..75703506f --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-carrots-march.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Allow unstable HttpApi middleware to declare multiple error schemas with arrays. + +Middleware errors now follow endpoint error behavior for response status resolution, client decoding, and generated API schemas. diff --git a/.repos/effect/.changeset/pre/calm-cars-rest.md b/.repos/effect/.changeset/pre/calm-cars-rest.md new file mode 100644 index 000000000..a61b82e77 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-cars-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add grouped subcommand support to `Command.withSubcommands`, including help output sections for named groups while keeping ungrouped commands under `SUBCOMMANDS`. diff --git a/.repos/effect/.changeset/pre/calm-coins-smile.md b/.repos/effect/.changeset/pre/calm-coins-smile.md new file mode 100644 index 000000000..973771855 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-contexts-rest.md b/.repos/effect/.changeset/pre/calm-contexts-rest.md new file mode 100644 index 000000000..1dede3ad9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-dates-view.md b/.repos/effect/.changeset/pre/calm-dates-view.md new file mode 100644 index 000000000..b9e963edb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-dragons-command.md b/.repos/effect/.changeset/pre/calm-dragons-command.md new file mode 100644 index 000000000..d98e1edd6 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-dragons-command.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Correct the runtime tag spelling for `CliError.UnknownSubcommand`. diff --git a/.repos/effect/.changeset/pre/calm-graphs-span.md b/.repos/effect/.changeset/pre/calm-graphs-span.md new file mode 100644 index 000000000..c53042d54 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-graphs-span.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add deterministic, index-preserving `Graph.minimumSpanningForest`. diff --git a/.repos/effect/.changeset/pre/calm-graphs-travel.md b/.repos/effect/.changeset/pre/calm-graphs-travel.md new file mode 100644 index 000000000..85618505b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-heads-close.md b/.repos/effect/.changeset/pre/calm-heads-close.md new file mode 100644 index 000000000..c717945ad --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-hounds-smile.md b/.repos/effect/.changeset/pre/calm-hounds-smile.md new file mode 100644 index 000000000..8f7a1640a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-keys-repeat.md b/.repos/effect/.changeset/pre/calm-keys-repeat.md new file mode 100644 index 000000000..82f669b0e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-masks-count.md b/.repos/effect/.changeset/pre/calm-masks-count.md new file mode 100644 index 000000000..b70fc9ae8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-pages-hide.md b/.repos/effect/.changeset/pre/calm-pages-hide.md new file mode 100644 index 000000000..22600772a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-pandas-retry.md b/.repos/effect/.changeset/pre/calm-pandas-retry.md new file mode 100644 index 000000000..b2372c6bd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-panthers-nail.md b/.repos/effect/.changeset/pre/calm-panthers-nail.md new file mode 100644 index 000000000..1f855e558 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-panthers-nail.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `disableFatalDefects` to `RpcServer.layerHttp`, `RpcServer.toHttpEffect`, and `RpcServer.toHttpEffectWebsocket` option types to match existing runtime support. diff --git a/.repos/effect/.changeset/pre/calm-pears-smile.md b/.repos/effect/.changeset/pre/calm-pears-smile.md new file mode 100644 index 000000000..a83413eea --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-queues-await.md b/.repos/effect/.changeset/pre/calm-queues-await.md new file mode 100644 index 000000000..18ac80170 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-ravens-reflect.md b/.repos/effect/.changeset/pre/calm-ravens-reflect.md new file mode 100644 index 000000000..ba31e47c2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-redis-clear.md b/.repos/effect/.changeset/pre/calm-redis-clear.md new file mode 100644 index 000000000..a707eadd9 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-redis-clear.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure clearing an empty Redis-backed persistence store succeeds. diff --git a/.repos/effect/.changeset/pre/calm-results-align.md b/.repos/effect/.changeset/pre/calm-results-align.md new file mode 100644 index 000000000..896040c70 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-schemas-encode.md b/.repos/effect/.changeset/pre/calm-schemas-encode.md new file mode 100644 index 000000000..6bec17848 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-schemas-encode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the encoded output type of `TestSchema.Encoding.encodeUnknownEffect`. diff --git a/.repos/effect/.changeset/pre/calm-seas-smile.md b/.repos/effect/.changeset/pre/calm-seas-smile.md new file mode 100644 index 000000000..bde18ad7f --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-seas-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Align workflow tags with RPCs by changing `Workflow.make` to accept the tag as its first argument, exposing workflow tags as `_tag`, and supporting `class MyWorkflow extends Workflow.make(...) {}`. diff --git a/.repos/effect/.changeset/pre/calm-servers-share.md b/.repos/effect/.changeset/pre/calm-servers-share.md new file mode 100644 index 000000000..504a98ce5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-services-rest.md b/.repos/effect/.changeset/pre/calm-services-rest.md new file mode 100644 index 000000000..400dade63 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-services-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Stop capturing definition-location stack frames in `Context.Service`. diff --git a/.repos/effect/.changeset/pre/calm-squids-hug.md b/.repos/effect/.changeset/pre/calm-squids-hug.md new file mode 100644 index 000000000..3ac3c61ab --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-squids-hug.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Context.Key type, used a base for Context.Service and Context.Reference diff --git a/.repos/effect/.changeset/pre/calm-tools-parse.md b/.repos/effect/.changeset/pre/calm-tools-parse.md new file mode 100644 index 000000000..779bde415 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-tools-remember.md b/.repos/effect/.changeset/pre/calm-tools-remember.md new file mode 100644 index 000000000..3fdf6410e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-tracers-sample.md b/.repos/effect/.changeset/pre/calm-tracers-sample.md new file mode 100644 index 000000000..3bbb0ad84 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-tracers-sample.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Preserve sampling decisions when adapting generic Effect external spans to OpenTelemetry. diff --git a/.repos/effect/.changeset/pre/calm-tuples-align.md b/.repos/effect/.changeset/pre/calm-tuples-align.md new file mode 100644 index 000000000..b62d5bdc4 --- /dev/null +++ b/.repos/effect/.changeset/pre/calm-tuples-align.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix arbitrary generation for tuples with multiple optional elements. diff --git a/.repos/effect/.changeset/pre/calm-tuples-pick.md b/.repos/effect/.changeset/pre/calm-tuples-pick.md new file mode 100644 index 000000000..0422f5814 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/calm-wolves-reduce.md b/.repos/effect/.changeset/pre/calm-wolves-reduce.md new file mode 100644 index 000000000..1b2556456 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cancel-tedious-requests.md b/.repos/effect/.changeset/pre/cancel-tedious-requests.md new file mode 100644 index 000000000..9b9ce459e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/canonical-number-schemas.md b/.repos/effect/.changeset/pre/canonical-number-schemas.md new file mode 100644 index 000000000..96fceebf4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cap-rpc-streaming-buffers.md b/.repos/effect/.changeset/pre/cap-rpc-streaming-buffers.md new file mode 100644 index 000000000..4f67ff43b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/chatty-poets-type.md b/.repos/effect/.changeset/pre/chatty-poets-type.md new file mode 100644 index 000000000..623273cfb --- /dev/null +++ b/.repos/effect/.changeset/pre/chatty-poets-type.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +remove Effect.Yieldable diff --git a/.repos/effect/.changeset/pre/chilled-mice-wash.md b/.repos/effect/.changeset/pre/chilled-mice-wash.md new file mode 100644 index 000000000..08e529a7e --- /dev/null +++ b/.repos/effect/.changeset/pre/chilled-mice-wash.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpServerResponse.fromClientResponse` for directly converting client responses into server responses. diff --git a/.repos/effect/.changeset/pre/chilly-pumas-rule.md b/.repos/effect/.changeset/pre/chilly-pumas-rule.md new file mode 100644 index 000000000..b53d3514c --- /dev/null +++ b/.repos/effect/.changeset/pre/chilly-pumas-rule.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make `Data.Class`, `Data.TaggedClass`, and `Cause.YieldableError` pipeable. diff --git a/.repos/effect/.changeset/pre/chubby-buckets-feel.md b/.repos/effect/.changeset/pre/chubby-buckets-feel.md new file mode 100644 index 000000000..bf1cc12f9 --- /dev/null +++ b/.repos/effect/.changeset/pre/chubby-buckets-feel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix issues with metro bundler diff --git a/.repos/effect/.changeset/pre/chubby-parents-flow.md b/.repos/effect/.changeset/pre/chubby-parents-flow.md new file mode 100644 index 000000000..8c87cad40 --- /dev/null +++ b/.repos/effect/.changeset/pre/chubby-parents-flow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow undefined for VariantSchema.Overridable input diff --git a/.repos/effect/.changeset/pre/chubby-planets-fall.md b/.repos/effect/.changeset/pre/chubby-planets-fall.md new file mode 100644 index 000000000..15c60d592 --- /dev/null +++ b/.repos/effect/.changeset/pre/chubby-planets-fall.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Remove duplicate `ToolApprovalResponsePartOptions` from Anthropic package diff --git a/.repos/effect/.changeset/pre/clean-balloons-tan.md b/.repos/effect/.changeset/pre/clean-balloons-tan.md new file mode 100644 index 000000000..1c4ff4718 --- /dev/null +++ b/.repos/effect/.changeset/pre/clean-balloons-tan.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Proxy function arity from `Effect.fn` APIs so wrapped functions preserve the original `length` value. diff --git a/.repos/effect/.changeset/pre/clean-bulldogs-care.md b/.repos/effect/.changeset/pre/clean-bulldogs-care.md new file mode 100644 index 000000000..48f430f9f --- /dev/null +++ b/.repos/effect/.changeset/pre/clean-bulldogs-care.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Use `pg_notify` in `PgClient.notify` so channel and payload are sent through parameters instead of a `NOTIFY` statement string. diff --git a/.repos/effect/.changeset/pre/clean-cats-document.md b/.repos/effect/.changeset/pre/clean-cats-document.md new file mode 100644 index 000000000..b2d26821d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/clean-dryers-sneeze.md b/.repos/effect/.changeset/pre/clean-dryers-sneeze.md new file mode 100644 index 000000000..96316e62f --- /dev/null +++ b/.repos/effect/.changeset/pre/clean-dryers-sneeze.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Random.nextBoolean` for generating random boolean values. diff --git a/.repos/effect/.changeset/pre/clean-formatters-agree.md b/.repos/effect/.changeset/pre/clean-formatters-agree.md new file mode 100644 index 000000000..2fea77679 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/clean-geese-work.md b/.repos/effect/.changeset/pre/clean-geese-work.md new file mode 100644 index 000000000..e91346b78 --- /dev/null +++ b/.repos/effect/.changeset/pre/clean-geese-work.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Inspectable.stringifyCircular` and fix `Formatter.formatJson` so shared object references are preserved while only circular references are omitted. diff --git a/.repos/effect/.changeset/pre/clean-goats-wave.md b/.repos/effect/.changeset/pre/clean-goats-wave.md new file mode 100644 index 000000000..944fd8430 --- /dev/null +++ b/.repos/effect/.changeset/pre/clean-goats-wave.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Ctrl-U` line clearing support to editable CLI prompts. diff --git a/.repos/effect/.changeset/pre/clean-lions-cancel.md b/.repos/effect/.changeset/pre/clean-lions-cancel.md new file mode 100644 index 000000000..f725e3d19 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/clean-needles-shake.md b/.repos/effect/.changeset/pre/clean-needles-shake.md new file mode 100644 index 000000000..17abeea6a --- /dev/null +++ b/.repos/effect/.changeset/pre/clean-needles-shake.md @@ -0,0 +1,23 @@ +--- +"effect": patch +--- + +Fix module-level side effects that defeated bundler tree-shaking. + +Bare top-level statements cannot be `#__PURE__`-annotated by the build, so +bundlers must retain them and everything they reference, even in bundles that +never use the code: + +- `Option`: the standalone `Object.defineProperty(SomeProto, "valueOrUndefined", ...)` + statement anchored the whole `Option` proto chain into every bundle. It is + now folded into the `SomeProto` initializer. +- `Headers`: same pattern with `Object.defineProperties(Proto, ...)`, folded + into the initializer. +- `Logger`: module-level `process.stdout.isTTY` property reads (potential + getters, never droppable) moved inside `consolePretty`. +- `Utils`: when `internalCall` was unused, its dropped binding left behind a + retained initializer tail (`standard`/`forced` probe with computed property + reads). The selection is now wrapped in a single pure-annotated call. + +A minimal `Effect.succeed(123).pipe(Effect.runFork)` bundle shrinks by ~1.3% +gzipped; bundles that don't use `Option` or `Headers` no longer pay for them. diff --git a/.repos/effect/.changeset/pre/clean-tires-guess.md b/.repos/effect/.changeset/pre/clean-tires-guess.md new file mode 100644 index 000000000..29463e91d --- /dev/null +++ b/.repos/effect/.changeset/pre/clean-tires-guess.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +clean up sql-pg constructors and layers diff --git a/.repos/effect/.changeset/pre/clear-graphs-reduce.md b/.repos/effect/.changeset/pre/clear-graphs-reduce.md new file mode 100644 index 000000000..0341ecbe6 --- /dev/null +++ b/.repos/effect/.changeset/pre/clear-graphs-reduce.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add index-preserving transitive reduction for directed acyclic graphs. diff --git a/.repos/effect/.changeset/pre/clear-hairs-pump.md b/.repos/effect/.changeset/pre/clear-hairs-pump.md new file mode 100644 index 000000000..ac42927cd --- /dev/null +++ b/.repos/effect/.changeset/pre/clear-hairs-pump.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix activity retry policy diff --git a/.repos/effect/.changeset/pre/clear-spies-boil.md b/.repos/effect/.changeset/pre/clear-spies-boil.md new file mode 100644 index 000000000..44318f6f5 --- /dev/null +++ b/.repos/effect/.changeset/pre/clear-spies-boil.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +improve idb support for compound indexes diff --git a/.repos/effect/.changeset/pre/clever-maps-care.md b/.repos/effect/.changeset/pre/clever-maps-care.md new file mode 100644 index 000000000..51de31746 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cli-config-built-ins.md b/.repos/effect/.changeset/pre/cli-config-built-ins.md new file mode 100644 index 000000000..6e0dbcd40 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cli-help-choices.md b/.repos/effect/.changeset/pre/cli-help-choices.md new file mode 100644 index 000000000..379f75198 --- /dev/null +++ b/.repos/effect/.changeset/pre/cli-help-choices.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Append concrete choice values to CLI flag help descriptions so generated help shows valid command-line inputs. diff --git a/.repos/effect/.changeset/pre/cli-wizard-mode.md b/.repos/effect/.changeset/pre/cli-wizard-mode.md new file mode 100644 index 000000000..0b7c9f2d6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/close-failed-resource-map-scopes.md b/.repos/effect/.changeset/pre/close-failed-resource-map-scopes.md new file mode 100644 index 000000000..2242910a8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cold-knives-lie.md b/.repos/effect/.changeset/pre/cold-knives-lie.md new file mode 100644 index 000000000..5e7051fd3 --- /dev/null +++ b/.repos/effect/.changeset/pre/cold-knives-lie.md @@ -0,0 +1,35 @@ +--- +"effect": patch +--- + +Port `Pipeable.Class` from v3. + +```ts +class MyClass extends Pipeable.Class() { + constructor(public a: number) { + super() + } + methodA() { + return this.a + } +} +console.log(new MyClass(2).pipe((x) => x.methodA())) // 2 +``` + +```ts +class A { + constructor(public a: number) {} + methodA() { + return this.a + } +} +class B extends Pipeable.Class(A) { + constructor(private b: string) { + super(b.length) + } + methodB() { + return [this.b, this.methodA()] + } +} +console.log(new B("pipe").pipe((x) => x.methodB())) // ['pipe', 4] +``` diff --git a/.repos/effect/.changeset/pre/cold-rooms-show.md b/.repos/effect/.changeset/pre/cold-rooms-show.md new file mode 100644 index 000000000..ab2258a81 --- /dev/null +++ b/.repos/effect/.changeset/pre/cold-rooms-show.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +use Cause.NoSuchElementError for idb .first queries diff --git a/.repos/effect/.changeset/pre/cold-sloths-wave.md b/.repos/effect/.changeset/pre/cold-sloths-wave.md new file mode 100644 index 000000000..6e6a1f7fc --- /dev/null +++ b/.repos/effect/.changeset/pre/cold-sloths-wave.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Add support for OpenAI `keepalive` response stream events. diff --git a/.repos/effect/.changeset/pre/common-mammals-tickle.md b/.repos/effect/.changeset/pre/common-mammals-tickle.md new file mode 100644 index 000000000..510898292 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/compact-json-schema-checks.md b/.repos/effect/.changeset/pre/compact-json-schema-checks.md new file mode 100644 index 000000000..957c58997 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/compact-json-schema-enum.md b/.repos/effect/.changeset/pre/compact-json-schema-enum.md new file mode 100644 index 000000000..f6b4e7633 --- /dev/null +++ b/.repos/effect/.changeset/pre/compact-json-schema-enum.md @@ -0,0 +1,25 @@ +--- +"effect": patch +--- + +Schema: collapse same-type literal branches in JSON Schema output into a single `enum` array, closes #1868. + +Before: + +```json +{ + "anyOf": [ + { "type": "string", "enum": ["A"] }, + { "type": "string", "enum": ["B"] } + ] +} +``` + +After: + +```json +{ + "type": "string", + "enum": ["A", "B"] +} +``` diff --git a/.repos/effect/.changeset/pre/config-provider-option-lookup.md b/.repos/effect/.changeset/pre/config-provider-option-lookup.md new file mode 100644 index 000000000..a671ebb39 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/config-withdefault-eager.md b/.repos/effect/.changeset/pre/config-withdefault-eager.md new file mode 100644 index 000000000..107cd6fd7 --- /dev/null +++ b/.repos/effect/.changeset/pre/config-withdefault-eager.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Revert `Config.withDefault` to v3 behavior, closes #1530. + +Make `Config.withDefault` accept an eager value instead of `LazyArg`, aligning with CLI module conventions. diff --git a/.repos/effect/.changeset/pre/configurable-schema-reference-policy.md b/.repos/effect/.changeset/pre/configurable-schema-reference-policy.md new file mode 100644 index 000000000..c46a7a543 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/configure-cluster-rpc-buffer-limits.md b/.repos/effect/.changeset/pre/configure-cluster-rpc-buffer-limits.md new file mode 100644 index 000000000..76c3172e7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/consolidate-encoding.md b/.repos/effect/.changeset/pre/consolidate-encoding.md new file mode 100644 index 000000000..8e0cf7c05 --- /dev/null +++ b/.repos/effect/.changeset/pre/consolidate-encoding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Encoding: consolidate `effect/encoding` sub-modules (Base64, Base64Url, Hex, EncodingError) into a top-level `Encoding` module. Functions are now prefixed: `encodeBase64`, `decodeBase64`, `encodeHex`, `decodeHex`, etc. The `effect/encoding` sub-path export is removed. diff --git a/.repos/effect/.changeset/pre/consolidate-sql-error.md b/.repos/effect/.changeset/pre/consolidate-sql-error.md new file mode 100644 index 000000000..fdefa5f8c --- /dev/null +++ b/.repos/effect/.changeset/pre/consolidate-sql-error.md @@ -0,0 +1,16 @@ +--- +"effect": patch +"@effect/sql-clickhouse": patch +"@effect/sql-d1": patch +"@effect/sql-libsql": patch +"@effect/sql-mssql": patch +"@effect/sql-mysql2": patch +"@effect/sql-pg": patch +"@effect/sql-sqlite-bun": patch +"@effect/sql-sqlite-do": patch +"@effect/sql-sqlite-node": patch +"@effect/sql-sqlite-react-native": patch +"@effect/sql-sqlite-wasm": patch +--- + +Consolidate the SqlError changes to the new reason-based shape across effect and the SQL drivers, classifying native failures into structured reasons with Unknown fallback where native codes are unavailable. diff --git a/.repos/effect/.changeset/pre/cozy-geese-remain.md b/.repos/effect/.changeset/pre/cozy-geese-remain.md new file mode 100644 index 000000000..52e90cd08 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/crisp-seas-warn.md b/.repos/effect/.changeset/pre/crisp-seas-warn.md new file mode 100644 index 000000000..31fac078f --- /dev/null +++ b/.repos/effect/.changeset/pre/crisp-seas-warn.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure transformed Atom's don't extend idle ttl diff --git a/.repos/effect/.changeset/pre/cron-locale-independent-aliases.md b/.repos/effect/.changeset/pre/cron-locale-independent-aliases.md new file mode 100644 index 000000000..9b84a1ced --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cron-single-value-step.md b/.repos/effect/.changeset/pre/cron-single-value-step.md new file mode 100644 index 000000000..02a9fff68 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cron-testclock-infinity.md b/.repos/effect/.changeset/pre/cron-testclock-infinity.md new file mode 100644 index 000000000..3aea00e89 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/cuddly-rooms-bet.md b/.repos/effect/.changeset/pre/cuddly-rooms-bet.md new file mode 100644 index 000000000..8d09b0ad8 --- /dev/null +++ b/.repos/effect/.changeset/pre/cuddly-rooms-bet.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Stream.timeoutOrElse diff --git a/.repos/effect/.changeset/pre/curly-files-range.md b/.repos/effect/.changeset/pre/curly-files-range.md new file mode 100644 index 000000000..aa2a20ade --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/curly-graphs-connect.md b/.repos/effect/.changeset/pre/curly-graphs-connect.md new file mode 100644 index 000000000..37f344445 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/curly-graphs-snapshot.md b/.repos/effect/.changeset/pre/curly-graphs-snapshot.md new file mode 100644 index 000000000..7c56a6daf --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/curly-poems-talk.md b/.repos/effect/.changeset/pre/curly-poems-talk.md new file mode 100644 index 000000000..97fa70b92 --- /dev/null +++ b/.repos/effect/.changeset/pre/curly-poems-talk.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add `Schema.HashSet` for decoding and encoding `HashSet` values. diff --git a/.repos/effect/.changeset/pre/curly-ravens-decode.md b/.repos/effect/.changeset/pre/curly-ravens-decode.md new file mode 100644 index 000000000..f8af5ea82 --- /dev/null +++ b/.repos/effect/.changeset/pre/curly-ravens-decode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Decode percent-encoded OTLP environment header values. diff --git a/.repos/effect/.changeset/pre/curly-spies-relax.md b/.repos/effect/.changeset/pre/curly-spies-relax.md new file mode 100644 index 000000000..a495ef8bc --- /dev/null +++ b/.repos/effect/.changeset/pre/curly-spies-relax.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +narrow types for Effect.retry/repeat while option diff --git a/.repos/effect/.changeset/pre/curly-streams-stop.md b/.repos/effect/.changeset/pre/curly-streams-stop.md new file mode 100644 index 000000000..e2a08a9df --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/curvy-apples-float.md b/.repos/effect/.changeset/pre/curvy-apples-float.md new file mode 100644 index 000000000..fcdfac634 --- /dev/null +++ b/.repos/effect/.changeset/pre/curvy-apples-float.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.validate` for validating collections while accumulating all failures, equivalent to the v3 `Effect.validateAll` behavior. diff --git a/.repos/effect/.changeset/pre/curvy-birds-float.md b/.repos/effect/.changeset/pre/curvy-birds-float.md new file mode 100644 index 000000000..e0a68dddc --- /dev/null +++ b/.repos/effect/.changeset/pre/curvy-birds-float.md @@ -0,0 +1,10 @@ +--- +"effect": patch +--- + +Update `Schema.Void` to model ignored `void` return values. + +Runtime parsing now accepts any present value and discards it as `undefined`. +This matches TypeScript `void` return values, where callers do not observe the +returned value. Use `Schema.Undefined` when the input must be exactly +`undefined`. diff --git a/.repos/effect/.changeset/pre/curvy-graphs-code.md b/.repos/effect/.changeset/pre/curvy-graphs-code.md new file mode 100644 index 000000000..758df43ff --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/curvy-melons-stare.md b/.repos/effect/.changeset/pre/curvy-melons-stare.md new file mode 100644 index 000000000..c9dd27576 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/curvy-otters-sync.md b/.repos/effect/.changeset/pre/curvy-otters-sync.md new file mode 100644 index 000000000..aa98e610d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/custom-cli-prompt-prefix.md b/.repos/effect/.changeset/pre/custom-cli-prompt-prefix.md new file mode 100644 index 000000000..e6c00a55a --- /dev/null +++ b/.repos/effect/.changeset/pre/custom-cli-prompt-prefix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow customizing the prefix displayed by CLI prompts. diff --git a/.repos/effect/.changeset/pre/custom-http-security-openapi-generator.md b/.repos/effect/.changeset/pre/custom-http-security-openapi-generator.md new file mode 100644 index 000000000..c338435df --- /dev/null +++ b/.repos/effect/.changeset/pre/custom-http-security-openapi-generator.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Add HttpApi generation support for custom OpenAPI HTTP security schemes. diff --git a/.repos/effect/.changeset/pre/cute-heads-thank.md b/.repos/effect/.changeset/pre/cute-heads-thank.md new file mode 100644 index 000000000..25a2f17e3 --- /dev/null +++ b/.repos/effect/.changeset/pre/cute-heads-thank.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +catch defects when building Entity handlers diff --git a/.repos/effect/.changeset/pre/cyan-loops-grow.md b/.repos/effect/.changeset/pre/cyan-loops-grow.md new file mode 100644 index 000000000..a807ca082 --- /dev/null +++ b/.repos/effect/.changeset/pre/cyan-loops-grow.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +improve idb transaction api diff --git a/.repos/effect/.changeset/pre/cyan-radios-switch.md b/.repos/effect/.changeset/pre/cyan-radios-switch.md new file mode 100644 index 000000000..6763e2953 --- /dev/null +++ b/.repos/effect/.changeset/pre/cyan-radios-switch.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": patch +--- + +Broaden the `vitest` peer dependency range to support both v3 and v4. diff --git a/.repos/effect/.changeset/pre/cyan-shirts-grin.md b/.repos/effect/.changeset/pre/cyan-shirts-grin.md new file mode 100644 index 000000000..96d0ce0d7 --- /dev/null +++ b/.repos/effect/.changeset/pre/cyan-shirts-grin.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fork memo map on nested builds diff --git a/.repos/effect/.changeset/pre/d1-batch-statements.md b/.repos/effect/.changeset/pre/d1-batch-statements.md new file mode 100644 index 000000000..779a545b7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/dark-dancers-rule.md b/.repos/effect/.changeset/pre/dark-dancers-rule.md new file mode 100644 index 000000000..e46ea884d --- /dev/null +++ b/.repos/effect/.changeset/pre/dark-dancers-rule.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use Context mapUnsafe in less call sites diff --git a/.repos/effect/.changeset/pre/deduplicate-json-schema-fallbacks.md b/.repos/effect/.changeset/pre/deduplicate-json-schema-fallbacks.md new file mode 100644 index 000000000..1c5aa3349 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/deep-rivers-spend.md b/.repos/effect/.changeset/pre/deep-rivers-spend.md new file mode 100644 index 000000000..2c89a0793 --- /dev/null +++ b/.repos/effect/.changeset/pre/deep-rivers-spend.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix Stream.withSpan options diff --git a/.repos/effect/.changeset/pre/deferred-cleanup-after-completion.md b/.repos/effect/.changeset/pre/deferred-cleanup-after-completion.md new file mode 100644 index 000000000..4434455d5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/deferred-resume-starvation.md b/.repos/effect/.changeset/pre/deferred-resume-starvation.md new file mode 100644 index 000000000..9a30bd76d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/deno-write-copy-errors.md b/.repos/effect/.changeset/pre/deno-write-copy-errors.md new file mode 100644 index 000000000..2b93ed38e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/dirty-lamps-trade.md b/.repos/effect/.changeset/pre/dirty-lamps-trade.md new file mode 100644 index 000000000..26c515d51 --- /dev/null +++ b/.repos/effect/.changeset/pre/dirty-lamps-trade.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Result.failVoid diff --git a/.repos/effect/.changeset/pre/dirty-laws-wear.md b/.repos/effect/.changeset/pre/dirty-laws-wear.md new file mode 100644 index 000000000..68ad0d5a8 --- /dev/null +++ b/.repos/effect/.changeset/pre/dirty-laws-wear.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +handle missing output array in openai responses diff --git a/.repos/effect/.changeset/pre/doctest-console-output.md b/.repos/effect/.changeset/pre/doctest-console-output.md new file mode 100644 index 000000000..cd06cfddb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/document-child-process-env.md b/.repos/effect/.changeset/pre/document-child-process-env.md new file mode 100644 index 000000000..f28d72881 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/dry-bugs-hug.md b/.repos/effect/.changeset/pre/dry-bugs-hug.md new file mode 100644 index 000000000..723f24935 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/duration-temporal-object-input.md b/.repos/effect/.changeset/pre/duration-temporal-object-input.md new file mode 100644 index 000000000..faa02f671 --- /dev/null +++ b/.repos/effect/.changeset/pre/duration-temporal-object-input.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Add `DurationObject` to `Duration.Input` to support Temporal-style object input. + +Durations can now be created from objects with named unit properties like `{ hours: 1, minutes: 30 }`, similar to `Temporal.Duration.from()`. Supported fields: `weeks`, `days`, `hours`, `minutes`, `seconds`, `millis`, `micros`, `nanos`. diff --git a/.repos/effect/.changeset/pre/eager-coats-cheat.md b/.repos/effect/.changeset/pre/eager-coats-cheat.md new file mode 100644 index 000000000..d75b97ae8 --- /dev/null +++ b/.repos/effect/.changeset/pre/eager-coats-cheat.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +rename SqlSchema.findOne\* apis diff --git a/.repos/effect/.changeset/pre/early-birds-dream.md b/.repos/effect/.changeset/pre/early-birds-dream.md new file mode 100644 index 000000000..a6de03733 --- /dev/null +++ b/.repos/effect/.changeset/pre/early-birds-dream.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add trait for customizing exit codes diff --git a/.repos/effect/.changeset/pre/early-donuts-argue.md b/.repos/effect/.changeset/pre/early-donuts-argue.md new file mode 100644 index 000000000..fe1b70177 --- /dev/null +++ b/.repos/effect/.changeset/pre/early-donuts-argue.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix cache constructor inference by moving the lookup option diff --git a/.repos/effect/.changeset/pre/early-jobs-bow.md b/.repos/effect/.changeset/pre/early-jobs-bow.md new file mode 100644 index 000000000..f62abe015 --- /dev/null +++ b/.repos/effect/.changeset/pre/early-jobs-bow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Defer memoized Layer state installation until Effect execution. diff --git a/.repos/effect/.changeset/pre/early-peaches-check.md b/.repos/effect/.changeset/pre/early-peaches-check.md new file mode 100644 index 000000000..0782936ad --- /dev/null +++ b/.repos/effect/.changeset/pre/early-peaches-check.md @@ -0,0 +1,6 @@ +--- +"@effect/platform-node-shared": patch +"effect": patch +--- + +generate binary arrays from streams with less copying diff --git a/.repos/effect/.changeset/pre/eff-115-sync-scheduler-microtask.md b/.repos/effect/.changeset/pre/eff-115-sync-scheduler-microtask.md new file mode 100644 index 000000000..f0d120781 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-117-hydration-reactivity.md b/.repos/effect/.changeset/pre/eff-117-hydration-reactivity.md new file mode 100644 index 000000000..948003938 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-121-http-router-web-handler.md b/.repos/effect/.changeset/pre/eff-121-http-router-web-handler.md new file mode 100644 index 000000000..4576cfec2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-123-openai-compat-unknown-events.md b/.repos/effect/.changeset/pre/eff-123-openai-compat-unknown-events.md new file mode 100644 index 000000000..0d587a2b8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-137-web-stream-interop.md b/.repos/effect/.changeset/pre/eff-137-web-stream-interop.md new file mode 100644 index 000000000..d3f164a89 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-140-deno-crypto.md b/.repos/effect/.changeset/pre/eff-140-deno-crypto.md new file mode 100644 index 000000000..1ea8d267a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-141-deno-child-process.md b/.repos/effect/.changeset/pre/eff-141-deno-child-process.md new file mode 100644 index 000000000..746c3ff42 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-142-deno-terminal.md b/.repos/effect/.changeset/pre/eff-142-deno-terminal.md new file mode 100644 index 000000000..b7eb5a35a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-143-deno-stdio.md b/.repos/effect/.changeset/pre/eff-143-deno-stdio.md new file mode 100644 index 000000000..c46576d61 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-145-deno-services.md b/.repos/effect/.changeset/pre/eff-145-deno-services.md new file mode 100644 index 000000000..c9ef16084 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-148-deno-http-platform.md b/.repos/effect/.changeset/pre/eff-148-deno-http-platform.md new file mode 100644 index 000000000..62766839f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-151-deno-redis.md b/.repos/effect/.changeset/pre/eff-151-deno-redis.md new file mode 100644 index 000000000..292d3fed1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-153-deno-http-server.md b/.repos/effect/.changeset/pre/eff-153-deno-http-server.md new file mode 100644 index 000000000..a2e261164 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-153-websocket-initial-frames.md b/.repos/effect/.changeset/pre/eff-153-websocket-initial-frames.md new file mode 100644 index 000000000..1f1142a06 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-154-deno-cluster-http.md b/.repos/effect/.changeset/pre/eff-154-deno-cluster-http.md new file mode 100644 index 000000000..36d032895 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-155-deno-cluster-socket.md b/.repos/effect/.changeset/pre/eff-155-deno-cluster-socket.md new file mode 100644 index 000000000..b079d9319 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-162-browser-crypto-chunks.md b/.repos/effect/.changeset/pre/eff-162-browser-crypto-chunks.md new file mode 100644 index 000000000..fb93845c9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-170-bun-multipart-stream.md b/.repos/effect/.changeset/pre/eff-170-bun-multipart-stream.md new file mode 100644 index 000000000..ccc6b55a9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-210-cookie-validation.md b/.repos/effect/.changeset/pre/eff-210-cookie-validation.md new file mode 100644 index 000000000..7050ceb33 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-212-secure-mssql-transport.md b/.repos/effect/.changeset/pre/eff-212-secure-mssql-transport.md new file mode 100644 index 000000000..f662266f9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-216-secure-http-redirects.md b/.repos/effect/.changeset/pre/eff-216-secure-http-redirects.md new file mode 100644 index 000000000..fbfe9fde4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-218-bound-sse-pending-state.md b/.repos/effect/.changeset/pre/eff-218-bound-sse-pending-state.md new file mode 100644 index 000000000..be71140d5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-219-key-value-store-file-keys.md b/.repos/effect/.changeset/pre/eff-219-key-value-store-file-keys.md new file mode 100644 index 000000000..ea7b27d88 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-220-cli-control-characters.md b/.repos/effect/.changeset/pre/eff-220-cli-control-characters.md new file mode 100644 index 000000000..7073b7cb3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-332-http-response-compression.md b/.repos/effect/.changeset/pre/eff-332-http-response-compression.md new file mode 100644 index 000000000..831d4f07b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-337-preserve-mssql-parameters.md b/.repos/effect/.changeset/pre/eff-337-preserve-mssql-parameters.md new file mode 100644 index 000000000..903a8ed19 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-342-clickhouse-number-binding.md b/.repos/effect/.changeset/pre/eff-342-clickhouse-number-binding.md new file mode 100644 index 000000000..847d5b566 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-389-execution-plan-attempts.md b/.repos/effect/.changeset/pre/eff-389-execution-plan-attempts.md new file mode 100644 index 000000000..dc4262d0c --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-389-execution-plan-attempts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject zero execution attempts in `ExecutionPlan` steps. diff --git a/.repos/effect/.changeset/pre/eff-428-pg-transaction-permit.md b/.repos/effect/.changeset/pre/eff-428-pg-transaction-permit.md new file mode 100644 index 000000000..be6a9a20e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-467-execution-plan-events.md b/.repos/effect/.changeset/pre/eff-467-execution-plan-events.md new file mode 100644 index 000000000..eae1e3bec --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-477-schedule-concat.md b/.repos/effect/.changeset/pre/eff-477-schedule-concat.md new file mode 100644 index 000000000..a9bbb3d9c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-487-web-tracer-shutdown.md b/.repos/effect/.changeset/pre/eff-487-web-tracer-shutdown.md new file mode 100644 index 000000000..bc9dba25a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-51-partitioned-semaphore-interruption.md b/.repos/effect/.changeset/pre/eff-51-partitioned-semaphore-interruption.md new file mode 100644 index 000000000..3d36a756c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-523-registry-scoped-atom-runtime.md b/.repos/effect/.changeset/pre/eff-523-registry-scoped-atom-runtime.md new file mode 100644 index 000000000..2b7a28424 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-532-stdio-terminal.md b/.repos/effect/.changeset/pre/eff-532-stdio-terminal.md new file mode 100644 index 000000000..992ca220f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-537-preserve-response-metadata.md b/.repos/effect/.changeset/pre/eff-537-preserve-response-metadata.md new file mode 100644 index 000000000..606ead5b4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-542-rc-ref-generation.md b/.repos/effect/.changeset/pre/eff-542-rc-ref-generation.md new file mode 100644 index 000000000..a7e8aeb36 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-547-node-tracer-timeout.md b/.repos/effect/.changeset/pre/eff-547-node-tracer-timeout.md new file mode 100644 index 000000000..67198957e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-548-bun-serve-scope.md b/.repos/effect/.changeset/pre/eff-548-bun-serve-scope.md new file mode 100644 index 000000000..a2ae20dd5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-549-worker-send-error.md b/.repos/effect/.changeset/pre/eff-549-worker-send-error.md new file mode 100644 index 000000000..5bab011f5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-552-txqueue-shutdown.md b/.repos/effect/.changeset/pre/eff-552-txqueue-shutdown.md new file mode 100644 index 000000000..8e02adb77 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-554-sql-resolver.md b/.repos/effect/.changeset/pre/eff-554-sql-resolver.md new file mode 100644 index 000000000..a7581b87b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-558-deno-file-web-range.md b/.repos/effect/.changeset/pre/eff-558-deno-file-web-range.md new file mode 100644 index 000000000..e4027e6b4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-691-default-logger-ordering.md b/.repos/effect/.changeset/pre/eff-691-default-logger-ordering.md new file mode 100644 index 000000000..91fe0547d --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-691-default-logger-ordering.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve message item ordering in the default logger when logging a `Cause` with message values. diff --git a/.repos/effect/.changeset/pre/eff-693-rpcgroup-handler-deps.md b/.repos/effect/.changeset/pre/eff-693-rpcgroup-handler-deps.md new file mode 100644 index 000000000..e812b194a --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-693-rpcgroup-handler-deps.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `RpcGroup.toLayer` and `RpcGroup.toLayerHandler` service requirement inference so handler dependencies are preserved for non-stream RPC handlers. diff --git a/.repos/effect/.changeset/pre/eff-694-cli-completions-module.md b/.repos/effect/.changeset/pre/eff-694-cli-completions-module.md new file mode 100644 index 000000000..fcce2923d --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-694-cli-completions-module.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Expose CLI completions as a public unstable module at `effect/unstable/cli/Completions`. diff --git a/.repos/effect/.changeset/pre/eff-695-layer-mock-dual-api.md b/.repos/effect/.changeset/pre/eff-695-layer-mock-dual-api.md new file mode 100644 index 000000000..b4766bb3f --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-695-layer-mock-dual-api.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make `Layer.mock` a dual API so it supports both `Layer.mock(Service)(impl)` and `Layer.mock(Service, impl)`. diff --git a/.repos/effect/.changeset/pre/eff-697-rpcserialization-json-array-decode.md b/.repos/effect/.changeset/pre/eff-697-rpcserialization-json-array-decode.md new file mode 100644 index 000000000..062fcf863 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-697-rpcserialization-json-array-decode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `RpcSerialization.json` decode so JSON array payloads are not wrapped in an extra outer array. diff --git a/.repos/effect/.changeset/pre/eff-698-rpcserialization-unreachable-branch.md b/.repos/effect/.changeset/pre/eff-698-rpcserialization-unreachable-branch.md new file mode 100644 index 000000000..cb1b1de99 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-698-rpcserialization-unreachable-branch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove an unreachable array branch in `decodeJsonRpcRaw` to simplify JSON-RPC decode logic without changing behavior. diff --git a/.repos/effect/.changeset/pre/eff-700-httpapi-middleware-errors.md b/.repos/effect/.changeset/pre/eff-700-httpapi-middleware-errors.md new file mode 100644 index 000000000..ef46ad705 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-700-httpapi-middleware-errors.md @@ -0,0 +1,12 @@ +--- +"effect": patch +--- + +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: + - addHttpApi + API-level middleware applied across merged groups + - missing middleware service diagnostics + - missing addHttpApi group layer diagnostics diff --git a/.repos/effect/.changeset/pre/eff-701-httpapierror-respondable.md b/.repos/effect/.changeset/pre/eff-701-httpapierror-respondable.md new file mode 100644 index 000000000..9b78e505e --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-701-httpapierror-respondable.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make all built-in `HttpApiError` classes implement `HttpServerRespondable`, so they can be returned directly from plain HTTP server handlers outside of `HttpApi`. diff --git a/.repos/effect/.changeset/pre/eff-704-stream-merge-predicate.md b/.repos/effect/.changeset/pre/eff-704-stream-merge-predicate.md new file mode 100644 index 000000000..a31226089 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-704-stream-merge-predicate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use predicate-based `dual` dispatch for `Stream.merge` so data-last calls with optional `options` are handled correctly. diff --git a/.repos/effect/.changeset/pre/eff-705-layer-tap-apis.md b/.repos/effect/.changeset/pre/eff-705-layer-tap-apis.md new file mode 100644 index 000000000..c6b96bc0d --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-705-layer-tap-apis.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Layer.tap`, `Layer.tapError`, and `Layer.tapCause` APIs for effectful observation of layer success and failure without changing layer outputs. diff --git a/.repos/effect/.changeset/pre/eff-706-servicemap-mutate.md b/.repos/effect/.changeset/pre/eff-706-servicemap-mutate.md new file mode 100644 index 000000000..6ce63c443 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-706-servicemap-mutate.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/opentelemetry": patch +--- + +Refactor call sites with multiple `Context` mutations to use `Context.mutate` for batched updates. diff --git a/.repos/effect/.changeset/pre/eff-716-response-id-tracker-map.md b/.repos/effect/.changeset/pre/eff-716-response-id-tracker-map.md new file mode 100644 index 000000000..8605f1bc8 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-716-response-id-tracker-map.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use a normal Map in ResponseIdTracker and clear it on divergence / reset instead of reallocating a WeakMap. diff --git a/.repos/effect/.changeset/pre/eff-717-openai-socket-cancel.md b/.repos/effect/.changeset/pre/eff-717-openai-socket-cancel.md new file mode 100644 index 000000000..d4adf58a7 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-717-openai-socket-cancel.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Ensure OpenAiSocket sends a `{"type":"response.cancel"}` websocket event when a response stream is interrupted. diff --git a/.repos/effect/.changeset/pre/eff-718-embedding-model-surface.md b/.repos/effect/.changeset/pre/eff-718-embedding-model-surface.md new file mode 100644 index 000000000..e8754bd3e --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-718-embedding-model-surface.md @@ -0,0 +1,13 @@ +--- +"effect": patch +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +--- + +Add unstable EmbeddingModel support across core and OpenAI providers. + +- Add the unstable EmbeddingModel module API surface in `effect`, including service, request, response, and provider types. +- Implement the unstable EmbeddingModel runtime constructor in `effect`, with `RequestResolver` batching, `embed` / `embedMany` spans, provider error propagation, deterministic ordering, and empty-input `embedMany` fast-path behavior. +- Add and align EmbeddingModel behavior tests in `effect` for embedding usage, batching, ordering, and error handling. +- Add `OpenAiEmbeddingModel` in `@effect/ai-openai`, including model / make / layer constructors, config overrides, and provider output index validation with deterministic reordering. +- Add OpenAI-compatible EmbeddingModel provider support in `@effect/ai-openai-compat`, including config overrides, layer constructors, and output index validation. diff --git a/.repos/effect/.changeset/pre/eff-725-fix-catch-jsdoc.md b/.repos/effect/.changeset/pre/eff-725-fix-catch-jsdoc.md new file mode 100644 index 000000000..a5ca6b522 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-725-fix-catch-jsdoc.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix JSDoc wording for `Effect.catch` to consistently reference the current API name. diff --git a/.repos/effect/.changeset/pre/eff-726-model-dimensions.md b/.repos/effect/.changeset/pre/eff-726-model-dimensions.md new file mode 100644 index 000000000..1a6022bcf --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-726-model-dimensions.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +--- + +Add `EmbeddingModel.ModelDimensions` and require dimensions in embedding provider `model` constructors. diff --git a/.repos/effect/.changeset/pre/eff-727-cli-help-alignment.md b/.repos/effect/.changeset/pre/eff-727-cli-help-alignment.md new file mode 100644 index 000000000..32e9ada50 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-727-cli-help-alignment.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Align CLI help flag and global flag descriptions to a single column even when some flag names are very long. diff --git a/.repos/effect/.changeset/pre/eff-730-language-model-incremental-fallback.md b/.repos/effect/.changeset/pre/eff-730-language-model-incremental-fallback.md new file mode 100644 index 000000000..55fa6a0a2 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-730-language-model-incremental-fallback.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `LanguageModel` incremental prompt fallback to reliably retry with the full prompt when an incremental request fails with `InvalidRequestError`. diff --git a/.repos/effect/.changeset/pre/eff-736-cached-with-ttl.md b/.repos/effect/.changeset/pre/eff-736-cached-with-ttl.md new file mode 100644 index 000000000..8ef05290b --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-736-cached-with-ttl.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.cachedWithTTL` and `Effect.cachedInvalidateWithTTL` to start TTL expiration when the cached value is produced instead of when computation starts. diff --git a/.repos/effect/.changeset/pre/eff-738-cron-prev.md b/.repos/effect/.changeset/pre/eff-738-cron-prev.md new file mode 100644 index 000000000..b9109d4b7 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-738-cron-prev.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Backport `Cron.prev` with reverse lookup tables and cron stepping logic, including DST-aware reverse traversal. diff --git a/.repos/effect/.changeset/pre/eff-739-openai-function-call-done.md b/.repos/effect/.changeset/pre/eff-739-openai-function-call-done.md new file mode 100644 index 000000000..906931377 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-739-openai-function-call-done.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Handle streamed OpenAI function calls from `response.function_call_arguments.done` so tool calls are emitted even when `response.output_item.done` is missing. diff --git a/.repos/effect/.changeset/pre/eff-740-missing-summary-parts.md b/.repos/effect/.changeset/pre/eff-740-missing-summary-parts.md new file mode 100644 index 000000000..c481d830e --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-740-missing-summary-parts.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Fix OpenAI reasoning stream state handling so out-of-order reasoning summary events do not crash when prior reasoning item state is missing. diff --git a/.repos/effect/.changeset/pre/eff-742-http-client-request-web.md b/.repos/effect/.changeset/pre/eff-742-http-client-request-web.md new file mode 100644 index 000000000..47c201686 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-742-http-client-request-web.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +unstable/http HttpClientRequest: add toWeb and fromWeb conversions for web Request objects diff --git a/.repos/effect/.changeset/pre/eff-744-sqlite-migrator-lock.md b/.repos/effect/.changeset/pre/eff-744-sqlite-migrator-lock.md new file mode 100644 index 000000000..badee2a51 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-744-sqlite-migrator-lock.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix sql migrator lock handling to only treat duplicate migration-row inserts as a concurrent migration lock. diff --git a/.repos/effect/.changeset/pre/eff-746-fixed-iteration-catchup.md b/.repos/effect/.changeset/pre/eff-746-fixed-iteration-catchup.md new file mode 100644 index 000000000..db267d594 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-746-fixed-iteration-catchup.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schedule.fixed` to run the next iteration immediately when the previous action takes longer than the configured interval. diff --git a/.repos/effect/.changeset/pre/eff-747-unify-effect.md b/.repos/effect/.changeset/pre/eff-747-unify-effect.md new file mode 100644 index 000000000..251737c05 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-747-unify-effect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Unify.unify` so unions of `Effect` values collapse to a single unified `Effect` type again. diff --git a/.repos/effect/.changeset/pre/eff-754-url-builder-any.md b/.repos/effect/.changeset/pre/eff-754-url-builder-any.md new file mode 100644 index 000000000..9dbd7bbc3 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-754-url-builder-any.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Relax `HttpApiClient.urlBuilder` to accept `HttpApi.Any` instead of requiring `HttpApi.AnyWithProps`. +This allows use in helpers generic over `HttpApi.Any` while preserving inferred URL builder types. diff --git a/.repos/effect/.changeset/pre/eff-755-references-core.md b/.repos/effect/.changeset/pre/eff-755-references-core.md new file mode 100644 index 000000000..76715f96a --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-755-references-core.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Re-export additional core runtime references from `effect/References`, including logger and error reporter references. diff --git a/.repos/effect/.changeset/pre/eff-769-select-text-highlight.md b/.repos/effect/.changeset/pre/eff-769-select-text-highlight.md new file mode 100644 index 000000000..f69988f18 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-769-select-text-highlight.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Highlight active option labels in `Prompt.select` and `Prompt.multiSelect` using cyan text so selection state is visible beyond the pointer / checkbox icon. diff --git a/.repos/effect/.changeset/pre/eff-774-mutable-list-append-all-empty-array.md b/.repos/effect/.changeset/pre/eff-774-mutable-list-append-all-empty-array.md new file mode 100644 index 000000000..7a3ee8cf0 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-774-mutable-list-append-all-empty-array.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `MutableList.appendAll` / `appendAllUnsafe` so empty arrays are treated as a no-op instead of leaving behind an empty internal bucket. diff --git a/.repos/effect/.changeset/pre/eff-777-schema-make-effect.md b/.repos/effect/.changeset/pre/eff-777-schema-make-effect.md new file mode 100644 index 000000000..d80d6151e --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-777-schema-make-effect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `schema.makeEffect(input, options?)` to `Schema.Bottom` and schema-backed classes, matching the existing constructor behavior exposed by `makeUnsafe` / `makeOption` while returning an `Effect` failure with `Schema.SchemaError`. diff --git a/.repos/effect/.changeset/pre/eff-778-http-middleware-path-logger.md b/.repos/effect/.changeset/pre/eff-778-http-middleware-path-logger.md new file mode 100644 index 000000000..716bc4733 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-778-http-middleware-path-logger.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update the unstable HTTP middleware logger to annotate only the request path in `http.url` instead of including the full URL (query / fragment), and add a regression test. diff --git a/.repos/effect/.changeset/pre/eff-779-keyvaluestore-layer-sql.md b/.repos/effect/.changeset/pre/eff-779-keyvaluestore-layer-sql.md new file mode 100644 index 000000000..ce80278ff --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-779-keyvaluestore-layer-sql.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `KeyValueStore.layerSql` to back key-value storage with a SQL database via `SqlClient`. diff --git a/.repos/effect/.changeset/pre/eff-780-layer-unify.md b/.repos/effect/.changeset/pre/eff-780-layer-unify.md new file mode 100644 index 000000000..59bc0c2a4 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-780-layer-unify.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Unify.unify` so Layer unions merge correctly, and add type tests covering Layer unification. diff --git a/.repos/effect/.changeset/pre/eff-781-fix-stream-toqueue-types.md b/.repos/effect/.changeset/pre/eff-781-fix-stream-toqueue-types.md new file mode 100644 index 000000000..3f3ff719f --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-781-fix-stream-toqueue-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.toQueue` types and implementation to return a `Queue.Dequeue` in both overloads and delegate to `Channel.toQueueArray`. diff --git a/.repos/effect/.changeset/pre/eff-782-httpapi-status-literals.md b/.repos/effect/.changeset/pre/eff-782-httpapi-status-literals.md new file mode 100644 index 000000000..6eb4c1ec9 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-782-httpapi-status-literals.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add support for common HTTP status string literals in `HttpApiSchema.status` (for example, `HttpApiSchema.status("Created")` resolves to status code `201`). diff --git a/.repos/effect/.changeset/pre/eff-783-atom-http-api-errors.md b/.repos/effect/.changeset/pre/eff-783-atom-http-api-errors.md new file mode 100644 index 000000000..f62f2ae8f --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-783-atom-http-api-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `AtomHttpApi` query and mutation error inference to include endpoint middleware and client middleware errors, matching `HttpApiClient` behavior (including response-only mutation mode). diff --git a/.repos/effect/.changeset/pre/eff-798-schema-parser.md b/.repos/effect/.changeset/pre/eff-798-schema-parser.md new file mode 100644 index 000000000..e7ca179c9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-802-tagged-union-match-or-else.md b/.repos/effect/.changeset/pre/eff-802-tagged-union-match-or-else.md new file mode 100644 index 000000000..0336f28d2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-806-schema-error-stack.md b/.repos/effect/.changeset/pre/eff-806-schema-error-stack.md new file mode 100644 index 000000000..339f7f367 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-819-cluster-workflow-shard-groups.md b/.repos/effect/.changeset/pre/eff-819-cluster-workflow-shard-groups.md new file mode 100644 index 000000000..852c58339 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-819-cluster-workflow-shard-groups.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure ClusterWorkflowEngine routes durable clock wakeups and registered workflow deferred completions through the owning workflow's shard group. diff --git a/.repos/effect/.changeset/pre/eff-825-pool-performance.md b/.repos/effect/.changeset/pre/eff-825-pool-performance.md new file mode 100644 index 000000000..8f9d71792 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-825-pool-use.md b/.repos/effect/.changeset/pre/eff-825-pool-use.md new file mode 100644 index 000000000..ddea9d1b0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-825-scope-single-finalizer.md b/.repos/effect/.changeset/pre/eff-825-scope-single-finalizer.md new file mode 100644 index 000000000..fb77cd78c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-837-dependency-updates.md b/.repos/effect/.changeset/pre/eff-837-dependency-updates.md new file mode 100644 index 000000000..2d249ad2c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-849-transpose-option.md b/.repos/effect/.changeset/pre/eff-849-transpose-option.md new file mode 100644 index 000000000..ccdbd4281 --- /dev/null +++ b/.repos/effect/.changeset/pre/eff-849-transpose-option.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.transposeOption` for converting an `Option>` into an `Effect, E, R>`. diff --git a/.repos/effect/.changeset/pre/eff-851-skip-empty-remote-write.md b/.repos/effect/.changeset/pre/eff-851-skip-empty-remote-write.md new file mode 100644 index 000000000..95de0f05e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-861-indexeddb-equals.md b/.repos/effect/.changeset/pre/eff-861-indexeddb-equals.md new file mode 100644 index 000000000..c3f17f1c8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-946-concurrent-traversal-cleanup.md b/.repos/effect/.changeset/pre/eff-946-concurrent-traversal-cleanup.md new file mode 100644 index 000000000..1ae7fabb1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-952-terminal-failure-stack.md b/.repos/effect/.changeset/pre/eff-952-terminal-failure-stack.md new file mode 100644 index 000000000..5c72f8d56 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-953-interruptor-stack-trace.md b/.repos/effect/.changeset/pre/eff-953-interruptor-stack-trace.md new file mode 100644 index 000000000..aeca222b2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-955-run-sync-dispatcher.md b/.repos/effect/.changeset/pre/eff-955-run-sync-dispatcher.md new file mode 100644 index 000000000..22865608b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eff-956-await-all-children.md b/.repos/effect/.changeset/pre/eff-956-await-all-children.md new file mode 100644 index 000000000..8f892c153 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eight-turkeys-own.md b/.repos/effect/.changeset/pre/eight-turkeys-own.md new file mode 100644 index 000000000..1665cd0a4 --- /dev/null +++ b/.repos/effect/.changeset/pre/eight-turkeys-own.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-node": patch +"@effect/platform-bun": patch +"effect": patch +--- + +use cause annotations for detecting client aborts diff --git a/.repos/effect/.changeset/pre/eighty-lies-deny.md b/.repos/effect/.changeset/pre/eighty-lies-deny.md new file mode 100644 index 000000000..3b6e333ce --- /dev/null +++ b/.repos/effect/.changeset/pre/eighty-lies-deny.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +change default ErrorReporter severity to Info diff --git a/.repos/effect/.changeset/pre/eighty-poets-draw.md b/.repos/effect/.changeset/pre/eighty-poets-draw.md new file mode 100644 index 000000000..27a23ed37 --- /dev/null +++ b/.repos/effect/.changeset/pre/eighty-poets-draw.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 dedicated AiError metadata interfaces per reason so provider packages can safely augment metadata without conflicting module declarations. diff --git a/.repos/effect/.changeset/pre/eighty-swans-scream.md b/.repos/effect/.changeset/pre/eighty-swans-scream.md new file mode 100644 index 000000000..eff509203 --- /dev/null +++ b/.repos/effect/.changeset/pre/eighty-swans-scream.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add HttpApiSchemaError for determining where a schema error originates from diff --git a/.repos/effect/.changeset/pre/eighty-teeth-sniff.md b/.repos/effect/.changeset/pre/eighty-teeth-sniff.md new file mode 100644 index 000000000..4ce98bc8c --- /dev/null +++ b/.repos/effect/.changeset/pre/eighty-teeth-sniff.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +simplify Filter by removing Args type parameter diff --git a/.repos/effect/.changeset/pre/eleven-apes-share.md b/.repos/effect/.changeset/pre/eleven-apes-share.md new file mode 100644 index 000000000..4e63ebc13 --- /dev/null +++ b/.repos/effect/.changeset/pre/eleven-apes-share.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Add a new `OpenAiClientGenerated` module that exposes the generated OpenAI client as a dedicated context service with `make`, `layer`, and `layerConfig` constructors. This provides a compatibility path for direct generated-client access while preserving existing auth, base URL, header, and `OpenAiConfig.transformClient` wiring. diff --git a/.repos/effect/.changeset/pre/eleven-numbers-bake.md b/.repos/effect/.changeset/pre/eleven-numbers-bake.md new file mode 100644 index 000000000..7966ef89b --- /dev/null +++ b/.repos/effect/.changeset/pre/eleven-numbers-bake.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +pass workflow parent on discard diff --git a/.repos/effect/.changeset/pre/empty-env-values-missing.md b/.repos/effect/.changeset/pre/empty-env-values-missing.md new file mode 100644 index 000000000..cd894f95d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/empty-geckos-dispatch.md b/.repos/effect/.changeset/pre/empty-geckos-dispatch.md new file mode 100644 index 000000000..e082b11e5 --- /dev/null +++ b/.repos/effect/.changeset/pre/empty-geckos-dispatch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Encode SSE events with empty data as dispatchable events. diff --git a/.repos/effect/.changeset/pre/empty-gifts-beg.md b/.repos/effect/.changeset/pre/empty-gifts-beg.md new file mode 100644 index 000000000..c7d39823c --- /dev/null +++ b/.repos/effect/.changeset/pre/empty-gifts-beg.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +process schema properties / elements concurrently diff --git a/.repos/effect/.changeset/pre/empty-http-rpc-client.md b/.repos/effect/.changeset/pre/empty-http-rpc-client.md new file mode 100644 index 000000000..36685f7ee --- /dev/null +++ b/.repos/effect/.changeset/pre/empty-http-rpc-client.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fail RpcClient HTTP requests when the server response contains no RPC messages instead of leaving requests pending. diff --git a/.repos/effect/.changeset/pre/empty-snakes-return.md b/.repos/effect/.changeset/pre/empty-snakes-return.md new file mode 100644 index 000000000..94ae6a96f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/escape-completion-choice-values.md b/.repos/effect/.changeset/pre/escape-completion-choice-values.md new file mode 100644 index 000000000..2b3433363 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/eventlog-unencrypted.md b/.repos/effect/.changeset/pre/eventlog-unencrypted.md new file mode 100644 index 000000000..2a3e8a8e8 --- /dev/null +++ b/.repos/effect/.changeset/pre/eventlog-unencrypted.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add EventLogServerUnencrypted module diff --git a/.repos/effect/.changeset/pre/every-olives-burn.md b/.repos/effect/.changeset/pre/every-olives-burn.md new file mode 100644 index 000000000..87e34c2c8 --- /dev/null +++ b/.repos/effect/.changeset/pre/every-olives-burn.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +change default operation in redis from LPUSH TO RPUSH diff --git a/.repos/effect/.changeset/pre/expand-schema-filter-output.md b/.repos/effect/.changeset/pre/expand-schema-filter-output.md new file mode 100644 index 000000000..c269804bd --- /dev/null +++ b/.repos/effect/.changeset/pre/expand-schema-filter-output.md @@ -0,0 +1,24 @@ +--- +"effect": patch +--- + +Schema: expand `FilterOutput` and add `FilterIssue` for richer filter failures. + +The return type of a `Schema.makeFilter` predicate now supports two additional shapes: + +- `{ path, issue }` where `issue` is `string | SchemaIssue.Issue` (previously only `{ path, message: string }` was accepted). The `issue` arm lets you attach a fully-formed `Issue` at a nested path without manually constructing a `Pointer`. +- `ReadonlyArray` to report several failures at once. An empty array is success, a single-element array is equivalent to returning that element, and multi-entry arrays are grouped into an `Issue.Composite`. This removes the need to import `SchemaIssue` and hand-build a `Composite` for multi-field validators. + +The single-failure shapes (`undefined`, `true`, `false`, `string`, `SchemaIssue.Issue`) are unchanged. + +**Breaking**: the object shape renamed from `{ path, message }` to `{ path, issue }`. Call sites that used the old shape must rename the field; the migration is mechanical. + +```ts +// before +Schema.makeFilter((o) => ({ path: ["a"], message: "bad" })) + +// after +Schema.makeFilter((o) => ({ path: ["a"], issue: "bad" })) +``` + +Also renamed `{ path, message }` to `{ path, issue }` in the accepted return type of `SchemaGetter.checkEffect`. diff --git a/.repos/effect/.changeset/pre/explicit-env-record.md b/.repos/effect/.changeset/pre/explicit-env-record.md new file mode 100644 index 000000000..2045bbd20 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/explicit-otel-service-identity.md b/.repos/effect/.changeset/pre/explicit-otel-service-identity.md new file mode 100644 index 000000000..a34030e33 --- /dev/null +++ b/.repos/effect/.changeset/pre/explicit-otel-service-identity.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prefer explicit OTLP resource configuration over environment configuration. diff --git a/.repos/effect/.changeset/pre/export-schema-encode-keys-interface.md b/.repos/effect/.changeset/pre/export-schema-encode-keys-interface.md new file mode 100644 index 000000000..2290cd688 --- /dev/null +++ b/.repos/effect/.changeset/pre/export-schema-encode-keys-interface.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Export the `Schema.encodeKeys` interface, closes #2070. + +Previously the interface was internal, so exporting a value whose inferred type referenced it triggered TypeScript error `TS4023: Exported variable has or is using name 'encodeKeys' from external module ... but cannot be named`, e.g.: diff --git a/.repos/effect/.changeset/pre/expose-ai-prompt-part-schemas.md b/.repos/effect/.changeset/pre/expose-ai-prompt-part-schemas.md new file mode 100644 index 000000000..b29af5e94 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/extract-semaphore-latch.md b/.repos/effect/.changeset/pre/extract-semaphore-latch.md new file mode 100644 index 000000000..755512bcc --- /dev/null +++ b/.repos/effect/.changeset/pre/extract-semaphore-latch.md @@ -0,0 +1,10 @@ +--- +"effect": patch +--- + +Extract `Semaphore` and `Latch` into their own modules. + +`Semaphore.make` / `Semaphore.makeUnsafe` replace `Effect.makeSemaphore` / `Effect.makeSemaphoreUnsafe`. +`Latch.make` / `Latch.makeUnsafe` replace `Effect.makeLatch` / `Effect.makeLatchUnsafe`. + +Merge `PartitionedSemaphore` into `Semaphore` as `Semaphore.Partitioned`, `Semaphore.makePartitioned`, `Semaphore.makePartitionedUnsafe`. diff --git a/.repos/effect/.changeset/pre/failed-otlp-checkpoints.md b/.repos/effect/.changeset/pre/failed-otlp-checkpoints.md new file mode 100644 index 000000000..8e806105d --- /dev/null +++ b/.repos/effect/.changeset/pre/failed-otlp-checkpoints.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve OTLP metric delta checkpoints when an export fails. diff --git a/.repos/effect/.changeset/pre/fair-bees-relax.md b/.repos/effect/.changeset/pre/fair-bees-relax.md new file mode 100644 index 000000000..4e711ae3e --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-bees-relax.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Fix missing `yield*` in `OpenAiLanguageModel.prepareResponseFormat` diff --git a/.repos/effect/.changeset/pre/fair-birds-limit.md b/.repos/effect/.changeset/pre/fair-birds-limit.md new file mode 100644 index 000000000..c72957459 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fair-buttons-share.md b/.repos/effect/.changeset/pre/fair-buttons-share.md new file mode 100644 index 000000000..397f1293e --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-buttons-share.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Make `AtomRpc.query` and `AtomHttpApi.query` return serializable atoms by default when query results are schema-backed. + +The atom serialization key now uses each API's built-in request schemas so dehydrated state can be keyed consistently across server and client. diff --git a/.repos/effect/.changeset/pre/fair-citations-stream.md b/.repos/effect/.changeset/pre/fair-citations-stream.md new file mode 100644 index 000000000..49f8e0ab1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fair-cooks-stop.md b/.repos/effect/.changeset/pre/fair-cooks-stop.md new file mode 100644 index 000000000..9aabf7f8f --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-cooks-stop.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schedule.jittered` to randomize schedule delays between 80% and 120% of the original delay. diff --git a/.repos/effect/.changeset/pre/fair-cups-train.md b/.repos/effect/.changeset/pre/fair-cups-train.md new file mode 100644 index 000000000..d72aa2f9f --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-cups-train.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `TxPubSub.publish` and `TxPubSub.publishAll` overloads to require `Effect.Transaction` in their return environment. diff --git a/.repos/effect/.changeset/pre/fair-dryers-speak.md b/.repos/effect/.changeset/pre/fair-dryers-speak.md new file mode 100644 index 000000000..cef623e1f --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-dryers-speak.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpClientRequest.bodyFormDataRecord` and `HttpBody.makeFormDataRecord` helpers for creating multipart form bodies from plain records. diff --git a/.repos/effect/.changeset/pre/fair-forks-shake.md b/.repos/effect/.changeset/pre/fair-forks-shake.md new file mode 100644 index 000000000..395d1ec43 --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-forks-shake.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix unstable CLI boolean flags so `Flag.optional(Flag.boolean(...))` returns `Option.none()` when omitted, and support canonical `--no-` negation for boolean flags. diff --git a/.repos/effect/.changeset/pre/fair-jobs-like.md b/.repos/effect/.changeset/pre/fair-jobs-like.md new file mode 100644 index 000000000..e98587334 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fair-logs-correlate.md b/.repos/effect/.changeset/pre/fair-logs-correlate.md new file mode 100644 index 000000000..dacdd7b02 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fair-logs-listen.md b/.repos/effect/.changeset/pre/fair-logs-listen.md new file mode 100644 index 000000000..6608c2236 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fair-pandas-prove.md b/.repos/effect/.changeset/pre/fair-pandas-prove.md new file mode 100644 index 000000000..d88f5fbc4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-pandas-prove.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Changed socket close handling so all close codes are treated as errors by default unless `closeCodeIsError` is overridden. diff --git a/.repos/effect/.changeset/pre/fair-pants-float.md b/.repos/effect/.changeset/pre/fair-pants-float.md new file mode 100644 index 000000000..020c76aff --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-pants-float.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix a race in `Semaphore.take` where interruption could leak permits after a waiter was resumed. diff --git a/.repos/effect/.changeset/pre/fair-poems-visit.md b/.repos/effect/.changeset/pre/fair-poems-visit.md new file mode 100644 index 000000000..3aa7a731d --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-poems-visit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpServerRequest.toClientRequest` for direct server-to-client request conversion. diff --git a/.repos/effect/.changeset/pre/fair-sampling-content.md b/.repos/effect/.changeset/pre/fair-sampling-content.md new file mode 100644 index 000000000..d88205d8f --- /dev/null +++ b/.repos/effect/.changeset/pre/fair-sampling-content.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve MCP sampling request preferences and response content. diff --git a/.repos/effect/.changeset/pre/fair-sinks-catch.md b/.repos/effect/.changeset/pre/fair-sinks-catch.md new file mode 100644 index 000000000..6ea07ee3e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/famous-loops-flow.md b/.repos/effect/.changeset/pre/famous-loops-flow.md new file mode 100644 index 000000000..ac6581223 --- /dev/null +++ b/.repos/effect/.changeset/pre/famous-loops-flow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Check symbol-keyed properties in Match object patterns. diff --git a/.repos/effect/.changeset/pre/famous-wolves-lead.md b/.repos/effect/.changeset/pre/famous-wolves-lead.md new file mode 100644 index 000000000..19f2bbad4 --- /dev/null +++ b/.repos/effect/.changeset/pre/famous-wolves-lead.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +don't omit reasoning from openai config diff --git a/.repos/effect/.changeset/pre/fancy-glasses-grow.md b/.repos/effect/.changeset/pre/fancy-glasses-grow.md new file mode 100644 index 000000000..1f55db96a --- /dev/null +++ b/.repos/effect/.changeset/pre/fancy-glasses-grow.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +add IndexedDb modules diff --git a/.repos/effect/.changeset/pre/fast-graph-path-queues.md b/.repos/effect/.changeset/pre/fast-graph-path-queues.md new file mode 100644 index 000000000..66d188167 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fast-times-camp.md b/.repos/effect/.changeset/pre/fast-times-camp.md new file mode 100644 index 000000000..f44c6cb8b --- /dev/null +++ b/.repos/effect/.changeset/pre/fast-times-camp.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allocate less effects when reading a file diff --git a/.repos/effect/.changeset/pre/few-birds-matter.md b/.repos/effect/.changeset/pre/few-birds-matter.md new file mode 100644 index 000000000..66fdca809 --- /dev/null +++ b/.repos/effect/.changeset/pre/few-birds-matter.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Allow custom request properties in openai-compat model config and chat request types, and forward model-level custom properties to chat-completions payloads. diff --git a/.repos/effect/.changeset/pre/few-cougars-dig.md b/.repos/effect/.changeset/pre/few-cougars-dig.md new file mode 100644 index 000000000..86055e698 --- /dev/null +++ b/.repos/effect/.changeset/pre/few-cougars-dig.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Effect.abortSignal diff --git a/.repos/effect/.changeset/pre/few-foxes-grin.md b/.repos/effect/.changeset/pre/few-foxes-grin.md new file mode 100644 index 000000000..8d7631f5c --- /dev/null +++ b/.repos/effect/.changeset/pre/few-foxes-grin.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Command.withExamples` to attach concrete usage examples to CLI commands, expose them through `HelpDoc.examples`, and render them in the default help formatter. diff --git a/.repos/effect/.changeset/pre/few-mirrors-pull.md b/.repos/effect/.changeset/pre/few-mirrors-pull.md new file mode 100644 index 000000000..dd1bee297 --- /dev/null +++ b/.repos/effect/.changeset/pre/few-mirrors-pull.md @@ -0,0 +1,14 @@ +--- +"effect": patch +--- + +Make CLI global settings directly yieldable and simplify built-in names. + +`GlobalFlag.setting` now takes `{ flag, defaultValue }` and returns a setting that is a `Context.Reference`, so handlers and `Command.provide*` effects can `yield*` global setting values directly. + +Built-in settings keep internal behavior in `runWith` (for example, `--log-level` still configures `References.MinimumLogLevel`) while also being readable as values. + +Also renamed built-in globals: + +- `GlobalFlag.CompletionsFlag` -> `GlobalFlag.Completions` +- `GlobalFlag.LogLevelFlag` -> `GlobalFlag.LogLevel` diff --git a/.repos/effect/.changeset/pre/few-socks-poke.md b/.repos/effect/.changeset/pre/few-socks-poke.md new file mode 100644 index 000000000..cdc249ea7 --- /dev/null +++ b/.repos/effect/.changeset/pre/few-socks-poke.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Accept both `in-memory` and `in_memory` for OpenAI `prompt_cache_retention` schema fields. diff --git a/.repos/effect/.changeset/pre/fiber-join-all-errors.md b/.repos/effect/.changeset/pre/fiber-join-all-errors.md new file mode 100644 index 000000000..ac883aedc --- /dev/null +++ b/.repos/effect/.changeset/pre/fiber-join-all-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve input fiber error types in `Fiber.joinAll`. diff --git a/.repos/effect/.changeset/pre/fiber-runtime-start-metrics.md b/.repos/effect/.changeset/pre/fiber-runtime-start-metrics.md new file mode 100644 index 000000000..212fc6493 --- /dev/null +++ b/.repos/effect/.changeset/pre/fiber-runtime-start-metrics.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Record fiber runtime start metrics when fibers are constructed so yielded fibers are only counted once. diff --git a/.repos/effect/.changeset/pre/fiery-jokes-care.md b/.repos/effect/.changeset/pre/fiery-jokes-care.md new file mode 100644 index 000000000..0093f2318 --- /dev/null +++ b/.repos/effect/.changeset/pre/fiery-jokes-care.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix Entity.keepAlive diff --git a/.repos/effect/.changeset/pre/fiery-mammals-call.md b/.repos/effect/.changeset/pre/fiery-mammals-call.md new file mode 100644 index 000000000..2fce3b1f4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fiery-mammals-call.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure tagged enum \_tag is correctly set diff --git a/.repos/effect/.changeset/pre/file-prompt-jk-filter.md b/.repos/effect/.changeset/pre/file-prompt-jk-filter.md new file mode 100644 index 000000000..d4d204e6b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fine-walls-decide.md b/.repos/effect/.changeset/pre/fine-walls-decide.md new file mode 100644 index 000000000..7f6e67dfc --- /dev/null +++ b/.repos/effect/.changeset/pre/fine-walls-decide.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +don't remove SIGINT listener until fiber exit diff --git a/.repos/effect/.changeset/pre/first-success-of.md b/.repos/effect/.changeset/pre/first-success-of.md new file mode 100644 index 000000000..e31e6792a --- /dev/null +++ b/.repos/effect/.changeset/pre/first-success-of.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Port `Effect.firstSuccessOf` from Effect v3. diff --git a/.repos/effect/.changeset/pre/five-parents-relax.md b/.repos/effect/.changeset/pre/five-parents-relax.md new file mode 100644 index 000000000..15192e8dc --- /dev/null +++ b/.repos/effect/.changeset/pre/five-parents-relax.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +allow customizing idb durability diff --git a/.repos/effect/.changeset/pre/five-worms-rhyme.md b/.repos/effect/.changeset/pre/five-worms-rhyme.md new file mode 100644 index 000000000..01707d165 --- /dev/null +++ b/.repos/effect/.changeset/pre/five-worms-rhyme.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix eventlog skipping entries diff --git a/.repos/effect/.changeset/pre/fix-1332.md b/.repos/effect/.changeset/pre/fix-1332.md new file mode 100644 index 000000000..4ab4fa123 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-1332.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: avoid eager resolution for type-level helpers, closes #1332 diff --git a/.repos/effect/.changeset/pre/fix-1917.md b/.repos/effect/.changeset/pre/fix-1917.md new file mode 100644 index 000000000..f7cf342ee --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-1917.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Simplify and align the default-value APIs. + +`Schema.withConstructorDefault` now accepts an `Effect` instead of `(o: Option) => Option | Effect>`. + +`Schema.withDecodingDefault` / `Schema.withDecodingDefaultKey` now accept an `Effect` instead of `() => T`, enabling effectful defaults. + +`SchemaGetter.withDefault` follows the same change, accepting `Effect` instead of `() => T`. diff --git a/.repos/effect/.changeset/pre/fix-1927.md b/.repos/effect/.changeset/pre/fix-1927.md new file mode 100644 index 000000000..7882a2191 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-1927.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reuse existing references when duplicate identifiers have the same representation, closes #1927. diff --git a/.repos/effect/.changeset/pre/fix-1940.md b/.repos/effect/.changeset/pre/fix-1940.md new file mode 100644 index 000000000..be9ef1a1f --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-1940.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `ErrorClass` and `TaggedErrorClass` `toString` to match native `Error` output format (e.g. `E: my message` instead of `E({"message":"my message"})`), closes #1940. + +Also fix prototype properties (e.g. `name`) being lost after `.extend()`. diff --git a/.repos/effect/.changeset/pre/fix-1947.md b/.repos/effect/.changeset/pre/fix-1947.md new file mode 100644 index 000000000..f5cd72ff7 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-1947.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Add `Schema.resolveAnnotationsKey` API to retrieve the context (key-level) annotations from a schema, closes #1947. + +Also rename `Schema.resolveInto` to `Schema.resolveAnnotations`. diff --git a/.repos/effect/.changeset/pre/fix-2002.md b/.repos/effect/.changeset/pre/fix-2002.md new file mode 100644 index 000000000..a2de20318 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2002.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.splitLines` to correctly handle standalone `\r` as a line terminator and flush the final unterminated line when the stream ends, closes #2002. diff --git a/.repos/effect/.changeset/pre/fix-2012.md b/.repos/effect/.changeset/pre/fix-2012.md new file mode 100644 index 000000000..6b7ae930a --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2012.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add withDecodingDefaultTypeKey / withDecodingDefaultType, closes #2012 diff --git a/.repos/effect/.changeset/pre/fix-2015.md b/.repos/effect/.changeset/pre/fix-2015.md new file mode 100644 index 000000000..067530685 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2015.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: allow `Class` constructors to accept `void` when all fields are optional, closes #2015. diff --git a/.repos/effect/.changeset/pre/fix-2260.md b/.repos/effect/.changeset/pre/fix-2260.md new file mode 100644 index 000000000..dddb2c1e4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2260.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add JSON Schema custom annotation passthrough option, closes #2260 diff --git a/.repos/effect/.changeset/pre/fix-2268.md b/.repos/effect/.changeset/pre/fix-2268.md new file mode 100644 index 000000000..b0977d4a1 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2268.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: reintroduce `.value` on `Schema.Array` and `Schema.NonEmptyArray` for consistency with other collection wrappers (`Chunk`, `HashSet`, etc.), closes #2268. diff --git a/.repos/effect/.changeset/pre/fix-2271.md b/.repos/effect/.changeset/pre/fix-2271.md new file mode 100644 index 000000000..1be94c36b --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2271.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Clarify that `Data.$is(tag)` only checks the `_tag` field, not the full structure, closes #2271. diff --git a/.repos/effect/.changeset/pre/fix-2384.md b/.repos/effect/.changeset/pre/fix-2384.md new file mode 100644 index 000000000..649490295 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2384.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +`Config.withDefault` now only recovers from missing data for literal/union +schemas. Invalid present values now propagate validation errors instead of +using the default, closes #2384. diff --git a/.repos/effect/.changeset/pre/fix-2414.md b/.repos/effect/.changeset/pre/fix-2414.md new file mode 100644 index 000000000..984fba98a --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2414.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Derive template literal arbitraries from encoded parts, closes #2414. diff --git a/.repos/effect/.changeset/pre/fix-2419.md b/.repos/effect/.changeset/pre/fix-2419.md new file mode 100644 index 000000000..ec9b13b85 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-2419.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow schema class `.extend` to accept a `Struct` and preserve checks from the extension schema, closes #2419. diff --git a/.repos/effect/.changeset/pre/fix-2497.md b/.repos/effect/.changeset/pre/fix-2497.md new file mode 100644 index 000000000..4425db712 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-2499.md b/.repos/effect/.changeset/pre/fix-2499.md new file mode 100644 index 000000000..9e26f847e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-6464.md b/.repos/effect/.changeset/pre/fix-6464.md new file mode 100644 index 000000000..13d7b52d2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-6491.md b/.repos/effect/.changeset/pre/fix-6491.md new file mode 100644 index 000000000..a37e2d0cc --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-6491.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve nested class construction when applying constructor defaults, closes #6491. diff --git a/.repos/effect/.changeset/pre/fix-6521.md b/.repos/effect/.changeset/pre/fix-6521.md new file mode 100644 index 000000000..ca7c85ad3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-ai-empty-params-structured-output.md b/.repos/effect/.changeset/pre/fix-ai-empty-params-structured-output.md new file mode 100644 index 000000000..aa3806870 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-ai-empty-params-structured-output.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve unstable AI structured output handling for empty tool params and add `Tool.EmptyParams`, closes #1749. diff --git a/.repos/effect/.changeset/pre/fix-ai-text-toolkit-typing.md b/.repos/effect/.changeset/pre/fix-ai-text-toolkit-typing.md new file mode 100644 index 000000000..da37eea4c --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-ai-text-toolkit-typing.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix AI text method toolkit typing to support generic handler toolkits, preserve toolkit union inference, and keep response part narrowing by tool name. diff --git a/.repos/effect/.changeset/pre/fix-ai-tool-call-id.md b/.repos/effect/.changeset/pre/fix-ai-tool-call-id.md new file mode 100644 index 000000000..42d87284a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-ansi-cursor-to.md b/.repos/effect/.changeset/pre/fix-ansi-cursor-to.md new file mode 100644 index 000000000..36309f832 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-anthropic-caller-toolid.md b/.repos/effect/.changeset/pre/fix-anthropic-caller-toolid.md new file mode 100644 index 000000000..866bc4dc8 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-anthropic-caller-toolid.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Fix non-streaming Anthropic responses throwing when a tool call carries `caller` metadata. The mapper emitted `caller.toolId: undefined`, but `ProviderMetadata` is `Record(String, NullOr(Json))` and `undefined` is not a valid Json value, so decoding the model's own response threw `Expected JSON value`. Emit `null` instead, matching the streaming mappers. diff --git a/.repos/effect/.changeset/pre/fix-anthropic-code-execution-deltas.md b/.repos/effect/.changeset/pre/fix-anthropic-code-execution-deltas.md new file mode 100644 index 000000000..a50a451a2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-anthropic-header-redaction.md b/.repos/effect/.changeset/pre/fix-anthropic-header-redaction.md new file mode 100644 index 000000000..3a8bd80b7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-anthropic-memory-tool-requires-handler.md b/.repos/effect/.changeset/pre/fix-anthropic-memory-tool-requires-handler.md new file mode 100644 index 000000000..f9bd9843f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-anthropic-memory-tool.md b/.repos/effect/.changeset/pre/fix-anthropic-memory-tool.md new file mode 100644 index 000000000..3a9a9cdbc --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-anthropic-plaintext-bytes.md b/.repos/effect/.changeset/pre/fix-anthropic-plaintext-bytes.md new file mode 100644 index 000000000..5d6c5740e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-array-non-finite-indexes.md b/.repos/effect/.changeset/pre/fix-array-non-finite-indexes.md new file mode 100644 index 000000000..dcfd9db0f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-atom-batch-dependencies.md b/.repos/effect/.changeset/pre/fix-atom-batch-dependencies.md new file mode 100644 index 000000000..5c1ad7071 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-atom-kvs-async-write.md b/.repos/effect/.changeset/pre/fix-atom-kvs-async-write.md new file mode 100644 index 000000000..2227e7aed --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-atom-suspense-registry-cache.md b/.repos/effect/.changeset/pre/fix-atom-suspense-registry-cache.md new file mode 100644 index 000000000..220407192 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-bash-subcommand-dispatch.md b/.repos/effect/.changeset/pre/fix-bash-subcommand-dispatch.md new file mode 100644 index 000000000..c43cfc220 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-bigint-gcd-lcm.md b/.repos/effect/.changeset/pre/fix-bigint-gcd-lcm.md new file mode 100644 index 000000000..bfffa0484 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cache-set-race.md b/.repos/effect/.changeset/pre/fix-cache-set-race.md new file mode 100644 index 000000000..be4cf636d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-catch-orelse-error-erasure.md b/.repos/effect/.changeset/pre/fix-catch-orelse-error-erasure.md new file mode 100644 index 000000000..0f4846bf3 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-catch-orelse-error-erasure.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fixed the `catch*` combinators silently dropping unhandled error types diff --git a/.repos/effect/.changeset/pre/fix-cause-map-annotations.md b/.repos/effect/.changeset/pre/fix-cause-map-annotations.md new file mode 100644 index 000000000..efc7e791f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-channel-effects-finalizer.md b/.repos/effect/.changeset/pre/fix-channel-effects-finalizer.md new file mode 100644 index 000000000..4c470c7fb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-channel-schema-decode-unknown.md b/.repos/effect/.changeset/pre/fix-channel-schema-decode-unknown.md new file mode 100644 index 000000000..c7ee9a2ba --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-chunk-fractional-counts.md b/.repos/effect/.changeset/pre/fix-chunk-fractional-counts.md new file mode 100644 index 000000000..0fae65c5a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-class-constructor-defaults.md b/.repos/effect/.changeset/pre/fix-class-constructor-defaults.md new file mode 100644 index 000000000..8e9e824b1 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-class-constructor-defaults.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: rename `MakeOptions.disableValidation` to `disableChecks`. Apply constructor defaults when `disableChecks` is true, closes #1841. diff --git a/.repos/effect/.changeset/pre/fix-cli-help-table-spacing.md b/.repos/effect/.changeset/pre/fix-cli-help-table-spacing.md new file mode 100644 index 000000000..e8b5ef302 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cli-missing-flag-values.md b/.repos/effect/.changeset/pre/fix-cli-missing-flag-values.md new file mode 100644 index 000000000..0dfb373f9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cli-mixed-global-flag-context.md b/.repos/effect/.changeset/pre/fix-cli-mixed-global-flag-context.md new file mode 100644 index 000000000..73ab0d1e4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-cli-mixed-global-flag-context.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `Command.withGlobalFlags` type inference when mixing `GlobalFlag.action` and `GlobalFlag.setting`. + +`Setting` service identifiers are now correctly removed from command requirements in mixed global flag arrays. diff --git a/.repos/effect/.changeset/pre/fix-cli-subcommands-requirements.md b/.repos/effect/.changeset/pre/fix-cli-subcommands-requirements.md new file mode 100644 index 000000000..563526731 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cli-unexpected-arguments.md b/.repos/effect/.changeset/pre/fix-cli-unexpected-arguments.md new file mode 100644 index 000000000..2f6b775cd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-clickhouse-connect-timeout.md b/.repos/effect/.changeset/pre/fix-clickhouse-connect-timeout.md new file mode 100644 index 000000000..41a80e369 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cluster-entity-context-bleed.md b/.repos/effect/.changeset/pre/fix-cluster-entity-context-bleed.md new file mode 100644 index 000000000..c9037d3d5 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-cluster-entity-context-bleed.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use registration context for cluster entities diff --git a/.repos/effect/.changeset/pre/fix-cluster-reply-defect-isolation.md b/.repos/effect/.changeset/pre/fix-cluster-reply-defect-isolation.md new file mode 100644 index 000000000..62f0d8472 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cluster-shutdown-deadlock.md b/.repos/effect/.changeset/pre/fix-cluster-shutdown-deadlock.md new file mode 100644 index 000000000..50649c4a0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cluster-strand-request-shutdown.md b/.repos/effect/.changeset/pre/fix-cluster-strand-request-shutdown.md new file mode 100644 index 000000000..886d22f7e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cluster-stream-recovery.md b/.repos/effect/.changeset/pre/fix-cluster-stream-recovery.md new file mode 100644 index 000000000..7c54c6381 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-config-array-default.md b/.repos/effect/.changeset/pre/fix-config-array-default.md new file mode 100644 index 000000000..129e3d182 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-config-array-default.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Config.schema` so missing array values are treated as missing data, allowing `Config.withDefault` to apply. diff --git a/.repos/effect/.changeset/pre/fix-config-or-else-evidence.md b/.repos/effect/.changeset/pre/fix-config-or-else-evidence.md new file mode 100644 index 000000000..ced02896e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-config-withDefault.md b/.repos/effect/.changeset/pre/fix-config-withDefault.md new file mode 100644 index 000000000..a0ce253d7 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-config-withDefault.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Config.withDefault` type inference, closes #1530. diff --git a/.repos/effect/.changeset/pre/fix-config-withdefault-filter.md b/.repos/effect/.changeset/pre/fix-config-withdefault-filter.md new file mode 100644 index 000000000..ddddc9442 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-config-withdefault-filter.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +`Config.withDefault` no longer recovers from schema filter failures. A filter +failure means a present value reached refinement checks, so using the default +could hide invalid configuration values. diff --git a/.repos/effect/.changeset/pre/fix-context-add-or-omit-types.md b/.repos/effect/.changeset/pre/fix-context-add-or-omit-types.md new file mode 100644 index 000000000..f05f8bb40 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cron-and-representations.md b/.repos/effect/.changeset/pre/fix-cron-and-representations.md new file mode 100644 index 000000000..064eeb3ee --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cron-make-validation.md b/.repos/effect/.changeset/pre/fix-cron-make-validation.md new file mode 100644 index 000000000..5f9e350f0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cron-next-missing-day-overflow.md b/.repos/effect/.changeset/pre/fix-cron-next-missing-day-overflow.md new file mode 100644 index 000000000..6b392d177 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-cron-next-missing-day-overflow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Cron.next` skipping earlier matching days when the upcoming day-of-month does not exist in the current month. diff --git a/.repos/effect/.changeset/pre/fix-cron-parser-semantics.md b/.repos/effect/.changeset/pre/fix-cron-parser-semantics.md new file mode 100644 index 000000000..840d6f06b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cron-prev-month-rollover.md b/.repos/effect/.changeset/pre/fix-cron-prev-month-rollover.md new file mode 100644 index 000000000..8085ddc1d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cron-prev-weekday-wrap.md b/.repos/effect/.changeset/pre/fix-cron-prev-weekday-wrap.md new file mode 100644 index 000000000..e4b830749 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-cron-timezone-hash.md b/.repos/effect/.changeset/pre/fix-cron-timezone-hash.md new file mode 100644 index 000000000..6ee572d04 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-datetime-gmt.md b/.repos/effect/.changeset/pre/fix-datetime-gmt.md new file mode 100644 index 000000000..02baaa85d --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-datetime-gmt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `DateTime.makeUnsafe` incorrectly appending "Z" to date strings containing "GMT" diff --git a/.repos/effect/.changeset/pre/fix-devtools-flush-on-teardown.md b/.repos/effect/.changeset/pre/fix-devtools-flush-on-teardown.md new file mode 100644 index 000000000..d5c46f9e1 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-devtools-flush-on-teardown.md @@ -0,0 +1,10 @@ +--- +"effect": patch +--- + +Fix DevToolsClient not flushing final span events on teardown. + +The stream consumer was `forkScoped`, causing it to be interrupted before +it could drain remaining queue items. Replaced with `forkChild` and +`Fiber.await` in the finalizer so the stream drains naturally after the +queue is failed. diff --git a/.repos/effect/.changeset/pre/fix-devtools-span-snapshot.md b/.repos/effect/.changeset/pre/fix-devtools-span-snapshot.md new file mode 100644 index 000000000..ca381c8d5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-durable-clock-fractional-wakeup.md b/.repos/effect/.changeset/pre/fix-durable-clock-fractional-wakeup.md new file mode 100644 index 000000000..d0a7390c8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-durable-deferred-race.md b/.repos/effect/.changeset/pre/fix-durable-deferred-race.md new file mode 100644 index 000000000..21b43f360 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-durable-race-replay.md b/.repos/effect/.changeset/pre/fix-durable-race-replay.md new file mode 100644 index 000000000..2b0e3b242 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-durable-race-replay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix replay of persisted `DurableDeferred.raceAll` results. diff --git a/.repos/effect/.changeset/pre/fix-duration-decimal-precision.md b/.repos/effect/.changeset/pre/fix-duration-decimal-precision.md new file mode 100644 index 000000000..2afc2db77 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-duration-symmetric-rounding.md b/.repos/effect/.changeset/pre/fix-duration-symmetric-rounding.md new file mode 100644 index 000000000..2fdb282e4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-duration-symmetric-rounding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Round fractional durations symmetrically when normalizing to nanoseconds. diff --git a/.repos/effect/.changeset/pre/fix-effect-fn-self.md b/.repos/effect/.changeset/pre/fix-effect-fn-self.md new file mode 100644 index 000000000..80eda7033 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-effect-schedule-errors.md b/.repos/effect/.changeset/pre/fix-effect-schedule-errors.md new file mode 100644 index 000000000..8a8910026 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-entity-manager-defect-replay.md b/.repos/effect/.changeset/pre/fix-entity-manager-defect-replay.md new file mode 100644 index 000000000..5428fc6d4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-entity-manager-defect-replay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix EntityManager defect restarts so in-flight requests are replayed instead of being dropped when the old entity scope is interrupted. diff --git a/.repos/effect/.changeset/pre/fix-entity-proxy-rpc-handler-context.md b/.repos/effect/.changeset/pre/fix-entity-proxy-rpc-handler-context.md new file mode 100644 index 000000000..9b4e1c3c1 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-entity-proxy-rpc-handler-context.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix entity proxy RPC handlers to provide the context expected by RpcServer. diff --git a/.repos/effect/.changeset/pre/fix-entity-proxy-server-path-params.md b/.repos/effect/.changeset/pre/fix-entity-proxy-server-path-params.md new file mode 100644 index 000000000..2cbf13f11 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-entity-proxy-server-path-params.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `EntityProxyServer.layerHttpApi` using `path.entityId` instead of `params.entityId` diff --git a/.repos/effect/.changeset/pre/fix-eventlog-duplicate-chunks.md b/.repos/effect/.changeset/pre/fix-eventlog-duplicate-chunks.md new file mode 100644 index 000000000..dc215a8b3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-fiber-exit-observers.md b/.repos/effect/.changeset/pre/fix-fiber-exit-observers.md new file mode 100644 index 000000000..50aebaee1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-fiberhandle-clear-race.md b/.repos/effect/.changeset/pre/fix-fiberhandle-clear-race.md new file mode 100644 index 000000000..567ba19e0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-fiberset-json-id.md b/.repos/effect/.changeset/pre/fix-fiberset-json-id.md new file mode 100644 index 000000000..158b963c1 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-fiberset-json-id.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the inspectable JSON identity of `FiberSet`. diff --git a/.repos/effect/.changeset/pre/fix-fiberset-runtime-interruption.md b/.repos/effect/.changeset/pre/fix-fiberset-runtime-interruption.md new file mode 100644 index 000000000..0d550e711 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-fish-command-path.md b/.repos/effect/.changeset/pre/fix-fish-command-path.md new file mode 100644 index 000000000..15053389d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-formatter-output-contracts.md b/.repos/effect/.changeset/pre/fix-formatter-output-contracts.md new file mode 100644 index 000000000..5312f9d8e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-from-json-string-identifier.md b/.repos/effect/.changeset/pre/fix-from-json-string-identifier.md new file mode 100644 index 000000000..b2e478fe7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-from-option-inline-inference.md b/.repos/effect/.changeset/pre/fix-from-option-inline-inference.md new file mode 100644 index 000000000..8be078ddd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-from-readable-stream-cancel-defect.md b/.repos/effect/.changeset/pre/fix-from-readable-stream-cancel-defect.md new file mode 100644 index 000000000..4c70506cf --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-from-readable-stream-cancel-defect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Stream.fromReadableStream: swallow the `reader.cancel()` rejection in the finalizer. Cancelling the reader of an already-errored ReadableStream rejects with the stored error, which turned the typed `onError` failure into a defect. diff --git a/.repos/effect/.changeset/pre/fix-graph-allocator-equality.md b/.repos/effect/.changeset/pre/fix-graph-allocator-equality.md new file mode 100644 index 000000000..d796cad07 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-bellman-ford-self-cycle.md b/.repos/effect/.changeset/pre/fix-graph-bellman-ford-self-cycle.md new file mode 100644 index 000000000..07dcc32a6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-curried-getters.md b/.repos/effect/.changeset/pre/fix-graph-curried-getters.md new file mode 100644 index 000000000..4d7947920 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-dfs-astar-edge-cases.md b/.repos/effect/.changeset/pre/fix-graph-dfs-astar-edge-cases.md new file mode 100644 index 000000000..894c65d01 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-edge-transforms.md b/.repos/effect/.changeset/pre/fix-graph-edge-transforms.md new file mode 100644 index 000000000..e0c410ee8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-finite-edge-weights.md b/.repos/effect/.changeset/pre/fix-graph-finite-edge-weights.md new file mode 100644 index 000000000..171304d07 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-mutable-hash.md b/.repos/effect/.changeset/pre/fix-graph-mutable-hash.md new file mode 100644 index 000000000..62463090c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-mutable-topo.md b/.repos/effect/.changeset/pre/fix-graph-mutable-topo.md new file mode 100644 index 000000000..8a929bde8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-mutation-errors.md b/.repos/effect/.changeset/pre/fix-graph-mutation-errors.md new file mode 100644 index 000000000..476d8c401 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-topo-types.md b/.repos/effect/.changeset/pre/fix-graph-topo-types.md new file mode 100644 index 000000000..adab4eb46 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graph-undirected-traversal.md b/.repos/effect/.changeset/pre/fix-graph-undirected-traversal.md new file mode 100644 index 000000000..e80e04680 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-graph-undirected-traversal.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Graph traversal and shortest-path algorithms to traverse undirected edges independently of their stored source/target orientation. diff --git a/.repos/effect/.changeset/pre/fix-graph-walker-repeatability.md b/.repos/effect/.changeset/pre/fix-graph-walker-repeatability.md new file mode 100644 index 000000000..25c1ccd98 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-graphviz-dot-escaping.md b/.repos/effect/.changeset/pre/fix-graphviz-dot-escaping.md new file mode 100644 index 000000000..35bd254d6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-group-by-key-types.md b/.repos/effect/.changeset/pre/fix-group-by-key-types.md new file mode 100644 index 000000000..de711323a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-has-interrupts-only-empty.md b/.repos/effect/.changeset/pre/fix-has-interrupts-only-empty.md new file mode 100644 index 000000000..5434d7a38 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-has-interrupts-only-empty.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Cause.hasInterruptsOnly` to return `false` for empty causes. diff --git a/.repos/effect/.changeset/pre/fix-hashmap-bit31-ordering.md b/.repos/effect/.changeset/pre/fix-hashmap-bit31-ordering.md new file mode 100644 index 000000000..b56bac473 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-hashmap-bit31-ordering.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +HashMap: compare HAMT bit positions as unsigned to preserve entry lookup when bit 31 is set diff --git a/.repos/effect/.changeset/pre/fix-hashmap-modify-hash.md b/.repos/effect/.changeset/pre/fix-hashmap-modify-hash.md new file mode 100644 index 000000000..ca36cabf5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-headers-proto-enumerability.md b/.repos/effect/.changeset/pre/fix-headers-proto-enumerability.md new file mode 100644 index 000000000..686c038ff --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-headers-proto-enumerability.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +unstable/http Headers: hide inspectable prototype methods from for..in iteration to avoid invalid header names in runtime fetch polyfills diff --git a/.repos/effect/.changeset/pre/fix-http-client-request-content-length.md b/.repos/effect/.changeset/pre/fix-http-client-request-content-length.md new file mode 100644 index 000000000..12f66418c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-http-incoming-message-parse-options.md b/.repos/effect/.changeset/pre/fix-http-incoming-message-parse-options.md new file mode 100644 index 000000000..1f71f78c8 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-http-incoming-message-parse-options.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpIncomingMessage.schemaBodyJson` to forward parse options via the `parseOptions` annotation key. diff --git a/.repos/effect/.changeset/pre/fix-http-pre-response-handler-types.md b/.repos/effect/.changeset/pre/fix-http-pre-response-handler-types.md new file mode 100644 index 000000000..66eeb8b70 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-http-server-request-raw-body.md b/.repos/effect/.changeset/pre/fix-http-server-request-raw-body.md new file mode 100644 index 000000000..148559e4a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-http-server-response-body-headers.md b/.repos/effect/.changeset/pre/fix-http-server-response-body-headers.md new file mode 100644 index 000000000..90f227817 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-http-tracer-response-cause.md b/.repos/effect/.changeset/pre/fix-http-tracer-response-cause.md new file mode 100644 index 000000000..30a5589df --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-http-tracer-response-cause.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Exclude response metadata from HTTP server span failures after response headers have been sent. diff --git a/.repos/effect/.changeset/pre/fix-httpapi-authorization-decoding.md b/.repos/effect/.changeset/pre/fix-httpapi-authorization-decoding.md new file mode 100644 index 000000000..b41cb0662 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-httpapi-client-error-content-type.md b/.repos/effect/.changeset/pre/fix-httpapi-client-error-content-type.md new file mode 100644 index 000000000..2de77be2c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-httpapi-endpoint-error-inference.md b/.repos/effect/.changeset/pre/fix-httpapi-endpoint-error-inference.md new file mode 100644 index 000000000..c476f25a2 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-httpapi-endpoint-error-inference.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix HttpApiEndpoint endpoint error inference when success schemas include streams. diff --git a/.repos/effect/.changeset/pre/fix-httpapi-malformed-json-400.md b/.repos/effect/.changeset/pre/fix-httpapi-malformed-json-400.md new file mode 100644 index 000000000..2f704e0e8 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-httpapi-malformed-json-400.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Map HttpApi json defects to SchemaError diff --git a/.repos/effect/.changeset/pre/fix-httpapi-runtime-shape.md b/.repos/effect/.changeset/pre/fix-httpapi-runtime-shape.md new file mode 100644 index 000000000..a8c9bd869 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-httpapi-schema-types.md b/.repos/effect/.changeset/pre/fix-httpapi-schema-types.md new file mode 100644 index 000000000..d5dd3394b --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-httpapi-schema-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix published HttpApi declaration files by exporting schema metadata types referenced by public declarations. diff --git a/.repos/effect/.changeset/pre/fix-httpapi-security-middleware-cache.md b/.repos/effect/.changeset/pre/fix-httpapi-security-middleware-cache.md new file mode 100644 index 000000000..cf9e68fbb --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-httpapi-security-middleware-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApiBuilder` security middleware caching so separate handler builds do not reuse the first provided middleware implementation. diff --git a/.repos/effect/.changeset/pre/fix-httpapi-single-array-query.md b/.repos/effect/.changeset/pre/fix-httpapi-single-array-query.md new file mode 100644 index 000000000..6a8a671eb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-invalid-openapi-examples.md b/.repos/effect/.changeset/pre/fix-invalid-openapi-examples.md new file mode 100644 index 000000000..3e0552953 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-invalid-value-doubled-expected.md b/.repos/effect/.changeset/pre/fix-invalid-value-doubled-expected.md new file mode 100644 index 000000000..a8e9deaee --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-is-json-dag.md b/.repos/effect/.changeset/pre/fix-is-json-dag.md new file mode 100644 index 000000000..68809e071 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-is-json-dag.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `SchemaAST.isJson` rejecting DAGs as cycles, closes #2021. + +The previous implementation marked every visited object in a single `seen` set and never removed it, so any value that referenced the same object through two different paths (a DAG, e.g. `{ x: shared, y: shared }`) was treated as a cycle and returned `false`. Cycle detection now tracks only the current recursion path (popping on exit) and memoizes fully validated subtrees, so DAGs are accepted while true cycles are still rejected. diff --git a/.repos/effect/.changeset/pre/fix-iterable-flatten-stack-safety.md b/.repos/effect/.changeset/pre/fix-iterable-flatten-stack-safety.md new file mode 100644 index 000000000..54894d0c6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-json-schema-anyof-oneof-siblings.md b/.repos/effect/.changeset/pre/fix-json-schema-anyof-oneof-siblings.md new file mode 100644 index 000000000..9f0840d1e --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-json-schema-anyof-oneof-siblings.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +SchemaRepresentation: support `anyOf`/`oneOf` with sibling keywords in `fromJsonSchemaMultiDocument` diff --git a/.repos/effect/.changeset/pre/fix-json-schema-import-json.md b/.repos/effect/.changeset/pre/fix-json-schema-import-json.md new file mode 100644 index 000000000..048a0d798 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-json-schema-import-json.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Import unconstrained JSON Schema nodes as `Schema.Json` instead of `Schema.Unknown`. diff --git a/.repos/effect/.changeset/pre/fix-json-schema-reference-resolution.md b/.repos/effect/.changeset/pre/fix-json-schema-reference-resolution.md new file mode 100644 index 000000000..52c87639a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-json-schema-unique-items-false.md b/.repos/effect/.changeset/pre/fix-json-schema-unique-items-false.md new file mode 100644 index 000000000..0365d9dc5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-keepalive-blocked-timers.md b/.repos/effect/.changeset/pre/fix-keepalive-blocked-timers.md new file mode 100644 index 000000000..8cdb6fa16 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-keepalive-blocked-timers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +runtime: guard keepAlive setInterval / clearInterval so Effect.runPromise works in runtimes that block timer APIs diff --git a/.repos/effect/.changeset/pre/fix-language-model-stream-concurrency.md b/.repos/effect/.changeset/pre/fix-language-model-stream-concurrency.md new file mode 100644 index 000000000..491b4fdcd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-latch-stale-flush.md b/.repos/effect/.changeset/pre/fix-latch-stale-flush.md new file mode 100644 index 000000000..c36527840 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-layermap-preload.md b/.repos/effect/.changeset/pre/fix-layermap-preload.md new file mode 100644 index 000000000..6b7260734 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mcp-call-tool-arguments.md b/.repos/effect/.changeset/pre/fix-mcp-call-tool-arguments.md new file mode 100644 index 000000000..952f6d0da --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mcp-completion-context.md b/.repos/effect/.changeset/pre/fix-mcp-completion-context.md new file mode 100644 index 000000000..82c30fc89 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mcp-param-name-resolution.md b/.repos/effect/.changeset/pre/fix-mcp-param-name-resolution.md new file mode 100644 index 000000000..2ec5b3841 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-mcp-param-name-resolution.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix MCP resource template parameter names resolving as `param0`, `param1` instead of actual names by checking `isParam` on the original schema before `toCodecStringTree` transformation. diff --git a/.repos/effect/.changeset/pre/fix-mcp-request-errors.md b/.repos/effect/.changeset/pre/fix-mcp-request-errors.md new file mode 100644 index 000000000..7da53c303 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-memory-journal-conflicts.md b/.repos/effect/.changeset/pre/fix-memory-journal-conflicts.md new file mode 100644 index 000000000..ae2c2ddd5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-memory-journal-next-sequence.md b/.repos/effect/.changeset/pre/fix-memory-journal-next-sequence.md new file mode 100644 index 000000000..f515118f0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-memory-journal-relay.md b/.repos/effect/.changeset/pre/fix-memory-journal-relay.md new file mode 100644 index 000000000..cee740fdd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-memory-runner-health.md b/.repos/effect/.changeset/pre/fix-memory-runner-health.md new file mode 100644 index 000000000..77a0e9ce8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-memory-workflow-interrupt.md b/.repos/effect/.changeset/pre/fix-memory-workflow-interrupt.md new file mode 100644 index 000000000..625013f2b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mermaid-escape-special-chars.md b/.repos/effect/.changeset/pre/fix-mermaid-escape-special-chars.md new file mode 100644 index 000000000..873f71b92 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-mermaid-escape-special-chars.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.toMermaid` to escape special characters using HTML entity codes per the Mermaid specification. diff --git a/.repos/effect/.changeset/pre/fix-message-storage-clear-address-dedup.md b/.repos/effect/.changeset/pre/fix-message-storage-clear-address-dedup.md new file mode 100644 index 000000000..f8d75f32a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-metric-attribute-key-collisions.md b/.repos/effect/.changeset/pre/fix-metric-attribute-key-collisions.md new file mode 100644 index 000000000..3d00cc8e7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-metric-is-metric.md b/.repos/effect/.changeset/pre/fix-metric-is-metric.md new file mode 100644 index 000000000..00e185430 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-metric-linear-boundaries.md b/.repos/effect/.changeset/pre/fix-metric-linear-boundaries.md new file mode 100644 index 000000000..d5a0219a4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-metric-negative-max.md b/.repos/effect/.changeset/pre/fix-metric-negative-max.md new file mode 100644 index 000000000..a2f1d2450 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mssql-multitable-persistence-upsert.md b/.repos/effect/.changeset/pre/fix-mssql-multitable-persistence-upsert.md new file mode 100644 index 000000000..8c870fa22 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-multipart-file-stream-limits.md b/.repos/effect/.changeset/pre/fix-multipart-file-stream-limits.md new file mode 100644 index 000000000..fd4d227f4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mutable-list-bounds.md b/.repos/effect/.changeset/pre/fix-mutable-list-bounds.md new file mode 100644 index 000000000..d481f7a74 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mutable-list-empty-filter.md b/.repos/effect/.changeset/pre/fix-mutable-list-empty-filter.md new file mode 100644 index 000000000..4352c7314 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-mutable-list-filter-length.md b/.repos/effect/.changeset/pre/fix-mutable-list-filter-length.md new file mode 100644 index 000000000..c6a6a84fd --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-mutable-list-filter-length.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `MutableList.filter` and `MutableList.remove` length updates. diff --git a/.repos/effect/.changeset/pre/fix-ndjson-split-utf8.md b/.repos/effect/.changeset/pre/fix-ndjson-split-utf8.md new file mode 100644 index 000000000..4474398b4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-node-http-stream-failure.md b/.repos/effect/.changeset/pre/fix-node-http-stream-failure.md new file mode 100644 index 000000000..b1049b475 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-node-path-file-url-flavor.md b/.repos/effect/.changeset/pre/fix-node-path-file-url-flavor.md new file mode 100644 index 000000000..d1cb74f62 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-node-pipeline-kill.md b/.repos/effect/.changeset/pre/fix-node-pipeline-kill.md new file mode 100644 index 000000000..b551d51da --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-number-remainder-scientific-notation.md b/.repos/effect/.changeset/pre/fix-number-remainder-scientific-notation.md new file mode 100644 index 000000000..9b43310d2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-object-keyword-json-schema.md b/.repos/effect/.changeset/pre/fix-object-keyword-json-schema.md new file mode 100644 index 000000000..411cc653e --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-object-keyword-json-schema.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Emit `Schema.ObjectKeyword` as an object-or-array JSON Schema union. diff --git a/.repos/effect/.changeset/pre/fix-one-shot-iterables.md b/.repos/effect/.changeset/pre/fix-one-shot-iterables.md new file mode 100644 index 000000000..9908168c1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openai-header-redaction.md b/.repos/effect/.changeset/pre/fix-openai-header-redaction.md new file mode 100644 index 000000000..1357ea76b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openai-mcp-tool-names.md b/.repos/effect/.changeset/pre/fix-openai-mcp-tool-names.md new file mode 100644 index 000000000..c6009ba2b --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-openai-mcp-tool-names.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Fix OpenAI MCP tool call handling to keep the canonical `OpenAiMcp` tool name across response and stream paths, including approval flows. diff --git a/.repos/effect/.changeset/pre/fix-openai-response-failure.md b/.repos/effect/.changeset/pre/fix-openai-response-failure.md new file mode 100644 index 000000000..dbe8abb99 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openai-specialized-tool-output.md b/.repos/effect/.changeset/pre/fix-openai-specialized-tool-output.md new file mode 100644 index 000000000..ac215a269 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openai-system-input-text.md b/.repos/effect/.changeset/pre/fix-openai-system-input-text.md new file mode 100644 index 000000000..830f6d00e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openai-telemetry-response-namespace.md b/.repos/effect/.changeset/pre/fix-openai-telemetry-response-namespace.md new file mode 100644 index 000000000..d0488f9d6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openai-web-search-action.md b/.repos/effect/.changeset/pre/fix-openai-web-search-action.md new file mode 100644 index 000000000..ac95aedf1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openapi-from-api-cache-copy.md b/.repos/effect/.changeset/pre/fix-openapi-from-api-cache-copy.md new file mode 100644 index 000000000..68a5c04f4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-openapi-generator-form-urlencoded.md b/.repos/effect/.changeset/pre/fix-openapi-generator-form-urlencoded.md new file mode 100644 index 000000000..0b37c34bd --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-openapi-generator-form-urlencoded.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Support `application/x-www-form-urlencoded` request bodies in `httpclient` output format. Previously, form-urlencoded request bodies were silently dropped, producing operations with no payload parameter. The generator now emits `HttpClientRequest.bodyUrlParams` for these endpoints, matching the existing pattern for `multipart/form-data` (`bodyFormData`) and `application/json` (`bodyJsonUnsafe`). The `httpapi` format was already handling this content type correctly. diff --git a/.repos/effect/.changeset/pre/fix-openapi-generator-swagger2openapi.md b/.repos/effect/.changeset/pre/fix-openapi-generator-swagger2openapi.md new file mode 100644 index 000000000..38ac06dc2 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-openapi-generator-swagger2openapi.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Declare `swagger2openapi` as a runtime dependency so published `openapigen` installs can resolve the converter imported by `OpenApiGenerator`. diff --git a/.repos/effect/.changeset/pre/fix-openapi-preserve-multiple-response-content-types.md b/.repos/effect/.changeset/pre/fix-openapi-preserve-multiple-response-content-types.md new file mode 100644 index 000000000..7074326ff --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-openapi-preserve-multiple-response-content-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `OpenApi.fromApi` preserving multiple response content types for one status code, closes #1485. diff --git a/.repos/effect/.changeset/pre/fix-openrouter-sparse-array.md b/.repos/effect/.changeset/pre/fix-openrouter-sparse-array.md new file mode 100644 index 000000000..74f87f92f --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-openrouter-sparse-array.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Fix sparse array crash in `streamText` tool call handling. diff --git a/.repos/effect/.changeset/pre/fix-otel-logger-clock-skew.md b/.repos/effect/.changeset/pre/fix-otel-logger-clock-skew.md new file mode 100644 index 000000000..5783219d7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-otel-logger-severity-number.md b/.repos/effect/.changeset/pre/fix-otel-logger-severity-number.md new file mode 100644 index 000000000..b411ad4d7 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-otel-logger-severity-number.md @@ -0,0 +1,20 @@ +--- +"@effect/opentelemetry": patch +--- + +Logger: emit OTel-spec `SeverityNumber` (1-24) instead of Effect's internal log-level ordinal. + +`OtelLogger.make` previously passed `LogLevel.getOrdinal(level)` (e.g. Info=20000, Error=40000) as `severityNumber`, which falls outside the OpenTelemetry logs data model spec range (1-24). Backends that validate the field (Honeycomb, Datadog, etc.) bucket such values as `UNSPECIFIED`. + +The mapping now follows the spec: + +| Effect LogLevel | OTel SeverityNumber | +| --------------- | ------------------- | +| Trace | TRACE (1) | +| Debug | DEBUG (5) | +| Info | INFO (9) | +| Warn | WARN (13) | +| Error | ERROR (17) | +| Fatal | FATAL (21) | + +Also exports the helper `logLevelToSeverityNumber` for downstream use. diff --git a/.repos/effect/.changeset/pre/fix-otel-logger-shutdown.md b/.repos/effect/.changeset/pre/fix-otel-logger-shutdown.md new file mode 100644 index 000000000..d81109863 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-otlp-exporter-shutdown.md b/.repos/effect/.changeset/pre/fix-otlp-exporter-shutdown.md new file mode 100644 index 000000000..444ba3ce4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-otlp-resource-attributes.md b/.repos/effect/.changeset/pre/fix-otlp-resource-attributes.md new file mode 100644 index 000000000..a461bfa33 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-pending-interruptible-mask.md b/.repos/effect/.changeset/pre/fix-pending-interruptible-mask.md new file mode 100644 index 000000000..e0a8494e4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-pending-interruptible-mask.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Deliver pending interrupts when interruptibleMask restores fiber interruptibility. diff --git a/.repos/effect/.changeset/pre/fix-persisted-cluster-reply-hang.md b/.repos/effect/.changeset/pre/fix-persisted-cluster-reply-hang.md new file mode 100644 index 000000000..2a1b8732f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-persisted-queue-attempt-accounting.md b/.repos/effect/.changeset/pre/fix-persisted-queue-attempt-accounting.md new file mode 100644 index 000000000..8737be085 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-prompt-all-iterables.md b/.repos/effect/.changeset/pre/fix-prompt-all-iterables.md new file mode 100644 index 000000000..070620a8c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-proto-record-assignment.md b/.repos/effect/.changeset/pre/fix-proto-record-assignment.md new file mode 100644 index 000000000..1d960ffa4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-pubsub-replay-retention.md b/.repos/effect/.changeset/pre/fix-pubsub-replay-retention.md new file mode 100644 index 000000000..632765fdc --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-queue-collect-duplication.md b/.repos/effect/.changeset/pre/fix-queue-collect-duplication.md new file mode 100644 index 000000000..bc29d2ee2 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-queue-collect-duplication.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Queue.collect: stop duplicating drained messages by appending each batch once diff --git a/.repos/effect/.changeset/pre/fix-random-string-seeds.md b/.repos/effect/.changeset/pre/fix-random-string-seeds.md new file mode 100644 index 000000000..201923e1d --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-random-string-seeds.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix string seed encoding in Random.withSeed so short, trailing, and astral UTF-8 bytes affect deterministic streams. diff --git a/.repos/effect/.changeset/pre/fix-rate-limiter-sleep.md b/.repos/effect/.changeset/pre/fix-rate-limiter-sleep.md new file mode 100644 index 000000000..73633920b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-ratelimiter-tokenbucket-redis-ttl.md b/.repos/effect/.changeset/pre/fix-ratelimiter-tokenbucket-redis-ttl.md new file mode 100644 index 000000000..874cd2ae3 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-ratelimiter-tokenbucket-redis-ttl.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the Redis `RateLimiterStore` token-bucket failing with opaque errors under memory pressure: it now writes its keys with a TTL and guards against a missing refill timestamp. diff --git a/.repos/effect/.changeset/pre/fix-react-native-sqlite-values.md b/.repos/effect/.changeset/pre/fix-react-native-sqlite-values.md new file mode 100644 index 000000000..97070d9b0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-reactive-query-metadata.md b/.repos/effect/.changeset/pre/fix-reactive-query-metadata.md new file mode 100644 index 000000000..6617c533b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-redis-persisted-queue.md b/.repos/effect/.changeset/pre/fix-redis-persisted-queue.md new file mode 100644 index 000000000..8ec524967 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-remainder-scientific-notation.md b/.repos/effect/.changeset/pre/fix-remainder-scientific-notation.md new file mode 100644 index 000000000..1a0b109d9 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-remainder-scientific-notation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +`Number.remainder`: fix incorrect results for small floats in scientific notation (e.g. `1e-7`). diff --git a/.repos/effect/.changeset/pre/fix-request-resolver-pending-batches-leak.md b/.repos/effect/.changeset/pre/fix-request-resolver-pending-batches-leak.md new file mode 100644 index 000000000..9721cd175 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-request-resolver-pending-batches-leak.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use `WeakMap` for `pendingBatches` instead of `Map`, to allow GC to collect resolvers diff --git a/.repos/effect/.changeset/pre/fix-retry-transient-autocomplete.md b/.repos/effect/.changeset/pre/fix-retry-transient-autocomplete.md new file mode 100644 index 000000000..0cdb6b2af --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-retry-transient-autocomplete.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpClient.retryTransient` autocomplete leaking `Schedule` internals by splitting the `{...} | Schedule` union into separate overloads. diff --git a/.repos/effect/.changeset/pre/fix-rpc-http-requestids-finalizer.md b/.repos/effect/.changeset/pre/fix-rpc-http-requestids-finalizer.md new file mode 100644 index 000000000..8c295f899 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-rpc-http-requestids-finalizer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix request ID tracking in the RPC server HTTP protocol finalizer. diff --git a/.repos/effect/.changeset/pre/fix-rpc-json-id-edges.md b/.repos/effect/.changeset/pre/fix-rpc-json-id-edges.md new file mode 100644 index 000000000..6b915e0d0 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-rpc-json-id-edges.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix JSON-RPC serialization for `id` values that are falsey but valid, including `0` and `""`, while still mapping `null` to Effect's internal notification sentinel. diff --git a/.repos/effect/.changeset/pre/fix-rpc-unknown-tag-isolation.md b/.repos/effect/.changeset/pre/fix-rpc-unknown-tag-isolation.md new file mode 100644 index 000000000..f8eb1b6bf --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-rpc-unknown-tag-isolation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Localize missing rpc method errors to the provided request id diff --git a/.repos/effect/.changeset/pre/fix-runner-stream-completion.md b/.repos/effect/.changeset/pre/fix-runner-stream-completion.md new file mode 100644 index 000000000..414fefe90 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-runner-stream-completion.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +End runner streams after emitting their terminal replies. diff --git a/.repos/effect/.changeset/pre/fix-schedule-during.md b/.repos/effect/.changeset/pre/fix-schedule-during.md new file mode 100644 index 000000000..c881221af --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schedule-fixed-double-exec.md b/.repos/effect/.changeset/pre/fix-schedule-fixed-double-exec.md new file mode 100644 index 000000000..1909b59a1 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schedule-fixed-double-exec.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Fix `Schedule.fixed` double-executing the effect due to clock jitter. + +The `elapsedSincePrevious > window` check included sleep time from the +previous step, so any timer imprecision (e.g. 1001ms for a 1000ms sleep) +triggered an immediate zero-delay re-execution. diff --git a/.repos/effect/.changeset/pre/fix-schedule-reduce-sync-state.md b/.repos/effect/.changeset/pre/fix-schedule-reduce-sync-state.md new file mode 100644 index 000000000..9c1d92935 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schedule-reduce-sync-state.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schedule.reduce` to persist state updates when the combine function returns a synchronous value. diff --git a/.repos/effect/.changeset/pre/fix-schema-arbitrary-exclusive-bounds.md b/.repos/effect/.changeset/pre/fix-schema-arbitrary-exclusive-bounds.md new file mode 100644 index 000000000..b14291c8d --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-arbitrary-exclusive-bounds.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix schema arbitrary constraints for exclusive BigInt, Date, and integer number bounds. diff --git a/.repos/effect/.changeset/pre/fix-schema-bracket-prototype-pollution.md b/.repos/effect/.changeset/pre/fix-schema-bracket-prototype-pollution.md new file mode 100644 index 000000000..f570f34ca --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-defect-message.md b/.repos/effect/.changeset/pre/fix-schema-defect-message.md new file mode 100644 index 000000000..15722dd3e --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-defect-message.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Schema.Defect JSON encoding for Error values whose message property is not a string. diff --git a/.repos/effect/.changeset/pre/fix-schema-encode-keys-property-keys.md b/.repos/effect/.changeset/pre/fix-schema-encode-keys-property-keys.md new file mode 100644 index 000000000..8e7255152 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-encode-keys-property-keys.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Struct` key renaming and `Schema.encodeKeys` to support symbol keys, and reject duplicate encoded keys. diff --git a/.repos/effect/.changeset/pre/fix-schema-encodekeys-class.md b/.repos/effect/.changeset/pre/fix-schema-encodekeys-class.md new file mode 100644 index 000000000..ebf61c22e --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-encodekeys-class.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema.encodeKeys: relax input constraint from Struct to schemas with fields so Schema.Class works, closes #1412. diff --git a/.repos/effect/.changeset/pre/fix-schema-encodekeys-struct.md b/.repos/effect/.changeset/pre/fix-schema-encodekeys-struct.md new file mode 100644 index 000000000..fd884d1ef --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-encodekeys-struct.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schema.encodeKeys` to encode non-remapped struct fields during encoding. diff --git a/.repos/effect/.changeset/pre/fix-schema-encoding-checks.md b/.repos/effect/.changeset/pre/fix-schema-encoding-checks.md new file mode 100644 index 000000000..ba56790cb --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-encoding-checks.md @@ -0,0 +1,12 @@ +--- +"effect": patch +--- + +Fix Schema handling of encoded-side checks for container ASTs. + +Checks added after `flip` are now preserved as `encodingChecks` across +`Declaration`, `Arrays`, `Objects`, and `Union`, even when rebuilding the AST +does not change child nodes. `toType` now projects those checks consistently, +and parsing applies encoded-side checks to the local encoded value when an +encoding chain is present without allowing encoded-side `parseOptions` +annotations to affect the current parser side. diff --git a/.repos/effect/.changeset/pre/fix-schema-identifier-expected-message.md b/.repos/effect/.changeset/pre/fix-schema-identifier-expected-message.md new file mode 100644 index 000000000..8db649677 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-identifier-expected-message.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use the `identifier` annotation as the expected message when available, closes #1458. diff --git a/.repos/effect/.changeset/pre/fix-schema-is-json-records.md b/.repos/effect/.changeset/pre/fix-schema-is-json-records.md new file mode 100644 index 000000000..5a71fc1cc --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-is-uuid.md b/.repos/effect/.changeset/pre/fix-schema-is-uuid.md new file mode 100644 index 000000000..ecd2eb12f --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-is-uuid.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schema.isUUID` so the `version` parameter is optional in its public signature. diff --git a/.repos/effect/.changeset/pre/fix-schema-json-tuple-allof.md b/.repos/effect/.changeset/pre/fix-schema-json-tuple-allof.md new file mode 100644 index 000000000..cbf16877b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-make-nested-class-union.md b/.repos/effect/.changeset/pre/fix-schema-make-nested-class-union.md new file mode 100644 index 000000000..cc53b35a4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-option-non-schema-failures.md b/.repos/effect/.changeset/pre/fix-schema-option-non-schema-failures.md new file mode 100644 index 000000000..516ec89fa --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-option-non-schema-failures.md @@ -0,0 +1,17 @@ +--- +"effect": patch +--- + +Normalize error behavior for Schema and SchemaParser boundary APIs. + +`SchemaError` now extends `Data.TaggedError`, so it is also a native `Error`. +SchemaParser Promise APIs now reject an `Error` whose cause is the +`SchemaIssue.Issue` for schema failures. + +Schema and SchemaParser `Effect` and `Exit` adapters now preserve full causes +while mapping schema issue failures to their public error type. The `is`, +`asserts`, `Promise`, `Sync`, `Result`, `Option`, `make`, and `makeOption` +adapters now distinguish schema issues from non-schema causes. Schema-only +failures are converted to the adapter's normal representation (`false`, +rejected or thrown schema error, `Result.fail`, or `None`), while non-schema +causes throw or reject with an `Error` whose cause is the underlying `Cause`. diff --git a/.repos/effect/.changeset/pre/fix-schema-parser-checks.md b/.repos/effect/.changeset/pre/fix-schema-parser-checks.md new file mode 100644 index 000000000..fd93a6c5b --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-schema-parser-checks.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Fix `Schema` encoding so container-level checks are validated against the decoded value instead of the encoded output. + +Disallow adding checks directly to `Schema.suspend(...)`; add the checks to the suspended schema instead. + +Fix `StructWithRest` so index signatures do not re-parse or overwrite fixed properties. diff --git a/.repos/effect/.changeset/pre/fix-schema-pattern-state.md b/.repos/effect/.changeset/pre/fix-schema-pattern-state.md new file mode 100644 index 000000000..2a321daad --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-representation-identifiers.md b/.repos/effect/.changeset/pre/fix-schema-representation-identifiers.md new file mode 100644 index 000000000..f4a8a2391 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-sentinel-declaration.md b/.repos/effect/.changeset/pre/fix-schema-sentinel-declaration.md new file mode 100644 index 000000000..f042aef64 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-tuple-post-rest-indexing.md b/.repos/effect/.changeset/pre/fix-schema-tuple-post-rest-indexing.md new file mode 100644 index 000000000..8546cc8be --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-union-dispatch-order.md b/.repos/effect/.changeset/pre/fix-schema-union-dispatch-order.md new file mode 100644 index 000000000..61876f469 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-schema-union-pruning.md b/.repos/effect/.changeset/pre/fix-schema-union-pruning.md new file mode 100644 index 000000000..c1029ae56 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-scoped-ref-failed-replacement.md b/.repos/effect/.changeset/pre/fix-scoped-ref-failed-replacement.md new file mode 100644 index 000000000..5caffbbca --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-searchparam-initial-decode.md b/.repos/effect/.changeset/pre/fix-searchparam-initial-decode.md new file mode 100644 index 000000000..f78d96a46 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-searchparam-initial-decode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Atom.searchParam: decode initial URL values correctly when a schema is provided diff --git a/.repos/effect/.changeset/pre/fix-semaphore-with-permits-interrupt-leak.md b/.repos/effect/.changeset/pre/fix-semaphore-with-permits-interrupt-leak.md new file mode 100644 index 000000000..aba537cb5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-serializable-wire-transfer.md b/.repos/effect/.changeset/pre/fix-serializable-wire-transfer.md new file mode 100644 index 000000000..64c580d87 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-serializable-wire-transfer.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `Atom.serializable` encode/decode for wire transfer. + +Use `Schema.toCodecJson` instead of `Schema.encodeSync`/`Schema.decodeSync` directly, so that encoded values are plain JSON objects that survive serialization roundtrips (JSON, seroval, etc.). Previously, `AsyncResult.Schema` encode produced instances with custom prototypes that were lost after wire transfer, causing decode to fail with "Expected AsyncResult" errors during SSR hydration. diff --git a/.repos/effect/.changeset/pre/fix-sliding-size-chunks.md b/.repos/effect/.changeset/pre/fix-sliding-size-chunks.md new file mode 100644 index 000000000..9d06bc18d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-sql-persisted-queue-lock-refresh.md b/.repos/effect/.changeset/pre/fix-sql-persisted-queue-lock-refresh.md new file mode 100644 index 000000000..19a98949b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-sqlite-persisted-queue.md b/.repos/effect/.changeset/pre/fix-sqlite-persisted-queue.md new file mode 100644 index 000000000..bb6bcc1b1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-sse-last-event-id.md b/.repos/effect/.changeset/pre/fix-sse-last-event-id.md new file mode 100644 index 000000000..69109fa35 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-sse-leading-bom.md b/.repos/effect/.changeset/pre/fix-sse-leading-bom.md new file mode 100644 index 000000000..c1b7716c8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-sse-retry-directives.md b/.repos/effect/.changeset/pre/fix-sse-retry-directives.md new file mode 100644 index 000000000..e80c35962 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-stream-aggregate-within-idle.md b/.repos/effect/.changeset/pre/fix-stream-aggregate-within-idle.md new file mode 100644 index 000000000..96744190c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-stream-execution-plan-retries.md b/.repos/effect/.changeset/pre/fix-stream-execution-plan-retries.md new file mode 100644 index 000000000..0174fbabe --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-stream-grouped-within-flush.md b/.repos/effect/.changeset/pre/fix-stream-grouped-within-flush.md new file mode 100644 index 000000000..fff052fc7 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-stream-grouped-within-flush.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.groupedWithin` dropping partial batches when the upstream ends or goes idle. diff --git a/.repos/effect/.changeset/pre/fix-stream-haltwhen.md b/.repos/effect/.changeset/pre/fix-stream-haltwhen.md new file mode 100644 index 000000000..8cb383c06 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-stream-map-accum-array-effect.md b/.repos/effect/.changeset/pre/fix-stream-map-accum-array-effect.md new file mode 100644 index 000000000..c67a81799 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-stream-range-zero-chunk.md b/.repos/effect/.changeset/pre/fix-stream-range-zero-chunk.md new file mode 100644 index 000000000..cc28d66a7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-stream-run-for-each-while.md b/.repos/effect/.changeset/pre/fix-stream-run-for-each-while.md new file mode 100644 index 000000000..91b9e1d2c --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-stream-run-for-each-while.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Stream.runForEachWhile so it continues across chunk boundaries while the predicate returns true and stops when the predicate returns false. diff --git a/.repos/effect/.changeset/pre/fix-stream-scan-effect.md b/.repos/effect/.changeset/pre/fix-stream-scan-effect.md new file mode 100644 index 000000000..a1d821f93 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-stream-scan-effect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.scanEffect` hanging and repeatedly emitting the initial state. diff --git a/.repos/effect/.changeset/pre/fix-stream-scoped-scope.md b/.repos/effect/.changeset/pre/fix-stream-scoped-scope.md new file mode 100644 index 000000000..c4437e656 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-stream-scoped-scope.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.scoped` and `Channel.scoped` so pull effects run with the scoped resource scope. diff --git a/.repos/effect/.changeset/pre/fix-string-case-digits.md b/.repos/effect/.changeset/pre/fix-string-case-digits.md new file mode 100644 index 000000000..5aaa2cd15 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-strip-approval-artifacts-multi-round.md b/.repos/effect/.changeset/pre/fix-strip-approval-artifacts-multi-round.md new file mode 100644 index 000000000..c091d0313 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-strip-approval-artifacts-multi-round.md @@ -0,0 +1,13 @@ +--- +"effect": patch +--- + +Fix LanguageModel stripping of resolved approval artifacts across multi-round conversations. + +Previously, `stripResolvedApprovals` only ran when there were pending approvals +in the current round. Stale artifacts from earlier rounds would leak to the +provider, causing errors. The stripping now runs unconditionally. + +In streaming mode, pre-resolved tool results are also emitted as stream parts +so `Chat.streamText` persists them to history, preventing re-resolution on +subsequent rounds. diff --git a/.repos/effect/.changeset/pre/fix-struct-utility-types-simplify.md b/.repos/effect/.changeset/pre/fix-struct-utility-types-simplify.md new file mode 100644 index 000000000..460770327 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-struct-utility-types-simplify.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Struct` utility return types (for example `pick`) to preserve the previous simplified shape instead of exposing raw utility types like `Pick`, closes #1855. diff --git a/.repos/effect/.changeset/pre/fix-structural-proto-equality.md b/.repos/effect/.changeset/pre/fix-structural-proto-equality.md new file mode 100644 index 000000000..eae88bf89 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-structwithrest-index-signatures.md b/.repos/effect/.changeset/pre/fix-structwithrest-index-signatures.md new file mode 100644 index 000000000..beee64ebe --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-structwithrest-index-signatures.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/ai-openai": patch +--- + +Validate `Schema.StructWithRest` fixed fields against rest index signatures at the type level so schemas cannot be constructed with incompatible decoded, encoded, or make shapes. This keeps `StructWithRest` types sound and updates the generated OpenAI conversation-items request schema to keep accepting arbitrary additional fields under the stricter validation. diff --git a/.repos/effect/.changeset/pre/fix-subscription-ref-get-and-update-some.md b/.repos/effect/.changeset/pre/fix-subscription-ref-get-and-update-some.md new file mode 100644 index 000000000..d231f9c6c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-subscriptionref-getandupdateeffect.md b/.repos/effect/.changeset/pre/fix-subscriptionref-getandupdateeffect.md new file mode 100644 index 000000000..4983ed0b0 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-subscriptionref-getandupdateeffect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `SubscriptionRef.getAndUpdateEffect` to execute the effectful update. diff --git a/.repos/effect/.changeset/pre/fix-tagged-union-class-sentinels.md b/.repos/effect/.changeset/pre/fix-tagged-union-class-sentinels.md new file mode 100644 index 000000000..f79912a97 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-tagged-union-class-sentinels.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schema.toTaggedUnion` discriminant detection for class-based schemas, including unique symbol tags, closes #1584. diff --git a/.repos/effect/.changeset/pre/fix-tagged-union-match-unify.md b/.repos/effect/.changeset/pre/fix-tagged-union-match-unify.md new file mode 100644 index 000000000..987abe5e6 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-tagged-union-match-unify.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Fix `TaggedUnion.match` to use `Unify` for return types, allowing +branches to return distinct Effect types that are properly merged. diff --git a/.repos/effect/.changeset/pre/fix-to-tagged-union-isanyof-custom-tags.md b/.repos/effect/.changeset/pre/fix-to-tagged-union-isanyof-custom-tags.md new file mode 100644 index 000000000..17f0ec54c --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-to-tagged-union-isanyof-custom-tags.md @@ -0,0 +1,10 @@ +--- +"effect": patch +--- + +Fix `Schema.toTaggedUnion(...).isAnyOf` narrowing for custom discriminant keys, closes #2386. + +Previously, the type predicate always extracted union members by `_tag`, even +when `toTaggedUnion` was created with a different discriminant key. Runtime +behavior already used the supplied key, so this aligns the type-level narrowing +with the existing runtime behavior. diff --git a/.repos/effect/.changeset/pre/fix-tool-provider-defined-clone.md b/.repos/effect/.changeset/pre/fix-tool-provider-defined-clone.md new file mode 100644 index 000000000..6306c054d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-trie-key-replacement.md b/.repos/effect/.changeset/pre/fix-trie-key-replacement.md new file mode 100644 index 000000000..00d370732 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-trie-longest-prefix.md b/.repos/effect/.changeset/pre/fix-trie-longest-prefix.md new file mode 100644 index 000000000..bf422ac6d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-trie-undefined-values.md b/.repos/effect/.changeset/pre/fix-trie-undefined-values.md new file mode 100644 index 000000000..12a9e3f13 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-index-drift.md b/.repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-index-drift.md new file mode 100644 index 000000000..155671bec --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-index-drift.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix TupleWithRest post-rest validation to check each tail index sequentially. diff --git a/.repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-validation.md b/.repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-validation.md new file mode 100644 index 000000000..60e4272f5 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-validation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schema.TupleWithRest` incorrectly accepting inputs with missing post-rest elements, closes #1410. diff --git a/.repos/effect/.changeset/pre/fix-txpubsub-publish-all-iterables.md b/.repos/effect/.changeset/pre/fix-txpubsub-publish-all-iterables.md new file mode 100644 index 000000000..390063b2b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-txqueue-closing-drain.md b/.repos/effect/.changeset/pre/fix-txqueue-closing-drain.md new file mode 100644 index 000000000..41b0208f8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-txqueue-offer-all-iterables.md b/.repos/effect/.changeset/pre/fix-txqueue-offer-all-iterables.md new file mode 100644 index 000000000..e6f159cf1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-types-voidifempty.md b/.repos/effect/.changeset/pre/fix-types-voidifempty.md new file mode 100644 index 000000000..f481e7795 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-types-voidifempty.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Types.VoidIfEmpty` to correctly detect empty object types. Remove deprecated `Types.MatchRecord` in favor of the simplified implementation, closes #1647. diff --git a/.repos/effect/.changeset/pre/fix-variant-schema-default-cache.md b/.repos/effect/.changeset/pre/fix-variant-schema-default-cache.md new file mode 100644 index 000000000..4449cb175 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-vitest-proxy-chained-helpers.md b/.repos/effect/.changeset/pre/fix-vitest-proxy-chained-helpers.md new file mode 100644 index 000000000..a6b5ecbf2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-vitest-record-schema-arbitrary.md b/.repos/effect/.changeset/pre/fix-vitest-record-schema-arbitrary.md new file mode 100644 index 000000000..840708d75 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-vitest-runner-import.md b/.repos/effect/.changeset/pre/fix-vitest-runner-import.md new file mode 100644 index 000000000..1f714c770 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-vitest-throws-assertions.md b/.repos/effect/.changeset/pre/fix-vitest-throws-assertions.md new file mode 100644 index 000000000..307bb3837 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-void-mcp-tool-results.md b/.repos/effect/.changeset/pre/fix-void-mcp-tool-results.md new file mode 100644 index 000000000..5febf7769 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-void-response-encoding.md b/.repos/effect/.changeset/pre/fix-void-response-encoding.md new file mode 100644 index 000000000..bb84b57ff --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-void-response-encoding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +unstable/httpapi HttpApiBuilder: fix void responses producing a non-empty body instead of `Response.empty`, closes #1628. diff --git a/.repos/effect/.changeset/pre/fix-worker-runner-cleanup.md b/.repos/effect/.changeset/pre/fix-worker-runner-cleanup.md new file mode 100644 index 000000000..ea8d118e0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-workflow-defect-reply-serialization.md b/.repos/effect/.changeset/pre/fix-workflow-defect-reply-serialization.md new file mode 100644 index 000000000..461124a65 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-workflow-defect-reply-serialization.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix cluster workflow activity defect hydration diff --git a/.repos/effect/.changeset/pre/fix-workflow-entity-client-collision.md b/.repos/effect/.changeset/pre/fix-workflow-entity-client-collision.md new file mode 100644 index 000000000..b682dc921 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-workflow-proxy-rpc-handler-context.md b/.repos/effect/.changeset/pre/fix-workflow-proxy-rpc-handler-context.md new file mode 100644 index 000000000..cc1541b66 --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-workflow-proxy-rpc-handler-context.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix workflow proxy RPC handlers to provide the context expected by RpcServer. diff --git a/.repos/effect/.changeset/pre/fix-workflow-trace-context.md b/.repos/effect/.changeset/pre/fix-workflow-trace-context.md new file mode 100644 index 000000000..5ede2477b --- /dev/null +++ b/.repos/effect/.changeset/pre/fix-workflow-trace-context.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Propagate trace context through persisted cluster workflow requests. diff --git a/.repos/effect/.changeset/pre/fix-xhr-form-data.md b/.repos/effect/.changeset/pre/fix-xhr-form-data.md new file mode 100644 index 000000000..28bc21691 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-zero-max-bytes.md b/.repos/effect/.changeset/pre/fix-zero-max-bytes.md new file mode 100644 index 000000000..c38fd6e0b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fix-zsh-mixed-command-completions.md b/.repos/effect/.changeset/pre/fix-zsh-mixed-command-completions.md new file mode 100644 index 000000000..67978c2bd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/flat-chicken-remain.md b/.repos/effect/.changeset/pre/flat-chicken-remain.md new file mode 100644 index 000000000..17b059859 --- /dev/null +++ b/.repos/effect/.changeset/pre/flat-chicken-remain.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix AI structured output schema generation for `Schema.Class` and `Schema.ErrorClass` by resolving top-level `$ref` entries before passing JSON Schema to providers and default codec transformers. diff --git a/.repos/effect/.changeset/pre/flat-graphs-agree.md b/.repos/effect/.changeset/pre/flat-graphs-agree.md new file mode 100644 index 000000000..8873ed9b4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/floppy-cows-spend.md b/.repos/effect/.changeset/pre/floppy-cows-spend.md new file mode 100644 index 000000000..895423806 --- /dev/null +++ b/.repos/effect/.changeset/pre/floppy-cows-spend.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow encoding Redacted by default, and add option to disallow encoding diff --git a/.repos/effect/.changeset/pre/floppy-frogs-juggle.md b/.repos/effect/.changeset/pre/floppy-frogs-juggle.md new file mode 100644 index 000000000..4f4f40cd4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/floppy-items-admire.md b/.repos/effect/.changeset/pre/floppy-items-admire.md new file mode 100644 index 000000000..40c313213 --- /dev/null +++ b/.repos/effect/.changeset/pre/floppy-items-admire.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Change `Type_<>` implementation, from using `Exclude` type util to `keyof F as xx`, this implementation keeps IDE provenance link. This enables clicking "Go to definition (F12)" in VSCode on an object made from Schema Struct jumps to the correct Struct field definition. diff --git a/.repos/effect/.changeset/pre/floppy-pigs-kiss.md b/.repos/effect/.changeset/pre/floppy-pigs-kiss.md new file mode 100644 index 000000000..0dfba7bb7 --- /dev/null +++ b/.repos/effect/.changeset/pre/floppy-pigs-kiss.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +use Option instead of undefined | A diff --git a/.repos/effect/.changeset/pre/floppy-rats-leave.md b/.repos/effect/.changeset/pre/floppy-rats-leave.md new file mode 100644 index 000000000..cb520a1fd --- /dev/null +++ b/.repos/effect/.changeset/pre/floppy-rats-leave.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fixed Clock.sleep handling of large durations diff --git a/.repos/effect/.changeset/pre/floyd-warshall-null-edge-data.md b/.repos/effect/.changeset/pre/floyd-warshall-null-edge-data.md new file mode 100644 index 000000000..368716363 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fluffy-meals-matter.md b/.repos/effect/.changeset/pre/fluffy-meals-matter.md new file mode 100644 index 000000000..fa656127b --- /dev/null +++ b/.repos/effect/.changeset/pre/fluffy-meals-matter.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +add OpenAiClient.withWebSocketMode diff --git a/.repos/effect/.changeset/pre/fluffy-pumas-push.md b/.repos/effect/.changeset/pre/fluffy-pumas-push.md new file mode 100644 index 000000000..64e2b1e01 --- /dev/null +++ b/.repos/effect/.changeset/pre/fluffy-pumas-push.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +disable sql traces for EventLog, RunnerStorage diff --git a/.repos/effect/.changeset/pre/forked-memo-maps.md b/.repos/effect/.changeset/pre/forked-memo-maps.md new file mode 100644 index 000000000..37dd43fab --- /dev/null +++ b/.repos/effect/.changeset/pre/forked-memo-maps.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/vitest": patch +--- + +Add forked memo maps so nested layer scopes can reuse parent allocations without leaking sibling-local layers. Update `@effect/vitest` to fork memo maps for nested `it.layer` suites, isolating sibling setup while preserving parent sharing. diff --git a/.repos/effect/.changeset/pre/forty-hounds-cheer.md b/.repos/effect/.changeset/pre/forty-hounds-cheer.md new file mode 100644 index 000000000..ffbf6d29d --- /dev/null +++ b/.repos/effect/.changeset/pre/forty-hounds-cheer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore unsupported Ctrl key combinations in interactive CLI prompts to avoid rendering control characters such as Ctrl+L form feed into prompt input. diff --git a/.repos/effect/.changeset/pre/forty-otters-cry.md b/.repos/effect/.changeset/pre/forty-otters-cry.md new file mode 100644 index 000000000..e95732ebb --- /dev/null +++ b/.repos/effect/.changeset/pre/forty-otters-cry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Port `SqlSchema.findOne` from effect v3 to return `Option` on empty results and add `SqlSchema.single` for the fail-on-empty behavior. diff --git a/.repos/effect/.changeset/pre/forty-rings-film.md b/.repos/effect/.changeset/pre/forty-rings-film.md new file mode 100644 index 000000000..7c66c965d --- /dev/null +++ b/.repos/effect/.changeset/pre/forty-rings-film.md @@ -0,0 +1,7 @@ +--- +"@effect/ai-openai": patch +--- + +Refactor `OpenAiClient` to the handwritten minimal-schema path so `client` now exposes the configured `HttpClient`, `createResponse` / `createResponseStream` / `createEmbedding` use `OpenAiSchema` request-response types, and websocket mode no longer depends on generated-client internals. + +Also migrate OpenAI language and embedding model request-response typing to `OpenAiSchema` and make embedding decoding explicitly reject non-vector (string/base64) payloads with `InvalidOutputError`. diff --git a/.repos/effect/.changeset/pre/forty-signs-stay.md b/.repos/effect/.changeset/pre/forty-signs-stay.md new file mode 100644 index 000000000..089873c77 --- /dev/null +++ b/.repos/effect/.changeset/pre/forty-signs-stay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add rpc ConnectionHooks diff --git a/.repos/effect/.changeset/pre/forty-swans-divide.md b/.repos/effect/.changeset/pre/forty-swans-divide.md new file mode 100644 index 000000000..98e79f33d --- /dev/null +++ b/.repos/effect/.changeset/pre/forty-swans-divide.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +improve Schema.TaggedUnion .match auto completion diff --git a/.repos/effect/.changeset/pre/forty-trees-pay.md b/.repos/effect/.changeset/pre/forty-trees-pay.md new file mode 100644 index 000000000..5e659393e --- /dev/null +++ b/.repos/effect/.changeset/pre/forty-trees-pay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add `availableShardGroups` to ShardingConfig, to ensure advisory locks do not conflict diff --git a/.repos/effect/.changeset/pre/four-papayas-bow.md b/.repos/effect/.changeset/pre/four-papayas-bow.md new file mode 100644 index 000000000..a925f5442 --- /dev/null +++ b/.repos/effect/.changeset/pre/four-papayas-bow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Support toolkit unions in `LanguageModel` options. diff --git a/.repos/effect/.changeset/pre/four-points-repeat.md b/.repos/effect/.changeset/pre/four-points-repeat.md new file mode 100644 index 000000000..62f6b4b56 --- /dev/null +++ b/.repos/effect/.changeset/pre/four-points-repeat.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add "Previously Known As" JSDoc migration notes for the `Semaphore` and `Latch` APIs extracted from `Effect`. diff --git a/.repos/effect/.changeset/pre/frank-apes-vanish.md b/.repos/effect/.changeset/pre/frank-apes-vanish.md new file mode 100644 index 000000000..603a16fd7 --- /dev/null +++ b/.repos/effect/.changeset/pre/frank-apes-vanish.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Close suspended workflow scopes after resumed completion. diff --git a/.repos/effect/.changeset/pre/fresh-cats-smash.md b/.repos/effect/.changeset/pre/fresh-cats-smash.md new file mode 100644 index 000000000..d563083f4 --- /dev/null +++ b/.repos/effect/.changeset/pre/fresh-cats-smash.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix stream requests in Entity.toLayerQueue diff --git a/.repos/effect/.changeset/pre/fresh-cycles-wait.md b/.repos/effect/.changeset/pre/fresh-cycles-wait.md new file mode 100644 index 000000000..f588122cd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fresh-deps-update.md b/.repos/effect/.changeset/pre/fresh-deps-update.md new file mode 100644 index 000000000..c6701bb51 --- /dev/null +++ b/.repos/effect/.changeset/pre/fresh-deps-update.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update dependencies across the Effect workspace. diff --git a/.repos/effect/.changeset/pre/fresh-emus-cheat.md b/.repos/effect/.changeset/pre/fresh-emus-cheat.md new file mode 100644 index 000000000..3ee2e11d0 --- /dev/null +++ b/.repos/effect/.changeset/pre/fresh-emus-cheat.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Export `Effect` do notation APIs (`Do`, `bindTo`, `bind`, and `let`) from `effect/Effect` and add runtime and type-level coverage. diff --git a/.repos/effect/.changeset/pre/fresh-files-seek.md b/.repos/effect/.changeset/pre/fresh-files-seek.md new file mode 100644 index 000000000..51fb44445 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fresh-forms-travel.md b/.repos/effect/.changeset/pre/fresh-forms-travel.md new file mode 100644 index 000000000..8b64d908d --- /dev/null +++ b/.repos/effect/.changeset/pre/fresh-forms-travel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve FormData bodies when converting client requests through HttpServerRequest. diff --git a/.repos/effect/.changeset/pre/fresh-graphs-cycle.md b/.repos/effect/.changeset/pre/fresh-graphs-cycle.md new file mode 100644 index 000000000..d57f63749 --- /dev/null +++ b/.repos/effect/.changeset/pre/fresh-graphs-cycle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Graph.findCycle` with exact node and edge witnesses. diff --git a/.repos/effect/.changeset/pre/fresh-images-generate.md b/.repos/effect/.changeset/pre/fresh-images-generate.md new file mode 100644 index 000000000..e8914d5f0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fresh-lines-wait.md b/.repos/effect/.changeset/pre/fresh-lines-wait.md new file mode 100644 index 000000000..d064c998e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fresh-monkeys-smoke.md b/.repos/effect/.changeset/pre/fresh-monkeys-smoke.md new file mode 100644 index 000000000..523ab35ad --- /dev/null +++ b/.repos/effect/.changeset/pre/fresh-monkeys-smoke.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 `Model.ModelName` and provide it from AI model constructors. diff --git a/.repos/effect/.changeset/pre/fresh-rivers-report.md b/.repos/effect/.changeset/pre/fresh-rivers-report.md new file mode 100644 index 000000000..121cea53e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/frozen-intrinsics-stack-trace-limit.md b/.repos/effect/.changeset/pre/frozen-intrinsics-stack-trace-limit.md new file mode 100644 index 000000000..04aa3ce46 --- /dev/null +++ b/.repos/effect/.changeset/pre/frozen-intrinsics-stack-trace-limit.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Avoid throwing when `Error.stackTraceLimit` is non-writable (frozen intrinsics / SES / deterministic sandboxes such as Temporal). + +Effect manipulates `Error.stackTraceLimit` in several internal spots to capture short or empty stack traces cheaply. In hardened environments where `Error` is frozen and `stackTraceLimit` is read-only, assigning to it throws, which broke Effect entirely. Stack-trace-limit manipulation is now best-effort and silently no-ops when the property cannot be modified, mirroring Node's own internal guard. Behavior in normal (writable) environments is unchanged. diff --git a/.repos/effect/.changeset/pre/fruity-houses-learn.md b/.repos/effect/.changeset/pre/fruity-houses-learn.md new file mode 100644 index 000000000..3e843ad2a --- /dev/null +++ b/.repos/effect/.changeset/pre/fruity-houses-learn.md @@ -0,0 +1,6 @@ +--- +"@effect/sql-mysql2": patch +"effect": patch +--- + +return resolvers directly from SqlModel.makeResolvers diff --git a/.repos/effect/.changeset/pre/fruity-sloths-walk.md b/.repos/effect/.changeset/pre/fruity-sloths-walk.md new file mode 100644 index 000000000..2e9ef2b1d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/full-adults-double.md b/.repos/effect/.changeset/pre/full-adults-double.md new file mode 100644 index 000000000..d1d236aff --- /dev/null +++ b/.repos/effect/.changeset/pre/full-adults-double.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add RpcGroup.omit diff --git a/.repos/effect/.changeset/pre/funny-crabs-hang.md b/.repos/effect/.changeset/pre/funny-crabs-hang.md new file mode 100644 index 000000000..2fd2a4cb7 --- /dev/null +++ b/.repos/effect/.changeset/pre/funny-crabs-hang.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"effect": patch +--- + +Ensure that OpenAI JSON schemas for tool calls and structured outputs are properly transformed diff --git a/.repos/effect/.changeset/pre/funny-ears-beam.md b/.repos/effect/.changeset/pre/funny-ears-beam.md new file mode 100644 index 000000000..00b7994c7 --- /dev/null +++ b/.repos/effect/.changeset/pre/funny-ears-beam.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent Effect.updateService and Effect.updateServiceScoped supertype widening diff --git a/.repos/effect/.changeset/pre/funny-forks-move.md b/.repos/effect/.changeset/pre/funny-forks-move.md new file mode 100644 index 000000000..b4f8cd507 --- /dev/null +++ b/.repos/effect/.changeset/pre/funny-forks-move.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Constrain `HttpServerRequest.source` to `object` and key server-side request weak caches by `request.source` so middleware request wrappers share the same cache entries. diff --git a/.repos/effect/.changeset/pre/fuzzy-batches-stop.md b/.repos/effect/.changeset/pre/fuzzy-batches-stop.md new file mode 100644 index 000000000..0dbd54ab9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-caches-expire.md b/.repos/effect/.changeset/pre/fuzzy-caches-expire.md new file mode 100644 index 000000000..5e511fe65 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-camels-hunt.md b/.repos/effect/.changeset/pre/fuzzy-camels-hunt.md new file mode 100644 index 000000000..969059757 --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-camels-hunt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Prompt.text` and related text prompts to initialize from `default` values so users can edit the default input directly. diff --git a/.repos/effect/.changeset/pre/fuzzy-cats-kill.md b/.repos/effect/.changeset/pre/fuzzy-cats-kill.md new file mode 100644 index 000000000..35bae2e9f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-cats-listen.md b/.repos/effect/.changeset/pre/fuzzy-cats-listen.md new file mode 100644 index 000000000..eb5b34707 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-crews-fold.md b/.repos/effect/.changeset/pre/fuzzy-crews-fold.md new file mode 100644 index 000000000..1bf7243e5 --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-crews-fold.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add custom error callbacks to Effect.fromOption. diff --git a/.repos/effect/.changeset/pre/fuzzy-databases-abort.md b/.repos/effect/.changeset/pre/fuzzy-databases-abort.md new file mode 100644 index 000000000..54923cc70 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-dodos-help.md b/.repos/effect/.changeset/pre/fuzzy-dodos-help.md new file mode 100644 index 000000000..9e2d5bddc --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-dodos-help.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add a CI check job that runs `pnpm ai-docgen` and fails if it produces uncommitted changes. diff --git a/.repos/effect/.changeset/pre/fuzzy-files-slice.md b/.repos/effect/.changeset/pre/fuzzy-files-slice.md new file mode 100644 index 000000000..8fde69fcc --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-graphs-transform.md b/.repos/effect/.changeset/pre/fuzzy-graphs-transform.md new file mode 100644 index 000000000..c5d8fae75 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-hornets-wish.md b/.repos/effect/.changeset/pre/fuzzy-hornets-wish.md new file mode 100644 index 000000000..d9d06218b --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-hornets-wish.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Handle accepted undefined fields during variant extraction. diff --git a/.repos/effect/.changeset/pre/fuzzy-lions-perform.md b/.repos/effect/.changeset/pre/fuzzy-lions-perform.md new file mode 100644 index 000000000..90ec37089 --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-lions-perform.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": minor +--- + +Add a new public `OpenAiSchema` module with minimal local schemas for responses, streaming SSE events (including unknown-event fallback), and embeddings. diff --git a/.repos/effect/.changeset/pre/fuzzy-lions-study.md b/.repos/effect/.changeset/pre/fuzzy-lions-study.md new file mode 100644 index 000000000..eb647e0ab --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-pandas-smile.md b/.repos/effect/.changeset/pre/fuzzy-pandas-smile.md new file mode 100644 index 000000000..835fefafd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-planets-sneeze.md b/.repos/effect/.changeset/pre/fuzzy-planets-sneeze.md new file mode 100644 index 000000000..7e10603c9 --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-planets-sneeze.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix AtomRef notifications when a listener re-subscribes itself during notification. diff --git a/.repos/effect/.changeset/pre/fuzzy-rabbits-cancel.md b/.repos/effect/.changeset/pre/fuzzy-rabbits-cancel.md new file mode 100644 index 000000000..4060d9c86 --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-rabbits-cancel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore MCP cancellation notifications for unknown request identifiers. diff --git a/.repos/effect/.changeset/pre/fuzzy-ravens-reason.md b/.repos/effect/.changeset/pre/fuzzy-ravens-reason.md new file mode 100644 index 000000000..1858474d8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/fuzzy-routers-smile.md b/.repos/effect/.changeset/pre/fuzzy-routers-smile.md new file mode 100644 index 000000000..a286099cc --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-routers-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix several edge cases in the vendored FindMyWay router. diff --git a/.repos/effect/.changeset/pre/fuzzy-stamps-care.md b/.repos/effect/.changeset/pre/fuzzy-stamps-care.md new file mode 100644 index 000000000..a6272595b --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-stamps-care.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +update dependencies diff --git a/.repos/effect/.changeset/pre/fuzzy-timers-smile.md b/.repos/effect/.changeset/pre/fuzzy-timers-smile.md new file mode 100644 index 000000000..874eef006 --- /dev/null +++ b/.repos/effect/.changeset/pre/fuzzy-timers-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep TestClock nanosecond access total after infinite adjustments. diff --git a/.repos/effect/.changeset/pre/general-json-schema-converters.md b/.repos/effect/.changeset/pre/general-json-schema-converters.md new file mode 100644 index 000000000..366962f93 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/giant-jeans-float.md b/.repos/effect/.changeset/pre/giant-jeans-float.md new file mode 100644 index 000000000..3f0eb7ef1 --- /dev/null +++ b/.repos/effect/.changeset/pre/giant-jeans-float.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure one-shot iterables work with Fiber apis diff --git a/.repos/effect/.changeset/pre/gold-meteors-move.md b/.repos/effect/.changeset/pre/gold-meteors-move.md new file mode 100644 index 000000000..62f8e5045 --- /dev/null +++ b/.repos/effect/.changeset/pre/gold-meteors-move.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow using Duration.Input with accessors diff --git a/.repos/effect/.changeset/pre/gold-readers-hug.md b/.repos/effect/.changeset/pre/gold-readers-hug.md new file mode 100644 index 000000000..c1e74afbb --- /dev/null +++ b/.repos/effect/.changeset/pre/gold-readers-hug.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +add .reactive to indexeddb .first queries diff --git a/.repos/effect/.changeset/pre/gold-rings-start.md b/.repos/effect/.changeset/pre/gold-rings-start.md new file mode 100644 index 000000000..a47549eef --- /dev/null +++ b/.repos/effect/.changeset/pre/gold-rings-start.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix Chat constructor types diff --git a/.repos/effect/.changeset/pre/good-cups-reply.md b/.repos/effect/.changeset/pre/good-cups-reply.md new file mode 100644 index 000000000..344af728a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/good-tools-work.md b/.repos/effect/.changeset/pre/good-tools-work.md new file mode 100644 index 000000000..7da12bb9e --- /dev/null +++ b/.repos/effect/.changeset/pre/good-tools-work.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +HttpServerResponse: fix `fromWeb` to preserve Content-Type header when response has a body + +Previously, when converting a web `Response` to an `HttpServerResponse` via `fromWeb`, the `Content-Type` header was not passed to `Body.stream()`, causing it to default to `application/octet-stream`. This affected any code using `HttpApp.fromWebHandler` to wrap web handlers, as JSON responses would incorrectly have their Content-Type set to `application/octet-stream` instead of `application/json`. diff --git a/.repos/effect/.changeset/pre/good-trees-pull.md b/.repos/effect/.changeset/pre/good-trees-pull.md new file mode 100644 index 000000000..eefb42c6e --- /dev/null +++ b/.repos/effect/.changeset/pre/good-trees-pull.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +align ProcessInput.Input runtime field name with type definition on Prompt.custom diff --git a/.repos/effect/.changeset/pre/graph-acyclic-parallel-undirected-edges.md b/.repos/effect/.changeset/pre/graph-acyclic-parallel-undirected-edges.md new file mode 100644 index 000000000..5bb98abbb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/graph-algorithm-fixes.md b/.repos/effect/.changeset/pre/graph-algorithm-fixes.md new file mode 100644 index 000000000..21c18e2ea --- /dev/null +++ b/.repos/effect/.changeset/pre/graph-algorithm-fixes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Graph.successors` and `Graph.predecessors`, deprecate `Graph.neighborsDirected`, and fix graph algorithm edge cases around reversal, undirected edge queries, shortest-path weight validation, topological sort initials, and strongly connected components. diff --git a/.repos/effect/.changeset/pre/graph-finalized-mutation-handle.md b/.repos/effect/.changeset/pre/graph-finalized-mutation-handle.md new file mode 100644 index 000000000..4f628c326 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/graph-guard-predicates.md b/.repos/effect/.changeset/pre/graph-guard-predicates.md new file mode 100644 index 000000000..da96b1a56 --- /dev/null +++ b/.repos/effect/.changeset/pre/graph-guard-predicates.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Graph.isGraph narrowing for mutable and undirected graphs. diff --git a/.repos/effect/.changeset/pre/graph-sync-mutation-callbacks.md b/.repos/effect/.changeset/pre/graph-sync-mutation-callbacks.md new file mode 100644 index 000000000..c006de9f8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/graph-undirected-edge-equality.md b/.repos/effect/.changeset/pre/graph-undirected-edge-equality.md new file mode 100644 index 000000000..25212ae68 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/graph-walker-iterator-receiver.md b/.repos/effect/.changeset/pre/graph-walker-iterator-receiver.md new file mode 100644 index 000000000..a44ad4655 --- /dev/null +++ b/.repos/effect/.changeset/pre/graph-walker-iterator-receiver.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.Walker` iteration for receiver-sensitive iterables. diff --git a/.repos/effect/.changeset/pre/great-trains-mate.md b/.repos/effect/.changeset/pre/great-trains-mate.md new file mode 100644 index 000000000..043f0ee30 --- /dev/null +++ b/.repos/effect/.changeset/pre/great-trains-mate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix Duration.toMillis regression diff --git a/.repos/effect/.changeset/pre/great-trams-report.md b/.repos/effect/.changeset/pre/great-trams-report.md new file mode 100644 index 000000000..ac6945091 --- /dev/null +++ b/.repos/effect/.changeset/pre/great-trams-report.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +cleanup IndexedDb prototypes diff --git a/.repos/effect/.changeset/pre/green-ads-camp.md b/.repos/effect/.changeset/pre/green-ads-camp.md new file mode 100644 index 000000000..aa0410482 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/green-beds-unref.md b/.repos/effect/.changeset/pre/green-beds-unref.md new file mode 100644 index 000000000..db4ec3b3d --- /dev/null +++ b/.repos/effect/.changeset/pre/green-beds-unref.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +--- + +Add `ChildProcessHandle.unref`, returning an `Effect` that restores the child process reference when run. diff --git a/.repos/effect/.changeset/pre/green-birds-close.md b/.repos/effect/.changeset/pre/green-birds-close.md new file mode 100644 index 000000000..e21ba8438 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/green-chips-wash.md b/.repos/effect/.changeset/pre/green-chips-wash.md new file mode 100644 index 000000000..3b396434e --- /dev/null +++ b/.repos/effect/.changeset/pre/green-chips-wash.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Finalize the OpenAPI generator public migration by replacing the `typeOnly` option and `--type-only` CLI flag with the `format` option and `--format` flag, and by adding `httpapi` as a supported output alongside `httpclient` and `httpclient-type-only`. diff --git a/.repos/effect/.changeset/pre/green-moons-smile.md b/.repos/effect/.changeset/pre/green-moons-smile.md new file mode 100644 index 000000000..6f3a83232 --- /dev/null +++ b/.repos/effect/.changeset/pre/green-moons-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve `Atom.withReactivity(...)` refresh behavior when registry initial values seed the wrapped atom. diff --git a/.repos/effect/.changeset/pre/green-pugs-play.md b/.repos/effect/.changeset/pre/green-pugs-play.md new file mode 100644 index 000000000..4b135069a --- /dev/null +++ b/.repos/effect/.changeset/pre/green-pugs-play.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +add defaults to indexeddb reactivity keys diff --git a/.repos/effect/.changeset/pre/green-rings-prove.md b/.repos/effect/.changeset/pre/green-rings-prove.md new file mode 100644 index 000000000..c7a2ea606 --- /dev/null +++ b/.repos/effect/.changeset/pre/green-rings-prove.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +update Model uuid helpers diff --git a/.repos/effect/.changeset/pre/happy-mirrors-dream.md b/.repos/effect/.changeset/pre/happy-mirrors-dream.md new file mode 100644 index 000000000..b944244b5 --- /dev/null +++ b/.repos/effect/.changeset/pre/happy-mirrors-dream.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Socket.make diff --git a/.repos/effect/.changeset/pre/harden-httpapi-documentation-html.md b/.repos/effect/.changeset/pre/harden-httpapi-documentation-html.md new file mode 100644 index 000000000..774dbe329 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/hash-sql-message-dedupe-keys.md b/.repos/effect/.changeset/pre/hash-sql-message-dedupe-keys.md new file mode 100644 index 000000000..9e386e019 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/heavy-loops-cut.md b/.repos/effect/.changeset/pre/heavy-loops-cut.md new file mode 100644 index 000000000..0e9730f7f --- /dev/null +++ b/.repos/effect/.changeset/pre/heavy-loops-cut.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Unwrap `_Success` schema to enable field access. diff --git a/.repos/effect/.changeset/pre/heavy-trams-fix.md b/.repos/effect/.changeset/pre/heavy-trams-fix.md new file mode 100644 index 000000000..507769677 --- /dev/null +++ b/.repos/effect/.changeset/pre/heavy-trams-fix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +make HttpClientResponse pipeable diff --git a/.repos/effect/.changeset/pre/hip-friends-kiss.md b/.repos/effect/.changeset/pre/hip-friends-kiss.md new file mode 100644 index 000000000..a5c66356c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/hip-socks-travel.md b/.repos/effect/.changeset/pre/hip-socks-travel.md new file mode 100644 index 000000000..3f67b064b --- /dev/null +++ b/.repos/effect/.changeset/pre/hip-socks-travel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +short circuit when Fiber.joinAll is called with an empty iterable diff --git a/.repos/effect/.changeset/pre/honest-pens-thank.md b/.repos/effect/.changeset/pre/honest-pens-thank.md new file mode 100644 index 000000000..d5f0ee6e0 --- /dev/null +++ b/.repos/effect/.changeset/pre/honest-pens-thank.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpClient.withRateLimiter` for integrating the `RateLimiter` service with HTTP clients, including optional response-header driven limit updates and automatic 429 retry behavior. diff --git a/.repos/effect/.changeset/pre/honest-rivers-notice.md b/.repos/effect/.changeset/pre/honest-rivers-notice.md new file mode 100644 index 000000000..f3da306ce --- /dev/null +++ b/.repos/effect/.changeset/pre/honest-rivers-notice.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +seperate scheduler dispatch from yield decisions diff --git a/.repos/effect/.changeset/pre/hot-taxis-fry.md b/.repos/effect/.changeset/pre/hot-taxis-fry.md new file mode 100644 index 000000000..2563a31ed --- /dev/null +++ b/.repos/effect/.changeset/pre/hot-taxis-fry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix issue with exported CLI `Completions` types diff --git a/.repos/effect/.changeset/pre/hot-teeth-clean.md b/.repos/effect/.changeset/pre/hot-teeth-clean.md new file mode 100644 index 000000000..c737634e7 --- /dev/null +++ b/.repos/effect/.changeset/pre/hot-teeth-clean.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +change rpc ids to string | number diff --git a/.repos/effect/.changeset/pre/http-json-reviver.md b/.repos/effect/.changeset/pre/http-json-reviver.md new file mode 100644 index 000000000..51ceb6ddf --- /dev/null +++ b/.repos/effect/.changeset/pre/http-json-reviver.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add custom reviver support to HTTP JSON parsing APIs. diff --git a/.repos/effect/.changeset/pre/http-server-websocket-options.md b/.repos/effect/.changeset/pre/http-server-websocket-options.md new file mode 100644 index 000000000..177d15b7e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/http-status-from-literal.md b/.repos/effect/.changeset/pre/http-status-from-literal.md new file mode 100644 index 000000000..e5942772a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/httpapi-endpoint-relax-constraints.md b/.repos/effect/.changeset/pre/httpapi-endpoint-relax-constraints.md new file mode 100644 index 000000000..b7bc906d7 --- /dev/null +++ b/.repos/effect/.changeset/pre/httpapi-endpoint-relax-constraints.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +HttpApiEndpoint: relax `params`, `query`, and `headers` constraints to accept a full schema in addition to a record of fields. diff --git a/.repos/effect/.changeset/pre/httpapi-schema-service-types.md b/.repos/effect/.changeset/pre/httpapi-schema-service-types.md new file mode 100644 index 000000000..bc86d0521 --- /dev/null +++ b/.repos/effect/.changeset/pre/httpapi-schema-service-types.md @@ -0,0 +1,6 @@ +--- +"@effect/openapi-generator": patch +"effect": patch +--- + +Add HTTP API streaming response support diff --git a/.repos/effect/.changeset/pre/huge-moons-rhyme.md b/.repos/effect/.changeset/pre/huge-moons-rhyme.md new file mode 100644 index 000000000..5488d5822 --- /dev/null +++ b/.repos/effect/.changeset/pre/huge-moons-rhyme.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +feat: Support Reference classes diff --git a/.repos/effect/.changeset/pre/humble-pigs-dig.md b/.repos/effect/.changeset/pre/humble-pigs-dig.md new file mode 100644 index 000000000..212d8895e --- /dev/null +++ b/.repos/effect/.changeset/pre/humble-pigs-dig.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +align HttpClientRequest constructors with http method names diff --git a/.repos/effect/.changeset/pre/hungry-kings-look.md b/.repos/effect/.changeset/pre/hungry-kings-look.md new file mode 100644 index 000000000..602fb3422 --- /dev/null +++ b/.repos/effect/.changeset/pre/hungry-kings-look.md @@ -0,0 +1,6 @@ +--- +"@effect/platform-node-shared": patch +"effect": patch +--- + +Add glob to filesystem diff --git a/.repos/effect/.changeset/pre/icy-flies-cross.md b/.repos/effect/.changeset/pre/icy-flies-cross.md new file mode 100644 index 000000000..a6272595b --- /dev/null +++ b/.repos/effect/.changeset/pre/icy-flies-cross.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +update dependencies diff --git a/.repos/effect/.changeset/pre/internal-json-string-schema.md b/.repos/effect/.changeset/pre/internal-json-string-schema.md new file mode 100644 index 000000000..2a5ab3549 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/isolate-sql-compiler-cache.md b/.repos/effect/.changeset/pre/isolate-sql-compiler-cache.md new file mode 100644 index 000000000..f45018a57 --- /dev/null +++ b/.repos/effect/.changeset/pre/isolate-sql-compiler-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Isolate compiled SQL fragment caches by compiler instance. diff --git a/.repos/effect/.changeset/pre/itchy-radios-poke.md b/.repos/effect/.changeset/pre/itchy-radios-poke.md new file mode 100644 index 000000000..0ab497b08 --- /dev/null +++ b/.repos/effect/.changeset/pre/itchy-radios-poke.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Abort HTTP client requests when response streams are consumed only partially. diff --git a/.repos/effect/.changeset/pre/itchy-results-bet.md b/.repos/effect/.changeset/pre/itchy-results-bet.md new file mode 100644 index 000000000..d3998b75e --- /dev/null +++ b/.repos/effect/.changeset/pre/itchy-results-bet.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure non-middleware http errors are correctly handled diff --git a/.repos/effect/.changeset/pre/itchy-shrimps-deny.md b/.repos/effect/.changeset/pre/itchy-shrimps-deny.md new file mode 100644 index 000000000..bb120fb38 --- /dev/null +++ b/.repos/effect/.changeset/pre/itchy-shrimps-deny.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add ErrorReporter module diff --git a/.repos/effect/.changeset/pre/itchy-toes-promise.md b/.repos/effect/.changeset/pre/itchy-toes-promise.md new file mode 100644 index 000000000..2c8c704b1 --- /dev/null +++ b/.repos/effect/.changeset/pre/itchy-toes-promise.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Revert `Effect.partition` to Effect v3 behavior by accumulating failures from the effect error channel and never failing. diff --git a/.repos/effect/.changeset/pre/k8s-last-transition-null.md b/.repos/effect/.changeset/pre/k8s-last-transition-null.md new file mode 100644 index 000000000..d55d7ecea --- /dev/null +++ b/.repos/effect/.changeset/pre/k8s-last-transition-null.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow Kubernetes pod condition `lastTransitionTime` values to be null in K8sHttpClient schemas. diff --git a/.repos/effect/.changeset/pre/keep-httpapi-composition-immutable.md b/.repos/effect/.changeset/pre/keep-httpapi-composition-immutable.md new file mode 100644 index 000000000..a4f979384 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/khaki-cats-learn.md b/.repos/effect/.changeset/pre/khaki-cats-learn.md new file mode 100644 index 000000000..5a017b62b --- /dev/null +++ b/.repos/effect/.changeset/pre/khaki-cats-learn.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpServerRequest.fromClientRequest` for direct client-request-backed server request conversion. diff --git a/.repos/effect/.changeset/pre/khaki-melons-appear.md b/.repos/effect/.changeset/pre/khaki-melons-appear.md new file mode 100644 index 000000000..72a00cf25 --- /dev/null +++ b/.repos/effect/.changeset/pre/khaki-melons-appear.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix RpcWorker Protocol service key diff --git a/.repos/effect/.changeset/pre/kind-flags-help.md b/.repos/effect/.changeset/pre/kind-flags-help.md new file mode 100644 index 000000000..ecf9af6d2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/kind-hounds-float.md b/.repos/effect/.changeset/pre/kind-hounds-float.md new file mode 100644 index 000000000..b108a2095 --- /dev/null +++ b/.repos/effect/.changeset/pre/kind-hounds-float.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make `Effect.retry` with `times` argument to propagate the original error. diff --git a/.repos/effect/.changeset/pre/kind-windows-fall.md b/.repos/effect/.changeset/pre/kind-windows-fall.md new file mode 100644 index 000000000..3786a89ca --- /dev/null +++ b/.repos/effect/.changeset/pre/kind-windows-fall.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +rename WorkflowEngine.layer diff --git a/.repos/effect/.changeset/pre/late-hotels-rule.md b/.repos/effect/.changeset/pre/late-hotels-rule.md new file mode 100644 index 000000000..4d7fc9e38 --- /dev/null +++ b/.repos/effect/.changeset/pre/late-hotels-rule.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the `Queue.takeN` documentation example to end the queue before showing a partial batch. diff --git a/.repos/effect/.changeset/pre/late-lamps-care.md b/.repos/effect/.changeset/pre/late-lamps-care.md new file mode 100644 index 000000000..cabcf77eb --- /dev/null +++ b/.repos/effect/.changeset/pre/late-lamps-care.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schema.DurationFromString` and `SchemaTransformation.durationFromString`, support `"Infinity"` and `"-Infinity"` in `Duration.fromInput`, and simplify config duration parsing around the shared schema codec, closes #2092. diff --git a/.repos/effect/.changeset/pre/late-rivers-applaud.md b/.repos/effect/.changeset/pre/late-rivers-applaud.md new file mode 100644 index 000000000..37170d6b8 --- /dev/null +++ b/.repos/effect/.changeset/pre/late-rivers-applaud.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: relax `asserts` and `is` constraints. diff --git a/.repos/effect/.changeset/pre/late-sockets-rest.md b/.repos/effect/.changeset/pre/late-sockets-rest.md new file mode 100644 index 000000000..b2fecca5e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/layer-map-dynamic-idle-ttl.md b/.repos/effect/.changeset/pre/layer-map-dynamic-idle-ttl.md new file mode 100644 index 000000000..8a2532a90 --- /dev/null +++ b/.repos/effect/.changeset/pre/layer-map-dynamic-idle-ttl.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Support key-derived `idleTimeToLive` in `LayerMap` options (`make`, `fromRecord`, and `LayerMap.Service`) and add `LayerMap` tests for dynamic TTL behavior. diff --git a/.repos/effect/.changeset/pre/layered-context-storage.md b/.repos/effect/.changeset/pre/layered-context-storage.md new file mode 100644 index 000000000..e1bd0703a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/lazy-openapi-initialization.md b/.repos/effect/.changeset/pre/lazy-openapi-initialization.md new file mode 100644 index 000000000..dc6a55330 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/lazy-queens-rush.md b/.repos/effect/.changeset/pre/lazy-queens-rush.md new file mode 100644 index 000000000..616b7a5cb --- /dev/null +++ b/.repos/effect/.changeset/pre/lazy-queens-rush.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Context.mutate diff --git a/.repos/effect/.changeset/pre/lazy-recursive-forward-refs.md b/.repos/effect/.changeset/pre/lazy-recursive-forward-refs.md new file mode 100644 index 000000000..29e13a726 --- /dev/null +++ b/.repos/effect/.changeset/pre/lazy-recursive-forward-refs.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Fix generated schema declaration ordering when non-recursive schemas reference recursive schemas, preventing TypeScript use-before-declaration errors in generated clients and HttpApi modules. diff --git a/.repos/effect/.changeset/pre/lazy-timers-exist.md b/.repos/effect/.changeset/pre/lazy-timers-exist.md new file mode 100644 index 000000000..e66de3a25 --- /dev/null +++ b/.repos/effect/.changeset/pre/lazy-timers-exist.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove fiber-level keep-alive intervals and keep the process alive from `Runtime.makeRunMain` instead. diff --git a/.repos/effect/.changeset/pre/legal-pants-drop.md b/.repos/effect/.changeset/pre/legal-pants-drop.md new file mode 100644 index 000000000..9bd6f6c6b --- /dev/null +++ b/.repos/effect/.changeset/pre/legal-pants-drop.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +remove body restriction for HttpClientRequest's diff --git a/.repos/effect/.changeset/pre/lemon-taxis-sin.md b/.repos/effect/.changeset/pre/lemon-taxis-sin.md new file mode 100644 index 000000000..750c32541 --- /dev/null +++ b/.repos/effect/.changeset/pre/lemon-taxis-sin.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +make HttpApi schema errors defects unless transformed diff --git a/.repos/effect/.changeset/pre/light-kids-sneeze.md b/.repos/effect/.changeset/pre/light-kids-sneeze.md new file mode 100644 index 000000000..926ba7aaf --- /dev/null +++ b/.repos/effect/.changeset/pre/light-kids-sneeze.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +omit scope from HttpApi handlers diff --git a/.repos/effect/.changeset/pre/little-dryers-allow.md b/.repos/effect/.changeset/pre/little-dryers-allow.md new file mode 100644 index 000000000..9211c9008 --- /dev/null +++ b/.repos/effect/.changeset/pre/little-dryers-allow.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Persist MCP client capability context across HTTP requests by resolving initialized payloads through the standard `Mcp-Session-Id` HTTP header in `McpServer`. + +Adds a regression test that initializes an MCP HTTP client, verifies the MCP server echoes `Mcp-Session-Id`, and then checks a later tool call can still read `McpServer.clientCapabilities`. diff --git a/.repos/effect/.changeset/pre/long-cameras-think.md b/.repos/effect/.changeset/pre/long-cameras-think.md new file mode 100644 index 000000000..aafbd1f5c --- /dev/null +++ b/.repos/effect/.changeset/pre/long-cameras-think.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Forward `OpenAiLanguageModel` `reasoning` config into chat-completions requests. diff --git a/.repos/effect/.changeset/pre/loose-wings-lie.md b/.repos/effect/.changeset/pre/loose-wings-lie.md new file mode 100644 index 000000000..fedc37514 --- /dev/null +++ b/.repos/effect/.changeset/pre/loose-wings-lie.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent Effect.provideServiceEffect supertype widening diff --git a/.repos/effect/.changeset/pre/lovely-cobras-change.md b/.repos/effect/.changeset/pre/lovely-cobras-change.md new file mode 100644 index 000000000..e15d453c8 --- /dev/null +++ b/.repos/effect/.changeset/pre/lovely-cobras-change.md @@ -0,0 +1,5 @@ +--- +"@effect/atom-solid": patch +--- + +allow atoms to be computed in solid bindings diff --git a/.repos/effect/.changeset/pre/lovely-frogs-rescue.md b/.repos/effect/.changeset/pre/lovely-frogs-rescue.md new file mode 100644 index 000000000..eb7ae29c9 --- /dev/null +++ b/.repos/effect/.changeset/pre/lovely-frogs-rescue.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow mcp errors to be encoded correctly diff --git a/.repos/effect/.changeset/pre/lucky-buttons-jump.md b/.repos/effect/.changeset/pre/lucky-buttons-jump.md new file mode 100644 index 000000000..771c41ac3 --- /dev/null +++ b/.repos/effect/.changeset/pre/lucky-buttons-jump.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add an optional `message` field to `Effect.ignore` and `Effect.ignoreCause` for custom log output. diff --git a/.repos/effect/.changeset/pre/lucky-dingos-smile.md b/.repos/effect/.changeset/pre/lucky-dingos-smile.md new file mode 100644 index 000000000..96d9a4ab3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/lucky-phones-listen.md b/.repos/effect/.changeset/pre/lucky-phones-listen.md new file mode 100644 index 000000000..0635e0afd --- /dev/null +++ b/.repos/effect/.changeset/pre/lucky-phones-listen.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpApiClient.urlBuilder` for type-safe endpoint URL construction from group + method/path keys. diff --git a/.repos/effect/.changeset/pre/lucky-worms-type.md b/.repos/effect/.changeset/pre/lucky-worms-type.md new file mode 100644 index 000000000..d4814ccfb --- /dev/null +++ b/.repos/effect/.changeset/pre/lucky-worms-type.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schema.HashMap` for decoding and encoding `HashMap` values. diff --git a/.repos/effect/.changeset/pre/major-chairs-design.md b/.repos/effect/.changeset/pre/major-chairs-design.md new file mode 100644 index 000000000..6e00b3ce6 --- /dev/null +++ b/.repos/effect/.changeset/pre/major-chairs-design.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix RequestResolver interruption diff --git a/.repos/effect/.changeset/pre/managed-runtime-async-dispose.md b/.repos/effect/.changeset/pre/managed-runtime-async-dispose.md new file mode 100644 index 000000000..1b4fe0b7c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/many-badgers-obey.md b/.repos/effect/.changeset/pre/many-badgers-obey.md new file mode 100644 index 000000000..6ec45f57f --- /dev/null +++ b/.repos/effect/.changeset/pre/many-badgers-obey.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow schemas provided to CLI flags / arguments to utilize the environment required by the CLI diff --git a/.repos/effect/.changeset/pre/match-generic-value-terminals.md b/.repos/effect/.changeset/pre/match-generic-value-terminals.md new file mode 100644 index 000000000..f72279c35 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/mcp-http-initialize-version-header.md b/.repos/effect/.changeset/pre/mcp-http-initialize-version-header.md new file mode 100644 index 000000000..5c49c3ad9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/mcp-protocol-versions.md b/.repos/effect/.changeset/pre/mcp-protocol-versions.md new file mode 100644 index 000000000..6c20ac168 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/mcp-tool-output-schema.md b/.repos/effect/.changeset/pre/mcp-tool-output-schema.md new file mode 100644 index 000000000..3c0399361 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/mean-dingos-share.md b/.repos/effect/.changeset/pre/mean-dingos-share.md new file mode 100644 index 000000000..2fd4aef38 --- /dev/null +++ b/.repos/effect/.changeset/pre/mean-dingos-share.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Command.annotate` and `Command.annotateMerge` to unstable CLI commands, and include command annotations in `HelpDoc` so custom help formatters can access command metadata. diff --git a/.repos/effect/.changeset/pre/mean-trains-smash.md b/.repos/effect/.changeset/pre/mean-trains-smash.md new file mode 100644 index 000000000..0054e6043 --- /dev/null +++ b/.repos/effect/.changeset/pre/mean-trains-smash.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Add BrowserPersistence.layerIndexedDb for composing Persistence.layer with the IndexedDB backing layer, and export BrowserPersistence from the package barrel. diff --git a/.repos/effect/.changeset/pre/memoize-idempotent-asts.md b/.repos/effect/.changeset/pre/memoize-idempotent-asts.md new file mode 100644 index 000000000..30c655d7c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/merge-resource-failures.md b/.repos/effect/.changeset/pre/merge-resource-failures.md new file mode 100644 index 000000000..0f5730493 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/metal-nails-sneeze.md b/.repos/effect/.changeset/pre/metal-nails-sneeze.md new file mode 100644 index 000000000..148d8c62b --- /dev/null +++ b/.repos/effect/.changeset/pre/metal-nails-sneeze.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add LayerRef module diff --git a/.repos/effect/.changeset/pre/metal-parts-yell.md b/.repos/effect/.changeset/pre/metal-parts-yell.md new file mode 100644 index 000000000..a6d67450a --- /dev/null +++ b/.repos/effect/.changeset/pre/metal-parts-yell.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +simplify http logger disabling diff --git a/.repos/effect/.changeset/pre/mighty-games-matter.md b/.repos/effect/.changeset/pre/mighty-games-matter.md new file mode 100644 index 000000000..d536ab350 --- /dev/null +++ b/.repos/effect/.changeset/pre/mighty-games-matter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +remove use of bigint literals diff --git a/.repos/effect/.changeset/pre/migrate-persisted-queues.md b/.repos/effect/.changeset/pre/migrate-persisted-queues.md new file mode 100644 index 000000000..6fefa1078 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/migrator-windows-file-url.md b/.repos/effect/.changeset/pre/migrator-windows-file-url.md new file mode 100644 index 000000000..9ad8a9d4d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/modern-carrots-see.md b/.repos/effect/.changeset/pre/modern-carrots-see.md new file mode 100644 index 000000000..83937c30e --- /dev/null +++ b/.repos/effect/.changeset/pre/modern-carrots-see.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +RpcGroup.toHandlers is definition first diff --git a/.repos/effect/.changeset/pre/modern-uuid-guid-filter.md b/.repos/effect/.changeset/pre/modern-uuid-guid-filter.md new file mode 100644 index 000000000..034948af6 --- /dev/null +++ b/.repos/effect/.changeset/pre/modern-uuid-guid-filter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schema.isGUID` and update `Schema.isUUID` to accept the RFC 9562 max UUID. diff --git a/.repos/effect/.changeset/pre/multipart-collect-linear.md b/.repos/effect/.changeset/pre/multipart-collect-linear.md new file mode 100644 index 000000000..93f37d8f1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/multipart-onDone-clobbers-error.md b/.repos/effect/.changeset/pre/multipart-onDone-clobbers-error.md new file mode 100644 index 000000000..621db7284 --- /dev/null +++ b/.repos/effect/.changeset/pre/multipart-onDone-clobbers-error.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix multipart parser limit violations being silently swallowed diff --git a/.repos/effect/.changeset/pre/multipart-parser-limits.md b/.repos/effect/.changeset/pre/multipart-parser-limits.md new file mode 100644 index 000000000..d62512922 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/mysql2-disable-prepared-statements.md b/.repos/effect/.changeset/pre/mysql2-disable-prepared-statements.md new file mode 100644 index 000000000..02150bcc4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/narrow-schedule-while.md b/.repos/effect/.changeset/pre/narrow-schedule-while.md new file mode 100644 index 000000000..85344bc32 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/nasty-geese-grow.md b/.repos/effect/.changeset/pre/nasty-geese-grow.md new file mode 100644 index 000000000..ed408dc28 --- /dev/null +++ b/.repos/effect/.changeset/pre/nasty-geese-grow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Scheduler.PreventSchedulerYield` and expose it via `References` so fibers can skip scheduler `shouldYield` checks when needed. diff --git a/.repos/effect/.changeset/pre/neat-goats-wave.md b/.repos/effect/.changeset/pre/neat-goats-wave.md new file mode 100644 index 000000000..bb3f2c4f7 --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-goats-wave.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Stream.service` and `Stream.serviceOption` for accessing services as single-element streams. diff --git a/.repos/effect/.changeset/pre/neat-graphs-induced.md b/.repos/effect/.changeset/pre/neat-graphs-induced.md new file mode 100644 index 000000000..74b21df26 --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-graphs-induced.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add index-preserving `Graph.inducedSubgraph`. diff --git a/.repos/effect/.changeset/pre/neat-kings-chew.md b/.repos/effect/.changeset/pre/neat-kings-chew.md new file mode 100644 index 000000000..57a18a6c6 --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-kings-chew.md @@ -0,0 +1,5 @@ +--- +"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/.repos/effect/.changeset/pre/neat-lions-rest.md b/.repos/effect/.changeset/pre/neat-lions-rest.md new file mode 100644 index 000000000..e0aa7c3fc --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-lions-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `WorkflowEngine.layer`, an in-memory layer for the unstable workflow engine. diff --git a/.repos/effect/.changeset/pre/neat-pandas-query.md b/.repos/effect/.changeset/pre/neat-pandas-query.md new file mode 100644 index 000000000..f78f49222 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/neat-snails-wash.md b/.repos/effect/.changeset/pre/neat-snails-wash.md new file mode 100644 index 000000000..3ab4515df --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-snails-wash.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Expose a `chunkSize` option on `Stream.fromIterable` to control emitted chunk boundaries when constructing streams from iterables. diff --git a/.repos/effect/.changeset/pre/neat-taxis-notice.md b/.repos/effect/.changeset/pre/neat-taxis-notice.md new file mode 100644 index 000000000..ffdd50ed4 --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-taxis-notice.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.forkScoped` data-first typings to include `Scope` in requirements. diff --git a/.repos/effect/.changeset/pre/neat-tuples-remember.md b/.repos/effect/.changeset/pre/neat-tuples-remember.md new file mode 100644 index 000000000..526502b74 --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-tuples-remember.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve literal element types in `Tuple.make`. diff --git a/.repos/effect/.changeset/pre/neat-windows-buy.md b/.repos/effect/.changeset/pre/neat-windows-buy.md new file mode 100644 index 000000000..661cff0cf --- /dev/null +++ b/.repos/effect/.changeset/pre/neat-windows-buy.md @@ -0,0 +1,10 @@ +--- +"@effect/sql-mssql": patch +--- + +Added some options for the MssqlClient: + +- multiSubnetFailover +- cancelTimeout +- connectionRetryInterval +- maxRetriesOnTransientErrors diff --git a/.repos/effect/.changeset/pre/nested-union-sentinels.md b/.repos/effect/.changeset/pre/nested-union-sentinels.md new file mode 100644 index 000000000..bba5671a1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/new-dogs-swim.md b/.repos/effect/.changeset/pre/new-dogs-swim.md new file mode 100644 index 000000000..ef306ca7b --- /dev/null +++ b/.repos/effect/.changeset/pre/new-dogs-swim.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +make defects a retryable network error in websocket mode diff --git a/.repos/effect/.changeset/pre/new-toes-stop.md b/.repos/effect/.changeset/pre/new-toes-stop.md new file mode 100644 index 000000000..4270fd00c --- /dev/null +++ b/.repos/effect/.changeset/pre/new-toes-stop.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +add idb stream and offset diff --git a/.repos/effect/.changeset/pre/ninety-geese-exist.md b/.repos/effect/.changeset/pre/ninety-geese-exist.md new file mode 100644 index 000000000..20e8ce1b5 --- /dev/null +++ b/.repos/effect/.changeset/pre/ninety-geese-exist.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add HttpApiSecurity.http for passing custom schemes diff --git a/.repos/effect/.changeset/pre/node-redis-client.md b/.repos/effect/.changeset/pre/node-redis-client.md new file mode 100644 index 000000000..1e869ad6d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/node-terminal-idle-ttl.md b/.repos/effect/.changeset/pre/node-terminal-idle-ttl.md new file mode 100644 index 000000000..71ac7237f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/node-terminal-stdin-eof.md b/.repos/effect/.changeset/pre/node-terminal-stdin-eof.md new file mode 100644 index 000000000..297b295aa --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/normalize-httpapi-payload-media-types.md b/.repos/effect/.changeset/pre/normalize-httpapi-payload-media-types.md new file mode 100644 index 000000000..ae6a0a78b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/normalize-unbounded-pubsub-replay.md b/.repos/effect/.changeset/pre/normalize-unbounded-pubsub-replay.md new file mode 100644 index 000000000..8cc87d665 --- /dev/null +++ b/.repos/effect/.changeset/pre/normalize-unbounded-pubsub-replay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize unbounded PubSub replay capacities to positive integers. diff --git a/.repos/effect/.changeset/pre/o8drprcu-sqlite-node-node-sqlite.md b/.repos/effect/.changeset/pre/o8drprcu-sqlite-node-node-sqlite.md new file mode 100644 index 000000000..b1840466f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/odd-boats-think.md b/.repos/effect/.changeset/pre/odd-boats-think.md new file mode 100644 index 000000000..7319b914e --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-boats-think.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `isMutableHashMap` and `isMutableHashSet`, and align nominal guard implementations and tests across collections and transactional data types. diff --git a/.repos/effect/.changeset/pre/odd-bulldogs-sleep.md b/.repos/effect/.changeset/pre/odd-bulldogs-sleep.md new file mode 100644 index 000000000..baf880811 --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-bulldogs-sleep.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add dtslint coverage for `Stream.catchIf` to lock in predicate and refinement inference behavior in both data-first and data-last forms. diff --git a/.repos/effect/.changeset/pre/odd-fans-glow.md b/.repos/effect/.changeset/pre/odd-fans-glow.md new file mode 100644 index 000000000..ac7a1a804 --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-fans-glow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.groupedWithin` to stop emitting empty arrays when schedule ticks fire while upstream is idle. diff --git a/.repos/effect/.changeset/pre/odd-forks-talk.md b/.repos/effect/.changeset/pre/odd-forks-talk.md new file mode 100644 index 000000000..150e01788 --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-forks-talk.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Add dynamic tool support to the Anthropic language model provider when preparing tool definitions for requests. diff --git a/.repos/effect/.changeset/pre/odd-laws-draw.md b/.repos/effect/.changeset/pre/odd-laws-draw.md new file mode 100644 index 000000000..695ffcefd --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-laws-draw.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schedule.duration`, a one-shot schedule that waits for the provided duration and then completes. diff --git a/.repos/effect/.changeset/pre/odd-owls-smoke.md b/.repos/effect/.changeset/pre/odd-owls-smoke.md new file mode 100644 index 000000000..7085a3b59 --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-owls-smoke.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Schedule.compose` in favor of `Schedule.both`, and update schedule examples to use `Schedule.both`. diff --git a/.repos/effect/.changeset/pre/odd-socks-boil.md b/.repos/effect/.changeset/pre/odd-socks-boil.md new file mode 100644 index 000000000..af72ca982 --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-socks-boil.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Use a dedicated PostgreSQL client for LISTEN / UNLISTEN subscriptions instead of checking out a pooled connection for the listener lifecycle. diff --git a/.repos/effect/.changeset/pre/odd-suns-dance.md b/.repos/effect/.changeset/pre/odd-suns-dance.md new file mode 100644 index 000000000..226e74ffd --- /dev/null +++ b/.repos/effect/.changeset/pre/odd-suns-dance.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename HttpApiClient request option `withResponse` to `responseMode` and add support for `responseMode: "response-only"` to return the raw `HttpClientResponse` without decoding. diff --git a/.repos/effect/.changeset/pre/old-brooms-cry.md b/.repos/effect/.changeset/pre/old-brooms-cry.md new file mode 100644 index 000000000..525a7a8f6 --- /dev/null +++ b/.repos/effect/.changeset/pre/old-brooms-cry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Filter.reason api diff --git a/.repos/effect/.changeset/pre/old-facts-stand.md b/.repos/effect/.changeset/pre/old-facts-stand.md new file mode 100644 index 000000000..99a811620 --- /dev/null +++ b/.repos/effect/.changeset/pre/old-facts-stand.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fixed the Error Type on AtomHttpApiClient (Server errors were being incorrectly reported, and we could not determine _tag to handle) diff --git a/.repos/effect/.changeset/pre/old-mirrors-float.md b/.repos/effect/.changeset/pre/old-mirrors-float.md new file mode 100644 index 000000000..a50aab5e7 --- /dev/null +++ b/.repos/effect/.changeset/pre/old-mirrors-float.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +make Layer.mock work with Stream and Channel diff --git a/.repos/effect/.changeset/pre/olive-poems-visit.md b/.repos/effect/.changeset/pre/olive-poems-visit.md new file mode 100644 index 000000000..f09144e38 --- /dev/null +++ b/.repos/effect/.changeset/pre/olive-poems-visit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Workflow.executionId` to use schema `makeUnsafe` instead of the removed `.make` API. diff --git a/.repos/effect/.changeset/pre/opaque-graph-interface.md b/.repos/effect/.changeset/pre/opaque-graph-interface.md new file mode 100644 index 000000000..47e27a0b9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/open-hotels-remain.md b/.repos/effect/.changeset/pre/open-hotels-remain.md new file mode 100644 index 000000000..633584cea --- /dev/null +++ b/.repos/effect/.changeset/pre/open-hotels-remain.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +extend McpSchema to work with extensions diff --git a/.repos/effect/.changeset/pre/open-objects-intersect.md b/.repos/effect/.changeset/pre/open-objects-intersect.md new file mode 100644 index 000000000..fcc55d4b2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-compat-decode-tool-params.md b/.repos/effect/.changeset/pre/openai-compat-decode-tool-params.md new file mode 100644 index 000000000..5286f106e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-compat-empty-assistant-content.md b/.repos/effect/.changeset/pre/openai-compat-empty-assistant-content.md new file mode 100644 index 000000000..8426aa01f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-compat-null-tool-calls.md b/.repos/effect/.changeset/pre/openai-compat-null-tool-calls.md new file mode 100644 index 000000000..06b23aed4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-compat-nullable-tool-name.md b/.repos/effect/.changeset/pre/openai-compat-nullable-tool-name.md new file mode 100644 index 000000000..96bc6caaa --- /dev/null +++ b/.repos/effect/.changeset/pre/openai-compat-nullable-tool-name.md @@ -0,0 +1,7 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Fix dropped streamed tool-call arguments when a provider sends `function.name: null` on continuation fragments. + +OpenAI-compatible providers such as Fireworks send the tool name only on the first streamed `tool_calls` fragment and `function.name: null` on the continuation fragments that carry the argument deltas. `ChatCompletionToolFunctionDelta.name` was `Schema.optionalKey(Schema.String)` (non-nullable), so chunk validation rejected every continuation and silently discarded its argument delta, leaving the assembled tool call with empty or partial params. `name` is now nullable (`Schema.NullOr(Schema.String)`). diff --git a/.repos/effect/.changeset/pre/openai-compat-parallel-tool-calls.md b/.repos/effect/.changeset/pre/openai-compat-parallel-tool-calls.md new file mode 100644 index 000000000..d2b2ce441 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-compat-reasoning.md b/.repos/effect/.changeset/pre/openai-compat-reasoning.md new file mode 100644 index 000000000..9a8e6b8ae --- /dev/null +++ b/.repos/effect/.changeset/pre/openai-compat-reasoning.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Surface reasoning tokens from OpenAI-compatible chat completion responses. diff --git a/.repos/effect/.changeset/pre/openai-compatible-quota-errors.md b/.repos/effect/.changeset/pre/openai-compatible-quota-errors.md new file mode 100644 index 000000000..a12a53a98 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-telemetry-response.md b/.repos/effect/.changeset/pre/openai-telemetry-response.md new file mode 100644 index 000000000..c2bf786a2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-text-prompt-cache.md b/.repos/effect/.changeset/pre/openai-text-prompt-cache.md new file mode 100644 index 000000000..f0f17bc01 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openai-tolerant-error-stream-event.md b/.repos/effect/.changeset/pre/openai-tolerant-error-stream-event.md new file mode 100644 index 000000000..ee2e518a0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openapi-generator-sse-constraint-decoder.md b/.repos/effect/.changeset/pre/openapi-generator-sse-constraint-decoder.md new file mode 100644 index 000000000..358451002 --- /dev/null +++ b/.repos/effect/.changeset/pre/openapi-generator-sse-constraint-decoder.md @@ -0,0 +1,7 @@ +--- +"@effect/openapi-generator": patch +--- + +Fix the generated SSE `sseRequest` helper to reference `Schema.ConstraintDecoder` instead of the no-longer-exported `Schema.Decoder`. + +For specs with `text/event-stream` responses the generator emitted a helper typed as `Schema.Decoder`, but `Schema` exports that decode-only interface as `ConstraintDecoder`, so generated clients failed to compile (`'"effect/Schema"' has no exported member named 'Decoder'`). The emitted helper now uses `Schema.ConstraintDecoder`. diff --git a/.repos/effect/.changeset/pre/openapi-path-parameters.md b/.repos/effect/.changeset/pre/openapi-path-parameters.md new file mode 100644 index 000000000..fcf771ed2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openapi-pointer-references.md b/.repos/effect/.changeset/pre/openapi-pointer-references.md new file mode 100644 index 000000000..e7ef50995 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openapi-response-variants.md b/.repos/effect/.changeset/pre/openapi-response-variants.md new file mode 100644 index 000000000..60a248f8c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openrouter-generation-usage-schema.md b/.repos/effect/.changeset/pre/openrouter-generation-usage-schema.md new file mode 100644 index 000000000..d9b9a6795 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/openrouter-input-audio.md b/.repos/effect/.changeset/pre/openrouter-input-audio.md new file mode 100644 index 000000000..8dc06d3eb --- /dev/null +++ b/.repos/effect/.changeset/pre/openrouter-input-audio.md @@ -0,0 +1,9 @@ +--- +"@effect/ai-openrouter": patch +--- + +Convert audio file parts in prompts into OpenRouter `input_audio` content blocks. + +Previously, every non-image file part was converted into a generic `file` content block. OpenRouter only accepts audio as base64-encoded `input_audio` content parts, so audio attachments were rejected or mishandled by the upstream provider. + +Audio file parts with a recognized `mediaType` (aac, aiff, flac, m4a, mp3, ogg, pcm16, pcm24, and wav) are now converted into `input_audio` blocks. Unsupported audio media types and `URL` audio data fail with an `AiError` since OpenRouter requires base64-encoded audio data and does not fetch audio URLs. diff --git a/.repos/effect/.changeset/pre/openrouter-tool-parameter-deltas.md b/.repos/effect/.changeset/pre/openrouter-tool-parameter-deltas.md new file mode 100644 index 000000000..41da9fcad --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/opentelemetry-render-causes.md b/.repos/effect/.changeset/pre/opentelemetry-render-causes.md new file mode 100644 index 000000000..4ebcd6ef3 --- /dev/null +++ b/.repos/effect/.changeset/pre/opentelemetry-render-causes.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Add `includeCauseInStack` option to `Cause.prettyErrors` diff --git a/.repos/effect/.changeset/pre/optic-standalone-functions.md b/.repos/effect/.changeset/pre/optic-standalone-functions.md new file mode 100644 index 000000000..fc0ebd947 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/optimize-array-equality.md b/.repos/effect/.changeset/pre/optimize-array-equality.md new file mode 100644 index 000000000..1ae1dede6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/optimize-httpapi-handler-types.md b/.repos/effect/.changeset/pre/optimize-httpapi-handler-types.md new file mode 100644 index 000000000..af3799598 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/optimize-node-http-server-response.md b/.repos/effect/.changeset/pre/optimize-node-http-server-response.md new file mode 100644 index 000000000..1cb193cf5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/optimize-schema-class-decoding.md b/.repos/effect/.changeset/pre/optimize-schema-class-decoding.md new file mode 100644 index 000000000..61b14ad4c --- /dev/null +++ b/.repos/effect/.changeset/pre/optimize-schema-class-decoding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Avoid validating `Schema.Class` fields twice when decoding. diff --git a/.repos/effect/.changeset/pre/optional-ai-embedding-usage.md b/.repos/effect/.changeset/pre/optional-ai-embedding-usage.md new file mode 100644 index 000000000..beb5997ae --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/optional-ai-response-fields.md b/.repos/effect/.changeset/pre/optional-ai-response-fields.md new file mode 100644 index 000000000..eebdd1ca0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/otel-resource-env-precedence.md b/.repos/effect/.changeset/pre/otel-resource-env-precedence.md new file mode 100644 index 000000000..c95fd27dd --- /dev/null +++ b/.repos/effect/.changeset/pre/otel-resource-env-precedence.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prefer OTEL resource environment variables over explicit `OtlpResource.fromConfig` options. diff --git a/.repos/effect/.changeset/pre/perfect-buckets-tickle.md b/.repos/effect/.changeset/pre/perfect-buckets-tickle.md new file mode 100644 index 000000000..a17e31a51 --- /dev/null +++ b/.repos/effect/.changeset/pre/perfect-buckets-tickle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add default value support to `Prompt.file`. diff --git a/.repos/effect/.changeset/pre/petite-months-allow.md b/.repos/effect/.changeset/pre/petite-months-allow.md new file mode 100644 index 000000000..04ad2d0b6 --- /dev/null +++ b/.repos/effect/.changeset/pre/petite-months-allow.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +--- + +add dynamic tooling for openai and openai-compact language models diff --git a/.repos/effect/.changeset/pre/pg-client-connect-error-handler.md b/.repos/effect/.changeset/pre/pg-client-connect-error-handler.md new file mode 100644 index 000000000..3eed00972 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/plain-variant-unions.md b/.repos/effect/.changeset/pre/plain-variant-unions.md new file mode 100644 index 000000000..367158347 --- /dev/null +++ b/.repos/effect/.changeset/pre/plain-variant-unions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include plain variant structs in the default variant union. diff --git a/.repos/effect/.changeset/pre/platform-crypto-service.md b/.repos/effect/.changeset/pre/platform-crypto-service.md new file mode 100644 index 000000000..afbf216e6 --- /dev/null +++ b/.repos/effect/.changeset/pre/platform-crypto-service.md @@ -0,0 +1,9 @@ +--- +"effect": patch +"@effect/platform-node": patch +"@effect/platform-node-shared": patch +"@effect/platform-bun": patch +"@effect/platform-browser": patch +--- + +Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. diff --git a/.repos/effect/.changeset/pre/platform-node-shared-barrel.md b/.repos/effect/.changeset/pre/platform-node-shared-barrel.md new file mode 100644 index 000000000..f73cae684 --- /dev/null +++ b/.repos/effect/.changeset/pre/platform-node-shared-barrel.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Add the package root barrel export. diff --git a/.repos/effect/.changeset/pre/plenty-moons-pull.md b/.repos/effect/.changeset/pre/plenty-moons-pull.md new file mode 100644 index 000000000..adac8d000 --- /dev/null +++ b/.repos/effect/.changeset/pre/plenty-moons-pull.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +use encoded types for idb queries diff --git a/.repos/effect/.changeset/pre/polite-brooms-tickle.md b/.repos/effect/.changeset/pre/polite-brooms-tickle.md new file mode 100644 index 000000000..89a9b755e --- /dev/null +++ b/.repos/effect/.changeset/pre/polite-brooms-tickle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `LogLevel.isEnabled` for checking a log level against `References.MinimumLogLevel`. diff --git a/.repos/effect/.changeset/pre/polite-cameras-rest.md b/.repos/effect/.changeset/pre/polite-cameras-rest.md new file mode 100644 index 000000000..348f740e9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/polite-dingos-unite.md b/.repos/effect/.changeset/pre/polite-dingos-unite.md new file mode 100644 index 000000000..7ae08b5bc --- /dev/null +++ b/.repos/effect/.changeset/pre/polite-dingos-unite.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve CRLF state across SSE input chunk boundaries. diff --git a/.repos/effect/.changeset/pre/polite-pigs-speak.md b/.repos/effect/.changeset/pre/polite-pigs-speak.md new file mode 100644 index 000000000..44d427f9d --- /dev/null +++ b/.repos/effect/.changeset/pre/polite-pigs-speak.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +use .toJSON for default .toString implementations diff --git a/.repos/effect/.changeset/pre/polite-tables-kneel.md b/.repos/effect/.changeset/pre/polite-tables-kneel.md new file mode 100644 index 000000000..e80db998f --- /dev/null +++ b/.repos/effect/.changeset/pre/polite-tables-kneel.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Fix tool calling for the Anthropic Effect AI SDK provider integration diff --git a/.repos/effect/.changeset/pre/port-effect-reduce.md b/.repos/effect/.changeset/pre/port-effect-reduce.md new file mode 100644 index 000000000..36632f7c2 --- /dev/null +++ b/.repos/effect/.changeset/pre/port-effect-reduce.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Port `Effect.reduce` from Effect v3. diff --git a/.repos/effect/.changeset/pre/port-react-hydration.md b/.repos/effect/.changeset/pre/port-react-hydration.md new file mode 100644 index 000000000..a7cd38e2f --- /dev/null +++ b/.repos/effect/.changeset/pre/port-react-hydration.md @@ -0,0 +1,8 @@ +--- +"effect": patch +"@effect/atom-react": patch +--- + +Port ReactHydration to effect-smol. + +Add `Hydration` module to `effect/unstable/reactivity` with `dehydrate`, `hydrate`, and `toValues` for SSR state serialization. Add `HydrationBoundary` React component to `@effect/atom-react` with two-phase hydration (new atoms in render, existing atoms after commit). diff --git a/.repos/effect/.changeset/pre/precise-clocks-rest.md b/.repos/effect/.changeset/pre/precise-clocks-rest.md new file mode 100644 index 000000000..afe1d8a1e --- /dev/null +++ b/.repos/effect/.changeset/pre/precise-clocks-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve nanosecond precision for large `TestClock` wall-clock timestamps. diff --git a/.repos/effect/.changeset/pre/precise-test-clock-nanos.md b/.repos/effect/.changeset/pre/precise-test-clock-nanos.md new file mode 100644 index 000000000..aec813c1a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/preserve-command-hidden-metadata.md b/.repos/effect/.changeset/pre/preserve-command-hidden-metadata.md new file mode 100644 index 000000000..ceb6c6868 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/preserve-config-all-input-evidence.md b/.repos/effect/.changeset/pre/preserve-config-all-input-evidence.md new file mode 100644 index 000000000..bd8b92ad8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/preserve-openapi-raw-json.md b/.repos/effect/.changeset/pre/preserve-openapi-raw-json.md new file mode 100644 index 000000000..8718904d1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/preserve-otel-parent-context.md b/.repos/effect/.changeset/pre/preserve-otel-parent-context.md new file mode 100644 index 000000000..d436bd971 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/preserve-unsafe-workflow-interrupts.md b/.repos/effect/.changeset/pre/preserve-unsafe-workflow-interrupts.md new file mode 100644 index 000000000..eaff87e4b --- /dev/null +++ b/.repos/effect/.changeset/pre/preserve-unsafe-workflow-interrupts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve unsafe in-memory workflow interrupts across replay. diff --git a/.repos/effect/.changeset/pre/pretty-moments-clap.md b/.repos/effect/.changeset/pre/pretty-moments-clap.md new file mode 100644 index 000000000..0dd186ea2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/protect-schema-issue-actuals.md b/.repos/effect/.changeset/pre/protect-schema-issue-actuals.md new file mode 100644 index 000000000..0a1c82a3b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/proud-ears-say.md b/.repos/effect/.changeset/pre/proud-ears-say.md new file mode 100644 index 000000000..01a09eae4 --- /dev/null +++ b/.repos/effect/.changeset/pre/proud-ears-say.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Support standalone Effect.forEach data-last usage + diff --git a/.repos/effect/.changeset/pre/provider-executed-tool-results.md b/.repos/effect/.changeset/pre/provider-executed-tool-results.md new file mode 100644 index 000000000..a589416b3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/public-deer-ring.md b/.repos/effect/.changeset/pre/public-deer-ring.md new file mode 100644 index 000000000..251292bbe --- /dev/null +++ b/.repos/effect/.changeset/pre/public-deer-ring.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add WorkflowEngine interruptUnsafe diff --git a/.repos/effect/.changeset/pre/public-jeans-stop.md b/.repos/effect/.changeset/pre/public-jeans-stop.md new file mode 100644 index 000000000..719c4407c --- /dev/null +++ b/.repos/effect/.changeset/pre/public-jeans-stop.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +reset openai websocket on error diff --git a/.repos/effect/.changeset/pre/pubsub-publish-false.md b/.repos/effect/.changeset/pre/pubsub-publish-false.md new file mode 100644 index 000000000..577022ba2 --- /dev/null +++ b/.repos/effect/.changeset/pre/pubsub-publish-false.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +PubSub.publish and PubSub.publishAll now return false on shutdown instead of interrupting, matching Queue.offer semantics. diff --git a/.repos/effect/.changeset/pre/puny-pens-clap.md b/.repos/effect/.changeset/pre/puny-pens-clap.md new file mode 100644 index 000000000..c3087e0b6 --- /dev/null +++ b/.repos/effect/.changeset/pre/puny-pens-clap.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"effect": patch +--- + +retry incremental prompt on invalid request diff --git a/.repos/effect/.changeset/pre/purple-bars-prove.md b/.repos/effect/.changeset/pre/purple-bars-prove.md new file mode 100644 index 000000000..ca16c9669 --- /dev/null +++ b/.repos/effect/.changeset/pre/purple-bars-prove.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +bump undici versions diff --git a/.repos/effect/.changeset/pre/purple-schools-float.md b/.repos/effect/.changeset/pre/purple-schools-float.md new file mode 100644 index 000000000..caac67d56 --- /dev/null +++ b/.repos/effect/.changeset/pre/purple-schools-float.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve `Prompt.file` to support incremental filtering while typing, including backspace and ctrl-u handling. diff --git a/.repos/effect/.changeset/pre/purple-turtles-draw.md b/.repos/effect/.changeset/pre/purple-turtles-draw.md new file mode 100644 index 000000000..95ad9fd7c --- /dev/null +++ b/.repos/effect/.changeset/pre/purple-turtles-draw.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +add .reverse() to idb select diff --git a/.repos/effect/.changeset/pre/quick-dragons-fix.md b/.repos/effect/.changeset/pre/quick-dragons-fix.md new file mode 100644 index 000000000..ded3d7779 --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-dragons-fix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Expose the optional `orElse` fallback parameter in `Effect.catchTags`. diff --git a/.repos/effect/.changeset/pre/quick-falcons-travel.md b/.repos/effect/.changeset/pre/quick-falcons-travel.md new file mode 100644 index 000000000..62cc670ae --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-falcons-travel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Argument.variadic(argument)` so it supports direct calls without options. diff --git a/.repos/effect/.changeset/pre/quick-geese-relax.md b/.repos/effect/.changeset/pre/quick-geese-relax.md new file mode 100644 index 000000000..d0d00ec17 --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-geese-relax.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix a regression in `PubSub.shutdown` so shutting down a pubsub interrupts suspended subscribers (including `takeAll`) by ensuring subscriptions are scoped under the pubsub shutdown scope. diff --git a/.repos/effect/.changeset/pre/quick-graphs-paths.md b/.repos/effect/.changeset/pre/quick-graphs-paths.md new file mode 100644 index 000000000..2465d0a83 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quick-kiwis-remember.md b/.repos/effect/.changeset/pre/quick-kiwis-remember.md new file mode 100644 index 000000000..2c1b55d09 --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-kiwis-remember.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Persist permanent entries in KVS `setMany` operations. diff --git a/.repos/effect/.changeset/pre/quick-lamps-dig.md b/.repos/effect/.changeset/pre/quick-lamps-dig.md new file mode 100644 index 000000000..c2398444f --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-lamps-dig.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add HttpApiMiddleware.layerSchemaErrorTransform diff --git a/.repos/effect/.changeset/pre/quick-lizards-fall.md b/.repos/effect/.changeset/pre/quick-lizards-fall.md new file mode 100644 index 000000000..10d387f47 --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-lizards-fall.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Add rebuild api to idb databases diff --git a/.repos/effect/.changeset/pre/quick-schedulers-promise.md b/.repos/effect/.changeset/pre/quick-schedulers-promise.md new file mode 100644 index 000000000..0cddcb3f3 --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-schedulers-promise.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use Promise microtasks for synchronous Scheduler dispatch. diff --git a/.repos/effect/.changeset/pre/quick-trees-join.md b/.repos/effect/.changeset/pre/quick-trees-join.md new file mode 100644 index 000000000..623775195 --- /dev/null +++ b/.repos/effect/.changeset/pre/quick-trees-join.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +remove all non-regional service usage diff --git a/.repos/effect/.changeset/pre/quiet-carpets-grin.md b/.repos/effect/.changeset/pre/quiet-carpets-grin.md new file mode 100644 index 000000000..e3a4e4370 --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-carpets-grin.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add \`Ctrl-A\` and \`Ctrl-E\` key handling for editable CLI text prompts to move the cursor to the beginning or end of the current input line. diff --git a/.repos/effect/.changeset/pre/quiet-clis-parse.md b/.repos/effect/.changeset/pre/quiet-clis-parse.md new file mode 100644 index 000000000..2fc507ec8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-crons-report.md b/.repos/effect/.changeset/pre/quiet-crons-report.md new file mode 100644 index 000000000..d29a3bd23 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-fibers-settle.md b/.repos/effect/.changeset/pre/quiet-fibers-settle.md new file mode 100644 index 000000000..81ca36e68 --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-fibers-settle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix fiber self-interuption from inside a running operation diff --git a/.repos/effect/.changeset/pre/quiet-files-hunt.md b/.repos/effect/.changeset/pre/quiet-files-hunt.md new file mode 100644 index 000000000..d534ce656 --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-files-hunt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +wrap httpapi request context with HttpRouter.Request diff --git a/.repos/effect/.changeset/pre/quiet-files-write.md b/.repos/effect/.changeset/pre/quiet-files-write.md new file mode 100644 index 000000000..fb549eb54 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-graphs-guard.md b/.repos/effect/.changeset/pre/quiet-graphs-guard.md new file mode 100644 index 000000000..ababf77f0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-graphs-search.md b/.repos/effect/.changeset/pre/quiet-graphs-search.md new file mode 100644 index 000000000..7c4a6c3ac --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-lamps-jam.md b/.repos/effect/.changeset/pre/quiet-lamps-jam.md new file mode 100644 index 000000000..139ce84ce --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-lamps-jam.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add OTEL environment variable configuration for unstable OTLP observability. diff --git a/.repos/effect/.changeset/pre/quiet-mice-negotiate.md b/.repos/effect/.changeset/pre/quiet-mice-negotiate.md new file mode 100644 index 000000000..c54d96dde --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-observers-report.md b/.repos/effect/.changeset/pre/quiet-observers-report.md new file mode 100644 index 000000000..d547f9aac --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-otters-retry.md b/.repos/effect/.changeset/pre/quiet-otters-retry.md new file mode 100644 index 000000000..7673c4702 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-owls-validate.md b/.repos/effect/.changeset/pre/quiet-owls-validate.md new file mode 100644 index 000000000..6a85ac0b3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-pandas-rebuild.md b/.repos/effect/.changeset/pre/quiet-pandas-rebuild.md new file mode 100644 index 000000000..879a8c442 --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-pandas-rebuild.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix failed `ResourceRef` rebuilds permanently blocking waiters. diff --git a/.repos/effect/.changeset/pre/quiet-pandas-respond.md b/.repos/effect/.changeset/pre/quiet-pandas-respond.md new file mode 100644 index 000000000..b52a3f1c3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-radios-wave.md b/.repos/effect/.changeset/pre/quiet-radios-wave.md new file mode 100644 index 000000000..fdbce5a88 --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-radios-wave.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Guard transaction connection acquisition in `PgClient.fromPool` so acquire failures stay in the `SqlError` channel. diff --git a/.repos/effect/.changeset/pre/quiet-redis-scripts.md b/.repos/effect/.changeset/pre/quiet-redis-scripts.md new file mode 100644 index 000000000..c9d068c41 --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-redis-scripts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Retry Redis scripts after `NOSCRIPT` and declare the token bucket refill key diff --git a/.repos/effect/.changeset/pre/quiet-rpcs-buffer.md b/.repos/effect/.changeset/pre/quiet-rpcs-buffer.md new file mode 100644 index 000000000..852a1f202 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-savepoints-wait.md b/.repos/effect/.changeset/pre/quiet-savepoints-wait.md new file mode 100644 index 000000000..e27e942f1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-sockets-close.md b/.repos/effect/.changeset/pre/quiet-sockets-close.md new file mode 100644 index 000000000..6d2d9ab73 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-spans-rest.md b/.repos/effect/.changeset/pre/quiet-spans-rest.md new file mode 100644 index 000000000..9e574309a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-tigers-yell.md b/.repos/effect/.changeset/pre/quiet-tigers-yell.md new file mode 100644 index 000000000..3c567ce80 --- /dev/null +++ b/.repos/effect/.changeset/pre/quiet-tigers-yell.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Update `NodeHttpServer.layerConfig`'s type to report the same provided Node services as `NodeHttpServer.layer`. diff --git a/.repos/effect/.changeset/pre/quiet-tools-smile.md b/.repos/effect/.changeset/pre/quiet-tools-smile.md new file mode 100644 index 000000000..d14443e6e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/quiet-turtles-smile.md b/.repos/effect/.changeset/pre/quiet-turtles-smile.md new file mode 100644 index 000000000..ef13d5bf9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/random-choice.md b/.repos/effect/.changeset/pre/random-choice.md new file mode 100644 index 000000000..b0bdd94dc --- /dev/null +++ b/.repos/effect/.changeset/pre/random-choice.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add Random.choice for selecting a random element from an iterable. diff --git a/.repos/effect/.changeset/pre/random-hex-encoding.md b/.repos/effect/.changeset/pre/random-hex-encoding.md new file mode 100644 index 000000000..9d894f62d --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/read-only-bun-sqlite.md b/.repos/effect/.changeset/pre/read-only-bun-sqlite.md new file mode 100644 index 000000000..9037b67ad --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/ready-olives-divide.md b/.repos/effect/.changeset/pre/ready-olives-divide.md new file mode 100644 index 000000000..2502b4d1a --- /dev/null +++ b/.repos/effect/.changeset/pre/ready-olives-divide.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +handle openai ws error events diff --git a/.repos/effect/.changeset/pre/real-trains-ring.md b/.repos/effect/.changeset/pre/real-trains-ring.md new file mode 100644 index 000000000..5cd40a0a3 --- /dev/null +++ b/.repos/effect/.changeset/pre/real-trains-ring.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow `HttpApiTest.groups` to accept an optional `baseUrl` override while preserving the existing default of `"http://localhost:3000"`. diff --git a/.repos/effect/.changeset/pre/record-from-iterable-by-dual.md b/.repos/effect/.changeset/pre/record-from-iterable-by-dual.md new file mode 100644 index 000000000..13fc30740 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/red-pigs-repair.md b/.repos/effect/.changeset/pre/red-pigs-repair.md new file mode 100644 index 000000000..005ae5f88 --- /dev/null +++ b/.repos/effect/.changeset/pre/red-pigs-repair.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.annotateLogsScoped` to apply log annotations for the current scope and automatically restore previous annotations when the scope closes. diff --git a/.repos/effect/.changeset/pre/redact-cli-wizard-values.md b/.repos/effect/.changeset/pre/redact-cli-wizard-values.md new file mode 100644 index 000000000..b5be1435f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/redacted-representation-options.md b/.repos/effect/.changeset/pre/redacted-representation-options.md new file mode 100644 index 000000000..da2529880 --- /dev/null +++ b/.repos/effect/.changeset/pre/redacted-representation-options.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Preserve `Schema.Redacted` options when roundtripping through schema representations. +This keeps `label` validation and `disallowJsonEncode` behavior intact when +schemas are revived from a representation or emitted through code generation. diff --git a/.repos/effect/.changeset/pre/redis-subscribe-api.md b/.repos/effect/.changeset/pre/redis-subscribe-api.md new file mode 100644 index 000000000..b6203f7e4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/refactor-cli-global-flags.md b/.repos/effect/.changeset/pre/refactor-cli-global-flags.md new file mode 100644 index 000000000..ccfecb928 --- /dev/null +++ b/.repos/effect/.changeset/pre/refactor-cli-global-flags.md @@ -0,0 +1,27 @@ +--- +"effect": patch +--- + +Refactor CLI built-in options to use Effect services with `GlobalFlag` + +Built-in CLI flags (`--help`, `--version`, `--completions`, `--log-level`) are now implemented as Effect services using `Context.Reference`. This provides: + +- **Visibility**: Built-in flags now appear in help output's "GLOBAL FLAGS" section +- **Extensibility**: Users can register custom global flags via `GlobalFlag.add` +- **Override capability**: Built-in flag behavior can be replaced or disabled +- **Composability**: Flags compose via Effect's service system + +New `GlobalFlag` module exports: + +- `Action` and `Setting` types for different flag behaviors +- `Help`, `Version`, `Completions`, `LogLevel` references for built-in flags +- `add`, `remove`, `clear` functions for managing global flags + +Example: + +```typescript +const app = Command.make("myapp") +Command.run(app, { version: "1.0.0" }).pipe( + GlobalFlag.add(CustomFlag, customFlagValue) +) +``` diff --git a/.repos/effect/.changeset/pre/refactor-config-provider.md b/.repos/effect/.changeset/pre/refactor-config-provider.md new file mode 100644 index 000000000..5e9a549c4 --- /dev/null +++ b/.repos/effect/.changeset/pre/refactor-config-provider.md @@ -0,0 +1,23 @@ +--- +"effect": patch +--- + +Fix config path composition and directory-backed lookup behavior. + +`ConfigProvider.orElse` now keeps each side's own `nested` and `mapInput` +behavior. Applying `nested` or `mapInput` to a combined provider now applies the +same transformation to both sides. + +`ConfigProvider` path transformations now compose as a single path function. +This makes `nested` and `mapInput` behave consistently with normal function +composition. + +`Config.nested` now tracks the logical config path in `Config` itself instead of +wrapping the provider. This keeps lookup paths and schema error paths aligned. +The low-level `Config.make` constructor is no longer exported; use config +constructors and combinators, or implement custom lookup behavior with +`ConfigProvider.make`. + +`ConfigProvider.fromDir` now returns `undefined` when neither a file nor a +directory exists at the requested path, so `orElse` can fall back instead of +failing with `SourceError`. diff --git a/.repos/effect/.changeset/pre/refactor-representation-references.md b/.repos/effect/.changeset/pre/refactor-representation-references.md new file mode 100644 index 000000000..e72607fe0 --- /dev/null +++ b/.repos/effect/.changeset/pre/refactor-representation-references.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +SchemaRepresentation: only create references for recursive/mutually recursive schemas and schemas with an `identifier` annotation, closes #1560. diff --git a/.repos/effect/.changeset/pre/refine-config-absence.md b/.repos/effect/.changeset/pre/refine-config-absence.md new file mode 100644 index 000000000..bd2a1c58c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/refresh-mcp-roots.md b/.repos/effect/.changeset/pre/refresh-mcp-roots.md new file mode 100644 index 000000000..501d714fd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/register-param-alternate-flags.md b/.repos/effect/.changeset/pre/register-param-alternate-flags.md new file mode 100644 index 000000000..511689a84 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/release-rpc-worker-pool-entries.md b/.repos/effect/.changeset/pre/release-rpc-worker-pool-entries.md new file mode 100644 index 000000000..fe2aa1ba1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-context-mutate.md b/.repos/effect/.changeset/pre/remove-context-mutate.md new file mode 100644 index 000000000..55d8b1a8c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-effect-transactionwith.md b/.repos/effect/.changeset/pre/remove-effect-transactionwith.md new file mode 100644 index 000000000..5f5c63e2d --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-effect-transactionwith.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename `Effect.transaction` to `Effect.tx` and `Effect.retryTransaction` to `Effect.txRetry`, remove `Effect.transactionWith` / `Effect.withTxState`, make nested `Effect.tx` calls compose into the active transaction, and make the public `Tx*` APIs establish atomic transactions without requiring `Transaction` in common usage. diff --git a/.repos/effect/.changeset/pre/remove-http-span-counter.md b/.repos/effect/.changeset/pre/remove-http-span-counter.md new file mode 100644 index 000000000..e0458e742 --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-http-span-counter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the auto-incrementing suffix from HTTP server logger log span names. diff --git a/.repos/effect/.changeset/pre/remove-kubernetes-types.md b/.repos/effect/.changeset/pre/remove-kubernetes-types.md new file mode 100644 index 000000000..395085e4f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-nullor.md b/.repos/effect/.changeset/pre/remove-nullor.md new file mode 100644 index 000000000..28617d3a3 --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-nullor.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove unused `effect/NullOr` module. diff --git a/.repos/effect/.changeset/pre/remove-openapi-fromapi-options.md b/.repos/effect/.changeset/pre/remove-openapi-fromapi-options.md new file mode 100644 index 000000000..97be4cd9a --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-openapi-fromapi-options.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Remove the `options` parameter from `OpenApi.fromApi`. + +The parameter only carried `additionalProperties`, but the function caches results in a `WeakMap` keyed solely on the `api` instance. Passing different options across calls for the same api was silently ignored, making the parameter order-dependent and effectively single-shot. No call sites were using it, so the signature is now simply `fromApi(api)`. diff --git a/.repos/effect/.changeset/pre/remove-schedule-apis.md b/.repos/effect/.changeset/pre/remove-schedule-apis.md new file mode 100644 index 000000000..79d641e92 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-schedule-either.md b/.repos/effect/.changeset/pre/remove-schedule-either.md new file mode 100644 index 000000000..ee0be8634 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-schedule-elapsed.md b/.repos/effect/.changeset/pre/remove-schedule-elapsed.md new file mode 100644 index 000000000..1863f1496 --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-schedule-elapsed.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Schedule.elapsed`. diff --git a/.repos/effect/.changeset/pre/remove-schedule-taps.md b/.repos/effect/.changeset/pre/remove-schedule-taps.md new file mode 100644 index 000000000..18572cb53 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-schema-key-value-combiner.md b/.repos/effect/.changeset/pre/remove-schema-key-value-combiner.md new file mode 100644 index 000000000..05add770f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-schema-stringtree-keep-declarations.md b/.repos/effect/.changeset/pre/remove-schema-stringtree-keep-declarations.md new file mode 100644 index 000000000..f09c22a1b --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-schema-stringtree-keep-declarations.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the `keepDeclarations` option from `Schema.toCodecStringTree`. diff --git a/.repos/effect/.changeset/pre/remove-schema-utils.md b/.repos/effect/.changeset/pre/remove-schema-utils.md new file mode 100644 index 000000000..cb605c700 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/remove-types-mergerecord.md b/.repos/effect/.changeset/pre/remove-types-mergerecord.md new file mode 100644 index 000000000..6533e1bfe --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-types-mergerecord.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the `Types.MergeRecord` alias. Use `Types.MergeLeft` instead. diff --git a/.repos/effect/.changeset/pre/remove-unused-utils-apis.md b/.repos/effect/.changeset/pre/remove-unused-utils-apis.md new file mode 100644 index 000000000..986dd0e0c --- /dev/null +++ b/.repos/effect/.changeset/pre/remove-unused-utils-apis.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove unused APIs from the `Utils` module. diff --git a/.repos/effect/.changeset/pre/remove-with-concurrency.md b/.repos/effect/.changeset/pre/remove-with-concurrency.md new file mode 100644 index 000000000..d1d820b42 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/rename-rebuild-out.md b/.repos/effect/.changeset/pre/rename-rebuild-out.md new file mode 100644 index 000000000..106bcc1e8 --- /dev/null +++ b/.repos/effect/.changeset/pre/rename-rebuild-out.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: rename `"~rebuild.out"` to `"Rebuild"` diff --git a/.repos/effect/.changeset/pre/rename-schema-error-constructors.md b/.repos/effect/.changeset/pre/rename-schema-error-constructors.md new file mode 100644 index 000000000..f4ebfb8fe --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/render-cli-user-errors.md b/.repos/effect/.changeset/pre/render-cli-user-errors.md new file mode 100644 index 000000000..cb8229f8a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/report-schema-input.md b/.repos/effect/.changeset/pre/report-schema-input.md new file mode 100644 index 000000000..038f5d936 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/report-transient-rpc-socket-errors.md b/.repos/effect/.changeset/pre/report-transient-rpc-socket-errors.md new file mode 100644 index 000000000..3c81a4050 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/required-boolean-flags.md b/.repos/effect/.changeset/pre/required-boolean-flags.md new file mode 100644 index 000000000..c49bea5ed --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/resource-subscriptions.md b/.repos/effect/.changeset/pre/resource-subscriptions.md new file mode 100644 index 000000000..c91274c3b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/restore-effect-head.md b/.repos/effect/.changeset/pre/restore-effect-head.md new file mode 100644 index 000000000..0c2f7d3e8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/restore-schema-parse-options.md b/.repos/effect/.changeset/pre/restore-schema-parse-options.md new file mode 100644 index 000000000..7a244549a --- /dev/null +++ b/.repos/effect/.changeset/pre/restore-schema-parse-options.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Restore support for passing schema parse options when creating decode and encode helpers, closes #2174. diff --git a/.repos/effect/.changeset/pre/result-map-error-success-identity.md b/.repos/effect/.changeset/pre/result-map-error-success-identity.md new file mode 100644 index 000000000..dbcacc774 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/retry-redis-script-load.md b/.repos/effect/.changeset/pre/retry-redis-script-load.md new file mode 100644 index 000000000..c4017e413 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/reuse-httpapi-response-schemas.md b/.repos/effect/.changeset/pre/reuse-httpapi-response-schemas.md new file mode 100644 index 000000000..1c7d4c087 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/rich-dots-push.md b/.repos/effect/.changeset/pre/rich-dots-push.md new file mode 100644 index 000000000..3424b6148 --- /dev/null +++ b/.repos/effect/.changeset/pre/rich-dots-push.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix `isNullish()` type predicate diff --git a/.repos/effect/.changeset/pre/rich-hoops-nail.md b/.repos/effect/.changeset/pre/rich-hoops-nail.md new file mode 100644 index 000000000..540a3d6aa --- /dev/null +++ b/.repos/effect/.changeset/pre/rich-hoops-nail.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +rename DurationInput to Duration.Input diff --git a/.repos/effect/.changeset/pre/rich-sloths-draw.md b/.repos/effect/.changeset/pre/rich-sloths-draw.md new file mode 100644 index 000000000..98a0a9944 --- /dev/null +++ b/.repos/effect/.changeset/pre/rich-sloths-draw.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure envelope payloads are correctly encoded for notify path diff --git a/.repos/effect/.changeset/pre/ripe-lies-battle.md b/.repos/effect/.changeset/pre/ripe-lies-battle.md new file mode 100644 index 000000000..3f6b04677 --- /dev/null +++ b/.repos/effect/.changeset/pre/ripe-lies-battle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +EventLog Identity string encodes to base 64 diff --git a/.repos/effect/.changeset/pre/rpc-client-http-early-close.md b/.repos/effect/.changeset/pre/rpc-client-http-early-close.md new file mode 100644 index 000000000..ce2b353d6 --- /dev/null +++ b/.repos/effect/.changeset/pre/rpc-client-http-early-close.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fail RpcClient HTTP requests with a defect when the response stream closes before the request receives a terminal response. diff --git a/.repos/effect/.changeset/pre/rpc-middleware-provides-fix.md b/.repos/effect/.changeset/pre/rpc-middleware-provides-fix.md new file mode 100644 index 000000000..30a26567d --- /dev/null +++ b/.repos/effect/.changeset/pre/rpc-middleware-provides-fix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Rpc.ExtractProvides` to use middleware service ID instead of constructor type. diff --git a/.repos/effect/.changeset/pre/rpc-server-notifications.md b/.repos/effect/.changeset/pre/rpc-server-notifications.md new file mode 100644 index 000000000..d4be791ee --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/safe-json-schema-patterns.md b/.repos/effect/.changeset/pre/safe-json-schema-patterns.md new file mode 100644 index 000000000..ab10a6aa0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/scalar-custom-fetch.md b/.repos/effect/.changeset/pre/scalar-custom-fetch.md new file mode 100644 index 000000000..6a03fa15e --- /dev/null +++ b/.repos/effect/.changeset/pre/scalar-custom-fetch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add support for configuring Scalar API reference pages with a custom fetch implementation. diff --git a/.repos/effect/.changeset/pre/schema-arbitrary-factory.md b/.repos/effect/.changeset/pre/schema-arbitrary-factory.md new file mode 100644 index 000000000..d0802c815 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-as-class.md b/.repos/effect/.changeset/pre/schema-as-class.md new file mode 100644 index 000000000..79572318c --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-as-class.md @@ -0,0 +1,17 @@ +--- +"effect": patch +--- + +Schema: add `asClass` API to turn any schema into a class with static method support. + +**Example** + +```ts +import { Schema } from "effect" + +class MyString extends Schema.asClass(Schema.String) { + static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) +} + +MyString.decodeUnknownSync("a") // "a" +``` diff --git a/.repos/effect/.changeset/pre/schema-asserts-signature.md b/.repos/effect/.changeset/pre/schema-asserts-signature.md new file mode 100644 index 000000000..173f65875 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-asserts-signature.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Change `Schema.asserts` and `SchemaParser.asserts` to assert a value directly with `asserts(schema, input)` and remove `Schema.Codec.ToAsserts`. diff --git a/.repos/effect/.changeset/pre/schema-aware-rpc-serialization.md b/.repos/effect/.changeset/pre/schema-aware-rpc-serialization.md new file mode 100644 index 000000000..bef86f0ae --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-binary-codec.md b/.repos/effect/.changeset/pre/schema-binary-codec.md new file mode 100644 index 000000000..b2628cb3a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-clean-up-additionalProperties.md b/.repos/effect/.changeset/pre/schema-clean-up-additionalProperties.md new file mode 100644 index 000000000..06d3a85fe --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-clean-up-additionalProperties.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Schema: `toJsonSchemaDocument` now emits JSON Schema `false` for unannotated +`Never` index signatures (including `additionalProperties`) instead of `{ not: {} }`. +Annotated `Never` still emits a schema object so metadata like `description` is preserved. diff --git a/.repos/effect/.changeset/pre/schema-codec-narrowing.md b/.repos/effect/.changeset/pre/schema-codec-narrowing.md new file mode 100644 index 000000000..c47989842 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-date-valid.md b/.repos/effect/.changeset/pre/schema-date-valid.md new file mode 100644 index 000000000..a7fbee4e9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-datetime-utc-from-string.md b/.repos/effect/.changeset/pre/schema-datetime-utc-from-string.md new file mode 100644 index 000000000..bd52ade89 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-datetime-utc-from-string.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: extract shared `dateTimeUtcFromString` transformation for `DateTimeUtc` and `DateTimeUtcFromString`. diff --git a/.repos/effect/.changeset/pre/schema-decoding-defaults-services.md b/.repos/effect/.changeset/pre/schema-decoding-defaults-services.md new file mode 100644 index 000000000..a3bd9dc37 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-decoding-defaults-services.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Allow Schema decoding defaults to require Effect services. + +The `Effect` passed to `Schema.withDecodingDefault`, `Schema.withDecodingDefaultKey`, `Schema.withDecodingDefaultType`, and `Schema.withDecodingDefaultTypeKey` now accepts a context `R` in its third type parameter. The required services are propagated into the resulting schema's `DecodingServices`. `SchemaGetter.withDefault` is widened in the same way. diff --git a/.repos/effect/.changeset/pre/schema-defaults-issue-channel.md b/.repos/effect/.changeset/pre/schema-defaults-issue-channel.md new file mode 100644 index 000000000..07a351b9e --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-defaults-issue-channel.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Allow Schema constructor and decoding defaults to fail with `SchemaError`. + +The `Effect` passed to `Schema.withConstructorDefault`, `Schema.withDecodingDefault`, `Schema.withDecodingDefaultKey`, `Schema.withDecodingDefaultType`, and `Schema.withDecodingDefaultTypeKey` now accepts `SchemaError` in its error channel. When a default fails, the parser unwraps the underlying `SchemaIssue.Issue` and propagates it as a parse failure with the surrounding path attached. This makes it easy to use another schema's `makeEffect` / `decode*` as the default value. diff --git a/.repos/effect/.changeset/pre/schema-direct-class-extension.md b/.repos/effect/.changeset/pre/schema-direct-class-extension.md new file mode 100644 index 000000000..43c457b1a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-dollar-prefix.md b/.repos/effect/.changeset/pre/schema-dollar-prefix.md new file mode 100644 index 000000000..947fc16d6 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-dollar-prefix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: rename `$` suffix to `$` prefix for type-level identifiers that conflict with built-in names (`Array$` → `$Array`, `Record$` → `$Record`, `ReadonlyMap$` → `$ReadonlyMap`, `ReadonlySet$` → `$ReadonlySet`). diff --git a/.repos/effect/.changeset/pre/schema-lazy-bottom.md b/.repos/effect/.changeset/pre/schema-lazy-bottom.md new file mode 100644 index 000000000..81ece1964 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-lazy-bottom.md @@ -0,0 +1,13 @@ +--- +"effect": patch +--- + +Improve Schema type-level performance by lazily computing schema views, +specializing common struct projections, and using lighter schema constraints at +API boundaries that do not need the full schema protocol. + +This also adds the Schema type-performance benchmark suite, introduces +`Schema.toCodecArrayFromSingle`, preserves canonical StringTree array codecs, +renames the arbitrary-generation annotation constraint for clarity, and updates +affected codec, parser, channel, SQL, HTTP API, persistence, RPC, AI, OpenAPI, +and workflow typings to match the refined Schema surface. diff --git a/.repos/effect/.changeset/pre/schema-missing-self-generic.md b/.repos/effect/.changeset/pre/schema-missing-self-generic.md new file mode 100644 index 000000000..3e877851a --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-missing-self-generic.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `MissingSelfGeneric` compile-time error for `Class`, `TaggedClass`, `ErrorClass`, and `TaggedErrorClass` when the `Self` type parameter is omitted. diff --git a/.repos/effect/.changeset/pre/schema-ordered-arbitrary-constraints.md b/.repos/effect/.changeset/pre/schema-ordered-arbitrary-constraints.md new file mode 100644 index 000000000..2c9617db7 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-ordered-arbitrary-constraints.md @@ -0,0 +1,12 @@ +--- +"effect": patch +--- + +Use generic ordered constraints for schema arbitrary derivation. + +Range checks such as `isGreaterThan`, `isLessThan`, and `isBetween` now populate `ctx.constraints.ordered` +instead of type-specific range fields on `number`, `date`, or `bigint` constraints. Custom `toArbitrary` +annotations that read range constraints should migrate to `ctx.constraints.ordered`. + +This also fixes BigDecimal arbitrary generation by adapting decimal bounds to the generated scale, avoiding +invalid fast-check bigint ranges for narrow decimal intervals. diff --git a/.repos/effect/.changeset/pre/schema-parser-adapter-errors.md b/.repos/effect/.changeset/pre/schema-parser-adapter-errors.md new file mode 100644 index 000000000..17aeb8989 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-parser-adapter-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Align Schema adapter failures: `Schema` result, promise, and sync adapters now surface `SchemaError`, while `SchemaParser` result, promise, and sync adapters expose `SchemaIssue.Issue`. Mark `SchemaParser` option adapters as internal because their error details are discarded. diff --git a/.repos/effect/.changeset/pre/schema-refactor-toCodecJson.md b/.repos/effect/.changeset/pre/schema-refactor-toCodecJson.md new file mode 100644 index 000000000..3bd03e700 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-refactor-toCodecJson.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: improve `Schema.Unknown` / `Schema.ObjectKeyword` handling in `toCodecJson` and `toCodecStringTree` diff --git a/.repos/effect/.changeset/pre/schema-remove-annotate-in.md b/.repos/effect/.changeset/pre/schema-remove-annotate-in.md new file mode 100644 index 000000000..93dd5bb12 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-remove-annotate-in.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: remove `"~annotate.in"` type from `Bottom` interface, inlining it where needed diff --git a/.repos/effect/.changeset/pre/schema-rename-makeUnsafe-to-make.md b/.repos/effect/.changeset/pre/schema-rename-makeUnsafe-to-make.md new file mode 100644 index 000000000..f2fa96b81 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-rename-makeUnsafe-to-make.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Rename `Schema.makeUnsafe` instance method back to `Schema.make` on all schemas and schema-backed classes. + +Also remove the `static readonly make` override from `ShardId` to avoid conflicting with the inherited schema `make` method. The module-level `ShardId.make(group, id)` function is still available. diff --git a/.repos/effect/.changeset/pre/schema-rename-parser-makeUnsafe.md b/.repos/effect/.changeset/pre/schema-rename-parser-makeUnsafe.md new file mode 100644 index 000000000..7573aa817 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-rename-parser-makeUnsafe.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename `SchemaParser.makeUnsafe` to `SchemaParser.make`. diff --git a/.repos/effect/.changeset/pre/schema-representation-refactoring.md b/.repos/effect/.changeset/pre/schema-representation-refactoring.md new file mode 100644 index 000000000..fddaef586 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-result-combinators.md b/.repos/effect/.changeset/pre/schema-result-combinators.md new file mode 100644 index 000000000..d313cf087 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-result-combinators.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `decodeUnknownResult` / `decodeResult` and `encodeUnknownResult` / `encodeResult` helpers for synchronous `Result`-based parsing. diff --git a/.repos/effect/.changeset/pre/schema-runtime-performance.md b/.repos/effect/.changeset/pre/schema-runtime-performance.md new file mode 100644 index 000000000..5d1b32274 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/schema-struct-simplify.md b/.repos/effect/.changeset/pre/schema-struct-simplify.md new file mode 100644 index 000000000..c86120ae4 --- /dev/null +++ b/.repos/effect/.changeset/pre/schema-struct-simplify.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: allow using `Struct` type helpers directly, e.g. `Schema.Struct.Type` instead of `Schema.Schema.Type>`. diff --git a/.repos/effect/.changeset/pre/schema-union-type-derivation.md b/.repos/effect/.changeset/pre/schema-union-type-derivation.md new file mode 100644 index 000000000..a7ac4dac1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/scope-persisted-queue-ids.md b/.repos/effect/.changeset/pre/scope-persisted-queue-ids.md new file mode 100644 index 000000000..ea6d42d1b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/secure-eventlog-identities.md b/.repos/effect/.changeset/pre/secure-eventlog-identities.md new file mode 100644 index 000000000..b7d1d9ad3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/semantic-matching.md b/.repos/effect/.changeset/pre/semantic-matching.md new file mode 100644 index 000000000..edf23cee6 --- /dev/null +++ b/.repos/effect/.changeset/pre/semantic-matching.md @@ -0,0 +1,13 @@ +--- +"effect": patch +--- + +Use semantic matching for TemplateLiteral parsing and index signature keys + +Replace regex-based TemplateLiteral parsing with backtracking segmentation over +template literal parts, applying part checks during matching. + +Use schema membership when selecting Record index signature keys, including +checked string, number, symbol, and TemplateLiteral parameters. Tighten valid +index signature parameters on both type and encoded sides, and preserve key +parameter semantics in codec transformations. diff --git a/.repos/effect/.changeset/pre/seven-mugs-marry.md b/.repos/effect/.changeset/pre/seven-mugs-marry.md new file mode 100644 index 000000000..fd3dd9d73 --- /dev/null +++ b/.repos/effect/.changeset/pre/seven-mugs-marry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix: update Service interface to use 'this: void' in 'of' method signatures diff --git a/.repos/effect/.changeset/pre/seven-poems-divide.md b/.repos/effect/.changeset/pre/seven-poems-divide.md new file mode 100644 index 000000000..8d9caf7f4 --- /dev/null +++ b/.repos/effect/.changeset/pre/seven-poems-divide.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor populated variables before dotenv expansion defaults in `ConfigProvider`. diff --git a/.repos/effect/.changeset/pre/shaggy-birds-stay.md b/.repos/effect/.changeset/pre/shaggy-birds-stay.md new file mode 100644 index 000000000..5834231b9 --- /dev/null +++ b/.repos/effect/.changeset/pre/shaggy-birds-stay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow passing void for request constructors diff --git a/.repos/effect/.changeset/pre/shaggy-cities-push.md b/.repos/effect/.changeset/pre/shaggy-cities-push.md new file mode 100644 index 000000000..dfff5eac2 --- /dev/null +++ b/.repos/effect/.changeset/pre/shaggy-cities-push.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +use NoInfer in Layer constructors to prevent type erasure diff --git a/.repos/effect/.changeset/pre/shaggy-numbers-accept.md b/.repos/effect/.changeset/pre/shaggy-numbers-accept.md new file mode 100644 index 000000000..df7340ac1 --- /dev/null +++ b/.repos/effect/.changeset/pre/shaggy-numbers-accept.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Correct the type of the schema parameter accepted by the `fileSchema` methods in the CLI to be `Schema.Decoder` diff --git a/.repos/effect/.changeset/pre/shaky-beans-throw.md b/.repos/effect/.changeset/pre/shaky-beans-throw.md new file mode 100644 index 000000000..e0fa124dd --- /dev/null +++ b/.repos/effect/.changeset/pre/shaky-beans-throw.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +accept UrlParams.Input in some UrlParams apis diff --git a/.repos/effect/.changeset/pre/sharp-emus-applaud.md b/.repos/effect/.changeset/pre/sharp-emus-applaud.md new file mode 100644 index 000000000..aa91f3401 --- /dev/null +++ b/.repos/effect/.changeset/pre/sharp-emus-applaud.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Command.withShortDescription` and use short descriptions for CLI subcommand listings, with fallback to the full command description. diff --git a/.repos/effect/.changeset/pre/sharp-goats-wink.md b/.repos/effect/.changeset/pre/sharp-goats-wink.md new file mode 100644 index 000000000..19588ff9b --- /dev/null +++ b/.repos/effect/.changeset/pre/sharp-goats-wink.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow unstable CLI fallback prompts to be created dynamically from an `Effect`. diff --git a/.repos/effect/.changeset/pre/sharp-pandas-care.md b/.repos/effect/.changeset/pre/sharp-pandas-care.md new file mode 100644 index 000000000..21c1cb90c --- /dev/null +++ b/.repos/effect/.changeset/pre/sharp-pandas-care.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix TestClock adjustment when its layer is provided to programs run without an ambient Scope. diff --git a/.repos/effect/.changeset/pre/sharp-peas-march.md b/.repos/effect/.changeset/pre/sharp-peas-march.md new file mode 100644 index 000000000..7bce2812c --- /dev/null +++ b/.repos/effect/.changeset/pre/sharp-peas-march.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix AI tool handler error typing so `LanguageModel.generateText` with a toolkit exposes wrapped `AiError` values rather than leaking raw `AiErrorReason` in the error channel. diff --git a/.repos/effect/.changeset/pre/sharp-rules-draw.md b/.repos/effect/.changeset/pre/sharp-rules-draw.md new file mode 100644 index 000000000..8246dde8a --- /dev/null +++ b/.repos/effect/.changeset/pre/sharp-rules-draw.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure each sql client gets a unique transaction service diff --git a/.repos/effect/.changeset/pre/sharp-singers-sort.md b/.repos/effect/.changeset/pre/sharp-singers-sort.md new file mode 100644 index 000000000..75c499727 --- /dev/null +++ b/.repos/effect/.changeset/pre/sharp-singers-sort.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix string messages and annotations being double-quoted by simple and logfmt loggers. diff --git a/.repos/effect/.changeset/pre/shiny-trains-hug.md b/.repos/effect/.changeset/pre/shiny-trains-hug.md new file mode 100644 index 000000000..451208484 --- /dev/null +++ b/.repos/effect/.changeset/pre/shiny-trains-hug.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add module-level helpers for `Semaphore`, `Latch`, and extracted `PartitionedSemaphore` operations. diff --git a/.repos/effect/.changeset/pre/short-cows-relate.md b/.repos/effect/.changeset/pre/short-cows-relate.md new file mode 100644 index 000000000..2eb2ae0ad --- /dev/null +++ b/.repos/effect/.changeset/pre/short-cows-relate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Set `Schema.TaggedErrorClass` instance `name` to the tag value, matching `Data.TaggedError` behavior. diff --git a/.repos/effect/.changeset/pre/short-foxes-admire.md b/.repos/effect/.changeset/pre/short-foxes-admire.md new file mode 100644 index 000000000..2da4d3a3d --- /dev/null +++ b/.repos/effect/.changeset/pre/short-foxes-admire.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Atom.swr` to `effect/unstable/reactivity` for staleTime-gated stale-while-revalidate reads, optional mount and window-focus revalidation, and forceful manual refresh. diff --git a/.repos/effect/.changeset/pre/short-stamps-throw.md b/.repos/effect/.changeset/pre/short-stamps-throw.md new file mode 100644 index 000000000..1948e6788 --- /dev/null +++ b/.repos/effect/.changeset/pre/short-stamps-throw.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +do not use performance.timeOrigin and calculate origins lazily diff --git a/.repos/effect/.changeset/pre/shy-cycles-flow.md b/.repos/effect/.changeset/pre/shy-cycles-flow.md new file mode 100644 index 000000000..60188e069 --- /dev/null +++ b/.repos/effect/.changeset/pre/shy-cycles-flow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Channel.decodeText corrupting UTF-8 characters split across chunk boundaries. diff --git a/.repos/effect/.changeset/pre/shy-geckos-sniff.md b/.repos/effect/.changeset/pre/shy-geckos-sniff.md new file mode 100644 index 000000000..bf5f07dcd --- /dev/null +++ b/.repos/effect/.changeset/pre/shy-geckos-sniff.md @@ -0,0 +1,30 @@ +--- +"@effect/sql-sqlite-react-native": major +"@effect/openapi-generator": major +"@effect/platform-node-shared": major +"@effect/platform-browser": major +"@effect/sql-sqlite-node": major +"@effect/sql-sqlite-wasm": major +"@effect/sql-clickhouse": major +"@effect/sql-sqlite-bun": major +"@effect/opentelemetry": major +"@effect/platform-node": major +"@effect/sql-sqlite-do": major +"@effect/ai-anthropic": major +"@effect/ai-openai-compat": major +"@effect/ai-openrouter": major +"@effect/platform-bun": major +"@effect/atom-react": major +"@effect/atom-solid": major +"@effect/sql-libsql": major +"@effect/sql-mysql2": major +"@effect/ai-openai": major +"@effect/sql-mssql": major +"@effect/atom-vue": major +"effect": major +"@effect/sql-d1": major +"@effect/sql-pg": major +"@effect/vitest": major +--- + +v4 beta diff --git a/.repos/effect/.changeset/pre/silent-geckos-matter.md b/.repos/effect/.changeset/pre/silent-geckos-matter.md new file mode 100644 index 000000000..df5aadf3b --- /dev/null +++ b/.repos/effect/.changeset/pre/silent-geckos-matter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +compare transaction connections by reference diff --git a/.repos/effect/.changeset/pre/silent-needles-design.md b/.repos/effect/.changeset/pre/silent-needles-design.md new file mode 100644 index 000000000..ee84c0eb7 --- /dev/null +++ b/.repos/effect/.changeset/pre/silent-needles-design.md @@ -0,0 +1,6 @@ +--- +"@effect/opentelemetry": patch +"effect": patch +--- + +Fix spans never having parent span diff --git a/.repos/effect/.changeset/pre/silent-plants-matter.md b/.repos/effect/.changeset/pre/silent-plants-matter.md new file mode 100644 index 000000000..e9609eab2 --- /dev/null +++ b/.repos/effect/.changeset/pre/silent-plants-matter.md @@ -0,0 +1,8 @@ +--- +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +"effect": patch +--- + +improve http body consumption diff --git a/.repos/effect/.changeset/pre/silent-spoons-stare.md b/.repos/effect/.changeset/pre/silent-spoons-stare.md new file mode 100644 index 000000000..6d45025ee --- /dev/null +++ b/.repos/effect/.changeset/pre/silent-spoons-stare.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +catch errors in pullIntoWritable diff --git a/.repos/effect/.changeset/pre/silly-dodos-update.md b/.repos/effect/.changeset/pre/silly-dodos-update.md new file mode 100644 index 000000000..e90a8a07a --- /dev/null +++ b/.repos/effect/.changeset/pre/silly-dodos-update.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `SynchronizedRef.getAndUpdateSome` to update its backing ref. diff --git a/.repos/effect/.changeset/pre/silly-loops-tickle.md b/.repos/effect/.changeset/pre/silly-loops-tickle.md new file mode 100644 index 000000000..a5493c8e0 --- /dev/null +++ b/.repos/effect/.changeset/pre/silly-loops-tickle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +DateTime.distance now returns a Duration diff --git a/.repos/effect/.changeset/pre/silver-bulk-indexeddb.md b/.repos/effect/.changeset/pre/silver-bulk-indexeddb.md new file mode 100644 index 000000000..7137730b4 --- /dev/null +++ b/.repos/effect/.changeset/pre/silver-bulk-indexeddb.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Fix IndexedDB bulk writes so `insertAll` and `upsertAll` resume when used inside `withTransaction`. diff --git a/.repos/effect/.changeset/pre/silver-emus-smoke.md b/.repos/effect/.changeset/pre/silver-emus-smoke.md new file mode 100644 index 000000000..19f10e09d --- /dev/null +++ b/.repos/effect/.changeset/pre/silver-emus-smoke.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove placeholder fallback behavior from CLI prompt inputs now that default values are prefilled. diff --git a/.repos/effect/.changeset/pre/silver-kings-poke.md b/.repos/effect/.changeset/pre/silver-kings-poke.md new file mode 100644 index 000000000..11d14a6b5 --- /dev/null +++ b/.repos/effect/.changeset/pre/silver-kings-poke.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +simplify SubscriptionRef diff --git a/.repos/effect/.changeset/pre/silver-snails-sqlite.md b/.repos/effect/.changeset/pre/silver-snails-sqlite.md new file mode 100644 index 000000000..dc4ddd43f --- /dev/null +++ b/.repos/effect/.changeset/pre/silver-snails-sqlite.md @@ -0,0 +1,17 @@ +--- +"effect": patch +"@effect/sql-libsql": patch +"@effect/sql-mssql": patch +"@effect/sql-mysql2": patch +"@effect/sql-pg": patch +"@effect/sql-pglite": patch +"@effect/sql-sqlite-bun": patch +"@effect/sql-sqlite-do": patch +"@effect/sql-sqlite-node": patch +"@effect/sql-sqlite-react-native": patch +"@effect/sql-sqlite-wasm": patch +--- + +Add `UniqueViolation` as a new SQL error reason. Supported unique constraint violations now classify as `UniqueViolation` instead of the broader `ConstraintError` reason. + +This covers PostgreSQL, PGlite, MySQL, MSSQL, and the shared SQLite classification used by the SQLite-family clients. `UniqueViolation.constraint` contains the best available constraint, index, or key identifier and falls back to exactly `"unknown"` when no reliable identifier is available. diff --git a/.repos/effect/.changeset/pre/silver-wings-watch.md b/.repos/effect/.changeset/pre/silver-wings-watch.md new file mode 100644 index 000000000..3a1d49eac --- /dev/null +++ b/.repos/effect/.changeset/pre/silver-wings-watch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +require a option to make AtomRpc.query atoms serializatable diff --git a/.repos/effect/.changeset/pre/simplify-optic-composition.md b/.repos/effect/.changeset/pre/simplify-optic-composition.md new file mode 100644 index 000000000..1ed7423a9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/six-cups-taste.md b/.repos/effect/.changeset/pre/six-cups-taste.md new file mode 100644 index 000000000..71d570b69 --- /dev/null +++ b/.repos/effect/.changeset/pre/six-cups-taste.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add args to Stdio service diff --git a/.repos/effect/.changeset/pre/six-pumas-take.md b/.repos/effect/.changeset/pre/six-pumas-take.md new file mode 100644 index 000000000..a58d9a049 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/sixty-mails-shout.md b/.repos/effect/.changeset/pre/sixty-mails-shout.md new file mode 100644 index 000000000..b4bff8395 --- /dev/null +++ b/.repos/effect/.changeset/pre/sixty-mails-shout.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix VariantSchema.Union diff --git a/.repos/effect/.changeset/pre/sixty-socks-yell.md b/.repos/effect/.changeset/pre/sixty-socks-yell.md new file mode 100644 index 000000000..1441984b7 --- /dev/null +++ b/.repos/effect/.changeset/pre/sixty-socks-yell.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update `HttpApiClient.urlBuilder` to mirror client shape, and encode params/query via endpoint schemas before building URLs. diff --git a/.repos/effect/.changeset/pre/slick-signs-wish.md b/.repos/effect/.changeset/pre/slick-signs-wish.md new file mode 100644 index 000000000..d7490cbb2 --- /dev/null +++ b/.repos/effect/.changeset/pre/slick-signs-wish.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `isOpen` to `Latch` to allow querying the latch's open state diff --git a/.repos/effect/.changeset/pre/slick-toes-rush.md b/.repos/effect/.changeset/pre/slick-toes-rush.md new file mode 100644 index 000000000..ff74b18b5 --- /dev/null +++ b/.repos/effect/.changeset/pre/slick-toes-rush.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +render causes in OtlpTracer exception events diff --git a/.repos/effect/.changeset/pre/slimy-melons-admire.md b/.repos/effect/.changeset/pre/slimy-melons-admire.md new file mode 100644 index 000000000..7cc69a0af --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/slimy-planets-divide.md b/.repos/effect/.changeset/pre/slimy-planets-divide.md new file mode 100644 index 000000000..aed2e85f2 --- /dev/null +++ b/.repos/effect/.changeset/pre/slimy-planets-divide.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow assigning Temporal types to DateTime & Duration input diff --git a/.repos/effect/.changeset/pre/slimy-turtles-juggle.md b/.repos/effect/.changeset/pre/slimy-turtles-juggle.md new file mode 100644 index 000000000..2cdb299ae --- /dev/null +++ b/.repos/effect/.changeset/pre/slimy-turtles-juggle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +refactor SqlSchema apis diff --git a/.repos/effect/.changeset/pre/slow-beans-battle.md b/.repos/effect/.changeset/pre/slow-beans-battle.md new file mode 100644 index 000000000..e3a80a369 --- /dev/null +++ b/.repos/effect/.changeset/pre/slow-beans-battle.md @@ -0,0 +1,28 @@ +--- +"@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-node": patch +"@effect/sql-sqlite-do": patch +"@effect/ai-anthropic": patch +"@effect/platform-bun": patch +"@effect/atom-react": patch +"@effect/sql-libsql": patch +"@effect/sql-mysql2": patch +"@effect/ai-openai": patch +"@effect/sql-mssql": patch +"effect": patch +"@effect/sql-d1": patch +"@effect/sql-pg": patch +"@effect/vitest": patch +--- + +Rename the `ServiceMap` module to `Context` across exports, docs, and tests. diff --git a/.repos/effect/.changeset/pre/slow-berries-enjoy.md b/.repos/effect/.changeset/pre/slow-berries-enjoy.md new file mode 100644 index 000000000..de89cc726 --- /dev/null +++ b/.repos/effect/.changeset/pre/slow-berries-enjoy.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Simplify internal and documented request usage by passing request resolvers directly to `Effect.request` instead of wrapping them with `Effect.succeed`. diff --git a/.repos/effect/.changeset/pre/slow-entities-register.md b/.repos/effect/.changeset/pre/slow-entities-register.md new file mode 100644 index 000000000..678da84a1 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/slow-spiders-refresh.md b/.repos/effect/.changeset/pre/slow-spiders-refresh.md new file mode 100644 index 000000000..109878758 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/small-bugs-hunt.md b/.repos/effect/.changeset/pre/small-bugs-hunt.md new file mode 100644 index 000000000..bad2315e9 --- /dev/null +++ b/.repos/effect/.changeset/pre/small-bugs-hunt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix JSON-RPC serialization to return an object for non-batched requests while preserving array responses for true batch requests. diff --git a/.repos/effect/.changeset/pre/small-crabs-care.md b/.repos/effect/.changeset/pre/small-crabs-care.md new file mode 100644 index 000000000..c79ad5cb4 --- /dev/null +++ b/.repos/effect/.changeset/pre/small-crabs-care.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Refine unstable CLI parent/subcommand flag composition. + +- Add `Command.withSharedFlags` conflict validation against existing subcommands, including the `withSubcommands(...).withSharedFlags(...)` composition order. +- Reorder `Command` type parameters to `Command` for clearer parent-context modeling. +- Make `Command.withSubcommands` input typing sound for downstream input-based combinators by reflecting that subcommand paths only carry parent context input. diff --git a/.repos/effect/.changeset/pre/small-pandas-cache.md b/.repos/effect/.changeset/pre/small-pandas-cache.md new file mode 100644 index 000000000..b1e5b3de5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/small-pets-sit.md b/.repos/effect/.changeset/pre/small-pets-sit.md new file mode 100644 index 000000000..5fd8650d5 --- /dev/null +++ b/.repos/effect/.changeset/pre/small-pets-sit.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Don't transform Tool result schemas, as they aren't sent to the providers as +json schemas diff --git a/.repos/effect/.changeset/pre/smart-ducks-jump.md b/.repos/effect/.changeset/pre/smart-ducks-jump.md new file mode 100644 index 000000000..2322e4f7e --- /dev/null +++ b/.repos/effect/.changeset/pre/smart-ducks-jump.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Fix `Schedule.andThenResult` to initialize the right schedule only after the left schedule completes. +This removes the extra immediate transition tick and correctly completes when the right schedule is finite. diff --git a/.repos/effect/.changeset/pre/smart-pillows-buy.md b/.repos/effect/.changeset/pre/smart-pillows-buy.md new file mode 100644 index 000000000..f4ac640fa --- /dev/null +++ b/.repos/effect/.changeset/pre/smart-pillows-buy.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/sql-pg": patch +--- + +Fix `ChildProcess` options type and implement `PgMigrator` diff --git a/.repos/effect/.changeset/pre/smart-timers-fly.md b/.repos/effect/.changeset/pre/smart-timers-fly.md new file mode 100644 index 000000000..e9a642781 --- /dev/null +++ b/.repos/effect/.changeset/pre/smart-timers-fly.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Cookies.expireCookie` / `expireCookieUnsafe` and `HttpServerResponse.expireCookie` / `expireCookieUnsafe` for emitting expired cookies. diff --git a/.repos/effect/.changeset/pre/smart-tips-sort.md b/.repos/effect/.changeset/pre/smart-tips-sort.md new file mode 100644 index 000000000..c23b25c0d --- /dev/null +++ b/.repos/effect/.changeset/pre/smart-tips-sort.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix Latch.release diff --git a/.repos/effect/.changeset/pre/social-hoops-knock.md b/.repos/effect/.changeset/pre/social-hoops-knock.md new file mode 100644 index 000000000..c91f7ae2e --- /dev/null +++ b/.repos/effect/.changeset/pre/social-hoops-knock.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Cleanup internals of CLI package diff --git a/.repos/effect/.changeset/pre/social-pumas-prove.md b/.repos/effect/.changeset/pre/social-pumas-prove.md new file mode 100644 index 000000000..1e71087bf --- /dev/null +++ b/.repos/effect/.changeset/pre/social-pumas-prove.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +disable tracer propagation for otlp exporter diff --git a/.repos/effect/.changeset/pre/soft-comics-wink.md b/.repos/effect/.changeset/pre/soft-comics-wink.md new file mode 100644 index 000000000..e93cf5249 --- /dev/null +++ b/.repos/effect/.changeset/pre/soft-comics-wink.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Default `Effect.context()` to `Effect.context()` when no type parameter is provided. diff --git a/.repos/effect/.changeset/pre/soft-delete-sqlmodel.md b/.repos/effect/.changeset/pre/soft-delete-sqlmodel.md new file mode 100644 index 000000000..08a90e58a --- /dev/null +++ b/.repos/effect/.changeset/pre/soft-delete-sqlmodel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add optional soft delete column support to SqlModel repositories and resolvers. diff --git a/.repos/effect/.changeset/pre/soft-seals-allow.md b/.repos/effect/.changeset/pre/soft-seals-allow.md new file mode 100644 index 000000000..54e3bc761 --- /dev/null +++ b/.repos/effect/.changeset/pre/soft-seals-allow.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +cache base idb query builders diff --git a/.repos/effect/.changeset/pre/soft-sockets-write.md b/.repos/effect/.changeset/pre/soft-sockets-write.md new file mode 100644 index 000000000..c0106d10b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/solid-cougars-attack.md b/.repos/effect/.changeset/pre/solid-cougars-attack.md new file mode 100644 index 000000000..199bb36f3 --- /dev/null +++ b/.repos/effect/.changeset/pre/solid-cougars-attack.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add -v alias for version flag diff --git a/.repos/effect/.changeset/pre/solid-doors-ring.md b/.repos/effect/.changeset/pre/solid-doors-ring.md new file mode 100644 index 000000000..6178bf731 --- /dev/null +++ b/.repos/effect/.changeset/pre/solid-doors-ring.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow creating standalone http handlers from HttpApiEndpoints diff --git a/.repos/effect/.changeset/pre/solid-items-tease.md b/.repos/effect/.changeset/pre/solid-items-tease.md new file mode 100644 index 000000000..ed43ea9b2 --- /dev/null +++ b/.repos/effect/.changeset/pre/solid-items-tease.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix atom node timeout cleanup diff --git a/.repos/effect/.changeset/pre/solid-towns-smoke.md b/.repos/effect/.changeset/pre/solid-towns-smoke.md new file mode 100644 index 000000000..44300aae1 --- /dev/null +++ b/.repos/effect/.changeset/pre/solid-towns-smoke.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +rename Model.Generated to Model.GeneratedByDb diff --git a/.repos/effect/.changeset/pre/sour-bees-sleep.md b/.repos/effect/.changeset/pre/sour-bees-sleep.md new file mode 100644 index 000000000..6c31527a9 --- /dev/null +++ b/.repos/effect/.changeset/pre/sour-bees-sleep.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Default empty Server-Sent Event types to `message`. diff --git a/.repos/effect/.changeset/pre/sour-canyons-rescue.md b/.repos/effect/.changeset/pre/sour-canyons-rescue.md new file mode 100644 index 000000000..7499eba6b --- /dev/null +++ b/.repos/effect/.changeset/pre/sour-canyons-rescue.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add `useCodecs` option to HttpClientEndpoint constructors diff --git a/.repos/effect/.changeset/pre/sparkly-bears-act.md b/.repos/effect/.changeset/pre/sparkly-bears-act.md new file mode 100644 index 000000000..655477a38 --- /dev/null +++ b/.repos/effect/.changeset/pre/sparkly-bears-act.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add `Effect.acquireDisposable` diff --git a/.repos/effect/.changeset/pre/sparkly-coins-sit.md b/.repos/effect/.changeset/pre/sparkly-coins-sit.md new file mode 100644 index 000000000..2de14b62f --- /dev/null +++ b/.repos/effect/.changeset/pre/sparkly-coins-sit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +expose mcp client capabilities diff --git a/.repos/effect/.changeset/pre/spicy-doors-unlist.md b/.repos/effect/.changeset/pre/spicy-doors-unlist.md new file mode 100644 index 000000000..fc4907aa6 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/split-clock-semantics.md b/.repos/effect/.changeset/pre/split-clock-semantics.md new file mode 100644 index 000000000..1d2390cbb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/spotty-comics-fry.md b/.repos/effect/.changeset/pre/spotty-comics-fry.md new file mode 100644 index 000000000..496c87adf --- /dev/null +++ b/.repos/effect/.changeset/pre/spotty-comics-fry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix OpenApi Multipart file upload schema generation diff --git a/.repos/effect/.changeset/pre/sql-begin-failure-typed.md b/.repos/effect/.changeset/pre/sql-begin-failure-typed.md new file mode 100644 index 000000000..adfdb341a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/sql-migrator-mjs-mts.md b/.repos/effect/.changeset/pre/sql-migrator-mjs-mts.md new file mode 100644 index 000000000..071d942c8 --- /dev/null +++ b/.repos/effect/.changeset/pre/sql-migrator-mjs-mts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Accept `.mjs` and `.mts` migration files in SQL migrator loaders. diff --git a/.repos/effect/.changeset/pre/sqlite-bun-prepare-error-channel.md b/.repos/effect/.changeset/pre/sqlite-bun-prepare-error-channel.md new file mode 100644 index 000000000..ab744c5f4 --- /dev/null +++ b/.repos/effect/.changeset/pre/sqlite-bun-prepare-error-channel.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-bun": patch +--- + +Fail with a typed `SqlError` when Bun SQLite statement preparation throws (for example a missing table or a syntax error), instead of letting the driver error escape as a defect, closes #2385. diff --git a/.repos/effect/.changeset/pre/sqlite-client-locking-defaults.md b/.repos/effect/.changeset/pre/sqlite-client-locking-defaults.md new file mode 100644 index 000000000..ee01605b9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/sqlite-do-durable-object-transactions.md b/.repos/effect/.changeset/pre/sqlite-do-durable-object-transactions.md new file mode 100644 index 000000000..b0016a9fc --- /dev/null +++ b/.repos/effect/.changeset/pre/sqlite-do-durable-object-transactions.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-do": patch +--- + +Support Cloudflare Durable Object SQLite transactions by allowing `SqliteClient` to be configured with `DurableObjectStorage` and routing `withTransaction` through `storage.transaction`. diff --git a/.repos/effect/.changeset/pre/stable-graph-traversal-config.md b/.repos/effect/.changeset/pre/stable-graph-traversal-config.md new file mode 100644 index 000000000..a4663f38a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/stale-dots-tell.md b/.repos/effect/.changeset/pre/stale-dots-tell.md new file mode 100644 index 000000000..c6609de6b --- /dev/null +++ b/.repos/effect/.changeset/pre/stale-dots-tell.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +improve HttpClient.withRateLimiter initial state tracking diff --git a/.repos/effect/.changeset/pre/stale-graph-traversal-skips.md b/.repos/effect/.changeset/pre/stale-graph-traversal-skips.md new file mode 100644 index 000000000..6bf269405 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/stale-laws-do.md b/.repos/effect/.changeset/pre/stale-laws-do.md new file mode 100644 index 000000000..f114fbec1 --- /dev/null +++ b/.repos/effect/.changeset/pre/stale-laws-do.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +optimize bun stream reading diff --git a/.repos/effect/.changeset/pre/stale-snakes-know.md b/.repos/effect/.changeset/pre/stale-snakes-know.md new file mode 100644 index 000000000..a6272595b --- /dev/null +++ b/.repos/effect/.changeset/pre/stale-snakes-know.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +update dependencies diff --git a/.repos/effect/.changeset/pre/standard-schema-module.md b/.repos/effect/.changeset/pre/standard-schema-module.md new file mode 100644 index 000000000..b3ac3eb10 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/strict-areas-end.md b/.repos/effect/.changeset/pre/strict-areas-end.md new file mode 100644 index 000000000..c469085e2 --- /dev/null +++ b/.repos/effect/.changeset/pre/strict-areas-end.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Track ManagedRuntime fibers in a scope diff --git a/.repos/effect/.changeset/pre/strict-buckets-hug.md b/.repos/effect/.changeset/pre/strict-buckets-hug.md new file mode 100644 index 000000000..73164ef28 --- /dev/null +++ b/.repos/effect/.changeset/pre/strict-buckets-hug.md @@ -0,0 +1,8 @@ +--- +"@effect/ai-openai-compat": patch +"@effect/ai-openrouter": patch +"@effect/ai-anthropic": patch +"@effect/ai-openai": patch +--- + +allow undefined for ai config diff --git a/.repos/effect/.changeset/pre/strip-resolved-approvals.md b/.repos/effect/.changeset/pre/strip-resolved-approvals.md new file mode 100644 index 000000000..4cdf441af --- /dev/null +++ b/.repos/effect/.changeset/pre/strip-resolved-approvals.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Strip resolved tool approval artifacts from prompt before sending to provider, preventing errors when providers reject pre-resolved approval requests. diff --git a/.repos/effect/.changeset/pre/strong-balloons-tickle.md b/.repos/effect/.changeset/pre/strong-balloons-tickle.md new file mode 100644 index 000000000..c77826402 --- /dev/null +++ b/.repos/effect/.changeset/pre/strong-balloons-tickle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include toolkit tool handler requirements in AI generation API environment inference. diff --git a/.repos/effect/.changeset/pre/strong-bees-queue.md b/.repos/effect/.changeset/pre/strong-bees-queue.md new file mode 100644 index 000000000..d7c401842 --- /dev/null +++ b/.repos/effect/.changeset/pre/strong-bees-queue.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add the unstable workflow DurableQueue module. diff --git a/.repos/effect/.changeset/pre/strong-insects-film.md b/.repos/effect/.changeset/pre/strong-insects-film.md new file mode 100644 index 000000000..075c42f85 --- /dev/null +++ b/.repos/effect/.changeset/pre/strong-insects-film.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure WithTransaction wraps entire rpc handler diff --git a/.repos/effect/.changeset/pre/struct-record.md b/.repos/effect/.changeset/pre/struct-record.md new file mode 100644 index 000000000..9a6d46a74 --- /dev/null +++ b/.repos/effect/.changeset/pre/struct-record.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Struct: add `Struct.Record` constructor for creating records with the given keys and value. diff --git a/.repos/effect/.changeset/pre/sunny-ads-hang.md b/.repos/effect/.changeset/pre/sunny-ads-hang.md new file mode 100644 index 000000000..d51143ef6 --- /dev/null +++ b/.repos/effect/.changeset/pre/sunny-ads-hang.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add ClusterSchema.WithTransaction annotation diff --git a/.repos/effect/.changeset/pre/sunny-bikes-sleep.md b/.repos/effect/.changeset/pre/sunny-bikes-sleep.md new file mode 100644 index 000000000..79412b327 --- /dev/null +++ b/.repos/effect/.changeset/pre/sunny-bikes-sleep.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +default ws close codes to 1001 in case they are undefined diff --git a/.repos/effect/.changeset/pre/sunny-rooms-invent.md b/.repos/effect/.changeset/pre/sunny-rooms-invent.md new file mode 100644 index 000000000..4e6390169 --- /dev/null +++ b/.repos/effect/.changeset/pre/sunny-rooms-invent.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Refine `ExtractServices` to omit tool handler requirements when automatic tool resolution is explicitly disabled through the `disableToolCallResolution` option. diff --git a/.repos/effect/.changeset/pre/sweet-donuts-bet.md b/.repos/effect/.changeset/pre/sweet-donuts-bet.md new file mode 100644 index 000000000..3d09822fc --- /dev/null +++ b/.repos/effect/.changeset/pre/sweet-donuts-bet.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add graceful shutdown to http servers diff --git a/.repos/effect/.changeset/pre/sweet-hotels-give.md b/.repos/effect/.changeset/pre/sweet-hotels-give.md new file mode 100644 index 000000000..a54bddee0 --- /dev/null +++ b/.repos/effect/.changeset/pre/sweet-hotels-give.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +make EntityResource lazy by default diff --git a/.repos/effect/.changeset/pre/sweet-lizards-sing.md b/.repos/effect/.changeset/pre/sweet-lizards-sing.md new file mode 100644 index 000000000..12248afd9 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/sweet-schedules-matter.md b/.repos/effect/.changeset/pre/sweet-schedules-matter.md new file mode 100644 index 000000000..dcc8d4a93 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/sweet-views-learn.md b/.repos/effect/.changeset/pre/sweet-views-learn.md new file mode 100644 index 000000000..dda17ba48 --- /dev/null +++ b/.repos/effect/.changeset/pre/sweet-views-learn.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure workflow failures are not squashed by suspension interrupts diff --git a/.repos/effect/.changeset/pre/swift-geese-count.md b/.repos/effect/.changeset/pre/swift-geese-count.md new file mode 100644 index 000000000..352cd448e --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/swift-pandas-parse.md b/.repos/effect/.changeset/pre/swift-pandas-parse.md new file mode 100644 index 000000000..6bbf259a5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/swift-spiders-unpack.md b/.repos/effect/.changeset/pre/swift-spiders-unpack.md new file mode 100644 index 000000000..3f1f957bc --- /dev/null +++ b/.repos/effect/.changeset/pre/swift-spiders-unpack.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Layer.suspend` as a lazy constructor for dynamically choosing a layer while preserving normal layer sharing. diff --git a/.repos/effect/.changeset/pre/swift-symbols-stand.md b/.repos/effect/.changeset/pre/swift-symbols-stand.md new file mode 100644 index 000000000..2dbec9911 --- /dev/null +++ b/.repos/effect/.changeset/pre/swift-symbols-stand.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +remove rpc client nesting to improve type performance diff --git a/.repos/effect/.changeset/pre/tagged-error-class-optional-empty-props.md b/.repos/effect/.changeset/pre/tagged-error-class-optional-empty-props.md new file mode 100644 index 000000000..7b63b56e5 --- /dev/null +++ b/.repos/effect/.changeset/pre/tagged-error-class-optional-empty-props.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +`Schema.TaggedErrorClass`, `Schema.Class`, and `Schema.ErrorClass` constructors now allow omitting the props argument when all fields have constructor defaults (e.g. `new MyError()` instead of `new MyError({})`). diff --git a/.repos/effect/.changeset/pre/tall-hairs-return.md b/.repos/effect/.changeset/pre/tall-hairs-return.md new file mode 100644 index 000000000..551d144e9 --- /dev/null +++ b/.repos/effect/.changeset/pre/tall-hairs-return.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpServerResponse.toClientResponse` for converting server responses into `HttpClientResponse` values. diff --git a/.repos/effect/.changeset/pre/tall-ideas-fix.md b/.repos/effect/.changeset/pre/tall-ideas-fix.md new file mode 100644 index 000000000..0cf7925e2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tall-mails-listen.md b/.repos/effect/.changeset/pre/tall-mails-listen.md new file mode 100644 index 000000000..96d7af428 --- /dev/null +++ b/.repos/effect/.changeset/pre/tall-mails-listen.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.catchNoSuchElement`, a renamed port of v3 `Effect.optionFromOptional` that converts `NoSuchElementError` failures into `Option.none`. diff --git a/.repos/effect/.changeset/pre/tall-melons-shave.md b/.repos/effect/.changeset/pre/tall-melons-shave.md new file mode 100644 index 000000000..cdb764935 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tall-queens-cheer.md b/.repos/effect/.changeset/pre/tall-queens-cheer.md new file mode 100644 index 000000000..cffebd795 --- /dev/null +++ b/.repos/effect/.changeset/pre/tall-queens-cheer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `UrlParams.Input` usage to accept interface-typed records in HTTP client and server helpers while keeping coercion constraints for url parameter values. diff --git a/.repos/effect/.changeset/pre/tall-wombats-wave.md b/.repos/effect/.changeset/pre/tall-wombats-wave.md new file mode 100644 index 000000000..8bf297cce --- /dev/null +++ b/.repos/effect/.changeset/pre/tall-wombats-wave.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix ai LanguageModel streaming finish parts so finish events are always emitted when a toolkit is provided. diff --git a/.repos/effect/.changeset/pre/tame-json-schema-intersections.md b/.repos/effect/.changeset/pre/tame-json-schema-intersections.md new file mode 100644 index 000000000..49d9af01c --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tangy-colts-lose.md b/.repos/effect/.changeset/pre/tangy-colts-lose.md new file mode 100644 index 000000000..9678f5343 --- /dev/null +++ b/.repos/effect/.changeset/pre/tangy-colts-lose.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +simplify NodeChildSpawner stdout streams diff --git a/.repos/effect/.changeset/pre/tangy-plants-run.md b/.repos/effect/.changeset/pre/tangy-plants-run.md new file mode 100644 index 000000000..111cd174b --- /dev/null +++ b/.repos/effect/.changeset/pre/tangy-plants-run.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +only interrupt cache lookup when all awaiters are gone diff --git a/.repos/effect/.changeset/pre/tasty-comics-send.md b/.repos/effect/.changeset/pre/tasty-comics-send.md new file mode 100644 index 000000000..78bed2e76 --- /dev/null +++ b/.repos/effect/.changeset/pre/tasty-comics-send.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +improve runSync error when executing async effects diff --git a/.repos/effect/.changeset/pre/tasty-moments-post.md b/.repos/effect/.changeset/pre/tasty-moments-post.md new file mode 100644 index 000000000..8a2d4e14d --- /dev/null +++ b/.repos/effect/.changeset/pre/tasty-moments-post.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Effect.setContext for fully replacing the fiber context diff --git a/.repos/effect/.changeset/pre/ten-kings-fry.md b/.repos/effect/.changeset/pre/ten-kings-fry.md new file mode 100644 index 000000000..593dc9460 --- /dev/null +++ b/.repos/effect/.changeset/pre/ten-kings-fry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix empty body decoding in HttpApiBuilder diff --git a/.repos/effect/.changeset/pre/tender-deserts-pull.md b/.repos/effect/.changeset/pre/tender-deserts-pull.md new file mode 100644 index 000000000..c7fda450b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tender-files-complete.md b/.repos/effect/.changeset/pre/tender-files-complete.md new file mode 100644 index 000000000..9b9210b4a --- /dev/null +++ b/.repos/effect/.changeset/pre/tender-files-complete.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve file and directory semantics in CLI completion descriptors. diff --git a/.repos/effect/.changeset/pre/tender-points-sleep.md b/.repos/effect/.changeset/pre/tender-points-sleep.md new file mode 100644 index 000000000..3206a87c2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/terminate-openai-failed-streams.md b/.repos/effect/.changeset/pre/terminate-openai-failed-streams.md new file mode 100644 index 000000000..66599bb5f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/theme-cli-prompts.md b/.repos/effect/.changeset/pre/theme-cli-prompts.md new file mode 100644 index 000000000..45ea5758b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/thick-pandas-wait.md b/.repos/effect/.changeset/pre/thick-pandas-wait.md new file mode 100644 index 000000000..5ddc89145 --- /dev/null +++ b/.repos/effect/.changeset/pre/thick-pandas-wait.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add default value support to CLI integer prompts. diff --git a/.repos/effect/.changeset/pre/thin-ducks-wonder.md b/.repos/effect/.changeset/pre/thin-ducks-wonder.md new file mode 100644 index 000000000..2176226f9 --- /dev/null +++ b/.repos/effect/.changeset/pre/thin-ducks-wonder.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename `HttpClient.retryTransient` option `mode` to `retryOn` and rename `"both"` to `"errors-and-responses"`. diff --git a/.repos/effect/.changeset/pre/thirty-ducks-go.md b/.repos/effect/.changeset/pre/thirty-ducks-go.md new file mode 100644 index 000000000..b3f3aa17e --- /dev/null +++ b/.repos/effect/.changeset/pre/thirty-ducks-go.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +HttpClient.withRateLimiter adds delay from retry-after headers diff --git a/.repos/effect/.changeset/pre/thirty-forks-march.md b/.repos/effect/.changeset/pre/thirty-forks-march.md new file mode 100644 index 000000000..0085d3d14 --- /dev/null +++ b/.repos/effect/.changeset/pre/thirty-forks-march.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Drop unreachable concurrency guard in iteratorEagerImpl + \ No newline at end of file diff --git a/.repos/effect/.changeset/pre/thirty-pans-love.md b/.repos/effect/.changeset/pre/thirty-pans-love.md new file mode 100644 index 000000000..0d0a4f9c1 --- /dev/null +++ b/.repos/effect/.changeset/pre/thirty-pans-love.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +allow Model.Class for indexeddb schemas diff --git a/.repos/effect/.changeset/pre/three-corners-sort.md b/.repos/effect/.changeset/pre/three-corners-sort.md new file mode 100644 index 000000000..ceb67c6a7 --- /dev/null +++ b/.repos/effect/.changeset/pre/three-corners-sort.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +port Url module from v3 diff --git a/.repos/effect/.changeset/pre/three-ravens-jam.md b/.repos/effect/.changeset/pre/three-ravens-jam.md new file mode 100644 index 000000000..ba528ccbf --- /dev/null +++ b/.repos/effect/.changeset/pre/three-ravens-jam.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.findFirst` and `Effect.findFirstFilter` for short-circuiting effectful searches over iterables. diff --git a/.repos/effect/.changeset/pre/three-tomatoes-wave.md b/.repos/effect/.changeset/pre/three-tomatoes-wave.md new file mode 100644 index 000000000..886f44fd4 --- /dev/null +++ b/.repos/effect/.changeset/pre/three-tomatoes-wave.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `unstable/sql/SqlSchema` request input typing so `findAll` and `findNonEmpty` accept `Request["Type"]` instead of `Request["Encoded"]`. diff --git a/.repos/effect/.changeset/pre/tidy-apples-rest.md b/.repos/effect/.changeset/pre/tidy-apples-rest.md new file mode 100644 index 000000000..1c9289733 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-carpets-smile.md b/.repos/effect/.changeset/pre/tidy-carpets-smile.md new file mode 100644 index 000000000..af1b48da1 --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-carpets-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent malformed encoded multipart filenames from throwing during parsing. diff --git a/.repos/effect/.changeset/pre/tidy-cats-smile.md b/.repos/effect/.changeset/pre/tidy-cats-smile.md new file mode 100644 index 000000000..951a09ca7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-cats-stream.md b/.repos/effect/.changeset/pre/tidy-cats-stream.md new file mode 100644 index 000000000..c14e87c6a --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-cats-stream.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve lexical ordering in streaming template interpolation. diff --git a/.repos/effect/.changeset/pre/tidy-codecs-inherit.md b/.repos/effect/.changeset/pre/tidy-codecs-inherit.md new file mode 100644 index 000000000..137e05fd8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-dates-smile.md b/.repos/effect/.changeset/pre/tidy-dates-smile.md new file mode 100644 index 000000000..529a47fdf --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-dates-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Correct year, ordinal, and meridiem date-mask formatting. diff --git a/.repos/effect/.changeset/pre/tidy-floats-edit.md b/.repos/effect/.changeset/pre/tidy-floats-edit.md new file mode 100644 index 000000000..10924f810 --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-floats-edit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve fractional leading zeros while editing float prompts. diff --git a/.repos/effect/.changeset/pre/tidy-foxes-own.md b/.repos/effect/.changeset/pre/tidy-foxes-own.md new file mode 100644 index 000000000..8a4f4b85e --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-foxes-own.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +clean up otlp config diff --git a/.repos/effect/.changeset/pre/tidy-geese-release.md b/.repos/effect/.changeset/pre/tidy-geese-release.md new file mode 100644 index 000000000..9b2cb9701 --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-geese-release.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `RcRef` leaking resources acquired before a failed acquisition. diff --git a/.repos/effect/.changeset/pre/tidy-graphs-cycle.md b/.repos/effect/.changeset/pre/tidy-graphs-cycle.md new file mode 100644 index 000000000..9d4cb2d1a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-icons-glow.md b/.repos/effect/.changeset/pre/tidy-icons-glow.md new file mode 100644 index 000000000..dde797923 --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-icons-glow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add a `Config.literals` convenience constructor for `Schema.Literals`, closes #2091. diff --git a/.repos/effect/.changeset/pre/tidy-int32-annotations.md b/.repos/effect/.changeset/pre/tidy-int32-annotations.md new file mode 100644 index 000000000..ca99cdd74 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-json-objects.md b/.repos/effect/.changeset/pre/tidy-json-objects.md new file mode 100644 index 000000000..80e746370 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-json-schema-object-scopes.md b/.repos/effect/.changeset/pre/tidy-json-schema-object-scopes.md new file mode 100644 index 000000000..2d3f1b7b4 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-lions-smile.md b/.repos/effect/.changeset/pre/tidy-lions-smile.md new file mode 100644 index 000000000..871c3ab65 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-matches-select.md b/.repos/effect/.changeset/pre/tidy-matches-select.md new file mode 100644 index 000000000..985f71da0 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-mice-grin.md b/.repos/effect/.changeset/pre/tidy-mice-grin.md new file mode 100644 index 000000000..1dcc53752 --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-mice-grin.md @@ -0,0 +1,6 @@ +--- +"@effect/platform-node-shared": patch +"effect": patch +--- + +remove file descriptor type diff --git a/.repos/effect/.changeset/pre/tidy-pandas-smile.md b/.repos/effect/.changeset/pre/tidy-pandas-smile.md new file mode 100644 index 000000000..73f4b8803 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-plums-remember.md b/.repos/effect/.changeset/pre/tidy-plums-remember.md new file mode 100644 index 000000000..e9361ab06 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-ravens-import.md b/.repos/effect/.changeset/pre/tidy-ravens-import.md new file mode 100644 index 000000000..58265b76b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-schema-errors.md b/.repos/effect/.changeset/pre/tidy-schema-errors.md new file mode 100644 index 000000000..1ad564ceb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-scoped-refs-close.md b/.repos/effect/.changeset/pre/tidy-scoped-refs-close.md new file mode 100644 index 000000000..4bb41bafa --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-spans-rest.md b/.repos/effect/.changeset/pre/tidy-spans-rest.md new file mode 100644 index 000000000..7dc60bafb --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-sse-events.md b/.repos/effect/.changeset/pre/tidy-sse-events.md new file mode 100644 index 000000000..2ccb437ba --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-stacks-encode.md b/.repos/effect/.changeset/pre/tidy-stacks-encode.md new file mode 100644 index 000000000..d9e9a2438 --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-stacks-encode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Encode HTTP API client path parameters when building request URLs. diff --git a/.repos/effect/.changeset/pre/tidy-stars-drive.md b/.repos/effect/.changeset/pre/tidy-stars-drive.md new file mode 100644 index 000000000..b31ea0808 --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-stars-drive.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `TestClock.currentTimeNanosUnsafe()` to floor fractional millisecond instants before converting them to `BigInt`. diff --git a/.repos/effect/.changeset/pre/tidy-tools-handle.md b/.repos/effect/.changeset/pre/tidy-tools-handle.md new file mode 100644 index 000000000..155071c9f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-tools-juggle.md b/.repos/effect/.changeset/pre/tidy-tools-juggle.md new file mode 100644 index 000000000..439548407 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tidy-tuples-rest.md b/.repos/effect/.changeset/pre/tidy-tuples-rest.md new file mode 100644 index 000000000..2adc9de9c --- /dev/null +++ b/.repos/effect/.changeset/pre/tidy-tuples-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve `maxItems` semantics when importing JSON Schema `prefixItems`. diff --git a/.repos/effect/.changeset/pre/tidy-wasps-wait.md b/.repos/effect/.changeset/pre/tidy-wasps-wait.md new file mode 100644 index 000000000..192e4ed6f --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tiny-buckets-wave.md b/.repos/effect/.changeset/pre/tiny-buckets-wave.md new file mode 100644 index 000000000..b01e82b81 --- /dev/null +++ b/.repos/effect/.changeset/pre/tiny-buckets-wave.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Underline the active label in CLI multi-select prompts and add a scratchpad example for manual verification. diff --git a/.repos/effect/.changeset/pre/tiny-dodos-juggle.md b/.repos/effect/.changeset/pre/tiny-dodos-juggle.md new file mode 100644 index 000000000..c721b6670 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tiny-files-flow.md b/.repos/effect/.changeset/pre/tiny-files-flow.md new file mode 100644 index 000000000..a8f71a0d8 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tiny-lilies-flash.md b/.repos/effect/.changeset/pre/tiny-lilies-flash.md new file mode 100644 index 000000000..f63686588 --- /dev/null +++ b/.repos/effect/.changeset/pre/tiny-lilies-flash.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +fix idb entries transaction diff --git a/.repos/effect/.changeset/pre/tiny-lizards-correct.md b/.repos/effect/.changeset/pre/tiny-lizards-correct.md new file mode 100644 index 000000000..82da87418 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tiny-rabbits-smile.md b/.repos/effect/.changeset/pre/tiny-rabbits-smile.md new file mode 100644 index 000000000..9cc27e378 --- /dev/null +++ b/.repos/effect/.changeset/pre/tiny-rabbits-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Relax `Ndjson` byte-stream channel signatures to accept plain `Uint8Array`. diff --git a/.repos/effect/.changeset/pre/to-codec-json-schema.md b/.repos/effect/.changeset/pre/to-codec-json-schema.md new file mode 100644 index 000000000..2cee85ea0 --- /dev/null +++ b/.repos/effect/.changeset/pre/to-codec-json-schema.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Expose the original input schema on `Schema.toType`, `Schema.toEncoded`, `Schema.toCodecJson`, and `Schema.toCodecStringTree` results via the `schema` property. This aligns these schema wrappers with other wrappers that retain their source schema for type-level and runtime introspection. diff --git a/.repos/effect/.changeset/pre/tocodecjson-return-json-type.md b/.repos/effect/.changeset/pre/tocodecjson-return-json-type.md new file mode 100644 index 000000000..401d58c2c --- /dev/null +++ b/.repos/effect/.changeset/pre/tocodecjson-return-json-type.md @@ -0,0 +1,10 @@ +--- +"effect": patch +"@effect/platform-browser": patch +"@effect/platform-bun": patch +"@effect/platform-node": patch +--- + +Schema: `toCodecJson` now returns `Codec` instead of `Codec`. + +Http: the `json` property on `HttpIncomingMessage`, `HttpClientResponse`, `HttpServerRequest`, and `HttpServerResponse` now returns `Effect` instead of `Effect`. diff --git a/.repos/effect/.changeset/pre/tool-get-json-schema-tests.md b/.repos/effect/.changeset/pre/tool-get-json-schema-tests.md new file mode 100644 index 000000000..f94d0cf77 --- /dev/null +++ b/.repos/effect/.changeset/pre/tool-get-json-schema-tests.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Tool.make` type and runtime behavior when `parameters` is not provided. diff --git a/.repos/effect/.changeset/pre/tough-graphs-cache.md b/.repos/effect/.changeset/pre/tough-graphs-cache.md new file mode 100644 index 000000000..010a1f831 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tough-rooms-camp.md b/.repos/effect/.changeset/pre/tough-rooms-camp.md new file mode 100644 index 000000000..26ae61b4f --- /dev/null +++ b/.repos/effect/.changeset/pre/tough-rooms-camp.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject NDJSON values without a JSON representation. diff --git a/.repos/effect/.changeset/pre/tough-taxis-own.md b/.repos/effect/.changeset/pre/tough-taxis-own.md new file mode 100644 index 000000000..74191d310 --- /dev/null +++ b/.repos/effect/.changeset/pre/tough-taxis-own.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Harden JSON-RPC wire message classification against inherited properties. diff --git a/.repos/effect/.changeset/pre/tracer-disabled-timing.md b/.repos/effect/.changeset/pre/tracer-disabled-timing.md new file mode 100644 index 000000000..42bf23ef5 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/tracer-perf.md b/.repos/effect/.changeset/pre/tracer-perf.md new file mode 100644 index 000000000..3a85e6d80 --- /dev/null +++ b/.repos/effect/.changeset/pre/tracer-perf.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve tracing performance in span creation and HTTP middleware. diff --git a/.repos/effect/.changeset/pre/true-actors-battle.md b/.repos/effect/.changeset/pre/true-actors-battle.md new file mode 100644 index 000000000..b93e18582 --- /dev/null +++ b/.repos/effect/.changeset/pre/true-actors-battle.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +fix fs.stat when blksize is undefined diff --git a/.repos/effect/.changeset/pre/try-promise-catch-defect.md b/.repos/effect/.changeset/pre/try-promise-catch-defect.md new file mode 100644 index 000000000..0d6d52edd --- /dev/null +++ b/.repos/effect/.changeset/pre/try-promise-catch-defect.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Fix `Effect.try` thunk usage and `Effect.tryPromise` mapper and signal handling defects. + +`Effect.try` now supports passing a thunk directly, matching `Effect.tryPromise`. Thrown values from direct-thunk usage are mapped to `Cause.UnknownError`. + +When a promise handled by `Effect.tryPromise` rejected and the custom `catch` mapper threw while mapping that rejection, the effect could remain pending and produce an unhandled rejection. The mapper is now guarded consistently with the synchronous throw path, so a thrown mapper error becomes an Effect defect. The JSDoc for `Effect.try` and `Effect.tryPromise` was also corrected. + +`Effect.tryPromise` now also only creates an `AbortController` when the wrapped thunk declares an `AbortSignal` parameter. diff --git a/.repos/effect/.changeset/pre/twelve-dragons-move.md b/.repos/effect/.changeset/pre/twelve-dragons-move.md new file mode 100644 index 000000000..bf9c5af1a --- /dev/null +++ b/.repos/effect/.changeset/pre/twelve-dragons-move.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +deduplicate SqlResolver.findById requests diff --git a/.repos/effect/.changeset/pre/twenty-buttons-cheer.md b/.repos/effect/.changeset/pre/twenty-buttons-cheer.md new file mode 100644 index 000000000..d1b709b46 --- /dev/null +++ b/.repos/effect/.changeset/pre/twenty-buttons-cheer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +expose more atom Node properties diff --git a/.repos/effect/.changeset/pre/twenty-facts-laugh.md b/.repos/effect/.changeset/pre/twenty-facts-laugh.md new file mode 100644 index 000000000..7430b4f99 --- /dev/null +++ b/.repos/effect/.changeset/pre/twenty-facts-laugh.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +use Sets to track atom relationships diff --git a/.repos/effect/.changeset/pre/twenty-garlics-marry.md b/.repos/effect/.changeset/pre/twenty-garlics-marry.md new file mode 100644 index 000000000..96087da85 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/two-roses-double.md b/.repos/effect/.changeset/pre/two-roses-double.md new file mode 100644 index 000000000..6efc5f52b --- /dev/null +++ b/.repos/effect/.changeset/pre/two-roses-double.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +improve openai websocket error status diff --git a/.repos/effect/.changeset/pre/unify-error-defect-stack-options.md b/.repos/effect/.changeset/pre/unify-error-defect-stack-options.md new file mode 100644 index 000000000..6c032f6ab --- /dev/null +++ b/.repos/effect/.changeset/pre/unify-error-defect-stack-options.md @@ -0,0 +1,23 @@ +--- +"effect": patch +--- + +Replace the `Schema.Error` and `Schema.Defect` schema constants with constructor +functions, `Schema.Error()` and `Schema.Defect()`. + +Unify `Schema.ErrorWithStack` into `Schema.Error({ includeStack: true })` and +`Schema.DefectWithStack` into `Schema.Defect({ includeStack: true })`. + +Error causes are encoded by default using the same JSON defect encoding +semantics used by `Schema.Defect`; pass `{ excludeCause: true }` to omit nested +cause data. + +Equivalent `Schema.Error` and `Schema.Defect` options are canonicalized, so +repeated constructor calls with the same option values reuse the same schema. + +`Schema.Defect()` now models defects as `unknown` values with a JSON encoded +form. Error-shaped JSON objects with a string `message` decode to JavaScript +`Error` values, so non-`Error` objects such as `{ message: "boom" }` do not +round-trip unchanged. Other non-`Error` values are normalized through JSON +serialization, with non-JSON values falling back to Effect's formatted string +representation. diff --git a/.repos/effect/.changeset/pre/unique-directed-neighbors.md b/.repos/effect/.changeset/pre/unique-directed-neighbors.md new file mode 100644 index 000000000..fd62b1615 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/update-schema-arbitrary-report.md b/.repos/effect/.changeset/pre/update-schema-arbitrary-report.md new file mode 100644 index 000000000..ed3b7942c --- /dev/null +++ b/.repos/effect/.changeset/pre/update-schema-arbitrary-report.md @@ -0,0 +1,22 @@ +--- +"effect": patch +--- + +Update Schema arbitrary derivation to use the new filter metadata, candidate generation, optional derivation reports, recursion-aware generation, and the renamed `OrderedConstraint` model. + +Migration from the previous v4 API: + +- Replace filter annotations from `toArbitraryConstraint: constraint` to `arbitrary: { constraint }`. When a filter cannot be described as a constraint, use `arbitrary: { candidate }` to add a weighted source that is still checked by the filter. +- Replace bucketed constraints with the flat `Schema.Annotations.ToArbitrary.Constraint` shape: + - `string.minLength`, `array.minLength`, object property counts, collection sizes -> `minLength` + - `string.maxLength`, `array.maxLength`, object property counts, collection sizes -> `maxLength` + - `string.patterns` -> `patterns` + - `number.isInteger` -> `integer` + - `number.noNaN` -> `noNaN` + - `number.noDefaultInfinity` -> `noInfinity` + - `date.noInvalidDate` -> `valid` + - `array.comparator` for uniqueness -> `unique` using Effect equality + - `ordered.min` / `minExcluded` / `max` / `maxExcluded` -> `ordered.minimum` / `exclusiveMinimum` / `maximum` / `exclusiveMaximum` +- In arbitrary hooks, read `context.constraint` instead of `context.constraints`. Replace `context.isSuspend` with `context.recursion`; when combining finite and recursive branches, pass `context.recursion` to `fc.oneof` with the finite branch first. +- Generic declaration hooks now receive type parameters as `{ arbitrary, terminal }`. Atomic declarations may still return a bare `FastCheck.Arbitrary`, but generic declarations should return `{ arbitrary, terminal }` when they can preserve a finite terminal branch. +- `Schema.toArbitrary(schema, { report: true })` now returns `{ value, report }`; without `{ report: true }`, it keeps returning the arbitrary directly. `Schema.toArbitraryLazy` always returns a lazy arbitrary. diff --git a/.repos/effect/.changeset/pre/upgrade-socket-error-listener.md b/.repos/effect/.changeset/pre/upgrade-socket-error-listener.md new file mode 100644 index 000000000..f09a83215 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/upset-colts-stick.md b/.repos/effect/.changeset/pre/upset-colts-stick.md new file mode 100644 index 000000000..5e87fdd32 --- /dev/null +++ b/.repos/effect/.changeset/pre/upset-colts-stick.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add missing Equivalence.Date diff --git a/.repos/effect/.changeset/pre/use-url-can-parse.md b/.repos/effect/.changeset/pre/use-url-can-parse.md new file mode 100644 index 000000000..959e2e641 --- /dev/null +++ b/.repos/effect/.changeset/pre/use-url-can-parse.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use `URL.canParse` to validate URL string schema decoding before constructing a `URL`. This avoids relying on thrown exceptions for routine validation while preserving the same invalid URL issue and successful decode output. diff --git a/.repos/effect/.changeset/pre/valid-owls-rest.md b/.repos/effect/.changeset/pre/valid-owls-rest.md new file mode 100644 index 000000000..35f256b23 --- /dev/null +++ b/.repos/effect/.changeset/pre/valid-owls-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Validate object-based DateTime instants before construction. diff --git a/.repos/effect/.changeset/pre/validate-httpapi-handler-registration.md b/.repos/effect/.changeset/pre/validate-httpapi-handler-registration.md new file mode 100644 index 000000000..a2896b0f2 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/validate-openapi-global-conflicts.md b/.repos/effect/.changeset/pre/validate-openapi-global-conflicts.md new file mode 100644 index 000000000..4859757c3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/vast-bananas-send.md b/.repos/effect/.changeset/pre/vast-bananas-send.md new file mode 100644 index 000000000..815d683fb --- /dev/null +++ b/.repos/effect/.changeset/pre/vast-bananas-send.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +clean up ShardId diff --git a/.repos/effect/.changeset/pre/vast-deserts-travel.md b/.repos/effect/.changeset/pre/vast-deserts-travel.md new file mode 100644 index 000000000..262cd5463 --- /dev/null +++ b/.repos/effect/.changeset/pre/vast-deserts-travel.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Add `endOnDone` option to Stdio stdout / stderr diff --git a/.repos/effect/.changeset/pre/violet-peaches-feel.md b/.repos/effect/.changeset/pre/violet-peaches-feel.md new file mode 100644 index 000000000..b4e2ca243 --- /dev/null +++ b/.repos/effect/.changeset/pre/violet-peaches-feel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Effectable module diff --git a/.repos/effect/.changeset/pre/violet-tips-open.md b/.repos/effect/.changeset/pre/violet-tips-open.md new file mode 100644 index 000000000..e26432ffd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/vitest-layer-top-level-options.md b/.repos/effect/.changeset/pre/vitest-layer-top-level-options.md new file mode 100644 index 000000000..2ef279d0f --- /dev/null +++ b/.repos/effect/.changeset/pre/vitest-layer-top-level-options.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": patch +--- + +Allow top-level `it.layer` to accept `memoMap`, `timeout`, and `excludeTestServices` options, matching the standalone `layer` export. Nested `it.layer` calls remain restricted to `timeout` only. diff --git a/.repos/effect/.changeset/pre/wacky-grapes-poke.md b/.repos/effect/.changeset/pre/wacky-grapes-poke.md new file mode 100644 index 000000000..6e9284d8e --- /dev/null +++ b/.repos/effect/.changeset/pre/wacky-grapes-poke.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove superfluous error from SqlSchema.findAll signature diff --git a/.repos/effect/.changeset/pre/wacky-rice-add.md b/.repos/effect/.changeset/pre/wacky-rice-add.md new file mode 100644 index 000000000..9de72c4df --- /dev/null +++ b/.repos/effect/.changeset/pre/wacky-rice-add.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +tighten Schema on \_meta fields in McpSchema; closes #1463 diff --git a/.repos/effect/.changeset/pre/warm-clocks-format.md b/.repos/effect/.changeset/pre/warm-clocks-format.md new file mode 100644 index 000000000..04173b8c3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/warm-dolls-brake.md b/.repos/effect/.changeset/pre/warm-dolls-brake.md new file mode 100644 index 000000000..dc177bf21 --- /dev/null +++ b/.repos/effect/.changeset/pre/warm-dolls-brake.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add meta-var that shows log level and bash options in command line. diff --git a/.repos/effect/.changeset/pre/warm-friends-tie.md b/.repos/effect/.changeset/pre/warm-friends-tie.md new file mode 100644 index 000000000..b600cb226 --- /dev/null +++ b/.repos/effect/.changeset/pre/warm-friends-tie.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix cli subcommand context diff --git a/.repos/effect/.changeset/pre/warm-graphs-degree.md b/.repos/effect/.changeset/pre/warm-graphs-degree.md new file mode 100644 index 000000000..f140e4d84 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/warm-rivers-cache.md b/.repos/effect/.changeset/pre/warm-rivers-cache.md new file mode 100644 index 000000000..23a22720b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/warm-snails-shop.md b/.repos/effect/.changeset/pre/warm-snails-shop.md new file mode 100644 index 000000000..907de8b5a --- /dev/null +++ b/.repos/effect/.changeset/pre/warm-snails-shop.md @@ -0,0 +1,7 @@ +--- +"@effect/ai-openrouter": patch +"@effect/ai-anthropic": patch +"@effect/ai-openai": patch +--- + +Fix the generated schemas for ai providers diff --git a/.repos/effect/.changeset/pre/wet-news-invent.md b/.repos/effect/.changeset/pre/wet-news-invent.md new file mode 100644 index 000000000..f13ab2efb --- /dev/null +++ b/.repos/effect/.changeset/pre/wet-news-invent.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add a `requireServicesAt` option to `PersistedCache.make` so lookup-service requirements can be configured like `Cache`. diff --git a/.repos/effect/.changeset/pre/whole-pets-build.md b/.repos/effect/.changeset/pre/whole-pets-build.md new file mode 100644 index 000000000..91629728a --- /dev/null +++ b/.repos/effect/.changeset/pre/whole-pets-build.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +clean up more references on fiber exit diff --git a/.repos/effect/.changeset/pre/wild-donuts-brake.md b/.repos/effect/.changeset/pre/wild-donuts-brake.md new file mode 100644 index 000000000..4bfeecea7 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/wild-readers-clean.md b/.repos/effect/.changeset/pre/wild-readers-clean.md new file mode 100644 index 000000000..40b517e50 --- /dev/null +++ b/.repos/effect/.changeset/pre/wild-readers-clean.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +mcp http request with no session header is 404 response diff --git a/.repos/effect/.changeset/pre/wild-suns-bearer-space.md b/.repos/effect/.changeset/pre/wild-suns-bearer-space.md new file mode 100644 index 000000000..71a6805b4 --- /dev/null +++ b/.repos/effect/.changeset/pre/wild-suns-bearer-space.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApiSecurity` bearer/http credential decoding diff --git a/.repos/effect/.changeset/pre/windows-hide-child-process-console.md b/.repos/effect/.changeset/pre/windows-hide-child-process-console.md new file mode 100644 index 000000000..77d53516a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/wise-ants-wave.md b/.repos/effect/.changeset/pre/wise-ants-wave.md new file mode 100644 index 000000000..e4966722f --- /dev/null +++ b/.repos/effect/.changeset/pre/wise-ants-wave.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Fix `AtomHttpApi.query` to forward v4 `params` / `query` request fields to `HttpApiClient` at runtime. +Also align `AtomHttpApi` endpoint type inference with v4 `HttpApiEndpoint` params/query naming and add a regression test. diff --git a/.repos/effect/.changeset/pre/wise-bats-encrypt.md b/.repos/effect/.changeset/pre/wise-bats-encrypt.md new file mode 100644 index 000000000..3a251349a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/wise-files-watch.md b/.repos/effect/.changeset/pre/wise-files-watch.md new file mode 100644 index 000000000..41cd84d9a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/wise-flags-shift.md b/.repos/effect/.changeset/pre/wise-flags-shift.md new file mode 100644 index 000000000..c470c58a6 --- /dev/null +++ b/.repos/effect/.changeset/pre/wise-flags-shift.md @@ -0,0 +1,22 @@ +--- +"effect": patch +--- + +Refactor unstable CLI global flags to command-scoped declarations. + +### Breaking changes + +- Remove `GlobalFlag.add`, `GlobalFlag.remove`, and `GlobalFlag.clear` +- Add `Command.withGlobalFlags(...)` as the declaration API for command/subcommand scope +- Change `GlobalFlag.setting` constructor to curried form which carries type-level identifier: + - before: `GlobalFlag.setting({ flag, ... })` + - after: `GlobalFlag.setting("id")({ flag })` +- Change setting context identity to a stable type-level string: + - `effect/unstable/cli/GlobalFlag/${id}` + +### Behavior changes + +- Global flags are now scoped by command path (root-to-leaf declarations) +- Out-of-scope global flags are rejected for the selected subcommand path +- Help now renders only global flags active for the requested command path +- Setting defaults are sourced from `Flag` combinators (`optional`, `withDefault`) rather than setting constructor defaults diff --git a/.repos/effect/.changeset/pre/wise-graphs-connect.md b/.repos/effect/.changeset/pre/wise-graphs-connect.md new file mode 100644 index 000000000..8ac3f4b7a --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/wise-oranges-stay.md b/.repos/effect/.changeset/pre/wise-oranges-stay.md new file mode 100644 index 000000000..263143b8c --- /dev/null +++ b/.repos/effect/.changeset/pre/wise-oranges-stay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +allow Context.Key to be covariant diff --git a/.repos/effect/.changeset/pre/wise-pandas-lock.md b/.repos/effect/.changeset/pre/wise-pandas-lock.md new file mode 100644 index 000000000..8f15bcab3 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/witty-lobsters-share.md b/.repos/effect/.changeset/pre/witty-lobsters-share.md new file mode 100644 index 000000000..c786da686 --- /dev/null +++ b/.repos/effect/.changeset/pre/witty-lobsters-share.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Replace the default HttpApi schema-validation error with `HttpApiError.BadRequestNoContent`. diff --git a/.repos/effect/.changeset/pre/worker-runner-multi-disconnect.md b/.repos/effect/.changeset/pre/worker-runner-multi-disconnect.md new file mode 100644 index 000000000..2d49ff0cd --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/workflow-proxy-discard-execution-id.md b/.repos/effect/.changeset/pre/workflow-proxy-discard-execution-id.md new file mode 100644 index 000000000..24b90519b --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.changeset/pre/yellow-adults-study.md b/.repos/effect/.changeset/pre/yellow-adults-study.md new file mode 100644 index 000000000..015ba9db5 --- /dev/null +++ b/.repos/effect/.changeset/pre/yellow-adults-study.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +openai ws tweaks diff --git a/.repos/effect/.changeset/pre/yellow-clocks-dance.md b/.repos/effect/.changeset/pre/yellow-clocks-dance.md new file mode 100644 index 000000000..707dfbc52 --- /dev/null +++ b/.repos/effect/.changeset/pre/yellow-clocks-dance.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Command.withAlias` for unstable CLI commands, including subcommand parsing by alias and help output that renders aliases as `name, alias` in subcommand listings. diff --git a/.repos/effect/.changeset/pre/yellow-dingos-jump.md b/.repos/effect/.changeset/pre/yellow-dingos-jump.md new file mode 100644 index 000000000..1a6af4ea8 --- /dev/null +++ b/.repos/effect/.changeset/pre/yellow-dingos-jump.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApi.prefix` so it updates endpoint path types the same way `HttpApiGroup.prefix` does. diff --git a/.repos/effect/.changeset/pre/young-doors-change.md b/.repos/effect/.changeset/pre/young-doors-change.md new file mode 100644 index 000000000..c5ec50dbe --- /dev/null +++ b/.repos/effect/.changeset/pre/young-doors-change.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Random.shuffle` to shuffle iterables with seeded randomness support. diff --git a/.repos/effect/.changeset/pre/zero-node-socket-timeout.md b/.repos/effect/.changeset/pre/zero-node-socket-timeout.md new file mode 100644 index 000000000..812377865 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.envrc b/.repos/effect/.envrc new file mode 100644 index 000000000..3550a30f2 --- /dev/null +++ b/.repos/effect/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.repos/effect/.github/actions/deploy-website/action.yml b/.repos/effect/.github/actions/deploy-website/action.yml new file mode 100644 index 000000000..436ec4944 --- /dev/null +++ b/.repos/effect/.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/.repos/effect/.github/actions/setup/action.yaml b/.repos/effect/.github/actions/setup/action.yaml new file mode 100644 index 000000000..bed189322 --- /dev/null +++ b/.repos/effect/.github/actions/setup/action.yaml @@ -0,0 +1,33 @@ +name: Setup +description: Perform standard setup and install dependencies using pnpm. +inputs: + deno-version: + description: The version of Deno to install + required: false + bun-version: + description: The version of Bun to install + required: false + +runs: + using: composite + steps: + - name: Install pnpm + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6 + - name: Install node + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + cache: pnpm + node-version: 26.4.0 + - name: Install deno + uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2 + if: ${{ inputs.deno-version != '' }} + with: + deno-version: ${{ inputs.deno-version }} + - name: Install bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + if: ${{ inputs.bun-version != '' }} + with: + bun-version: ${{ inputs.bun-version }} + - name: Install dependencies + shell: bash + run: pnpm install diff --git a/.repos/effect/.github/workflows/ai-codegen.yml b/.repos/effect/.github/workflows/ai-codegen.yml new file mode 100644 index 000000000..d46bec4e3 --- /dev/null +++ b/.repos/effect/.github/workflows/ai-codegen.yml @@ -0,0 +1,67 @@ +name: Nightly AI Codegen + +on: + schedule: + - cron: "0 0 * * *" # Midnight UTC daily + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + codegen: + name: AI Codegen + if: github.repository_owner == 'Effect-Ts' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Install dependencies + uses: ./.github/actions/setup + + - name: Run AI Codegen + run: node packages/tools/ai-codegen/src/bin.ts generate + + - name: Check for changes + id: changes + run: | + # Only consider changes in packages/ai/ + providers=$(git diff --name-only | grep "^packages/ai/" | cut -d'/' -f3 | sort -u | sed 's/^/- /' || true) + if [ -z "$providers" ]; then + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + { + echo "updated_providers<> $GITHUB_OUTPUT + fi + + - name: Create Pull Request + if: steps.changes.outputs.has_changes == 'true' + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: chore/ai-codegen-update + delete-branch: true + title: "chore: update AI codegen generated files" + body: | + Automated update of AI provider generated code. + + ## Updated Providers + + ${{ steps.changes.outputs.updated_providers }} + + --- + + *This PR was automatically generated by the nightly codegen workflow.* + commit-message: "chore: regenerate AI provider code" + labels: automated diff --git a/.repos/effect/.github/workflows/bundle-comment.yml b/.repos/effect/.github/workflows/bundle-comment.yml new file mode 100644 index 000000000..49405cfff --- /dev/null +++ b/.repos/effect/.github/workflows/bundle-comment.yml @@ -0,0 +1,105 @@ +name: Bundle Size Comment +on: + workflow_run: + workflows: ["Check"] + types: + - completed + +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + +permissions: {} + +jobs: + comment: + name: Bundle + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + permissions: + actions: read + pull-requests: write + timeout-minutes: 1 + steps: + - name: Download Artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: bundle-stats + path: bundle-stats + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - 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<<${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 + env: + GH_TOKEN: ${{ github.token }} + PR_TARGET_REPO: ${{ github.repository }} + PR_BRANCH: |- + ${{ + (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) + && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) + || github.event.workflow_run.head_branch + }} + 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@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@e8674b075228eee787fea43ef493e45ece1004c9 # v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUNDLE_STATS: "${{ steps.stats.outputs.stats }}" + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ steps.pr-context.outputs.number }} + edit-mode: replace + body: | + + ## Bundle Size Analysis + Generated from PR build output; treat the content below as untrusted. + + ${{ env.BUNDLE_STATS }} diff --git a/.repos/effect/.github/workflows/check.yml b/.repos/effect/.github/workflows/check.yml new file mode 100644 index 000000000..bbb1c979d --- /dev/null +++ b/.repos/effect/.github/workflows/check.yml @@ -0,0 +1,211 @@ +name: Check +on: + workflow_dispatch: + pull_request: + branches: [main, v4/next-minor, v4/next-major] + push: + branches: [main, v4/next-minor, v4/next-major] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + - run: pnpm lint + + types: + name: Types + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + - run: pnpm check + - run: pnpm test-types --target '>=5.9' + + build: + name: Build + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + - name: Set strip internals config + run: | + 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@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + with: + deno-version: v2.9.4 + - 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 + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + - name: Clone base ref + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + path: base + ref: ${{ github.event.pull_request.base.ref }} + - name: Set strip internals config + run: | + sed -i 's/"stripInternal": false/"stripInternal": true/' tsconfig.base.json + sed -i 's/"stripInternal": false/"stripInternal": true/' base/tsconfig.base.json + - name: Build + run: | + pnpm build & + cd base && pnpm install && pnpm build & + wait + - 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: bundle-stats + path: stats.txt + if-no-files-found: error + + test: + name: Test + runs-on: ubuntu-latest + env: + EFFECT_INTEGRATION_TESTS: "1" + permissions: + contents: read + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + shard: [1/2, 2/2] + runtime: [Node, Deno] + steps: + - 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.9.4 + - name: Test + if: matrix.runtime == 'Deno' + run: deno task test --shard ${{ matrix.shard }} + + test-bun: + name: Test on Bun + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + - name: Install dependencies + uses: ./.github/actions/setup + with: + bun-version: 1.3.13 + - name: Test + run: bun node_modules/vitest/vitest.mjs run --project @effect/platform-bun + + doctest: + name: Documentation Tests + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + - name: Test Documentation + run: pnpm doctest + + ai-docgen: + name: AI Documentation Generation + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # 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 + + circular: + name: Circular Dependencies + runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + - name: Check for circular dependencies + run: pnpm circular diff --git a/.repos/effect/.github/workflows/cluster.yml b/.repos/effect/.github/workflows/cluster.yml new file mode 100644 index 000000000..606ee8018 --- /dev/null +++ b/.repos/effect/.github/workflows/cluster.yml @@ -0,0 +1,29 @@ +name: Cluster Integration +on: + workflow_dispatch: + +permissions: {} + +jobs: + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + EFFECT_CLUSTER_TESTS: "1" + permissions: + contents: read + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Pre-pull test container images + run: | + docker pull testcontainers/ryuk:0.14.0 & + docker pull postgres:alpine & + docker pull mysql:lts & + wait + + - name: Install dependencies + uses: ./.github/actions/setup + - name: Test + run: pnpm test-cluster diff --git a/.repos/effect/.github/workflows/release-queue.yml b/.repos/effect/.github/workflows/release-queue.yml new file mode 100644 index 000000000..cbf7d2472 --- /dev/null +++ b/.repos/effect/.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@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/.repos/effect/.github/workflows/release.yml b/.repos/effect/.github/workflows/release.yml new file mode 100644 index 000000000..43931cad5 --- /dev/null +++ b/.repos/effect/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Release +on: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false # Prevent newer push from interrupting active release + +permissions: {} + +jobs: + release: + if: github.repository_owner == 'Effect-Ts' + name: Release + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: write + pull-requests: write + id-token: write + steps: + - 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. + # Otherwise the default token will be used, and actions on that versioned + # release branch will not be triggered. + persist-credentials: false + - name: Install dependencies + uses: ./.github/actions/setup + - name: Upgrade npm for OIDC support + run: npm install -g npm@11 + - name: Create Release Pull Request or Publish + id: changesets + uses: changesets/action@d0ee272882939fa35f22d979828acb7e61e0bd47 # v2.0.0-next.4 + with: + 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 }} + - 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/.repos/effect/.github/workflows/snapshot.yml b/.repos/effect/.github/workflows/snapshot.yml new file mode 100644 index 000000000..30fca3d8a --- /dev/null +++ b/.repos/effect/.github/workflows/snapshot.yml @@ -0,0 +1,45 @@ +name: Snapshot +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + approval-gate: + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + runs-on: ubuntu-latest + environment: fork + steps: + - run: echo "Fork PR approved by maintainer." + + snapshot: + name: Snapshot + needs: [approval-gate] + if: >- + !cancelled() + && (needs.approval-gate.result == 'success' || needs.approval-gate.result == 'skipped') + && github.repository_owner == 'Effect-Ts' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Install dependencies + uses: ./.github/actions/setup + - name: Set strip internals config + run: | + sed -i 's/"stripInternal": false/"stripInternal": true/' tsconfig.base.json + - name: Run codemods + run: pnpm codemod + - name: Build package + run: pnpm build + - name: Create snapshot + id: snapshot + run: pnpm exec pkg-pr-new publish --pnpm --comment=off ./packages/* ./packages/atom/* ./packages/ai/* ./packages/platform/* ./packages/sql/* ./packages/tools/* diff --git a/.repos/effect/.gitignore b/.repos/effect/.gitignore new file mode 100644 index 000000000..b9c2b253e --- /dev/null +++ b/.repos/effect/.gitignore @@ -0,0 +1,48 @@ +# Generated by Direnv +.direnv/ +.env + +# Generated by TypeScript +dist/ +build/ +**/*.tsbuildinfo + +# Generated by Pnpm +node_modules/ +.pnpm-store/ + +# Auto-generated from scripts +coverage/ +tmp/ + +# Generated by MacOS +.DS_Store + +# scratchpad files +scratchpad/**/* + +# Agent instructions +/AGENTS.md +/packages/**/AGENTS.md +/packages/**/CLAUDE.md +/packages/**/ai-docs/ + +# lalph +.lalph/ +.repos/ + +# ralph auto loop (runtime output) +.ralph-auto/ + +# Claude Code +.claude/ + +# OpenCode local tooling +.opencode/ +opencode.json + +# Repositories +.repos/ + +# oxlint data +.data/ diff --git a/.repos/effect/.oxlintrc.json b/.repos/effect/.oxlintrc.json new file mode 100644 index 000000000..b27dad545 --- /dev/null +++ b/.repos/effect/.oxlintrc.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "extends": ["./packages/tools/oxc/oxlintrc.json"], + "ignorePatterns": [ + "LLMS.md", + "**/dist", + "**/build", + "**/docs", + "**/.tsbuildinfo", + "packages/effect/typeperf/**/*", + "packages/effect/src/StandardSchema.ts", + "packages/**/CHANGELOG.md", + "!scratchpad/**/*", + ".changeset/**/*", + ".agents/**/*", + ".context/**/*", + ".specs/**/*" + ], + "jsPlugins": ["@effect/oxc/oxlint"], + "overrides": [{ + "files": ["**/{test,typetest,examples,ai-docs,benchmark,bundle,scripts,scratchpad}/**"], + "rules": { + "effect/no-bigint-literals": "off", + "eslint/no-console": "off", + "effect/no-import-from-barrel-package": "off" + } + }] +} diff --git a/.repos/effect/.patterns/dynamic-records.md b/.repos/effect/.patterns/dynamic-records.md new file mode 100644 index 000000000..de4b25bb1 --- /dev/null +++ b/.repos/effect/.patterns/dynamic-records.md @@ -0,0 +1,41 @@ +# Dynamic Record Safety + +An **open key** comes from external data or is determined at runtime. A key is +**closed** only when selected from an explicit internal list; TypeScript types +do not close external input at runtime. + +## Rules + +1. **Owned internal dictionary:** use + `Object.create(null) as Record` (or `Map` without record/JSON + interop). Direct open-key reads and writes are safe. Use `Object.hasOwn` when + stored `undefined` must differ from absence. +2. **External record:** use `Object.hasOwn(record, key)` for presence. Never use + `in`, truthiness, or `record[key] !== undefined`. Enumerate with + `Object.keys` / `values` / `entries`, never `for...in`. +3. **Normal object or instance:** write open keys with + `Record.assignProperty(target, key, value)`. It protects only writes; + presence checks still require `Object.hasOwn`. +4. **Existing public API:** preserve its output prototype. Build with a + null-prototype dictionary, then return `{ ...internalMap }` when a normal + object is required. + +## `Object.assign` + +| Case | Policy | +| ------------------------------------ | ----------------------------------------------------- | +| Null-rooted target + open source | `Object.assign` allowed | +| New normal target + open source | Use `{ ...source }` | +| New custom-prototype object | Use `Object.setPrototypeOf({ ...source }, Proto)` | +| Existing normal target + open source | Copy own enumerable keys with `Record.assignProperty` | +| Normal target + closed source | `Object.assign` allowed | + +Therefore `Object.assign({}, openSource)` is forbidden. A source is closed only +when constructed internally with explicit properties, for example +`{ name: options.name }`; passing `options` directly is not closed. +Use `Reflect.ownKeys` plus an enumerable check when symbols must be copied. + +## Property Syntax + +- Safe: `{ [key]: value }` and `{ ...source }`; both create own data properties. +- Unsafe for data: `{ __proto__: value }`; it changes the literal's prototype. diff --git a/.repos/effect/.patterns/effect.md b/.repos/effect/.patterns/effect.md new file mode 100644 index 000000000..a778ae85c --- /dev/null +++ b/.repos/effect/.patterns/effect.md @@ -0,0 +1,102 @@ +# 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/.repos/effect/.patterns/jsdoc.md b/.repos/effect/.patterns/jsdoc.md new file mode 100644 index 000000000..a3ac911f3 --- /dev/null +++ b/.repos/effect/.patterns/jsdoc.md @@ -0,0 +1,136 @@ +# 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. + +## Example Best Practices + +### Quality Checklist + +Use this checklist when authoring or reviewing an example: + +- **Classify execution:** Make the example clearly one of a runnable observation, typechecked definition, test registration, + runtime entrypoint, or external-infrastructure illustration. Do not combine alternative runtimes or deployment paths in + one executable module; present them as separately labeled, non-evaluated alternatives. +- **Order setup, operation, observation:** Make the documented API and its result scannable. Inline simple setup into the + assertion; otherwise arrange setup first, the operation second, and a separate observation block after one blank line. +- **Teach one primary semantic contract:** Include only the adjacent concepts needed to observe that behavior. Remove unused + errors, services, imports, alternate programs, and fictional generic-type scaffolding. Integration examples may include + more concepts only when the integration is the lesson. +- **Observe the promised semantic boundary:** Assert the full semantic value when practical. If unstable or irrelevant data + requires a projection, choose stable fields that distinguish the promised behavior from neighboring outcomes. For + example, prefer `Exit.fail("missing")` over observing only `_tag === "Failure"`. +- **Prefer direct observation:** Use the abstraction's return value, collector, or fold before introducing a mutable probe. + Console output or successful execution alone does not establish semantic behavior. + + Good: + + ```ts + await Effect.runPromise(Stream.runCollect(Stream.make(1, 2, 3))) // => [1, 2, 3] + ``` + + Counterexample: + + ```ts + const values: Array = [] + + await Effect.runPromise( + Stream.make(1, 2, 3).pipe(Stream.runForEach((value) => Effect.sync(() => values.push(value)))) + ) + values // => [1, 2, 3] + ``` + +- **Use local probes only when the API has no direct result:** A local mutable probe is appropriate for + `acquireRelease`/finalizers and callback-oriented APIs when lifecycle order or emitted events are the contract. Keep the + probe local and sequential. + + ```ts + const events: Array = [] + const resource = Effect.acquireRelease( + Effect.sync(() => events.push("acquire")), + () => Effect.sync(() => events.push("release")) + ) + + await Effect.runPromise(Effect.scoped(resource)) + events // => ["acquire", "release"] + ``` + +- **Use Effect-managed observers for concurrency:** Prefer `Ref`, `Deferred`, or `Queue` over mutable arrays or flags when + fibers, concurrent consumers, interruption, or races are part of the behavior. +- **Keep execution bounded and deterministic:** Bound retries, repeats, polling, generated streams, tool loops, and + concurrent consumers unless non-termination is the documented entrypoint behavior. Avoid live clocks, randomness, + scheduling accidents, external services, machine-specific state, and unawaited work; use controlled inputs and ensure + cleanup completes. +- **Choose runners deliberately:** Prefer awaited `Effect.runPromise` in runnable examples. Use `Effect.runSync` only when + synchronous execution is the documented contract or materially clarifies an Effect known to be synchronous; do not use + it merely as a shorter doctest runner. +- **Progress multiple examples by behavior:** Move from basic success to a defining boundary or failure, then composition or + lifecycle behavior. Do not repeat equivalent happy paths with renamed values or alternate syntax unless the calling style + or overload dispatch is itself part of the contract. +- **Keep type-only examples type-only:** Retain runnable metadata for extraction and typechecking, but do not add tautological + runtime assertions such as assigning a typed literal and asserting that the literal is unchanged. Add an assertion only + when the API also performs runtime transformation or validation. + +### Doctest Mechanics + +- Mark runnable TypeScript examples with `````ts import.meta.vitest`` so `pnpm doctest` executes them. +- Use a trailing `// =>` comment to assert an expression or single initialized `const` identifier against a TypeScript expression on the same line. Values use Effect's `Equal.equals` semantics, and examples without markers remain execution-only. Write asynchronous execution explicitly; the transform does not run Effects or await promises automatically. +- Prefer asserting the API call directly. Keep bindings only for reuse, mutation, identity checks, or meaningful multi-step setup; put a blank line before a separate assertion block. +- Keep calls on one line when the complete line is at most 120 characters. Format expected arrays densely (`[1, 2]`, `[[1], [2]]`, `Option.some([1, 2])`) while retaining normal object spacing. +- Assert semantic constructors such as `Option.some`, `Result.succeed`, and `Exit.fail`, not rendered console output. Preserve runnable markers on type-level examples without adding fake runtime assertions. +- Keep runnable examples complete, deterministic, bounded, and independent of external services or machine-specific state. Await asynchronous work so failures and cleanup remain inside the doctest. +- Import public APIs and include all required setup. Do not use undeclared placeholders or rely on declarations from surrounding prose. +- Leave examples that register Vitest tests or suites as plain `````ts`` fences; the doctest collector executes runnable + snippets inside tests, where nested test registration is invalid. Invoke registration APIs directly so the snippet still + shows the intended top-level usage. +- Leave intentionally non-executable snippets as plain `````ts`` fences. +- Run `pnpm doctest --run ` from the repository root after changing runnable examples. diff --git a/.repos/effect/.patterns/testing.md b/.repos/effect/.patterns/testing.md new file mode 100644 index 000000000..119f4f17e --- /dev/null +++ b/.repos/effect/.patterns/testing.md @@ -0,0 +1,76 @@ +# Testing Patterns + +## Testing Framework Selection + +Use `it.effect` for tests that return Effects. + +`it.effect` and `it.live` each provide and close a `Scope` for every test. Return scoped effects directly; do not wrap +the test body in `Effect.scoped`. + +```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 unit tests. Runnable documentation may use it only for the intentional synchronous-runner + cases described in `.patterns/jsdoc.md`. +- 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 +``` + +### Testing Displayed Types + +Ordinary Tstyche assertions such as `toBe` compare types structurally. They cannot +catch regressions where a public type is semantically correct but TypeScript +displays an internal alias or an unsimplified intersection in editor quick info. + +To test the displayed form, deliberately produce an assignment error and use +Tstyche's checked `@ts-expect-error` message to match a distinctive substring of +the rendered type: + +```typescript +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 +}) +``` + +Before accepting the test, temporarily restore the broken type and confirm that +the diagnostic-message match fails. Keep the expected substring as small as +possible while still distinguishing the desired public type from the leaked +implementation type, because diagnostic wording can change between TypeScript +versions. Run the targeted test against every TypeScript version configured by +`pnpm test-types`. diff --git a/.repos/effect/.specs/README.md b/.repos/effect/.specs/README.md new file mode 100644 index 000000000..c6abd9919 --- /dev/null +++ b/.repos/effect/.specs/README.md @@ -0,0 +1,2 @@ +# Specifications + diff --git a/.repos/effect/.vscode/extensions.json b/.repos/effect/.vscode/extensions.json new file mode 100644 index 000000000..1a118e9e7 --- /dev/null +++ b/.repos/effect/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "oxc.oxc-vscode", + "dprint.dprint" + ] +} diff --git a/.repos/effect/.vscode/settings.json b/.repos/effect/.vscode/settings.json new file mode 100644 index 000000000..721746cf3 --- /dev/null +++ b/.repos/effect/.vscode/settings.json @@ -0,0 +1,51 @@ +{ + "dprint.path": "node_modules/.bin/dprint", + "editor.formatOnSave": true, + "editor.defaultFormatter": "dprint.dprint", + "editor.formatOnSaveMode": "file", + "editor.codeActionsOnSave": { + "source.fixAll.oxc": "explicit" + }, + "editor.quickSuggestions": { + "other": true, + "comments": false, + "strings": false + }, + "editor.acceptSuggestionOnCommitCharacter": true, + "editor.acceptSuggestionOnEnter": "on", + "editor.quickSuggestionsDelay": 10, + "editor.suggestOnTriggerCharacters": true, + "editor.tabCompletion": "off", + "editor.suggest.localityBonus": true, + "editor.suggestSelection": "recentlyUsed", + "editor.wordBasedSuggestions": "matchingDocuments", + "editor.parameterHints.enabled": true, + "files.insertFinalNewline": true, + "[typescript]": { + "editor.defaultFormatter": "dprint.dprint" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "dprint.dprint" + }, + "[javascript]": { + "editor.defaultFormatter": "dprint.dprint" + }, + "[json]": { + "editor.defaultFormatter": "dprint.dprint" + }, + "[jsonc]": { + "editor.defaultFormatter": "dprint.dprint" + }, + "[markdown]": { + "editor.defaultFormatter": "dprint.dprint" + }, + "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/.repos/effect/LICENSE b/.repos/effect/LICENSE new file mode 100644 index 000000000..be1f5c14c --- /dev/null +++ b/.repos/effect/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Effectful Technologies Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.repos/effect/LLMS.md b/.repos/effect/LLMS.md new file mode 100644 index 000000000..b3021967e --- /dev/null +++ b/.repos/effect/LLMS.md @@ -0,0 +1,395 @@ +# Effect library documentation + +This documentation covers the Effect library and its related packages. + +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. + +### Using Effect.gen + +Use `Effect.gen` to write code in an imperative style similar to async await. +You can use `yield*` to access the result of an effect. + +```ts +import { Effect, Schema } from "effect" + +Effect.gen(function*() { + yield* Effect.log("Starting the file processing...") + yield* Effect.log("Reading file...") + + // Always return when raising an error, to ensure typescript understands that + // the function will not continue executing. + return yield* new FileProcessingError({ message: "Failed to read the file" }) +}).pipe( + // Add additional functionality with .pipe + Effect.catch((error) => Effect.logError(`An error occurred: ${error}`)), + Effect.withSpan("fileProcessing", { + attributes: { + method: "Effect.gen" + } + }) +) + +// Use Schema.TaggedError to define a custom error +export class FileProcessingError extends Schema.TaggedError()("FileProcessingError", { + message: Schema.String +}) {} +``` + +### Using Effect.fn + +When writing functions that return an Effect, use `Effect.fn` to use the +generator syntax. + +**Avoid creating functions that return an Effect.gen**, use `Effect.fn` +instead. + +```ts +import { Effect, Schema } from "effect" + +// Pass a string to Effect.fn, which will improve stack traces and also +// attach a tracing span (using Effect.withSpan behind the scenes). +// +// The name string should match the function name. +// +export const effectFunction = Effect.fn("effectFunction")( + // You can use `Effect.fn.Return` to specify the return type of the function. + // It accepts the same type parameters as `Effect.Effect`. + function*(n: number): Effect.fn.Return { + yield* Effect.logInfo("Received number:", n) + + // Always return when raising an error, to ensure typescript understands that + // the function will not continue executing. + return yield* new SomeError({ message: "Failed to read the file" }) + }, + // Add additional functionality by passing in additional arguments. + // **Do not** use .pipe with Effect.fn + Effect.catch((error) => Effect.logError(`An error occurred: ${error}`)), + Effect.annotateLogs({ + method: "effectFunction" + }) +) + +// Use Schema.TaggedError to define a custom error +export class SomeError extends Schema.TaggedError()("SomeError", { + message: Schema.String +}) {} +``` + +### More examples + +- **[Creating effects from common sources](./ai-docs/src/01_effect/01_basics/10_creating-effects.ts)**: + 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 +services to encapsulate behaviour over other approaches, as it ensures that your +code is modular, testable, and maintainable. + +### Context.Service + +The default way to define a service is to extend `Context.Service`, +passing in the service interface as a type parameter. + +```ts +// file: src/db/Database.ts +import { Context, Effect, Layer, Schema } from "effect" + +// Pass in the service class name as the first type parameter, and the service +// interface as the second type parameter. +export class Database extends Context.Service, DatabaseError> +}>()( + // The string identifier for the service, which should include the package + // name and the subdirectory path to the service file. + "myapp/db/Database" +) { + // Attach a static layer to the service, which will be used to provide an + // implementation of the service. + static readonly layer = Layer.effect( + Database, + Effect.gen(function*() { + // Define the service methods using Effect.fn + const query = Effect.fn("Database.query")(function*(sql: string) { + yield* Effect.log("Executing SQL query:", sql) + return [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }] + }) + + // Return an instance of the service using Database.of, passing in an + // object that implements the service interface. + return Database.of({ + query + }) + }) + ) +} + +export class DatabaseError extends Schema.TaggedError()("DatabaseError", { + cause: Schema.Defect() +}) {} + +// If you ever need to access the service type, use `Database["Service"]` +export type DatabaseService = Database["Service"] +``` + +### More examples + +- **[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/03_services/20_layer-unwrap.ts)**: Build a layer dynamically from an Effect / Config with `Layer.unwrap`. + +## Error handling + +### Error handling basics + +Defining custom errors and handling them with Effect.catch and Effect.catchTag. + +```ts +import { Effect, Schema } from "effect" + +// Define custom errors using Schema.TaggedError +export class ParseError extends Schema.TaggedError()("ParseError", { + input: Schema.String, + message: Schema.String +}) {} + +export class ReservedPortError extends Schema.TaggedError()("ReservedPortError", { + port: Schema.Int +}) {} + +declare const loadPort: (input: string) => Effect.Effect + +export const recovered = loadPort("80").pipe( + // Catch multiple errors with Effect.catchTag, and return a default port number. + Effect.catchTag(["ParseError", "ReservedPortError"], (_) => Effect.succeed(3000)) +) + +export const withFinalFallback = loadPort("invalid").pipe( + // Catch a specific error with Effect.catchTag + Effect.catchTag("ReservedPortError", (_) => Effect.succeed(3000)), + // Catch all errors with Effect.catch + Effect.catch((_) => Effect.succeed(3000)) +) +``` + +### More examples + +- **[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`. + +## Managing resources and `Scope`s + +Learn how to safely manage resources in Effect using `Scope`s and finalizers. + +- **[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/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/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/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/03_stream/10_creating-streams.ts)**: + Learn how to create streams from various data sources. Includes: + + - `Stream.fromIterable` for arrays and other iterables + - `Stream.fromEffectSchedule` for polling effects + - `Stream.paginate` for paginated APIs + - `Stream.fromAsyncIterable` for async iterables + - `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/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` & `Msgpack` modules to + decode and encode streams of structured data. + +## Integrating Effect into existing applications + +`ManagedRuntime` bridges Effect programs with non-Effect code. Build one runtime +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/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 + +Learn how to batch multiple requests into fewer external calls. + +- **[Batching requests with RequestResolver](./ai-docs/src/05_batching/10_request-resolver.ts)**: Define request types with `Request.Class`, resolve them in batches with `RequestResolver`. + +## Working with Schedules + +Schedules define recurring patterns for retries, repeats and polling. + +- **[Working with the Schedule module](./ai-docs/src/06_schedule/10_schedules.ts)**: Build schedules, compose them, and use them with `Effect.retry` and `Effect.repeat`. + +## Working with DateTime + +When working with dates and time, use the `DateTime` module instead of `Date` and `Date.now`. + +Use it when your Effect programs need testable current time, safe parsing, stable ISO formatting, time-zone conversion, or calendar arithmetic. + +- **[Creating and formatting DateTime values](./ai-docs/src/07_datetime/10_creating-and-formatting.ts)**: + Parse incoming date values safely, use Clock-powered current time, and format + instants for API payloads or user-facing labels. +- **[Working with time zones](./ai-docs/src/07_datetime/20_time-zones.ts)**: + Attach IANA zones to instants, render zoned ISO strings, and provide a + CurrentTimeZone service for code that should use the workspace/user zone. + +## Observability + +Effect has built-in support for structured logging, distributed tracing, and +metrics. For exporting telemetry, use the lightweight Otlp modules from +`effect/unstable/observability` in new projects, or use +`@effect/opentelemetry` NodeSdk when integrating with an existing OpenTelemetry +setup. + +- **[Customizing logging](./ai-docs/src/08_observability/10_logging.ts)**: Configure loggers & log-level filtering for production applications. +- **[Setting up tracing with Otlp modules](./ai-docs/src/08_observability/20_otlp-tracing.ts)**: Configure Otlp tracing + log export with a reusable observability layer. + +## Testing Effect programs + +- **[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. + +- **[Getting started with HttpClient](./ai-docs/src/50_http-client/10_basics.ts)**: Define a service that uses the HttpClient module to fetch data from an external API + +## Building HttpApi servers + +`HttpApi` gives you schema-first, type-safe HTTP APIs with runtime validation, typed clients, and OpenAPI docs from one definition. + +- **[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`. + +- **[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. + +## Building CLI applications + +Use the "effect/unstable/cli" modules to build CLI applications. These modules +provide utilities for parsing command-line arguments, handling user input, and +managing the flow of a CLI application. + +- **[Getting started with Effect CLI modules](./ai-docs/src/70_cli/10_basics.ts)**: + Build a command-line app with typed arguments and flags, then wire subcommand + handlers into a single executable command. + +## Working with AI modules + +Effect's AI modules provide a provider-agnostic interface for language models. +You can generate text, decode structured objects with `Schema` and stream partial +responses. + +- **[Using LanguageModel for text, objects, and streams](./ai-docs/src/71_ai/10_language-model.ts)**: + Configure a provider once, then use `LanguageModel` for plain text + generation, schema-validated object generation, and streaming responses. +- **[Defining and using AI tools](./ai-docs/src/71_ai/20_tools.ts)**: + Define tools with schemas, group them into toolkits, implement handlers, + and pass them to `LanguageModel.generateText`. +- **[Stateful chat sessions](./ai-docs/src/71_ai/30_chat.ts)**: + The AI `Chat` module maintains conversation history automatically. Build + AI agents or chat assistants. + +## Building distributed applications with cluster + +The cluster modules let you model stateful services as entities and distribute +them across multiple machines. + +- **[Defining cluster entities](./ai-docs/src/80_cluster/10_entities.ts)**: Define distributed entity RPCs and run them in a cluster. \ No newline at end of file diff --git a/.repos/effect/MIGRATION.md b/.repos/effect/MIGRATION.md new file mode 100644 index 000000000..902888a68 --- /dev/null +++ b/.repos/effect/MIGRATION.md @@ -0,0 +1,83 @@ +# Migrating from Effect v3 to Effect v4 + +> **Note:** Effect v4 is currently in beta. APIs may change between beta +> releases. This guide will evolve as the beta progresses and community +> feedback is incorporated. + +## Background + +Effect v4 is a major release with structural and organizational changes across +the ecosystem. The core programming model — `Effect`, `Layer`, `Schema`, +`Stream`, etc. — remains the same, but how packages are organized, versioned, +and imported has changed significantly. + +### Versioning + +All Effect ecosystem packages now share a **single version number** and are +released together. In v3, packages were versioned independently (e.g. +`effect@3.x`, `@effect/platform@0.x`, `@effect/sql@0.x`), making compatibility +between packages difficult to track. In v4, if you use `effect@4.0.0-beta.0`, +the matching SQL package is `@effect/sql-pg@4.0.0-beta.0`. + +### Package Consolidation + +Many previously separate packages have been merged into the core `effect` +package. Functionality from `@effect/platform`, `@effect/rpc`, +`@effect/cluster`, and others now lives directly in `effect`. + +Packages that remain separate are platform-specific, provider-specific, or +technology-specific: + +- `@effect/platform-*` — platform packages +- `@effect/sql-*` — SQL driver packages +- `@effect/ai-*` — AI provider packages +- `@effect/opentelemetry` — OpenTelemetry integration +- `@effect/atom-*` — framework-specific atom bindings +- `@effect/vitest` — Vitest testing utilities + +These packages must be bumped to matching v4 beta versions alongside `effect`. + +### Unstable Module System + +v4 introduces **unstable modules** under `effect/unstable/*` import paths. +These modules may receive breaking changes in minor releases, while modules +outside `unstable/` follow strict semver. + +Unstable modules include: `ai`, `cli`, `cluster`, `devtools`, `eventlog`, +`http`, `httpapi`, `jsonschema`, `observability`, `persistence`, `process`, +`reactivity`, `rpc`, `schema`, `socket`, `sql`, `workflow`, `workers`. + +As these modules stabilize, they graduate to the top-level `effect/*` namespace. + +### Performance and Bundle Size + +The fiber runtime has been rewritten for reduced memory overhead and faster +execution. The core `effect` package supports aggressive tree-shaking — a +minimal Effect program bundles to ~6.3 KB (minified + gzipped). With Schema, +~15 KB. + +--- + +## Migration Guides + +### Import and API Rename Maps + +- [v3 to v4 Import and API Rename Maps](./migration/v3-to-v4.md) + +### Core + +- [Services: `Context.Tag` → `Context.Service`](./migration/services.md) +- [Cause: Flattened Structure](./migration/cause.md) +- [Error Handling: `catch*` Renamings](./migration/error-handling.md) +- [Forking: Renamed Combinators and New Options](./migration/forking.md) +- [Effect Subtyping → Yieldable](./migration/yieldable.md) +- [Fiber Keep-Alive: Automatic Process Lifetime Management](./migration/fiber-keep-alive.md) +- [Layer Memoization Across `Effect.provide` Calls](./migration/layer-memoization.md) +- [FiberRef: `FiberRef` → `Context.Reference`](./migration/fiberref.md) +- [Runtime: `Runtime` Removed](./migration/runtime.md) +- [Scope](./migration/scope.md) +- [Equality](./migration/equality.md) + +### Modules + +- [Schema v4 Migration Guide](./migration/schema.md) diff --git a/.repos/effect/README.md b/.repos/effect/README.md new file mode 100644 index 000000000..93973efa2 --- /dev/null +++ b/.repos/effect/README.md @@ -0,0 +1,73 @@ + + +[![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/.repos/effect/ai-docs/README.md b/.repos/effect/ai-docs/README.md new file mode 100644 index 000000000..dd4488c62 --- /dev/null +++ b/.repos/effect/ai-docs/README.md @@ -0,0 +1,44 @@ +# AI docs + +`LLMS.md` is generated from `ai-docs/src`. + +## Add content + +1. Add or update markdown in `ai-docs/src/**/index.md` for section intro text. +2. Add examples as `.ts` files in the same folder. +3. Run `pnpm ai-docgen` to regenerate `LLMS.md`. + +## Source file conventions + +- Use numeric filename prefixes to control ordering (`10_`, `20_`, etc). Avoid starting with `0` unless explicity requested to do so. +- Use a top JSDoc block with `@title` and optional description to control rendered title/description. +- `fixtures` directories are ignored and not included in the generated + documentation. Use them for any supporting code or data needed for examples. + +## Example guidelines + +Before writing an example, look at the existing examples in `ai-docs/src` to +learn the style and conventions used in this project. Also read the current +`LLMS.md` to understand the content and style of the documentation. + +**All code examples should be well commented** explaining the how and why of the +code, not just what the code is doing. The goal is to teach users how to use the +API. + +**Code must represent real world usage and best practices.** +Do not include toy examples that are not representative of how the API should be +used in practice. + +- **Prefer using the service style** of structuring code, as this represents real + world usage and best practices. +- Use the `fixtures` directory to illustrate / suggest best practices for + project structure, file organization, and code organization. + +Pull requests with only ai documentation changes **DO NOT** need a changeset. + +## Regeneration + +- One-shot: `pnpm ai-docgen` +- Watch mode: `pnpm ai-docgen:watch` + +`pnpm ai-docgen` regenerates `LLMS.md` files from content in `ai-docs/src`. diff --git a/.repos/effect/ai-docs/package.json b/.repos/effect/ai-docs/package.json new file mode 100644 index 000000000..90a59f266 --- /dev/null +++ b/.repos/effect/ai-docs/package.json @@ -0,0 +1,36 @@ +{ + "name": "ai-docs", + "private": true, + "type": "module", + "version": "0.0.0", + "dependencies": { + "@effect/ai-anthropic": "workspace:*", + "@effect/ai-openai": "workspace:*", + "@effect/ai-openai-compat": "workspace:*", + "@effect/ai-openrouter": "workspace:*", + "@effect/atom-react": "workspace:*", + "@effect/atom-solid": "workspace:*", + "@effect/atom-vue": "workspace:*", + "@effect/opentelemetry": "workspace:*", + "@effect/platform-bun": "workspace:*", + "@effect/platform-node": "workspace:*", + "@effect/sql-clickhouse": "workspace:*", + "@effect/sql-d1": "workspace:*", + "@effect/sql-libsql": "workspace:*", + "@effect/sql-mssql": "workspace:*", + "@effect/sql-mysql2": "workspace:*", + "@effect/sql-pg": "workspace:*", + "@effect/sql-sqlite-bun": "workspace:*", + "@effect/sql-sqlite-do": "workspace:*", + "@effect/sql-sqlite-node": "workspace:*", + "@effect/sql-sqlite-react-native": "workspace:*", + "@effect/sql-sqlite-wasm": "workspace:*", + "@effect/vitest": "workspace:*", + "effect": "workspace:*", + "hono": "^4.13.3", + "nodemailer": "^9.0.5" + }, + "devDependencies": { + "@types/nodemailer": "^8.0.1" + } +} diff --git a/.repos/effect/ai-docs/src/01_effect/01_basics/01_effect-gen.ts b/.repos/effect/ai-docs/src/01_effect/01_basics/01_effect-gen.ts new file mode 100644 index 000000000..d82288953 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/01_basics/01_effect-gen.ts @@ -0,0 +1,30 @@ +/** + * @title Using Effect.gen + * + * Use `Effect.gen` to write code in an imperative style similar to async await. + * You can use `yield*` to access the result of an effect. + */ + +import { Effect, Schema } from "effect" + +Effect.gen(function*() { + yield* Effect.log("Starting the file processing...") + yield* Effect.log("Reading file...") + + // Always return when raising an error, to ensure typescript understands that + // the function will not continue executing. + return yield* new FileProcessingError({ message: "Failed to read the file" }) +}).pipe( + // Add additional functionality with .pipe + Effect.catch((error) => Effect.logError(`An error occurred: ${error}`)), + Effect.withSpan("fileProcessing", { + attributes: { + method: "Effect.gen" + } + }) +) + +// Use Schema.TaggedError to define a custom error +export class FileProcessingError extends Schema.TaggedError()("FileProcessingError", { + message: Schema.String +}) {} diff --git a/.repos/effect/ai-docs/src/01_effect/01_basics/02_effect-fn.ts b/.repos/effect/ai-docs/src/01_effect/01_basics/02_effect-fn.ts new file mode 100644 index 000000000..1d41aaa15 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/01_basics/02_effect-fn.ts @@ -0,0 +1,39 @@ +/** + * @title Using Effect.fn + * + * When writing functions that return an Effect, use `Effect.fn` to use the + * generator syntax. + * + * **Avoid creating functions that return an Effect.gen**, use `Effect.fn` + * instead. + */ + +import { Effect, Schema } from "effect" + +// Pass a string to Effect.fn, which will improve stack traces and also +// attach a tracing span (using Effect.withSpan behind the scenes). +// +// The name string should match the function name. +// +export const effectFunction = Effect.fn("effectFunction")( + // You can use `Effect.fn.Return` to specify the return type of the function. + // It accepts the same type parameters as `Effect.Effect`. + function*(n: number): Effect.fn.Return { + yield* Effect.logInfo("Received number:", n) + + // Always return when raising an error, to ensure typescript understands that + // the function will not continue executing. + return yield* new SomeError({ message: "Failed to read the file" }) + }, + // Add additional functionality by passing in additional arguments. + // **Do not** use .pipe with Effect.fn + Effect.catch((error) => Effect.logError(`An error occurred: ${error}`)), + Effect.annotateLogs({ + method: "effectFunction" + }) +) + +// Use Schema.TaggedError to define a custom error +export class SomeError extends Schema.TaggedError()("SomeError", { + message: Schema.String +}) {} diff --git a/.repos/effect/ai-docs/src/01_effect/01_basics/10_creating-effects.ts b/.repos/effect/ai-docs/src/01_effect/01_basics/10_creating-effects.ts new file mode 100644 index 000000000..7f331047f --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/01_basics/10_creating-effects.ts @@ -0,0 +1,74 @@ +/** + * @title Creating effects from common sources + * + * Learn how to create effects from various sources, including plain values, + * synchronous code, Promise APIs, optional values, and callback-based APIs. + */ +import { Effect, Schema } from "effect" + +class InvalidPayload extends Schema.TaggedError()("InvalidPayload", { + input: Schema.String, + cause: Schema.Defect() +}) {} + +class UserLookupError extends Schema.TaggedError()("UserLookupError", { + userId: Schema.Int, + cause: Schema.Defect() +}) {} + +class MissingWorkspaceId extends Schema.TaggedError()("MissingWorkspaceId", {}) {} + +// Some request fields are optional and may be absent. +const requestHeaders = new Map([ + ["x-request-id", "req_1"] +]) + +// `Effect.succeed` wraps values you already have in memory. +export const fromValue = Effect.succeed({ env: "prod", retries: 3 }) + +// `Effect.sync` wraps synchronous side effects that should not throw. +export const fromSyncSideEffect = Effect.sync(() => Date.now()) + +// `Effect.try` wraps synchronous code that may throw. +export const parsePayload = Effect.fn("parsePayload")((input: string) => + Effect.try({ + try: () => JSON.parse(input) as { readonly userId: number }, + catch: (cause) => new InvalidPayload({ input, cause }) + }) +) + +const users = new Map([ + [1, { id: 1, name: "Ada" }], + [2, { id: 2, name: "Lin" }] +]) + +// `Effect.tryPromise` wraps Promise-based APIs that can reject or throw. +export const fetchUser = Effect.fn("fetchUser")((userId: number) => + Effect.tryPromise({ + async try() { + const user = users.get(userId) + if (!user) { + throw new Error(`Missing user ${userId}`) + } + return user + }, + catch: (cause) => new UserLookupError({ userId, cause }) + }) +) + +// `Effect.fromNullishOr` turns nullable values into a typed effect. +export const fromNullishHeader = Effect.fromNullishOr(requestHeaders.get("x-workspace-id")).pipe( + Effect.mapError(() => new MissingWorkspaceId()) +) + +// `Effect.callback` wraps callback-style asynchronous APIs. +export const fromCallback = Effect.callback((resume) => { + const timeoutId = setTimeout(() => { + resume(Effect.succeed(200)) + }, 10) + + // Return a finalizer so interruption can cancel the callback source. + return Effect.sync(() => { + clearTimeout(timeoutId) + }) +}) diff --git a/.repos/effect/ai-docs/src/01_effect/01_basics/index.md b/.repos/effect/ai-docs/src/01_effect/01_basics/index.md new file mode 100644 index 000000000..ae13d7a31 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/01_basics/index.md @@ -0,0 +1,5 @@ +## 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. diff --git a/.repos/effect/ai-docs/src/01_effect/02_schema/10_schema-basics.ts b/.repos/effect/ai-docs/src/01_effect/02_schema/10_schema-basics.ts new file mode 100644 index 000000000..99155a590 --- /dev/null +++ b/.repos/effect/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/.repos/effect/ai-docs/src/01_effect/02_schema/index.md b/.repos/effect/ai-docs/src/01_effect/02_schema/index.md new file mode 100644 index 000000000..2e93dea9a --- /dev/null +++ b/.repos/effect/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/.repos/effect/ai-docs/src/01_effect/03_services/01_service.ts b/.repos/effect/ai-docs/src/01_effect/03_services/01_service.ts new file mode 100644 index 000000000..14478cf6d --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/03_services/01_service.ts @@ -0,0 +1,45 @@ +/** + * @title Context.Service + * + * The default way to define a service is to extend `Context.Service`, + * passing in the service interface as a type parameter. + */ + +// file: src/db/Database.ts +import { Context, Effect, Layer, Schema } from "effect" + +// Pass in the service class name as the first type parameter, and the service +// interface as the second type parameter. +export class Database extends Context.Service, DatabaseError> +}>()( + // The string identifier for the service, which should include the package + // name and the subdirectory path to the service file. + "myapp/db/Database" +) { + // Attach a static layer to the service, which will be used to provide an + // implementation of the service. + static readonly layer = Layer.effect( + Database, + Effect.gen(function*() { + // Define the service methods using Effect.fn + const query = Effect.fn("Database.query")(function*(sql: string) { + yield* Effect.log("Executing SQL query:", sql) + return [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }] + }) + + // Return an instance of the service using Database.of, passing in an + // object that implements the service interface. + return Database.of({ + query + }) + }) + ) +} + +export class DatabaseError extends Schema.TaggedError()("DatabaseError", { + cause: Schema.Defect() +}) {} + +// If you ever need to access the service type, use `Database["Service"]` +export type DatabaseService = Database["Service"] diff --git a/.repos/effect/ai-docs/src/01_effect/03_services/10_reference.ts b/.repos/effect/ai-docs/src/01_effect/03_services/10_reference.ts new file mode 100644 index 000000000..17d71d900 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/03_services/10_reference.ts @@ -0,0 +1,10 @@ +/** + * @title Context.Reference + * + * For defining configuration values, feature flags, or any other service that has a default value. + */ +import { Context } from "effect" + +export const FeatureFlag = Context.Reference("myapp/FeatureFlag", { + defaultValue: () => false +}) diff --git a/.repos/effect/ai-docs/src/01_effect/03_services/20_layer-composition.ts b/.repos/effect/ai-docs/src/01_effect/03_services/20_layer-composition.ts new file mode 100644 index 000000000..99bff184f --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/03_services/20_layer-composition.ts @@ -0,0 +1,70 @@ +/** + * @title Composing services with the Layer module + * + * Build focused service layers, then compose them with `Layer.provide` and + * `Layer.provideMerge` based on what services you want to expose. + */ + +import { PgClient } from "@effect/sql-pg" +import { Array, Config, Context, Effect, Layer, type Option, Schema } from "effect" +import { SqlClient, SqlError } from "effect/unstable/sql" + +// Define a layer for the SqlClient service +export const SqlClientLayer: Layer.Layer< + PgClient.PgClient | SqlClient.SqlClient, + Config.ConfigError | SqlError.SqlError +> = PgClient.layerConfig({ + url: Config.redacted("DATABASE_URL") +}) + +export class UserRespositoryError extends Schema.TaggedError()("UserRespositoryError", { + reason: SqlError.SqlError +}) {} + +export class UserRepository extends Context.Service, + UserRespositoryError + > +}>()("myapp/UserRepository") { + // Implement the layer for the UserRepository service, which depends on the + // SqlClient service + static readonly layerNoDeps: Layer.Layer< + UserRepository, + never, + SqlClient.SqlClient + > = Layer.effect( + UserRepository, + Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + + const findById = Effect.fn("UserRepository.findById")(function*(id: string) { + const results = yield* sql<{ + readonly id: string + readonly name: string + }>`SELECT * FROM users WHERE id = '${id}'` + return Array.head(results) + }, Effect.mapError((reason) => new UserRespositoryError({ reason }))) + + return UserRepository.of({ findById }) + }) + ) + + // Use Layer.provide to compose the UserRepository layer with the SqlClient + // layer, exposing only the UserRepository service + static readonly layer: Layer.Layer< + UserRepository, + Config.ConfigError | SqlError.SqlError + > = this.layerNoDeps.pipe( + Layer.provide(SqlClientLayer) + ) + + // Use Layer.provideMerge to compose the UserRepository layer with the SqlClient + // layer, exposing both the UserRepository and SqlClient services + static readonly layerWithSqlClient: Layer.Layer< + UserRepository | SqlClient.SqlClient, + Config.ConfigError | SqlError.SqlError + > = this.layerNoDeps.pipe( + Layer.provideMerge(SqlClientLayer) + ) +} diff --git a/.repos/effect/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts b/.repos/effect/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts new file mode 100644 index 000000000..75a8488ec --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts @@ -0,0 +1,66 @@ +/** + * @title Creating Layers from configuration and/or Effects + * + * Build a layer dynamically from an Effect / Config with `Layer.unwrap`. + */ +import { Config, Context, Effect, Layer, Schema } from "effect" + +export class MessageStoreError extends Schema.TaggedError()("MessageStoreError", { + cause: Schema.Defect() +}) {} + +export class MessageStore extends Context.Service + readonly all: Effect.Effect> +}>()("myapp/MessageStore") { + static readonly layerInMemory = Layer.effect( + MessageStore, + Effect.sync(() => { + const messages: Array = [] + + return MessageStore.of({ + append: (message) => + Effect.sync(() => { + messages.push(message) + }), + all: Effect.sync(() => [...messages]) + }) + }) + ) + + static readonly layerRemote = (url: URL) => + Layer.effect( + MessageStore, + Effect.try({ + try: () => { + // In a real app this is where you would open a network connection. + const messages: Array = [] + + return MessageStore.of({ + append: (message) => + Effect.sync(() => { + messages.push(`[${url.host}] ${message}`) + }), + all: Effect.sync(() => [...messages]) + }) + }, + catch: (cause) => new MessageStoreError({ cause }) + }) + ) + + static readonly layer = Layer.unwrap( + Effect.gen(function*() { + // Read config inside an Effect, then choose which concrete layer to use. + const useInMemory = yield* Config.boolean("MESSAGE_STORE_IN_MEMORY").pipe( + Config.withDefault(false) + ) + + if (useInMemory) { + return MessageStore.layerInMemory + } + + const remoteUrl = yield* Config.url("MESSAGE_STORE_URL") + return MessageStore.layerRemote(remoteUrl) + }) + ) +} diff --git a/.repos/effect/ai-docs/src/01_effect/03_services/index.md b/.repos/effect/ai-docs/src/01_effect/03_services/index.md new file mode 100644 index 000000000..ac938fc6d --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/03_services/index.md @@ -0,0 +1,5 @@ +## Writing Effect services + +Effect services are the most common way to structure Effect code. Prefer using +services to encapsulate behaviour over other approaches, as it ensures that your +code is modular, testable, and maintainable. diff --git a/.repos/effect/ai-docs/src/01_effect/04_errors/01_error-handling.ts b/.repos/effect/ai-docs/src/01_effect/04_errors/01_error-handling.ts new file mode 100644 index 000000000..b4a7bfb9e --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/04_errors/01_error-handling.ts @@ -0,0 +1,30 @@ +/** + * @title Error handling basics + * + * Defining custom errors and handling them with Effect.catch and Effect.catchTag. + */ +import { Effect, Schema } from "effect" + +// Define custom errors using Schema.TaggedError +export class ParseError extends Schema.TaggedError()("ParseError", { + input: Schema.String, + message: Schema.String +}) {} + +export class ReservedPortError extends Schema.TaggedError()("ReservedPortError", { + port: Schema.Int +}) {} + +declare const loadPort: (input: string) => Effect.Effect + +export const recovered = loadPort("80").pipe( + // Catch multiple errors with Effect.catchTag, and return a default port number. + Effect.catchTag(["ParseError", "ReservedPortError"], (_) => Effect.succeed(3000)) +) + +export const withFinalFallback = loadPort("invalid").pipe( + // Catch a specific error with Effect.catchTag + Effect.catchTag("ReservedPortError", (_) => Effect.succeed(3000)), + // Catch all errors with Effect.catch + Effect.catch((_) => Effect.succeed(3000)) +) diff --git a/.repos/effect/ai-docs/src/01_effect/04_errors/10_catch-tags.ts b/.repos/effect/ai-docs/src/01_effect/04_errors/10_catch-tags.ts new file mode 100644 index 000000000..88d14d47a --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/04_errors/10_catch-tags.ts @@ -0,0 +1,24 @@ +/** + * @title Catch multiple errors with Effect.catchTags + * + * Use `Effect.catchTags` to handle several tagged errors in one place. + */ + +import { Effect, Schema } from "effect" + +export class ValidationError extends Schema.TaggedError()("ValidationError", { + message: Schema.String +}) {} + +export class NetworkError extends Schema.TaggedError()("NetworkError", { + statusCode: Schema.Int +}) {} + +declare const fetchUser: (id: string) => Effect.Effect + +export const userOrFallback = fetchUser("123").pipe( + Effect.catchTags({ + ValidationError: (error) => Effect.succeed(`Validation failed: ${error.message}`), + NetworkError: (error) => Effect.succeed(`Network request failed with status ${error.statusCode}`) + }) +) diff --git a/.repos/effect/ai-docs/src/01_effect/04_errors/20_reason-errors.ts b/.repos/effect/ai-docs/src/01_effect/04_errors/20_reason-errors.ts new file mode 100644 index 000000000..504dbdd9f --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/04_errors/20_reason-errors.ts @@ -0,0 +1,64 @@ +/** + * @title Creating and handling errors with reasons + * + * 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`. + */ + +import { Effect, Schema } from "effect" + +export class RateLimitError extends Schema.TaggedError()("RateLimitError", { + retryAfter: Schema.Finite +}) {} + +export class QuotaExceededError extends Schema.TaggedError()("QuotaExceededError", { + limit: Schema.Int +}) {} + +export class SafetyBlockedError extends Schema.TaggedError()("SafetyBlockedError", { + category: Schema.String +}) {} + +export class AiError extends Schema.TaggedError()("AiError", { + reason: Schema.Union([RateLimitError, QuotaExceededError, SafetyBlockedError]) +}) {} + +declare const callModel: Effect.Effect + +export const handleOneReason = callModel.pipe( + // Use `Effect.catchReason` to handle a specific reason type + Effect.catchReason( + "AiError", // The parent error _tag to catch + "RateLimitError", // The reason _tag to catch + // The handler for the caught reason + (reason) => Effect.succeed(`Retry after ${reason.retryAfter} seconds`), + // Optionally handle all the other reasons with a catch-all handler + (reason) => Effect.succeed(`Model call failed for reason: ${reason._tag}`) + ) +) + +export const handleMultipleReasons = callModel.pipe( + // Use `Effect.catchReasons` to handle multiple reason types for a given error + // in one go + Effect.catchReasons( + "AiError", + { + RateLimitError: (reason) => Effect.succeed(`Retry after ${reason.retryAfter} seconds`), + QuotaExceededError: (reason) => Effect.succeed(`Quota exceeded at ${reason.limit} tokens`) + } + // Optionally handle all the other reasons with a catch-all handler + // (reason) => Effect.succeed(`Unhandled reason: ${reason._tag}`) + ) +) + +export const unwrapAndHandle = callModel.pipe( + // Use `Effect.unwrapReason` to move the reasons into the error channel, then + // handle them all with `Effect.catchTags` or other error handling combinators + Effect.unwrapReason("AiError"), + Effect.catchTags({ + RateLimitError: (reason) => Effect.succeed(`Back off for ${reason.retryAfter} seconds`), + QuotaExceededError: (reason) => Effect.succeed(`Increase quota beyond ${reason.limit}`), + SafetyBlockedError: (reason) => Effect.succeed(`Blocked by safety category: ${reason.category}`) + }) +) diff --git a/.repos/effect/ai-docs/src/01_effect/04_errors/index.md b/.repos/effect/ai-docs/src/01_effect/04_errors/index.md new file mode 100644 index 000000000..14d16146d --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/04_errors/index.md @@ -0,0 +1 @@ +## Error handling diff --git a/.repos/effect/ai-docs/src/01_effect/05_resources/10_acquire-release.ts b/.repos/effect/ai-docs/src/01_effect/05_resources/10_acquire-release.ts new file mode 100644 index 000000000..4adb090c7 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/05_resources/10_acquire-release.ts @@ -0,0 +1,105 @@ +/** + * @title Acquiring resources with Effect.acquireRelease + * + * 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. + */ +import { Config, Context, Effect, Layer, Redacted, Schema } from "effect" +import * as NodeMailer from "nodemailer" + +export class SmtpError extends Schema.Error("SmtpError")({ + cause: Schema.Defect() +}) {} + +export class Smtp extends Context.Service +}>()("app/Smtp") { + static readonly layer = Layer.effect( + Smtp, + Effect.gen(function*() { + const user = yield* Config.string("SMTP_USER") + const pass = yield* Config.redacted("SMTP_PASS") + + // Use `Effect.acquireRelease` to manage the lifecycle of the SMTP + // transporter. + // + // When the Layer is built, the transporter will be created. When the + // Layer is torn down, the transporter will be closed, ensuring that + // resources are always cleaned up properly. + const transporter = yield* Effect.acquireRelease( + Effect.sync(() => + NodeMailer.createTransport({ + host: "smtp.example.com", + port: 587, + secure: false, + auth: { user, pass: Redacted.value(pass) } + }) + ), + (transporter) => Effect.sync(() => transporter.close()) + ) + + const send = Effect.fn("Smtp.send")((message: { + readonly to: string + readonly subject: string + readonly body: string + }) => + Effect.tryPromise({ + try: () => + transporter.sendMail({ + from: "Acme Cloud ", + to: message.to, + subject: message.subject, + text: message.body + }), + catch: (cause) => new SmtpError({ cause }) + }).pipe( + Effect.asVoid + ) + ) + + return Smtp.of({ send }) + }) + ) +} + +// We can then use the `Smtp` service in another service, and the transporter +// will be properly managed by the Layer system. + +export class MailerError extends Schema.TaggedError()("MailerError", { + reason: SmtpError +}) {} + +export class Mailer extends Context.Service +}>()("app/Mailer") { + static readonly layerNoDeps = Layer.effect( + Mailer, + Effect.gen(function*() { + const smtp = yield* Smtp + + const sendWelcomeEmail = Effect.fn("Mailer.sendWelcomeEmail")(function*(to: string) { + yield* smtp.send({ + to, + subject: "Welcome to Acme Cloud!", + body: "Thanks for signing up for Acme Cloud. We're glad to have you!" + }).pipe( + Effect.mapError((reason) => new MailerError({ reason })) + ) + yield* Effect.logInfo(`Sent welcome email to ${to}`) + }) + + return Mailer.of({ sendWelcomeEmail }) + }) + ) + + // Locally provide the Smtp layer to the Mailer layer, to eliminate all the + // requirements + static readonly layer = this.layerNoDeps.pipe( + Layer.provide(Smtp.layer) + ) +} diff --git a/.repos/effect/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts b/.repos/effect/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts new file mode 100644 index 000000000..3d3dfb8f5 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts @@ -0,0 +1,31 @@ +/** + * @title Creating Layers that run background tasks + * + * Use Layer.effectDiscard to encapsulate background tasks without a service interface. + */ +import { NodeRuntime } from "@effect/platform-node" +import { Effect, Layer } from "effect" + +// Use Layer.effectDiscard when you want to create a layer that runs an effect +// but does not provide any services. +const BackgroundTask = Layer.effectDiscard(Effect.gen(function*() { + yield* Effect.logInfo("Starting background task...") + + yield* Effect.gen(function*() { + while (true) { + yield* Effect.sleep("5 seconds") + yield* Effect.logInfo("Background task running...") + } + }).pipe( + Effect.onInterrupt(() => Effect.logInfo("Background task interrupted: layer scope closed")), + Effect.forkScoped + ) +})) + +// Run the background task layer. It will start when the layer is launched and +// will be automatically interrupted when the layer scope is closed (e.g. when +// the program exits). +BackgroundTask.pipe( + Layer.launch, + NodeRuntime.runMain +) diff --git a/.repos/effect/ai-docs/src/01_effect/05_resources/30_layer-map.ts b/.repos/effect/ai-docs/src/01_effect/05_resources/30_layer-map.ts new file mode 100644 index 000000000..5ba15c5fb --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/05_resources/30_layer-map.ts @@ -0,0 +1,86 @@ +/** + * @title Dynamic resources with LayerMap + * + * Use `LayerMap.Service` to dynamically build and manage resources that are + * keyed by some identifier, such as a tenant ID. + */ +import { Context, Effect, Layer, LayerMap, Schema } from "effect" + +class DatabaseQueryError extends Schema.TaggedError()("DatabaseQueryError", { + tenantId: Schema.String, + cause: Schema.Defect() +}) {} + +type UserRecord = { + readonly id: number + readonly email: string +} + +let nextConnectionId = 0 + +export class DatabasePool extends Context.Service Effect.Effect, DatabaseQueryError> +}>()("app/DatabasePool") { + // A layer factory that builds one pool per tenant. + static readonly layer = (tenantId: string) => + Layer.effect( + DatabasePool, + Effect.acquireRelease( + Effect.sync(() => { + const connectionId = ++nextConnectionId + + return DatabasePool.of({ + tenantId, + connectionId, + query: Effect.fn("DatabasePool.query")((_sql: string) => + Effect.succeed([ + { id: 1, email: `admin@${tenantId}.example.com` }, + { id: 2, email: `ops@${tenantId}.example.com` } + ]) + ) + }) + }), + (pool) => Effect.logInfo(`Closing tenant pool ${pool.tenantId}#${pool.connectionId}`) + ) + ) +} + +// extend `LayerMap.Service` to create a `LayerMap` service +export class PoolMap extends LayerMap.Service()("app/PoolMap", { + // `lookup` tells LayerMap how to build a layer for each tenant key. + lookup: (tenantId: string) => DatabasePool.layer(tenantId), + + // You can also use the layers option for a static set of layers + // layers: { + // acme: DatabasePool.layer("acme"), + // globex: DatabasePool.layer("globex") + // }, + + // If a pool is not used for this duration, it is released automatically. + idleTimeToLive: "1 minute" +}) {} + +const queryUsersForCurrentTenant = Effect.gen(function*() { + // Run a query agnostic of the tenant. The correct pool will be provided by + // the LayerMap. + const pool = yield* DatabasePool + return yield* pool.query("SELECT id, email FROM users ORDER BY id") +}) + +export const program = Effect.gen(function*() { + yield* queryUsersForCurrentTenant.pipe( + // Use `PoolMap.get` to access the pool for a specific tenant. The first + // time this is called for a tenant, the pool will be built using the + // `lookup` function defined in `PoolMap`. Subsequent calls will reuse the + // cached pool until it is idle for too long or invalidated. + Effect.provide(PoolMap.get("acme")) + ) + + // `PoolMap.invalidate` forces a key to rebuild on the next access. + yield* PoolMap.invalidate("acme") +}).pipe( + // Provide the `PoolMap` layer to the entire program. + Effect.provide(PoolMap.layer) +) diff --git a/.repos/effect/ai-docs/src/01_effect/05_resources/index.md b/.repos/effect/ai-docs/src/01_effect/05_resources/index.md new file mode 100644 index 000000000..cda2f507b --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/05_resources/index.md @@ -0,0 +1,3 @@ +## Managing resources and `Scope`s + +Learn how to safely manage resources in Effect using `Scope`s and finalizers. diff --git a/.repos/effect/ai-docs/src/01_effect/06_running/10_run-main.ts b/.repos/effect/ai-docs/src/01_effect/06_running/10_run-main.ts new file mode 100644 index 000000000..3e1786cb2 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/06_running/10_run-main.ts @@ -0,0 +1,30 @@ +/** + * @title Running effects with NodeRuntime and BunRuntime + * + * Use `NodeRuntime.runMain` to run an Effect program as your process entrypoint. + */ +import { BunRuntime } from "@effect/platform-bun" +import { NodeRuntime } from "@effect/platform-node" +import { Effect, Layer } from "effect" + +const Worker = Layer.effectDiscard(Effect.gen(function*() { + yield* Effect.logInfo("Starting worker...") + yield* Effect.forkScoped(Effect.gen(function*() { + while (true) { + yield* Effect.logInfo("Working...") + yield* Effect.sleep("1 second") + } + })) +})) + +const program = Layer.launch(Worker) + +// `runMain` installs SIGINT / SIGTERM handlers and interrupts running fibers +// for graceful shutdown. +NodeRuntime.runMain(program, { + // Disable automatic error reporting if your app already centralizes it. + disableErrorReporting: true +}) + +// Bun has the same API shape: +BunRuntime.runMain(program, { disableErrorReporting: true }) diff --git a/.repos/effect/ai-docs/src/01_effect/06_running/20_layer-launch.ts b/.repos/effect/ai-docs/src/01_effect/06_running/20_layer-launch.ts new file mode 100644 index 000000000..e92e1242d --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/06_running/20_layer-launch.ts @@ -0,0 +1,27 @@ +/** + * @title Using Layer.launch as the application entry point + * + * Use `Layer.launch` to run a long-running Effect program as your process entrypoint. + */ +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer } from "effect" +import { HttpRouter, HttpServerResponse } from "effect/unstable/http" +import { createServer } from "node:http" + +// Build a tiny HTTP app with a health-check endpoint. +export const HealthRoutes = HttpRouter.use(Effect.fn(function*(router) { + yield* router.add("GET", "/health", Effect.succeed(HttpServerResponse.text("ok"))) + yield* router.add("GET", "/healthz", Effect.succeed(HttpServerResponse.text("ok"))) +})) + +// Turn the routes into a server layer and provide the Node HTTP server backend. +export const HttpServerLive = HttpRouter.serve(HealthRoutes).pipe( + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +// `Layer.launch` converts the layer into a long-running Effect. +export const main = Layer.launch(HttpServerLive) + +// This entrypoint pattern works well when the whole app is represented as +// layers (for example: HTTP server + background workers). +NodeRuntime.runMain(main) diff --git a/.repos/effect/ai-docs/src/01_effect/06_running/index.md b/.repos/effect/ai-docs/src/01_effect/06_running/index.md new file mode 100644 index 000000000..e4c2c874c --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/06_running/index.md @@ -0,0 +1 @@ +## Running Effect programs diff --git a/.repos/effect/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts b/.repos/effect/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts new file mode 100644 index 000000000..6bb876a54 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts @@ -0,0 +1,56 @@ +/** + * @title Broadcasting domain events with PubSub + * + * Build an in-process event bus with `PubSub` and expose it as a service. + */ +import { Context, Effect, Layer, PubSub, Stream } from "effect" + +export type OrderEvent = + | { readonly _tag: "OrderPlaced"; readonly orderId: string } + | { readonly _tag: "PaymentCaptured"; readonly orderId: string } + | { readonly _tag: "OrderShipped"; readonly orderId: string } + +export class OrderEvents extends Context.Service + publishAll(events: ReadonlyArray): Effect.Effect + readonly subscribe: Stream.Stream +}>()("acme/OrderEvents") { + static readonly layer = Layer.effect( + OrderEvents, + Effect.gen(function*() { + // Use PubSub.bounded to create a PubSub with backpressure support. + // You can also use PubSub.unbounded if you don't need backpressure. + const pubsub = yield* PubSub.bounded({ + capacity: 256, + // Optionally add a replay buffer to let late subscribers catch up on + // recent events after restarts. + replay: 50 + }) + + // Ensure the PubSub is properly shut down when the service is no longer + // needed. + yield* Effect.addFinalizer(() => PubSub.shutdown(pubsub)) + + const publish = Effect.fn("OrderEvents.publish")(function*(event: OrderEvent) { + yield* PubSub.publish(pubsub, event) + }) + + const publishAll = Effect.fn("OrderEvents.publishAll")(function*(events: ReadonlyArray) { + yield* PubSub.publishAll(pubsub, events) + }) + + // Create a Stream that emits events published to the PubSub. + // + // Each subscriber will receive all events published after they subscribe, + // and if a replay buffer is configured, they will also receive the most + // recent events that were published before they subscribed. + const subscribe = Stream.fromPubSub(pubsub) + + return OrderEvents.of({ + publish, + publishAll, + subscribe + }) + }) + ) +} diff --git a/.repos/effect/ai-docs/src/01_effect/07_pubsub/index.md b/.repos/effect/ai-docs/src/01_effect/07_pubsub/index.md new file mode 100644 index 000000000..22b0ce037 --- /dev/null +++ b/.repos/effect/ai-docs/src/01_effect/07_pubsub/index.md @@ -0,0 +1,3 @@ +## Broadcasting messages with PubSub + +Use `PubSub` when you need one producer to fan out messages to many consumers. diff --git a/.repos/effect/ai-docs/src/03_stream/10_creating-streams.ts b/.repos/effect/ai-docs/src/03_stream/10_creating-streams.ts new file mode 100644 index 000000000..262a1f196 --- /dev/null +++ b/.repos/effect/ai-docs/src/03_stream/10_creating-streams.ts @@ -0,0 +1,103 @@ +/** + * @title Creating streams from common data sources + * + * Learn how to create streams from various data sources. Includes: + * + * - `Stream.fromIterable` for arrays and other iterables + * - `Stream.fromEffectSchedule` for polling effects + * - `Stream.paginate` for paginated APIs + * - `Stream.fromAsyncIterable` for async iterables + * - `Stream.fromEventListener` for DOM events + * - `Stream.callback` for any callback-based API + * - `NodeStream.fromReadable` for Node.js readable streams + */ +import { NodeStream } from "@effect/platform-node" +import { Array, Effect, Queue, Schedule, Schema, Stream } from "effect" +import * as Option from "effect/Option" +import { Readable } from "node:stream" + +// `Stream.fromIterable` turns any iterable into a stream. +export const numbers = Stream.fromIterable([1, 2, 3, 4, 5]) + +// `Stream.fromEffectSchedule` turns a single effect into a polling stream. +// This is useful for metrics, health checks, and cache refresh loops. +export const samples = Stream.fromEffectSchedule( + Effect.succeed(3), + Schedule.spaced("30 seconds") +).pipe( + // Stream.take limits the number of elements emitted by the stream. + Stream.take(3) +) + +// Use `Stream.paginate` when reading APIs that return one page at a time. +// The function returns the current page of values and optionally the next +// cursor. +export const fetchJobsPage = Stream.paginate( + 0, // start with page 0 (the cursor) + Effect.fn(function*(page) { + // Simulate network latency + yield* Effect.sleep("50 millis") + + const results = Array.range(0, 100).map((i) => `Job ${i + 1 + page * 100}`) + + // only return 10 pages of results + const nextPage = page <= 10 + ? Option.some(page + 1) + : Option.none() + + return [results, nextPage] as const + }) +) + +class LetterError extends Schema.TaggedError()("LetterError", { + cause: Schema.Defect() +}) {} + +async function* asyncIterable() { + yield "a" + yield "b" + yield "c" +} + +// Create a stream from an async iterable. +// The second argument is a function that converts any errors thrown by the +// async iterable into a typed error. +export const letters = Stream.fromAsyncIterable( + asyncIterable(), + (cause) => new LetterError({ cause }) +) + +const button = document.getElementById("my-button")! + +// `Stream.fromEventListener` creates a stream from an event listener. +export const events = Stream.fromEventListener(button, "click") + +// You can also use `Stream.callback` to create a stream from any callback-based +// API. +export const callbackStream = Stream.callback(Effect.fn(function*(queue) { + // You can use the `Queue` apis to emit values into the stream from the + // callback. + function onEvent(event: PointerEvent) { + Queue.offerUnsafe(queue, event) + } + // register the event listener and add a finalizer to unregister it when the + // stream is finished. + yield* Effect.acquireRelease( + Effect.sync(() => button.addEventListener("click", onEvent)), + () => Effect.sync(() => button.removeEventListener("click", onEvent)) + ) +})) + +export class NodeStreamError extends Schema.TaggedError()("NodeStreamError", { + cause: Schema.Defect() +}) {} + +// Create a stream from a Node.js readable stream. +// +// It takes options to convert any errors emitted by the stream into a typed +// error, and to evaluate the stream lazily. +export const nodeStream = NodeStream.fromReadable({ + evaluate: () => Readable.from(["Hello", " ", "world", "!"]), + onError: (cause) => new NodeStreamError({ cause }), + closeOnDone: true // true by default +}) diff --git a/.repos/effect/ai-docs/src/03_stream/20_consuming-streams.ts b/.repos/effect/ai-docs/src/03_stream/20_consuming-streams.ts new file mode 100644 index 000000000..cfe8b92e4 --- /dev/null +++ b/.repos/effect/ai-docs/src/03_stream/20_consuming-streams.ts @@ -0,0 +1,137 @@ +/** + * @title Consuming and transforming streams + * + * How to transform and consume streams using operators like `map`, `flatMap`, `filter`, `mapEffect`, and various `run*` methods. + */ +import { Effect, Sink, Stream } from "effect" + +interface Order { + readonly id: string + readonly customerId: string + readonly status: "paid" | "refunded" + readonly subtotalCents: number + readonly shippingCents: number + readonly country: "US" | "CA" | "NZ" +} + +interface NormalizedOrder extends Order { + readonly totalCents: number +} + +interface EnrichedOrder extends NormalizedOrder { + readonly taxCents: number + readonly grandTotalCents: number + readonly priority: "normal" | "high" +} + +// Start with structured order events from an in-memory source. +export const orderEvents = Stream.succeed({ + id: "ord_1001", + customerId: "cus_1", + status: "paid", + subtotalCents: 4_500, + shippingCents: 500, + country: "US" +}) + +// Use `Stream.map` for pure per-element transforms. +export const normalizedOrders = orderEvents.pipe( + Stream.map((order): NormalizedOrder => ({ + ...order, + totalCents: order.subtotalCents + order.shippingCents + })) +) + +// `Stream.filter` lets you exclude elements that don't match a predicate. +export const paidOrders = normalizedOrders.pipe( + Stream.filter((order) => order.status === "paid") +) + +// Use `Stream.flatMap` to transform each element into a stream, and flatten the +// results. +export const allOrders = Stream.make("US", "CA", "NZ").pipe( + Stream.flatMap( + (country) => + Stream.range(1, 50).pipe( + Stream.map((i): Order => ({ + id: `ord_${country}_${i}`, + customerId: `cus_${i}`, + status: i % 10 === 0 ? "refunded" : "paid", + subtotalCents: Math.round(Math.random() * 100_000), + shippingCents: Math.round(Math.random() * 10_000), + country + })) + ), + // Optionally control the concurrency of the flatMap with the second argument. + { concurrency: 2 } + ) +) + +const enrichOrder = Effect.fn(function*(order: NormalizedOrder): Effect.fn.Return { + // Simulate effectful enrichment (for example, tax/risk lookup). + yield* Effect.sleep("5 millis") + + const taxRate = order.country === "US" ? 0.08 : 0.13 + const taxCents = Math.round(order.totalCents * taxRate) + + return { + ...order, + taxCents, + grandTotalCents: order.totalCents + taxCents, + priority: order.totalCents >= 20_000 ? "high" : "normal" + } +}) + +// `Stream.mapEffect` performs effectful per-element transforms with concurrency control. +export const enrichedPaidOrders = paidOrders.pipe( + Stream.mapEffect(enrichOrder, { concurrency: 4 }) +) + +// `runCollect` gathers all stream outputs into an immutable array. +export const collectedOrders = Stream.runCollect(enrichedPaidOrders) + +// `runDrain` runs the stream for its effects, ignoring all outputs. +export const drained = Stream.runDrain(enrichedPaidOrders) + +// `runForEach` executes an effectful consumer for every element. +export const logOrders = enrichedPaidOrders.pipe( + Stream.runForEach((order) => Effect.logInfo(`Order ${order.id} total=$${(order.grandTotalCents / 100).toFixed(2)}`)) +) + +// `runFold` reduces the stream to one accumulated value. +export const totalRevenueCents = enrichedPaidOrders.pipe( + Stream.runFold(() => 0, (acc: number, order) => acc + order.grandTotalCents) +) + +// `run` lets you consume a stream through any Sink. +export const totalRevenueViaSink = enrichedPaidOrders.pipe( + Stream.map((order) => order.grandTotalCents), + Stream.run(Sink.sum) +) + +// `runHead` and `runLast` capture edge elements as Option values. +export const firstLargeOrder = enrichedPaidOrders.pipe( + Stream.filter((order) => order.priority === "high"), + Stream.runHead +) + +export const lastLargeOrder = enrichedPaidOrders.pipe( + Stream.filter((order) => order.priority === "high"), + Stream.runLast +) + +// Windowing-style operators help shape what downstream consumers see. +export const firstTwoOrders = enrichedPaidOrders.pipe( + Stream.take(2), + Stream.runCollect +) + +export const afterWarmupOrder = enrichedPaidOrders.pipe( + Stream.drop(1), + Stream.runCollect +) + +export const untilLargeOrder = enrichedPaidOrders.pipe( + Stream.takeWhile((order) => order.priority === "normal"), + Stream.runCollect +) diff --git a/.repos/effect/ai-docs/src/03_stream/30_encoding.ts b/.repos/effect/ai-docs/src/03_stream/30_encoding.ts new file mode 100644 index 000000000..c0577c851 --- /dev/null +++ b/.repos/effect/ai-docs/src/03_stream/30_encoding.ts @@ -0,0 +1,165 @@ +/** + * @title Decoding and encoding streams + * + * Use `Stream.pipeThroughChannel` with the `Ndjson` & `Msgpack` modules to + * decode and encode streams of structured data. + */ +import { DateTime, Schema, Stream } from "effect" +import { Msgpack, Ndjson } 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.Int, + name: Schema.String +})) + +// --------------------------------------------------------------------------- +// Domain +// --------------------------------------------------------------------------- + +// A log entry schema representing structured log events. In practice these +// would come from a file, HTTP body, or socket connection. +// `DateTimeUtcFromString` decodes an ISO-8601 string into a `DateTime.Utc`. +class LogEntry extends Schema.Class("LogEntry")({ + timestamp: Schema.DateTimeUtcFromString, + level: Schema.Literals(["info", "warn", "error"]), + message: Schema.String +}) {} + +// --------------------------------------------------------------------------- +// Decoding NDJSON strings → objects +// --------------------------------------------------------------------------- + +// Suppose we receive raw NDJSON text from a file or network socket. +// `Ndjson.decodeString()` is a Channel that splits incoming strings on +// newlines and `JSON.parse`s each line. +// Pipe the stream through the channel with `Stream.pipeThroughChannel`. +export const decodeUntyped = Stream.make( + "{\"timestamp\":\"2025-06-01T00:00:00Z\",\"level\":\"info\",\"message\":\"start\"}\n" + + "{\"timestamp\":\"2025-06-01T00:00:01Z\",\"level\":\"error\",\"message\":\"oops\"}\n" +).pipe( + Stream.pipeThroughChannel(Ndjson.decodeString()), + Stream.runCollect +) + +// When you need schema validation on top of the raw JSON parse, use +// `Ndjson.decodeSchemaString(Schema)()`. This decodes each line, parses the +// JSON, and then validates each value against the schema — all in one channel. +export const decodeTyped = Stream.make( + "{\"timestamp\":\"2025-06-01T00:00:00Z\",\"level\":\"info\",\"message\":\"start\"}\n" + + "{\"timestamp\":\"2025-06-01T00:00:01Z\",\"level\":\"error\",\"message\":\"oops\"}\n" +).pipe( + Stream.pipeThroughChannel(Ndjson.decodeSchemaString(LogEntry)()), + Stream.runCollect +) + +// --------------------------------------------------------------------------- +// Encoding objects → NDJSON strings +// --------------------------------------------------------------------------- + +// `Ndjson.encodeString()` serialises each value to a JSON line. +// The resulting stream emits ready-to-write NDJSON strings. +export const encodeUntyped = Stream.make( + { timestamp: "2025-06-01T00:00:00Z", level: "info", message: "start" }, + { timestamp: "2025-06-01T00:00:01Z", level: "error", message: "oops" } +).pipe( + Stream.pipeThroughChannel(Ndjson.encodeString()), + Stream.runCollect +) + +// `Ndjson.encodeSchemaString(Schema)()` encodes each value through the schema +// first (applying any transformations such as date formatting), then +// serialises it to an NDJSON line. +export const encodeTyped = Stream.make( + new LogEntry({ + timestamp: DateTime.makeUnsafe("2025-06-01T00:00:00Z"), + level: "info", + message: "start" + }), + new LogEntry({ + timestamp: DateTime.makeUnsafe("2025-06-01T00:00:01Z"), + level: "error", + message: "oops" + }) +).pipe( + Stream.pipeThroughChannel(Ndjson.encodeSchemaString(LogEntry)()), + Stream.runCollect +) + +// --------------------------------------------------------------------------- +// Binary (Uint8Array) variants +// --------------------------------------------------------------------------- + +// When working with binary I/O (e.g. TCP sockets, file descriptors) use the +// non-string variants. `Ndjson.decode()` expects `Uint8Array` chunks and +// handles text decoding internally. `Ndjson.encode()` produces `Uint8Array` +// output. +const enc = new TextEncoder() + +export const decodeBinary = Stream.make( + enc.encode("{\"level\":\"info\",\"message\":\"binary\"}\n") +).pipe( + Stream.pipeThroughChannel(Ndjson.decode()), + Stream.runCollect +) + +export const encodeBinary = Stream.make( + { level: "info", message: "binary" } +).pipe( + Stream.pipeThroughChannel(Ndjson.encode()), + Stream.runCollect +) + +// --------------------------------------------------------------------------- +// Handling empty lines +// --------------------------------------------------------------------------- + +// NDJSON files sometimes contain blank lines (e.g. trailing newlines or +// pretty-printed output). Pass `{ ignoreEmptyLines: true }` to skip them +// instead of raising an `NdjsonError`. +export const decodeIgnoringBlanks = Stream.make( + "{\"ok\":true}\n\n{\"ok\":false}\n" +).pipe( + Stream.pipeThroughChannel(Ndjson.decodeString({ ignoreEmptyLines: true })), + Stream.runCollect +) + +// --------------------------------------------------------------------------- +// Error handling +// --------------------------------------------------------------------------- + +// `Ndjson.NdjsonError` is raised when encoding (`kind: "Pack"`) or decoding +// (`kind: "Unpack"`) fails. You can catch it with `Stream.catchTag` or +// `Effect.catchTag`. +export const handleDecodeErrors = Stream.make("not-valid-json\n").pipe( + Stream.pipeThroughChannel(Ndjson.decodeString()), + Stream.catchTag("NdjsonError", (err) => + // The `kind` field indicates whether the error occurred during + // encoding ("Pack") or decoding ("Unpack"), and `cause` contains + // the underlying exception. + Stream.succeed({ recovered: true, kind: err.kind })), + Stream.runCollect +) + +// --------------------------------------------------------------------------- +// Realistic pipeline: decode → transform → re-encode +// --------------------------------------------------------------------------- + +// A common pattern is to read NDJSON, transform each record, and write it +// back as NDJSON. This example filters error-level log entries and re-encodes +// them. +const ndjsonInput = "{\"timestamp\":\"2025-06-01T00:00:00Z\",\"level\":\"info\",\"message\":\"ok\"}\n" + + "{\"timestamp\":\"2025-06-01T00:00:01Z\",\"level\":\"error\",\"message\":\"fail\"}\n" + + "{\"timestamp\":\"2025-06-01T00:00:02Z\",\"level\":\"warn\",\"message\":\"slow\"}\n" + +export const filterAndReencode = Stream.make(ndjsonInput).pipe( + // Decode each line into a validated LogEntry + Stream.pipeThroughChannel(Ndjson.decodeSchemaString(LogEntry)()), + // Keep only error-level entries + Stream.filter((entry) => entry.level === "error"), + // Re-encode the filtered entries back to NDJSON strings + Stream.pipeThroughChannel(Ndjson.encodeSchemaString(LogEntry)()), + Stream.runCollect +) diff --git a/.repos/effect/ai-docs/src/03_stream/index.md b/.repos/effect/ai-docs/src/03_stream/index.md new file mode 100644 index 000000000..710e143d5 --- /dev/null +++ b/.repos/effect/ai-docs/src/03_stream/index.md @@ -0,0 +1,4 @@ +## Working with Streams + +Effect Streams represent effectful, pull-based sequences of values over time. +They let you model finite or infinite data sources. diff --git a/.repos/effect/ai-docs/src/04_integration/10_managed-runtime.ts b/.repos/effect/ai-docs/src/04_integration/10_managed-runtime.ts new file mode 100644 index 000000000..b7393a9f4 --- /dev/null +++ b/.repos/effect/ai-docs/src/04_integration/10_managed-runtime.ts @@ -0,0 +1,129 @@ +/** + * @title Using ManagedRuntime with Hono + * + * Use `ManagedRuntime` to run Effect programs from external frameworks while keeping your domain logic in services and Layers. + */ +import { Context, Effect, Layer, ManagedRuntime, Ref, Schema } from "effect" +import { Hono } from "hono" + +class Todo extends Schema.Class("Todo")({ + id: Schema.Int, + title: Schema.String, + completed: Schema.Boolean +}) {} + +class CreateTodoPayload extends Schema.Class("CreateTodoPayload")({ + title: Schema.String +}) {} + +class TodoNotFound extends Schema.TaggedError()("TodoNotFound", { + id: Schema.Int +}) {} + +export class TodoRepo extends Context.Service> + getById(id: number): Effect.Effect + create(payload: CreateTodoPayload): Effect.Effect +}>()("app/TodoRepo") { + static readonly layer = Layer.effect( + TodoRepo, + Effect.gen(function*() { + const store = new Map() + const nextId = yield* Ref.make(1) + + const getAll = Effect.gen(function*() { + return Array.from(store.values()) + }).pipe( + Effect.withSpan("TodoRepo.getAll") + ) + + const getById = Effect.fn("TodoRepo.getById")(function*(id: number) { + const todo = store.get(id) + if (todo === undefined) { + return yield* new TodoNotFound({ id }) + } + return todo + }) + + const create = Effect.fn("TodoRepo.create")(function*(payload: CreateTodoPayload) { + const id = yield* Ref.getAndUpdate(nextId, (current) => current + 1) + const todo = new Todo({ id, title: payload.title, completed: false }) + store.set(id, todo) + return todo + }) + + return TodoRepo.of({ getAll, getById, create }) + }) + ) +} + +// Create a global memo map that can be shared across the app. This is necessary +// for memoization to work correctly across ManagedRuntime instances. +export const appMemoMap = Layer.makeMemoMapUnsafe() + +// Create a ManagedRuntime for the TodoRepo layer. This runtime can be shared +// across all handlers in the app, and it will manage the lifecycle of the +// TodoRepo service and any resources it uses. +export const runtime = ManagedRuntime.make(TodoRepo.layer, { + memoMap: appMemoMap +}) + +export const app = new Hono() + +app.get("/todos", async (context) => { + const todos = await runtime.runPromise( + TodoRepo.use((repo) => repo.getAll) + ) + return context.json(todos) +}) + +app.get("/todos/:id", async (context) => { + const id = Number(context.req.param("id")) + if (!Number.isFinite(id)) { + return context.json({ message: "Todo id must be a number" }, 400) + } + + const todo = await runtime.runPromise( + TodoRepo.use((repo) => repo.getById(id)).pipe( + Effect.catchTag("TodoNotFound", () => Effect.succeed(null)) + ) + ) + + if (todo === null) { + return context.json({ message: "Todo not found" }, 404) + } + + return context.json(todo) +}) + +const decodeCreateTodoPayload = Schema.decodeUnknownSync(CreateTodoPayload) + +app.post("/todos", async (context) => { + const body = await context.req.json() + + let payload: CreateTodoPayload + try { + payload = decodeCreateTodoPayload(body) + } catch { + return context.json({ message: "Invalid request body" }, 400) + } + + const todo = await runtime.runPromise( + TodoRepo.use((repo) => repo.create(payload)) + ) + + return context.json(todo, 201) +}) + +// The same bridge pattern works for Express, Fastify, Koa, and other frameworks. +// Use `runtime.runSync` for synchronous edges or `runtime.runCallback` for +// callback-only APIs. + +// When the process receives a shutdown signal, dispose the runtime to clean up +// any resources used by the TodoRepo service and its dependencies. +const shutdown = () => { + void runtime.dispose() +} + +process.once("SIGINT", shutdown) +process.once("SIGTERM", shutdown) diff --git a/.repos/effect/ai-docs/src/04_integration/index.md b/.repos/effect/ai-docs/src/04_integration/index.md new file mode 100644 index 000000000..dd01cdbdb --- /dev/null +++ b/.repos/effect/ai-docs/src/04_integration/index.md @@ -0,0 +1,5 @@ +## Integrating Effect into existing applications + +`ManagedRuntime` bridges Effect programs with non-Effect code. Build one runtime +from your application Layer, then use it anywhere you need imperative execution, +like web handlers, framework hooks, worker queues, or legacy callback APIs. diff --git a/.repos/effect/ai-docs/src/05_batching/10_request-resolver.ts b/.repos/effect/ai-docs/src/05_batching/10_request-resolver.ts new file mode 100644 index 000000000..3990ff7be --- /dev/null +++ b/.repos/effect/ai-docs/src/05_batching/10_request-resolver.ts @@ -0,0 +1,89 @@ +/** + * @title Batching requests with RequestResolver + * + * Define request types with `Request.Class`, resolve them in batches with `RequestResolver`. + */ +import { Context, Effect, Exit, Layer, Request, RequestResolver, Schema, Tracer } from "effect" + +export class User extends Schema.Class("User")({ + id: Schema.Int, + name: Schema.String, + email: Schema.String +}) {} + +export class UserNotFound extends Schema.TaggedError()("UserNotFound", { + id: Schema.Int +}) {} + +export class Users extends Context.Service +}>()("app/Users") { + static readonly layer = Layer.effect( + Users, + Effect.gen(function*() { + // Request classes model a single external lookup. + class GetUserById extends Request.Class< + { readonly id: number }, + User, // The success type of the request + UserNotFound, // The error type of the request + never // The requirements type of the request, if any + > {} + + // Simulate an external data source that supports batched lookup. + const usersTable = new Map([ + [1, new User({ id: 1, name: "Ada Lovelace", email: "ada@acme.dev" })], + [2, new User({ id: 2, name: "Alan Turing", email: "alan@acme.dev" })], + [3, new User({ id: 3, name: "Grace Hopper", email: "grace@acme.dev" })] + ]) + + const resolver = yield* RequestResolver.make(Effect.fn(function*(entries) { + for (const entry of entries) { + const user = usersTable.get(entry.request.id) + + // If the request had requirements, you can access them with + // `entry.context` + const requestSpan = Context.getOption(entry.context, Tracer.ParentSpan) + console.log("Request span", requestSpan) + + if (user) { + // Complete requests with .completeUnsafe and pass in an Exit value + entry.completeUnsafe(Exit.succeed(user)) + } else { + entry.completeUnsafe(Exit.fail(new UserNotFound({ id: entry.request.id }))) + } + } + })).pipe( + // Control the delay before the resolver is executed. This allows more + // requests to be batched together, but also adds latency to the first + // request. + RequestResolver.setDelay("10 millis"), + // RequestResolver.withSpan adds a span around the resolver execution, + // and also sets up span links for each request + RequestResolver.withSpan("Users.getUserById.resolver"), + // RequestResolver.withCache adds a simple LRU cache to avoid repeated + // lookups for the same ID. + RequestResolver.withCache({ capacity: 1024 }) + ) + + // Wrap the resolver in a service method. The resolver batches calls to + // `getUserById` that occur within the delay window. + const getUserById = (id: number) => + Effect.request(new GetUserById({ id }), resolver).pipe( + Effect.withSpan("Users.getUserById", { attributes: { userId: id } }) + ) + + return { getUserById } as const + }) + ) +} + +// Run multiple lookups concurrently. The resolver receives one batch and +// internally deduplicates repeated IDs for the external call. +export const batchedLookupExample = Effect.gen(function*() { + const { getUserById } = yield* Users + + // This will only trigger a single call to the resolver with the unique IDs [1, 2, 3]. + yield* Effect.forEach([1, 2, 1, 3, 2], getUserById, { + concurrency: "unbounded" + }) +}) diff --git a/.repos/effect/ai-docs/src/05_batching/index.md b/.repos/effect/ai-docs/src/05_batching/index.md new file mode 100644 index 000000000..4166039c9 --- /dev/null +++ b/.repos/effect/ai-docs/src/05_batching/index.md @@ -0,0 +1,3 @@ +## Batching external requests + +Learn how to batch multiple requests into fewer external calls. diff --git a/.repos/effect/ai-docs/src/06_schedule/10_schedules.ts b/.repos/effect/ai-docs/src/06_schedule/10_schedules.ts new file mode 100644 index 000000000..d42072add --- /dev/null +++ b/.repos/effect/ai-docs/src/06_schedule/10_schedules.ts @@ -0,0 +1,110 @@ +/** + * @title Working with the Schedule module + * + * Build schedules, compose them, and use them with `Effect.retry` and `Effect.repeat`. + */ +import { Duration, Effect, Random, Schedule, Schema } from "effect" + +// 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.min([ + Schedule.exponential("250 millis"), + // Cap the delay at 10 seconds to avoid excessively long waits. + Schedule.spaced("10 seconds") +]).pipe( + Schedule.jittered, + Schedule.setInputType(), + Schedule.while(({ input }) => input.retryable) +) + +export const fetchUserProfile = Effect.fn("fetchUserProfile")( + function*(userId: string) { + const random = yield* Random.next + const status = random > 0.7 + ? 200 + : random > 0.3 + ? 503 + : 401 + + if (status !== 200) { + return yield* new HttpError({ + message: `Request for ${userId} failed`, + status, + retryable: status >= 500 + }) + } + + return { + id: userId, + name: "Ada Lovelace" + } as const + } +) + +// Use the schedule with `Effect.retry` to retry failures. +export const loadUserWithRetry = fetchUserProfile("user-123").pipe( + Effect.retry(productionRetrySchedule), + // If the effect still fails after exhausting the schedule, turn the error + // into a fatal one. + Effect.orDie +) + +export const loadUserWithInferredInput = fetchUserProfile("user-123").pipe( + // You can also pass a schedule builder function that assists with inferring + // the input type. This is especially useful when the schedule needs to + // inspect the error to determine retryability. + Effect.retry(($) => + $(Schedule.spaced("1 seconds")).pipe( + Schedule.while(({ input }) => input.retryable) + ) + ), + 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/.repos/effect/ai-docs/src/06_schedule/index.md b/.repos/effect/ai-docs/src/06_schedule/index.md new file mode 100644 index 000000000..b56aa235c --- /dev/null +++ b/.repos/effect/ai-docs/src/06_schedule/index.md @@ -0,0 +1,3 @@ +## Working with Schedules + +Schedules define recurring patterns for retries, repeats and polling. diff --git a/.repos/effect/ai-docs/src/07_datetime/10_creating-and-formatting.ts b/.repos/effect/ai-docs/src/07_datetime/10_creating-and-formatting.ts new file mode 100644 index 000000000..6e5a77414 --- /dev/null +++ b/.repos/effect/ai-docs/src/07_datetime/10_creating-and-formatting.ts @@ -0,0 +1,30 @@ +/** + * @title Creating and formatting DateTime values + * + * Parse incoming date values safely, use Clock-powered current time, and format + * instants for API payloads or user-facing labels. + */ +import { DateTime, Effect, Option } from "effect" + +Effect.gen(function*() { + // Use DateTime.now to get the current time from Effect's Clock service. + // Using the Clock service ensures tests can use the `TestClock` module to + // control time. + const now = yield* DateTime.now + + // Use DateTime.make to parse a date input, such as a user-entered string or a + // epoch timestamp. It returns an Option depending on whether the input was + // valid. + const parsedOption: Option.Option = DateTime.make("2024-06-15T14:30:00.000Z") + + // you can then use the Option apis to unwrap the value + Option.getOrUndefined(parsedOption) + + // Calendar/date-time math returns a new DateTime value; the original value is + // immutable. + const endsAt = now.pipe(DateTime.add({ hours: 2 })) + + // The DateTime.format* functions can be used to convert a DateTime value to + // differen formats. + yield* Effect.log("ISO string:", DateTime.formatIso(endsAt)) +}) diff --git a/.repos/effect/ai-docs/src/07_datetime/20_time-zones.ts b/.repos/effect/ai-docs/src/07_datetime/20_time-zones.ts new file mode 100644 index 000000000..7ceed4208 --- /dev/null +++ b/.repos/effect/ai-docs/src/07_datetime/20_time-zones.ts @@ -0,0 +1,44 @@ +/** + * @title Working with time zones + * + * Attach IANA zones to instants, render zoned ISO strings, and provide a + * CurrentTimeZone service for code that should use the workspace/user zone. + */ +import { NodeRuntime } from "@effect/platform-node" +import { DateTime, Effect, Option } from "effect" + +Effect.gen(function*() { + // Use DateTime.now to get the current time from Effect's Clock service. + const now = yield* DateTime.now + + // To attach a named IANA zone to a DateTime value + const nowInAuckland = now.pipe( + // Use DateTime.setZoneNamedUnsafe when you know the zone is valid. + DateTime.setZoneNamedUnsafe("Pacific/Auckland") + ) + yield* Effect.log("Now in Auckland:", nowInAuckland) + + // Use DateTime.setZoneNamed when you don't know the zone is valid. + const nowInSydneyOption: Option.Option = now.pipe( + DateTime.setZoneNamed("Australia/Sydney") + ) + + yield* Effect.log("Now in Sydney:", Option.getOrUndefined(nowInSydneyOption)) + + // To generate a `DateTime.Zoned` in the `DateTime.CurrentTimeZone` + const nowInNewYork = yield* DateTime.nowInCurrentZone + yield* Effect.log("Now in New York:", nowInNewYork) + + // If you have a date string that you know is in a particular IANA zone, you + // can convert it to a DateTime.Zoned to ensure the instant is correct + const dateInAuckland: DateTime.Zoned = DateTime.makeZonedUnsafe("2026-06-05", { + timeZone: "Pacific/Auckland", + // adjustForTimeZone will adjust the input to the given zone, otherwise it + // will be treated as UTC. + adjustForTimeZone: true + }) + yield* Effect.log("Date in Auckland:", dateInAuckland) +}).pipe( + Effect.provide(DateTime.layerCurrentZoneNamed("America/New_York")), + NodeRuntime.runMain +) diff --git a/.repos/effect/ai-docs/src/07_datetime/index.md b/.repos/effect/ai-docs/src/07_datetime/index.md new file mode 100644 index 000000000..08d30a9c3 --- /dev/null +++ b/.repos/effect/ai-docs/src/07_datetime/index.md @@ -0,0 +1,5 @@ +## Working with DateTime + +When working with dates and time, use the `DateTime` module instead of `Date` and `Date.now`. + +Use it when your Effect programs need testable current time, safe parsing, stable ISO formatting, time-zone conversion, or calendar arithmetic. diff --git a/.repos/effect/ai-docs/src/08_observability/10_logging.ts b/.repos/effect/ai-docs/src/08_observability/10_logging.ts new file mode 100644 index 000000000..7cff7a2c9 --- /dev/null +++ b/.repos/effect/ai-docs/src/08_observability/10_logging.ts @@ -0,0 +1,66 @@ +/** + * @title Customizing logging + * + * Configure loggers & log-level filtering for production applications. + */ +import { NodeFileSystem } from "@effect/platform-node" +import { Config, Effect, Layer, Logger, References } from "effect" + +// Build a logger layer that emits one JSON line per log entry. +export const JsonLoggerLayer = Logger.layer([Logger.consoleJson]) + +// Raise the minimum level to "Warn" to skip debug/info logs. +export const WarnAndAbove = Layer.succeed(References.MinimumLogLevel, "Warn") + +// There is a built-in logger for writing to a file +export const FileLoggerLayer = Logger.layer([ + Logger.toFile(Logger.formatSimple, "app.log") +]).pipe( + Layer.provide(NodeFileSystem.layer) +) + +// Define a custom logger for app-specific formatting and routing. +export const appLogger = Effect.gen(function*() { + // Here you could initialize a connection to an external logging service, set + // up log file rotation, etc. + yield* Effect.logDebug("initializing app logger") + + return yield* Logger.batched(Logger.formatStructured, { + window: "1 second", + flush: Effect.fn(function*(batch) { + // In a real implementation, this is where you would send the batch of log entries to an external logging service or write them to a file. + console.log(`Flushing ${batch.length} log entries`) + }) + }) +}) + +export const AppLoggerLayer = Logger.layer([appLogger]).pipe( + Layer.provideMerge(WarnAndAbove) // Start with "Warn" level for the app logger. +) + +// 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")) + if (env === "production") { + return AppLoggerLayer + } + return Logger.layer([Logger.defaultLogger]) +})) + +// Example effect that logs at various levels during a checkout flow. +export const logCheckoutFlow = Effect.gen(function*() { + yield* Effect.logDebug("loading checkout state") + + yield* Effect.logInfo("validating cart") + yield* Effect.logWarning("inventory is low for one line item") + yield* Effect.logError("payment provider timeout") +}).pipe( + // Attach structured metadata to all log lines emitted by this effect. + Effect.annotateLogs({ + service: "checkout-api", + route: "POST /checkout" + }), + // Add a duration span so each log line includes checkout=ms metadata. + Effect.withLogSpan("checkout") +) diff --git a/.repos/effect/ai-docs/src/08_observability/20_otlp-tracing.ts b/.repos/effect/ai-docs/src/08_observability/20_otlp-tracing.ts new file mode 100644 index 000000000..121e795db --- /dev/null +++ b/.repos/effect/ai-docs/src/08_observability/20_otlp-tracing.ts @@ -0,0 +1,95 @@ +/** + * @title Setting up tracing with Otlp modules + * + * Configure Otlp tracing + log export with a reusable observability layer. + */ +import { NodeRuntime } from "@effect/platform-node" +import { Context, Effect, Layer } from "effect" +import { FetchHttpClient } from "effect/unstable/http" +import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability" + +// Configure OTLP span export. +export const OtlpTracingLayer = OtlpTracer.layer({ + url: "http://localhost:4318/v1/traces", + resource: { + serviceName: "checkout-api", + serviceVersion: "1.0.0", + attributes: { + "deployment.environment": "staging" + } + } +}) + +// Configure OTLP log export. +export const OtlpLoggingLayer = OtlpLogger.layer({ + url: "http://localhost:4318/v1/logs", + resource: { + serviceName: "checkout-api", + serviceVersion: "1.0.0" + } +}) + +// Reusable app-wide observability layer. +// +// - OtlpTracer/OtlpLogger require an OTLP serializer and an HttpClient. +// - FetchHttpClient.layer provides the HttpClient used by the exporter. +export const ObservabilityLayer = Layer.merge(OtlpTracingLayer, OtlpLoggingLayer).pipe( + Layer.provide(OtlpSerialization.layerJson), + Layer.provide(FetchHttpClient.layer) +) + +export class Checkout extends Context.Service +}>()("acme/Checkout") { + static readonly layer = Layer.effect( + Checkout, + Effect.gen(function*() { + yield* Effect.logInfo("setting up checkout service") + + return Checkout.of({ + processCheckout: Effect.fn("Checkout.processCheckout")(function*(orderId: string) { + yield* Effect.logInfo("starting checkout", { orderId }) + + yield* Effect.sleep("50 millis").pipe( + Effect.withSpan("checkout.charge-card"), + Effect.annotateSpans({ + "checkout.order_id": orderId, + "checkout.provider": "acme-pay" + }) + ) + + yield* Effect.sleep("20 millis").pipe( + Effect.withSpan("checkout.persist-order") + ) + + yield* Effect.logInfo("checkout completed", { orderId }) + }) + }) + }) + ) +} + +// Example usage of the Checkout service. +const CheckoutTest = Layer.effectDiscard( + Effect.gen(function*() { + const checkout = yield* Checkout + yield* checkout.processCheckout("ord_123") + }).pipe( + Effect.withSpan("checkout-test-run") + ) +).pipe( + // You can also attach spans to Layers + Layer.withSpan("checkout-test"), + Layer.provide(Checkout.layer) +) + +const Main = CheckoutTest.pipe( + // Provide the observability layer at the very end, so that all spans created + // by the app are exported. + Layer.provide(ObservabilityLayer) +) + +// Launch the app +Layer.launch(Main).pipe( + NodeRuntime.runMain +) diff --git a/.repos/effect/ai-docs/src/08_observability/index.md b/.repos/effect/ai-docs/src/08_observability/index.md new file mode 100644 index 000000000..df0399ad4 --- /dev/null +++ b/.repos/effect/ai-docs/src/08_observability/index.md @@ -0,0 +1,7 @@ +## Observability + +Effect has built-in support for structured logging, distributed tracing, and +metrics. For exporting telemetry, use the lightweight Otlp modules from +`effect/unstable/observability` in new projects, or use +`@effect/opentelemetry` NodeSdk when integrating with an existing OpenTelemetry +setup. diff --git a/.repos/effect/ai-docs/src/09_testing/10_effect-tests.ts b/.repos/effect/ai-docs/src/09_testing/10_effect-tests.ts new file mode 100644 index 000000000..393d29daf --- /dev/null +++ b/.repos/effect/ai-docs/src/09_testing/10_effect-tests.ts @@ -0,0 +1,55 @@ +/** + * @title Writing Effect tests with @effect/vitest + * + * Using `it.effect` for Effect-based tests. + */ +import { assert, describe, it } from "@effect/vitest" +import { Effect, Fiber, Schema } from "effect" +import { TestClock } from "effect/testing" + +describe("@effect/vitest basics", () => { + it.effect("runs Effect code with assert helpers", () => + Effect.gen(function*() { + const upper = ["ada", "lin"].map((name) => name.toUpperCase()) + assert.deepStrictEqual(upper, ["ADA", "LIN"]) + assert.strictEqual(upper.length, 2) + assert.isTrue(upper.includes("ADA")) + })) + + it.effect.each([ + { input: " Ada ", expected: "ada" }, + { input: " Lin ", expected: "lin" }, + { input: " Nia ", expected: "nia" } + ])("parameterized normalization %#", ({ input, expected }) => + Effect.gen(function*() { + assert.strictEqual(input.trim().toLowerCase(), expected) + })) + + it.effect("controls time with TestClock", () => + Effect.gen(function*() { + const fiber = yield* Effect.forkChild( + Effect.sleep(60_000).pipe(Effect.as("done" as const)) + ) + + // Move virtual time forward to complete sleeping fibers immediately. + yield* TestClock.adjust(60_000) + + const value = yield* Fiber.join(fiber) + assert.strictEqual(value, "done") + })) + + it.live("uses real runtime services", () => + Effect.gen(function*() { + const startedAt = Date.now() + yield* Effect.sleep(1) + assert.isTrue(Date.now() >= startedAt) + })) + + // For property-based testing, use `it.effect.prop` with Schema-based + // arbitraries + it.effect.prop("reversing twice is identity", [Schema.String], ([value]) => + Effect.gen(function*() { + const reversedTwice = value.split("").reverse().reverse().join("") + assert.strictEqual(reversedTwice, value) + })) +}) diff --git a/.repos/effect/ai-docs/src/09_testing/20_layer-tests.ts b/.repos/effect/ai-docs/src/09_testing/20_layer-tests.ts new file mode 100644 index 000000000..556dc8d51 --- /dev/null +++ b/.repos/effect/ai-docs/src/09_testing/20_layer-tests.ts @@ -0,0 +1,138 @@ +/** + * @title Testing services with shared layers + * + * How to test Effect services that depend on other services. + */ +import { assert, describe, it, layer } from "@effect/vitest" +import { Array, Context, Effect, Layer, Ref } from "effect" + +export interface Todo { + readonly id: number + readonly title: string +} + +// Create a test ref service that can be used to store and manipulate test data +// in layers. +export class TodoRepoTestRef extends Context.Service>>()("app/TodoRepoTestRef") { + static readonly layer = Layer.effect(TodoRepoTestRef, Ref.make(Array.empty())) +} + +class TodoRepo extends Context.Service + readonly list: Effect.Effect> +}>()("app/TodoRepo") { + static readonly layerTest = Layer.effect( + TodoRepo, + Effect.gen(function*() { + const store = yield* TodoRepoTestRef + + const create = Effect.fn("TodoRepo.create")(function*(title: string) { + const todos = yield* Ref.get(store) + const todo = { id: todos.length + 1, title } + yield* Ref.set(store, [...todos, todo]) + return todo + }) + + const list = Ref.get(store) + + return TodoRepo.of({ + create, + list + }) + }) + ).pipe( + // Provide the test ref layer as a dependency for the test repo layer. + // Use Layer.provideMerge so the tests can also access the test ref directly + // if needed. + Layer.provideMerge(TodoRepoTestRef.layer) + ) +} + +class TodoService extends Context.Service + readonly titles: Effect.Effect> +}>()("app/TodoService") { + static readonly layerNoDeps = Layer.effect( + TodoService, + Effect.gen(function*() { + const repo = yield* TodoRepo + + const addAndCount = Effect.fn("TodoService.addAndCount")(function*(title: string) { + yield* repo.create(title) + const todos = yield* repo.list + return todos.length + }) + + const titles = repo.list.pipe( + Effect.map((todos) => todos.map((todo) => todo.title)) + ) + + return TodoService.of({ + addAndCount, + titles + }) + }) + ) + + // You would also add a live layer here that provides real dependencies for + // production code. + // + // static readonly layer = Layer.effect(TodoService, ...).pipe( + // Layer.provide(TodoRepo.layer) + // ) + + static readonly layerTest = this.layerNoDeps.pipe( + // Provide the test repo layer as a dependency for the test service layer. + // Use `Layer.provideMerge` so the tests can also access the test repo + // directly if needed, as well as the test ref through the repo layer. + Layer.provideMerge(TodoRepo.layerTest) + ) +} + +// `layer(...)` creates one shared layer for the block and tears it down in +// `afterAll`, so all tests inside can access the same service context. +layer(TodoRepo.layerTest)("TodoRepo", (it) => { + it.effect("tests repository behavior", () => + Effect.gen(function*() { + const repo = yield* TodoRepo + const before = (yield* repo.list).length + assert.strictEqual(before, 0) + + yield* repo.create("Write docs") + + const after = (yield* repo.list).length + assert.strictEqual(after, 1) + })) + + it.effect("layer is shared", () => + Effect.gen(function*() { + const repo = yield* TodoRepo + const before = (yield* repo.list).length + assert.strictEqual(before, 1) + + yield* repo.create("Write docs again") + + // because the layer is shared between tests, the todo created in the + // previous test is still present, so the count should be 2, not 1 + const after = (yield* repo.list).length + assert.strictEqual(after, 2) + })) +}) + +describe("TodoService", () => { + it.effect("tests higher-level service logic", () => + Effect.gen(function*() { + const ref = yield* TodoRepoTestRef + const service = yield* TodoService + const count = yield* service.addAndCount("Review docs") + const titles = yield* service.titles + + assert.isTrue(count >= 1) + assert.isTrue(titles.some((title) => title.includes("Review docs"))) + + // You can also access the test ref directly to make assertions about the + // underlying data. + const todos = yield* Ref.get(ref) + assert.isTrue(todos.length >= 1) + }).pipe(Effect.provide(TodoService.layerTest))) +}) diff --git a/.repos/effect/ai-docs/src/09_testing/index.md b/.repos/effect/ai-docs/src/09_testing/index.md new file mode 100644 index 000000000..5df1fab50 --- /dev/null +++ b/.repos/effect/ai-docs/src/09_testing/index.md @@ -0,0 +1 @@ +## Testing Effect programs diff --git a/.repos/effect/ai-docs/src/10_predicate/01_basics.ts b/.repos/effect/ai-docs/src/10_predicate/01_basics.ts new file mode 100644 index 000000000..8ff78326c --- /dev/null +++ b/.repos/effect/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/.repos/effect/ai-docs/src/10_predicate/index.md b/.repos/effect/ai-docs/src/10_predicate/index.md new file mode 100644 index 000000000..5149c3221 --- /dev/null +++ b/.repos/effect/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/.repos/effect/ai-docs/src/40_sql/10_basics.ts b/.repos/effect/ai-docs/src/40_sql/10_basics.ts new file mode 100644 index 000000000..59a590ae2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/ai-docs/src/40_sql/index.md b/.repos/effect/ai-docs/src/40_sql/index.md new file mode 100644 index 000000000..cf026b2c8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/ai-docs/src/50_http-client/10_basics.ts b/.repos/effect/ai-docs/src/50_http-client/10_basics.ts new file mode 100644 index 000000000..195d5fe55 --- /dev/null +++ b/.repos/effect/ai-docs/src/50_http-client/10_basics.ts @@ -0,0 +1,102 @@ +/** + * @title Getting started with HttpClient + * + * Define a service that uses the HttpClient module to fetch data from an external API + */ +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.Int, + id: Schema.Int, + title: Schema.String, + completed: Schema.Boolean +}) {} + +export class JsonPlaceholder extends Context.Service, JsonPlaceholderError> + getTodo(id: number): Effect.Effect + createTodo(todo: Omit): Effect.Effect +}>()("app/JsonPlaceholder") { + static readonly layer = Layer.effect( + JsonPlaceholder, + Effect.gen(function*() { + // Access the HttpClient service, and apply some common middleware to all + // requests: + const client = (yield* HttpClient.HttpClient).pipe( + // Add a base URL to all requests made with this client, and set the + // Accept header to expect JSON responses + HttpClient.mapRequest(flow( + HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com"), + HttpClientRequest.acceptJson + )), + // Fail if the response status is not 2xx + HttpClient.filterStatusOk, + // Retry transient errors (network issues, 5xx responses) with an + // exponential backoff. + // + // See the schedule documentation for more complex retry strategies. + HttpClient.retryTransient({ + schedule: Schedule.exponential(100), + times: 3 + }) + ) + + const allTodos = client.get("/todos").pipe( + Effect.flatMap(HttpClientResponse.schemaBodyJson(Schema.Array(Todo))), + Effect.mapError((cause) => new JsonPlaceholderError({ cause })), + Effect.withSpan("JsonPlaceholder.allTodos") + ) + + // Use the HttpClient to fetch a todo item by id, and decode the response + // using the Todo schema. + const getTodo = Effect.fn("JsonPlaceholder.getTodo")(function*(id: number) { + // Annotate the current span with the id of the todo being fetched, so + // that it shows up in telemetry for this request. + yield* Effect.annotateCurrentSpan({ id }) + + const todo = yield* client.get(`/todos/${id}`, { + // You can pass additional options to individual requests. + // There are options for query parameters, request body, headers, and + // more. + urlParams: { format: "json" } + }).pipe( + Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)), + Effect.mapError((cause) => new JsonPlaceholderError({ cause })) + ) + + return todo + }) + + // You can use the HttpClientRequest module to build up more complex + // requests: + const createTodo = Effect.fn("JsonPlaceholder.createTodo")(function*(todo: Omit) { + yield* Effect.annotateCurrentSpan({ title: todo.title }) + + const createdTodo = yield* HttpClientRequest.post("/todos").pipe( + // The HttpClientRequest module has many helper functions for building requests. + HttpClientRequest.setUrlParams({ format: "json" }), + HttpClientRequest.bodyJsonUnsafe(todo), + client.execute, + Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)), + Effect.mapError((cause) => new JsonPlaceholderError({ cause })) + ) + + return createdTodo + }) + + return JsonPlaceholder.of({ + allTodos, + getTodo, + createTodo + }) + }) + ).pipe( + // Provide the fetch-based HttpClient implementation + Layer.provide(FetchHttpClient.layer) + ) +} + +export class JsonPlaceholderError extends Schema.TaggedError()("JsonPlaceholderError", { + cause: Schema.Defect() +}) {} diff --git a/.repos/effect/ai-docs/src/50_http-client/index.md b/.repos/effect/ai-docs/src/50_http-client/index.md new file mode 100644 index 000000000..7baa9010f --- /dev/null +++ b/.repos/effect/ai-docs/src/50_http-client/index.md @@ -0,0 +1,3 @@ +## Effect HttpClient + +Build http clients with the `HttpClient` module. diff --git a/.repos/effect/ai-docs/src/51_http-server/10_basics.ts b/.repos/effect/ai-docs/src/51_http-server/10_basics.ts new file mode 100644 index 000000000..0edea4465 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/10_basics.ts @@ -0,0 +1,118 @@ +/** + * @title Getting started with HttpApi + * + * Define a schema-first API, implement handlers, secure endpoints with + * middleware, serve it over HTTP, and call it using a generated typed client. + */ +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Context, Effect, flow, Layer, Schedule } from "effect" +import { FetchHttpClient, HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http" +import { HttpApiBuilder, HttpApiClient, HttpApiMiddleware, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" +// Api definitions should **always** be seperate from the server implementation, +// so that they can be shared between the server and client without leaking +// server code into clients. +// Ideally, the would use a seperate package in a monorepo. +import { Api } from "./fixtures/api/Api.ts" +import { Authorization } from "./fixtures/api/Authorization.ts" +import { UsersApiHandlers } from "./fixtures/server/Users/http.ts" + +// This walkthrough focuses on runtime wiring and typed client usage. +// See the fixture files for the API schemas, endpoint definitions and handlers: + +const SystemApiHandlers = HttpApiBuilder.group( + Api, + "system", + Effect.fn(function*(handlers) { + return handlers.handleAll({ + health: () => Effect.void + }) + }) +) + +const ApiRoutes = HttpApiBuilder.layer(Api, { + openapiPath: "/openapi.json" +}).pipe( + // Provide all the handler Layers for the API. + Layer.provide([UsersApiHandlers, SystemApiHandlers]) +) + +// Define a /docs route that serves scalar documentation +const DocsRoute = HttpApiScalar.layer(Api, { + path: "/docs" +}) + +// Merge all the http routes together +const AllRoutes = Layer.mergeAll(ApiRoutes, DocsRoute) + +// Create an HTTP server Layer that serves the API routes. +// +// Here we are using the NodeHttpServer, but you could also use the +// BunHttpServer +export const HttpServerLayer = HttpRouter.serve(AllRoutes).pipe( + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +// Then run the server using Layer.launch +Layer.launch(HttpServerLayer).pipe( + NodeRuntime.runMain +) + +// Or create a web handler, which can be used in serverless environments +export const { handler, dispose } = HttpRouter.toWebHandler(AllRoutes.pipe( + Layer.provide(HttpServer.layerServices) +)) + +// ----------------- +// Client side setup +// ----------------- + +export const AuthorizationClient = HttpApiMiddleware.layerClient( + Authorization, + Effect.fn(function*({ next, request }) { + // Here you can modify the request and pass it down the middleware chain. + // This is where you would add authentication tokens, custom headers, etc. + // For this example, we just add a hardcoded bearer token to all requests. + return yield* next(HttpClientRequest.bearerToken(request, "dev-token")) + }) +) + +// Define the HttpApiClient service, which will be used to make requests to the +// API. +export class ApiClient extends Context.Service>()("acme/ApiClient") { + static readonly layer = Layer.effect( + ApiClient, + HttpApiClient.make(Api, { + // Use transformClient to apply middleware to the generated client. This + // is useful for settings the base url and applying retry policies. + transformClient: (client) => + client.pipe( + HttpClient.mapRequest(flow( + HttpClientRequest.prependUrl("http://localhost:3000") + )), + HttpClient.retryTransient({ + schedule: Schedule.exponential(100), + times: 3 + }) + ) + }) + ).pipe( + // Provide the client implementation of the Authorization middleware, which + // is required. + Layer.provide(AuthorizationClient), + // Supply a HttpClient implementation to use for making requests. Here we + // use the FetchHttpClient, but you could also use the NodeHttpClient or + // BunHttpClient. + Layer.provide(FetchHttpClient.layer) + ) +} + +// The generated client mirrors your API definition, so renames and schema +// changes are checked end-to-end at compile time. +export const callApi = Effect.gen(function*() { + const client = yield* ApiClient + + yield* client.health() +}).pipe( + Effect.provide(ApiClient.layer) +) diff --git a/.repos/effect/ai-docs/src/51_http-server/20_testing.ts b/.repos/effect/ai-docs/src/51_http-server/20_testing.ts new file mode 100644 index 000000000..e34d13d22 --- /dev/null +++ b/.repos/effect/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/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Api.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Api.ts new file mode 100644 index 000000000..20140ae7c --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Api.ts @@ -0,0 +1,14 @@ +import { HttpApi, OpenApi } from "effect/unstable/httpapi" +import { SystemApi } from "./System.ts" +import { UsersApiGroup } from "./Users.ts" + +// Defined the root API, which combines all of the groups together. This is the +// API that you will serve and generate clients for. You can also annotate the +// API with OpenAPI metadata. +export class Api extends HttpApi.make("user-api") + .add(UsersApiGroup) + .add(SystemApi) + .annotateMerge(OpenApi.annotations({ + title: "Acme User API" + })) +{} diff --git a/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Authorization.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Authorization.ts new file mode 100644 index 000000000..30898c729 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Authorization.ts @@ -0,0 +1,36 @@ +import { Context, Schema } from "effect" +import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" +import type { User } from "../domain/User.ts" + +export class CurrentUser extends Context.Service()("acme/HttpApi/Authorization/CurrentUser") {} + +export class Unauthorized extends Schema.TaggedError()( + "Unauthorized", + { + message: Schema.String + }, + // You can define error status codes directly on the error class + { httpApiStatus: 401 } +) {} + +export class Authorization extends HttpApiMiddleware.Service()("acme/HttpApi/Authorization", { + // This middleware requires clients to also provide an implementation, to + // inject a api key + requiredForClient: true, + // Middleware can optionally define security schemes, which are used to + // generate OpenAPI docs and decode credientials from incoming requests for + // you. + security: { + bearer: HttpApiSecurity.bearer + }, + // Middlware can specify errors that it may raise + error: Unauthorized +}) {} diff --git a/.repos/effect/ai-docs/src/51_http-server/fixtures/api/System.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/System.ts new file mode 100644 index 000000000..c66565b06 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/System.ts @@ -0,0 +1,10 @@ +import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" + +// Top level groups are added to the root of the derived HttpApiClient. +// +// `client.health()` +export class SystemApi extends HttpApiGroup.make("system", { topLevel: true }).add( + HttpApiEndpoint.get("health", "/health", { + success: HttpApiSchema.NoContent + }) +) {} diff --git a/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Users.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Users.ts new file mode 100644 index 000000000..ad370e163 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/api/Users.ts @@ -0,0 +1,107 @@ +import { Schema } from "effect" +import { HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" +import { User, UserId } from "../domain/User.ts" +import { SearchQueryTooShort, UserNotFound } from "../domain/UserErrors.ts" +import { Authorization } from "./Authorization.ts" + +export class UsersApiGroup extends HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("list", "/", { + query: { + search: Schema.optional(Schema.String) + }, + // 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 + payload: { + search: Schema.String + }, + success: [ + Schema.Array(User.json), + Schema.String.pipe(HttpApiSchema.asText({ + contentType: "text/csv" + })) + ], + error: [ + SearchQueryTooShort.pipe( + // If you want an error to return no content, you can use + // `HttpApiSchema.asNoContent` and provide a decoder that transforms the + // error into the appropriate type. + HttpApiSchema.asNoContent({ + decode: () => new SearchQueryTooShort() + }) + ), + // You can also add some of the built in `HttpApiError`s to handle common + // error cases like bad requests, unauthorized, etc. + HttpApiError.RequestTimeoutNoContent + ] + }), + HttpApiEndpoint.get("getById", "/:id", { + params: { + // 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.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 + // error into the appropriate type. + HttpApiSchema.asNoContent({ + decode: () => new UserNotFound() + }) + ) + }), + HttpApiEndpoint.post("create", "/", { + // 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. + // + // 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.json, + error: UserNotFound.pipe(HttpApiSchema.status(404)) + }) + ) + // You can apply middleware to entire groups, which is useful for things like + // authentication and authorization. + // + // You can also apply middleware to individual endpoints if you need more + // fine-grained control. + .middleware(Authorization) + // To add a common prefix to all endpoints in a group, you can use the `prefix` + // method. This is useful for grouping related endpoints together under a common + // path segment. In this case, all endpoints in the `UsersApiGroup` will be + // prefixed with `/users`. + .prefix("/users") + // You can add OpenAPI annotations to groups, endpoints, and even parameters and + // request bodies. These will be merged together to generate the final OpenAPI + // docs for the API + .annotateMerge(OpenApi.annotations({ + title: "Users", + description: "User management endpoints" + })) +{} diff --git a/.repos/effect/ai-docs/src/51_http-server/fixtures/domain/User.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/domain/User.ts new file mode 100644 index 000000000..43ac4f5cf --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/domain/User.ts @@ -0,0 +1,21 @@ +import { Schema } from "effect" +import { Model } from "effect/unstable/schema" + +export const UserId = Schema.String.pipe(Schema.brand("UserId")) +export type UserId = typeof UserId.Type + +// `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, + // 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/.repos/effect/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts new file mode 100644 index 000000000..fde17e7fc --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts @@ -0,0 +1,22 @@ +import { Schema } from "effect" + +export class UserNotFound extends Schema.TaggedError()( + "UserNotFound", + {}, + // You can specify the status code for this error inline + { httpApiStatus: 404 } +) {} + +export class SearchQueryTooShort + extends Schema.TaggedError()("SearchQueryTooShort", {}, { httpApiStatus: 422 }) +{ + static readonly minimumLength = 2 +} + +// Create a wrapper error class for all errors in the Users API. +// +// This prevents adding too many error types to services / endpoint definitions. +// +export class UsersError extends Schema.TaggedError()("UsersError", { + reason: Schema.Union([UserNotFound, SearchQueryTooShort]) +}) {} diff --git a/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Authorization.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Authorization.ts new file mode 100644 index 000000000..51ae21dc0 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Authorization.ts @@ -0,0 +1,37 @@ +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( + Authorization, + Effect.gen(function*() { + // Here you could access services required by the middleware, like a + // database or an external auth provider. + yield* Effect.logInfo("Starting Authorization middleware") + + return Authorization.of({ + bearer: Effect.fn(function*(httpEffect, { credential }) { + // Validate the token and return an Unauthorized error if it's invalid. + const token = Redacted.value(credential) + if (token !== "dev-token") { + return yield* new Unauthorized({ message: "Missing or invalid bearer token" }) + } + + // 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, devUser) + }) + }) + }) +) diff --git a/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Users.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Users.ts new file mode 100644 index 000000000..485d1e348 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Users.ts @@ -0,0 +1,183 @@ +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: typeof User.jsonCreate.Type): Effect.Effect + update(id: UserId, input: typeof User.jsonUpdate.Type): Effect.Effect +}>()("acme/Users") { + // 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 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() + }) + } + yield* Effect.annotateCurrentSpan({ search }) + return yield* Effect.orDie(searchUsers(search)) + }) + + 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 + } else if (search.length < SearchQueryTooShort.minimumLength) { + return yield* new UsersError({ + reason: new SearchQueryTooShort() + }) + } + yield* Effect.annotateCurrentSpan({ search }) + const normalized = search.toLowerCase() + return allUsers.filter((user) => + user.name.toLowerCase().includes(normalized) || user.email.toLowerCase().includes(normalized) + ) + }) + + const getById = Effect.fn("Users.getById")(function*(id: UserId) { + yield* Effect.annotateCurrentSpan({ id }) + const user = users.get(id) + if (user === undefined) { + return yield* new UsersError({ + reason: new UserNotFound() + }) + } + return user + }) + + const create = Effect.fn("Users.create")(function*(input: typeof User.jsonCreate.Type) { + const user = yield* makeUser(input) + users.set(user.id, user) + return user + }) + + 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/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Users/http.ts b/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Users/http.ts new file mode 100644 index 000000000..d60563d83 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/fixtures/server/Users/http.ts @@ -0,0 +1,80 @@ +import { Effect, Layer } from "effect" +import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi" +import { Api } from "../../api/Api.ts" +import { CurrentUser } from "../../api/Authorization.ts" +import { AuthorizationLayer } from "../Authorization.ts" +import { Users } from "../Users.ts" + +// 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.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 + ), + 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 + ) + ) + }), + 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) + ), + create: ({ payload }) => + users.create(payload).pipe( + Effect.orDie + // You could alse use Effect.unwrapReason to moves rror reasons up to + // the top level, so you can handle them with Effect.catch or + // Effect.catchTag etc. + // + // Effect.unwrapReason("UsersError"), + // Effect.catchTags({ + // UserNotFound: Effect.die, + // SearchQueryTooShort: Effect.die + // }) + ), + 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 + }) + }) +) + +// 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/.repos/effect/ai-docs/src/51_http-server/index.md b/.repos/effect/ai-docs/src/51_http-server/index.md new file mode 100644 index 000000000..e926d9960 --- /dev/null +++ b/.repos/effect/ai-docs/src/51_http-server/index.md @@ -0,0 +1,3 @@ +## Building HttpApi servers + +`HttpApi` gives you schema-first, type-safe HTTP APIs with runtime validation, typed clients, and OpenAPI docs from one definition. diff --git a/.repos/effect/ai-docs/src/60_child-process/10_working-with-child-processes.ts b/.repos/effect/ai-docs/src/60_child-process/10_working-with-child-processes.ts new file mode 100644 index 000000000..c7b1bbbcd --- /dev/null +++ b/.repos/effect/ai-docs/src/60_child-process/10_working-with-child-processes.ts @@ -0,0 +1,117 @@ +/** + * @title Working with child processes + * + * This example shows how to collect process output, compose pipelines, and stream long-running command output. + */ +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.TaggedError()("DevToolsError", { + cause: Schema.Defect() +}) {} + +export class DevTools extends Context.Service + readonly recentCommitSubjects: Effect.Effect, DevToolsError> + readonly runLintFix: Effect.Effect + changedTypeScriptFiles(baseRef: string): Effect.Effect, DevToolsError> +}>()("docs/DevTools") { + static readonly layer = Layer.effect( + DevTools, + Effect.gen(function*() { + // To run child processes, we need access to a `ChildProcessSpawner`. + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner + + // Use `spawner.string` when you want to collect the entire output of a + // command as a string. This runs `node --version` and collects the + // output. + const nodeVersion = spawner.string( + ChildProcess.make("node", ["--version"]) + ).pipe( + Effect.map(String.trim), + Effect.mapError((cause) => new DevToolsError({ cause })) + ) + + const changedTypeScriptFiles = Effect.fn("DevTools.changedTypeScriptFiles")(function*(baseRef: string) { + yield* Effect.annotateCurrentSpan({ baseRef }) + + // `spawner.lines` is a convenience helper for line-oriented command + // output. + const files = yield* spawner.lines( + ChildProcess.make("git", ["diff", "--name-only", `${baseRef}...HEAD`]) + ).pipe( + Effect.mapError((cause) => new DevToolsError({ cause })) + ) + + return files.filter((file) => file.endsWith(".ts")) + }) + + // Build a pipeline from two command values. This runs: + // `git log --pretty=format:%s -n 20 | head -n 5` + const recentCommitSubjects = spawner.lines( + ChildProcess.make("git", ["log", "--pretty=format:%s", "-n", "20"]).pipe( + ChildProcess.pipeTo(ChildProcess.make("head", ["-n", "5"])) + ) + ).pipe( + Effect.mapError((cause) => new DevToolsError({ cause })) + ) + + const runLintFix = Effect.gen(function*() { + // Use `spawn` when you want the process handle and stream output while + // the process is still running. + const handle = yield* spawner.spawn( + ChildProcess.make("pnpm", ["lint-fix"], { + env: { FORCE_COLOR: "1" }, + extendEnv: true + }) + ).pipe( + Effect.mapError((cause) => new DevToolsError({ cause })) + ) + + yield* handle.all.pipe( + Stream.decodeText(), + Stream.splitLines, + Stream.runForEach((line) => Console.log(`[lint-fix] ${line}`)), + Effect.mapError((cause) => new DevToolsError({ cause })) + ) + + const exitCode = yield* handle.exitCode.pipe( + Effect.mapError((cause) => new DevToolsError({ cause })) + ) + + if (exitCode !== ChildProcessSpawner.ExitCode(0)) { + return yield* new DevToolsError({ + cause: new Error(`pnpm lint-fix failed with exit code ${exitCode}`) + }) + } + }).pipe( + // `spawner.spawn` adds a `Scope` requirement to manage the lifecycle of + // the child process. We can use `Effect.scoped` to provide a `Scope` + // and close it when the effect completes. + Effect.scoped + ) + + return DevTools.of({ + nodeVersion, + changedTypeScriptFiles, + recentCommitSubjects, + runLintFix + }) + }) + ).pipe( + // Provide the `ChildProcessSpawner` dependency from `NodeServices.layer`. + Layer.provide(NodeServices.layer) + ) +} + +export const program = Effect.gen(function*() { + const tools = yield* DevTools + + const version = yield* tools.nodeVersion + yield* Effect.log(`node=${version}`) +}).pipe( + // `ChildProcess` requires a platform implementation of + // `ChildProcessSpawner`. In Node.js, `NodeServices.layer` provides it. + Effect.provide(DevTools.layer) +) diff --git a/.repos/effect/ai-docs/src/60_child-process/index.md b/.repos/effect/ai-docs/src/60_child-process/index.md new file mode 100644 index 000000000..ae0016bc3 --- /dev/null +++ b/.repos/effect/ai-docs/src/60_child-process/index.md @@ -0,0 +1,3 @@ +## Working with child processes + +Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner`. diff --git a/.repos/effect/ai-docs/src/70_cli/10_basics.ts b/.repos/effect/ai-docs/src/70_cli/10_basics.ts new file mode 100644 index 000000000..9d33b9db5 --- /dev/null +++ b/.repos/effect/ai-docs/src/70_cli/10_basics.ts @@ -0,0 +1,162 @@ +/** + * @title Getting started with Effect CLI modules + * + * Build a command-line app with typed arguments and flags, then wire subcommand + * handlers into a single executable command. + */ +import { NodeRuntime, NodeServices } from "@effect/platform-node" +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( + Flag.withAlias("w"), + Flag.withDescription("Workspace to operate on"), + Flag.withDefault("personal") +) + +// Start with a root command and explicitly share the parent flags that should +// be available to all subcommands. +const tasks = Command.make("tasks").pipe( + Command.withSharedFlags({ + workspace, + verbose: Flag.boolean("verbose").pipe( + Flag.withAlias("v"), + 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"), + // 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( + 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*({ assignee, priority, title }) { + // Subcommands can read parent command input by yielding the parent command. + const root = yield* tasks + + if (root.verbose) { + yield* Console.log(`workspace=${root.workspace} action=create`) + } + + 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"), + Command.withExamples([ + { + 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" + } + ]) +) + +const list = Command.make( + "list", + { + status: Flag.choice("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"), + Flag.withDefault(false) + ) + }, + Effect.fn(function*({ status, json }) { + const root = yield* tasks + const items = [ + { title: "Ship 4.0", status: "open" }, + { title: "Update onboarding guide", status: "done" } + ] as const + const filtered = status === "all" + ? items + : items.filter((item) => item.status === status) + + if (root.verbose) { + yield* Console.log(`workspace=${root.workspace} action=list`) + } + + if (json) { + yield* Console.log(JSON.stringify( + { + workspace: root.workspace, + status, + items: filtered + }, + null, + 2 + )) + return + } + + yield* Console.log(`Listing ${status} tasks in ${root.workspace}`) + if (filtered.length === 0) { + yield* Console.log("- No tasks found") + return + } + + for (const item of filtered) { + yield* Console.log(`- ${item.title}`) + } + }) +).pipe( + Command.withDescription("List tasks"), + Command.withAlias("ls"), + Command.withExamples([ + { + command: "tasks --workspace team-a list --status open", + description: "List open tasks in a specific workspace" + }, + { + command: "tasks --workspace team-b ls --status open", + description: "List open tasks in another workspace" + } + ]) +) + +// Finally, compose the subcommands into a single command and then run it. +tasks.pipe( + Command.withSubcommands([create, list]), + Command.run({ + version: "1.0.0" + }), + // Provide the services for the platform you are targeting. In this case, + // Node.js + Effect.provide(NodeServices.layer), + NodeRuntime.runMain +) diff --git a/.repos/effect/ai-docs/src/70_cli/index.md b/.repos/effect/ai-docs/src/70_cli/index.md new file mode 100644 index 000000000..96a3b756f --- /dev/null +++ b/.repos/effect/ai-docs/src/70_cli/index.md @@ -0,0 +1,5 @@ +## Building CLI applications + +Use the "effect/unstable/cli" modules to build CLI applications. These modules +provide utilities for parsing command-line arguments, handling user input, and +managing the flow of a CLI application. diff --git a/.repos/effect/ai-docs/src/71_ai/10_language-model.ts b/.repos/effect/ai-docs/src/71_ai/10_language-model.ts new file mode 100644 index 000000000..68e959426 --- /dev/null +++ b/.repos/effect/ai-docs/src/71_ai/10_language-model.ts @@ -0,0 +1,156 @@ +/** + * @title Using LanguageModel for text, objects, and streams + * + * Configure a provider once, then use `LanguageModel` for plain text + * generation, schema-validated object generation, and streaming responses. + */ +import { AnthropicClient, AnthropicLanguageModel } from "@effect/ai-anthropic" +import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai" +import { Config, Context, Effect, ExecutionPlan, Layer, Schema, Stream } from "effect" +import { AiError, LanguageModel, Model, type Response } from "effect/unstable/ai" +import { FetchHttpClient } from "effect/unstable/http" +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") +}).pipe( + // Providers typically require an HttpClient, but you can choose which one to + // use. + Layer.provide(FetchHttpClient.layer) +) + +const OpenAiClientLayer = OpenAiClient.layerConfig({ + apiKey: Config.redacted("OPENAI_API_KEY") +}).pipe( + Layer.provide(FetchHttpClient.layer) +) + +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 +}) { + static fromAiError(error: AiError.AiError) { + return new AiWriterError({ + reason: error.reason + }) + } +} + +// You can use `ExecutionPlan` to define a strategy for trying multiple +// providers with different configurations. In this example, we try a cheaper +// OpenAI model first, then fall back to a more expensive Anthropic model if the +// first one fails. +const DraftPlan = ExecutionPlan.make( + { + provide: OpenAiLanguageModel.model("gpt-5.2"), + // Attempt to use the openai model up to 3 times before falling back to the + // anthropic model. + attempts: 3 + }, + { + provide: AnthropicLanguageModel.model("claude-opus-4-6"), + attempts: 2 + } +) + +export class AiWriter extends Context.Service + extractLaunchPlan(notes: string): Effect.Effect + streamReleaseHighlights(version: string): Stream.Stream +}>()("docs/AiWriter") { + static readonly layer = Layer.effect( + AiWriter, + Effect.gen(function*() { + // Calling `captureRequirements` on an `ExecutionPlan` will move the + // requirements of the plan (in this case the ai clients) into the Layer + // requirements. + const draftsModel = yield* DraftPlan.captureRequirements + + // Use a different model for the launch plan extraction + const launchPlanModel = yield* OpenAiLanguageModel.model("gpt-4.1").captureRequirements + + const draftAnnouncement = Effect.fn("AiWriter.draftAnnouncement")( + function*(product: string) { + const model = yield* LanguageModel.LanguageModel + const provider = yield* Model.ProviderName + const response = yield* model.generateText({ + prompt: `Write a short launch announcement for ${product}. ` + + "Keep it concise and include one concrete user benefit." + }) + + // `LanguageModel.generateText` exposes convenience fields so you can + // inspect usage and finish reason without parsing content parts. + yield* Effect.logInfo( + `${provider} finished with ${response.finishReason}. outputTokens=${response.usage.outputTokens.total}` + ) + + return { + provider, + text: response.text + } + }, + // To apply an `ExecutionPlan`, we use `Effect.withExecutionPlan` + Effect.withExecutionPlan(draftsModel), + // Map AiError into our custom error type + Effect.mapError((error) => AiWriterError.fromAiError(error)) + ) + + const extractLaunchPlan = Effect.fn("AiWriter.extractLaunchPlan")( + function*(notes: string) { + const model = yield* LanguageModel.LanguageModel + const response = yield* model.generateObject({ + objectName: "launch_plan", + prompt: + "Convert these notes into a launch plan object with audience, channels, launchDate, summary, and keyRisks:\n" + + notes, + // The generated object is validated and decoded through this schema. + schema: LaunchPlan + }) + + return response.value + }, + // The .model(...) apis return a Layer that can be used with + // Effect.provide + Effect.provide(launchPlanModel), + // Map AiError into our custom error type + Effect.mapError((error) => AiWriterError.fromAiError(error)) + ) + + const streamReleaseHighlights = (version: string) => + LanguageModel.streamText({ + prompt: `Write release highlights for version ${version} as a short bulleted list.` + }).pipe( + Stream.filter((part): part is Response.TextDeltaPart => part.type === "text-delta"), + Stream.map((part) => part.delta), + Stream.provide(launchPlanModel), + // Map AiError into our custom error type + Stream.mapError((error) => AiWriterError.fromAiError(error)) + ) + + return AiWriter.of({ + draftAnnouncement, + extractLaunchPlan, + streamReleaseHighlights + }) + }) + ).pipe( + // This Layer has requirements for both the OpenAI and Anthropic clients, + // since the ExecutionPlan includes models from both providers. + Layer.provide([OpenAiClientLayer, AnthropicClientLayer]) + ) +} + +// We can now use `AiWriter` like any other Effect service. +export const program: Effect.Effect< + void, + AiWriterError, + AiWriter +> = Effect.gen(function*() { + const writer = yield* AiWriter + yield* writer.draftAnnouncement("Effect Cloud") +}) diff --git a/.repos/effect/ai-docs/src/71_ai/20_tools.ts b/.repos/effect/ai-docs/src/71_ai/20_tools.ts new file mode 100644 index 000000000..599178368 --- /dev/null +++ b/.repos/effect/ai-docs/src/71_ai/20_tools.ts @@ -0,0 +1,226 @@ +/** + * @title Defining and using AI tools + * + * Define tools with schemas, group them into toolkits, implement handlers, + * and pass them to `LanguageModel.generateText`. + */ +import { OpenAiClient, OpenAiLanguageModel, OpenAiTool } from "@effect/ai-openai" +import { Config, Context, Effect, Layer, Schema } from "effect" +import { AiError, LanguageModel, Tool, Toolkit } from "effect/unstable/ai" +import { FetchHttpClient } from "effect/unstable/http" + +// --------------------------------------------------------------------------- +// 1. Defining tools +// --------------------------------------------------------------------------- + +const ProductId = Schema.String.pipe(Schema.brand("ProductId")).annotate({ + description: "A unique identifier for a product, e.g. 'p-123'" +}) + +class Product extends Schema.Class("acme/domain/Product")({ + id: ProductId, + name: Schema.String, + price: Schema.Finite +}) {} + +// Each tool has a name, an optional description, a parameters schema that the +// model fills in, and a success schema for the handler result. The description +// is shown to the model to help it decide when to call the tool. +const SearchProducts = Tool.make("SearchProducts", { + description: "Search the product catalog by keyword", + parameters: Schema.Struct({ + query: Schema.String.annotate({ + // Add a description to individual parameters for even better model + // guidance. + description: "The search query, e.g. 'wireless headphones'" + }), + maxResults: Schema.Natural.pipe(Schema.withDecodingDefault(Effect.succeed(10))).annotate({ + description: "The maximum number of results to return" + }) + }), + success: Schema.Array(Product), + // The strategy used for handling errors returned from tool call handler + // execution. + // + // If set to `"error"` (the default), errors that occur during tool call handler + // execution will be returned in the error channel of the calling effect. + // + // If set to `"return"`, errors that occur during tool call handler execution + // will be captured and returned as part of the tool call result. + failureMode: "error" +}) + +const GetInventory = Tool.make("GetInventory", { + description: "Check current stock level for a product", + parameters: Schema.Struct({ + productId: ProductId + }), + success: Schema.Struct({ + productId: ProductId, + available: Schema.Natural + }) +}) + +// --------------------------------------------------------------------------- +// 2. Grouping tools into a Toolkit +// --------------------------------------------------------------------------- + +// `Toolkit.make` accepts any number of tools and produces a typed toolkit that +// knows the names and schemas of every tool it contains. +const ProductToolkit = Toolkit.make(SearchProducts, GetInventory) + +// --------------------------------------------------------------------------- +// 3. Implementing handlers via toLayer +// --------------------------------------------------------------------------- + +// `toLayer` returns a `Layer` that satisfies the handler requirements for every +// tool in the toolkit. Each handler receives the decoded parameters and returns +// an Effect producing the success type. +const ProductToolkitLayer = ProductToolkit.toLayer(Effect.gen(function*() { + yield* Effect.log("Initializing ProductToolkitLive") + // Here you could access other services or resources needed to implement the + // handlers, e.g. a database client or external API client. + // + // const client = yield* SomeDatabaseClient + return ProductToolkit.of({ + SearchProducts: Effect.fn("ProductToolkit.SearchProducts")(function*({ query, maxResults }) { + return [ + new Product({ id: ProductId.make("p-1"), name: `${query} widget`, price: 19.99 }), + new Product({ id: ProductId.make("p-2"), name: `${query} gadget`, price: 29.99 }) + ].slice(0, maxResults) + }), + GetInventory: Effect.fn("ProductToolkit.GetInventory")(function*({ productId }) { + return { productId, available: 42 } + }) + }) +})) + +// --------------------------------------------------------------------------- +// 4. Using tools with LanguageModel +// --------------------------------------------------------------------------- + +// Provider setup (same pattern as the language-model example). +const OpenAiClientLayer = OpenAiClient.layerConfig({ + apiKey: Config.redacted("OPENAI_API_KEY") +}).pipe(Layer.provide(FetchHttpClient.layer)) + +export class ProductAssistantError extends Schema.TaggedError()( + "ProductAssistantError", + { reason: AiError.AiErrorReason } +) {} + +// Wrap tool-enabled generation in a service +export class ProductAssistant extends Context.Service +}>()("docs/ProductAssistant") { + static readonly layer = Layer.effect( + ProductAssistant, + Effect.gen(function*() { + // Access the toolkit's handlers by yielding the toolkit definition. + const toolkit = yield* ProductToolkit + + // Choose a model to use + const model = yield* OpenAiLanguageModel.model("gpt-5.2").captureRequirements + + const answer = Effect.fn("ProductAssistant.answer")( + function*(question: string) { + // Pass the toolkit to `generateText`. The model can call any tool in + // the toolkit; the framework resolves parameters, invokes handlers, + // and feeds results back automatically. + const response = yield* LanguageModel.generateText({ + prompt: question, + toolkit, + // You can set `toolChoice` to "required" to force the model to call + // a tool before responding with text. + // + // By default it is set to "auto" + toolChoice: "required" + }) + + // ------------------------------------------------------------------- + // 5. Inspecting tool calls and results + // ------------------------------------------------------------------- + + // `response.toolCalls` lists every tool the model invoked, each with + // the tool name, a unique id, and the decoded parameters. + for (const call of response.toolCalls) { + yield* Effect.log(`Tool call: ${call.name} id=${call.id}`) + } + + // `response.toolResults` lists the resolved results, each with the + // tool name, id, decoded result, and an `isFailure` flag. + for (const result of response.toolResults) { + yield* Effect.log( + `Tool result: ${result.name} id=${result.id} isFailure=${result.isFailure}` + ) + } + + return { + text: response.text, + toolCallCount: response.toolCalls.length + } + }, + // Provide the chosen model to use + Effect.provide(model), + (_) => _, + // Map AI errors into our domain error type + Effect.catchTag( + "AiError", + (error) => + Effect.fail( + new ProductAssistantError({ + reason: error.reason + }) + ), + // For unexpected errors, die with the original error + (e) => Effect.die(e) + ) + ) + + return ProductAssistant.of({ answer }) + }) + ).pipe( + // The toolkit handler layer must be provided so the framework can invoke + // the tool handlers when the model makes tool calls. + Layer.provide(ProductToolkitLayer), + // Also provide the openai client required by OpenAiLanguageModel.model + Layer.provide(OpenAiClientLayer) + ) +} + +// --------------------------------------------------------------------------- +// 6. Provider-defined tools +// --------------------------------------------------------------------------- + +// Some providers offer built-in tools (web search, code interpreter, etc.) +// that run server-side. Use `Tool.providerDefined` or the pre-built +// definitions from provider packages. + +// OpenAI's web search tool is pre-defined in `@effect/ai-openai`. Calling it +// produces a tool instance that can be merged into any toolkit. +const webSearch = OpenAiTool.WebSearch({ + search_context_size: "medium" +}) + +// Combine user-defined and provider-defined tools in a single toolkit. +const AssistantToolkit = Toolkit.make(SearchProducts, GetInventory, webSearch) + +// Only user-defined tools that require handlers appear in `toLayer`. The +// provider-defined `WebSearch` is executed server-side by the provider. +export const AssistantToolkitLayer = AssistantToolkit.toLayer(Effect.gen(function*() { + yield* Effect.log("Initializing AssistantToolkitLive") + return AssistantToolkit.of({ + SearchProducts: Effect.fn("AssistantToolkit.SearchProducts")(function*({ query, maxResults }) { + return [ + new Product({ id: ProductId.make("p-1"), name: `${query} widget`, price: 19.99 }), + new Product({ id: ProductId.make("p-2"), name: `${query} gadget`, price: 29.99 }) + ].slice(0, maxResults) + }), + GetInventory: Effect.fn("AssistantToolkit.GetInventory")(function*({ productId }) { + return { productId, available: 42 } + }) + }) +})) diff --git a/.repos/effect/ai-docs/src/71_ai/30_chat.ts b/.repos/effect/ai-docs/src/71_ai/30_chat.ts new file mode 100644 index 000000000..f90ea1af6 --- /dev/null +++ b/.repos/effect/ai-docs/src/71_ai/30_chat.ts @@ -0,0 +1,158 @@ +/** + * @title Stateful chat sessions + * + * The AI `Chat` module maintains conversation history automatically. Build + * AI agents or chat assistants. + */ +import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai" +import { Config, Context, DateTime, Effect, Layer, Ref, Schema } from "effect" +import { AiError, Chat, Prompt, Tool, Toolkit } from "effect/unstable/ai" +import { FetchHttpClient } from "effect/unstable/http" + +// --------------------------------------------------------------------------- +// Provider setup +// --------------------------------------------------------------------------- + +const OpenAiClientLayer = OpenAiClient.layerConfig({ + apiKey: Config.redacted("OPENAI_API_KEY") +}).pipe(Layer.provide(FetchHttpClient.layer)) + +// --------------------------------------------------------------------------- +// Tools for the agentic loop +// --------------------------------------------------------------------------- + +const Tools = Toolkit.make(Tool.make("getCurrentTime", { + description: "Get the current time in ISO format", + parameters: Schema.Struct({ + id: Schema.String + }), + success: Schema.String +})) + +const ToolsLayer = Tools.toLayer(Effect.gen(function*() { + yield* Effect.logDebug("Initializing tools...") + return Tools.of({ + getCurrentTime: Effect.fn("Tools.getCurrentTime")(function*(_) { + const now = yield* DateTime.now + return DateTime.formatIso(now) + }) + }) +})) + +// --------------------------------------------------------------------------- +// Service that wraps Chat for a domain use-case +// --------------------------------------------------------------------------- + +export class AiAssistantError extends Schema.TaggedError()("AiAssistantError", { + reason: AiError.AiErrorReason +}) { + static fromAiError(error: AiError.AiError) { + return new AiAssistantError({ reason: error.reason }) + } +} + +export class AiAssistant extends Context.Service + // Ask a question and use an agentic loop with tool calls to answer it. + agent(question: string): Effect.Effect +}>()("acme/AiAssistant") { + static readonly layer = Layer.effect( + AiAssistant, + Effect.gen(function*() { + // Choose the model you want to use for the chat sessions. + const modelLayer = yield* OpenAiLanguageModel.model("gpt-5.2").captureRequirements + + // --------------------------------------------------------------------------- + // 1. Chat.empty — basic multi-turn conversation + // --------------------------------------------------------------------------- + + // Create a new chat session with `Chat.empty` or `Chat.fromPrompt`. The + // session maintains conversation history automatically, so you can focus on + // the current turn without having to manage context. + const newSession = yield* Chat.fromPrompt(Prompt.empty.pipe( + Prompt.setSystem("You are a helpful assistant that answers questions.") + )) + + // You can also create a chat using a json export. + const json = yield* newSession.exportJson + const session = yield* Chat.fromJson(json) + + const chat = Effect.fn("AiAssistant.chat")( + function*(message: string) { + // Create a new turn in the conversation by passing the user's message + // to `session.generateText`. + const response = yield* session.generateText({ prompt: message }).pipe( + // Provide the model layer to use. + // You could potentially use different models for different turns, + // or even switch models in the middle of a conversation. + Effect.provide(modelLayer) + ) + + // You can inspect the accumulated history at any point through the + // `history` ref on the chat instance. + const history = yield* Ref.get(session.history) + yield* Effect.logInfo( + `Conversation has ${history.content.length} messages` + ) + + return response.text + }, + Effect.mapError((error) => AiAssistantError.fromAiError(error)) + ) + + // --------------------------------------------------------------------------- + // 2. Create agentic loops with tools + // --------------------------------------------------------------------------- + + const tools = yield* Tools + const agent = Effect.fn("AiAssistant.agent")( + function*(question: string) { + // We start the agent with a system prompt and the user question. The + // agent can then call tools in a loop until it decides to return a + // final answer. + const session = yield* Chat.fromPrompt([ + { role: "system", content: "You are an assistant that can use tools to answer questions." }, + { role: "user", content: question } + ]) + + while (true) { + const response = yield* session.generateText({ + prompt: [], // No additional prompt — the model has full access to the conversation history + toolkit: tools // Provide the tools to the model + }).pipe( + // Provide the model layer to use. + // You could potentially use different models for different turns, + // or even switch models in the middle of a conversation. + Effect.provide(modelLayer) + ) + if (response.toolCalls.length > 0) { + // If the model called any tools, execute them and the Chat module + // will automatically add the tool results to the conversation + // history before the next turn. + continue + } + // If there are no tool calls, the model has returned a final answer + // and we can exit the loop. + return response.text + } + }, + // Remap AI errors to our domain-specific error type, but die on + // unexpected errors. + Effect.catchTag( + "AiError", + (error) => Effect.fail(AiAssistantError.fromAiError(error)), + (e) => Effect.die(e) + ) + ) + + return AiAssistant.of({ + chat, + agent + }) + }) + ).pipe( + // Provide the OpenAI client and tools layers to the AiAssistant service. + Layer.provide([OpenAiClientLayer, ToolsLayer]) + ) +} diff --git a/.repos/effect/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts b/.repos/effect/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts new file mode 100644 index 000000000..6dd8d33c6 --- /dev/null +++ b/.repos/effect/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts @@ -0,0 +1,9 @@ +import { Schema } from "effect" + +export class LaunchPlan extends Schema.Class("LaunchPlan")({ + audience: Schema.Literals(["developers", "operators", "platform teams"]), + channels: Schema.Array(Schema.String), + launchDate: Schema.String, + summary: Schema.String, + keyRisks: Schema.Array(Schema.String) +}) {} diff --git a/.repos/effect/ai-docs/src/71_ai/index.md b/.repos/effect/ai-docs/src/71_ai/index.md new file mode 100644 index 000000000..425f4b46b --- /dev/null +++ b/.repos/effect/ai-docs/src/71_ai/index.md @@ -0,0 +1,5 @@ +## Working with AI modules + +Effect's AI modules provide a provider-agnostic interface for language models. +You can generate text, decode structured objects with `Schema` and stream partial +responses. diff --git a/.repos/effect/ai-docs/src/80_cluster/10_entities.ts b/.repos/effect/ai-docs/src/80_cluster/10_entities.ts new file mode 100644 index 000000000..466197f9b --- /dev/null +++ b/.repos/effect/ai-docs/src/80_cluster/10_entities.ts @@ -0,0 +1,97 @@ +/** + * @title Defining cluster entities + * + * Define distributed entity RPCs and run them in a cluster. + */ +import { NodeClusterSocket, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Ref, Schema } from "effect" +import { ClusterSchema, Entity, TestRunner } from "effect/unstable/cluster" +import { Rpc } from "effect/unstable/rpc" +import type { SqlClient } from "effect/unstable/sql" + +export const Increment = Rpc.make("Increment", { + payload: { amount: Schema.Int }, + success: Schema.Int +}) + +export const GetCount = Rpc.make("GetCount", { + success: Schema.Int +}) + // If you want GetCount messages to be persisted, you can annotate the RPC + // schema with `ClusterSchema.Persisted`. + // + // By default, messages are volatile and only sent over a network. + .annotate(ClusterSchema.Persisted, true) + +// `Entity.make` takes an array of Rpc definitions +export const Counter = Entity.make("Counter", [Increment, GetCount]) + +// Entity handlers can keep in-memory state while the entity is active. +// `maxIdleTime` controls passivation: if the entity is idle long enough, it is +// stopped and later recreated on demand. +export const CounterEntityLayer = Counter.toLayer( + Effect.gen(function*() { + const count = yield* Ref.make(0) + + return Counter.of({ + Increment: ({ payload }) => Ref.updateAndGet(count, (current) => current + payload.amount), + GetCount: () => + Ref.get(count).pipe( + // Add Rpc.fork to allow the GetCount handler to run concurrently with + // Increment handlers. + // + // This opts-out of the default behavior where all handlers for a + // given entity run sequentially. + Rpc.fork + ) + }) + }), + { maxIdleTime: "5 minutes" } +) + +// If you ever need to access an entity client, you can use the `client` +// property on the entity definition. +export const useCounter = Effect.gen(function*() { + const clientFor = yield* Counter.client + const counter = clientFor("counter-123") + + const afterIncrement = yield* counter.Increment({ amount: 1 }) + const currentCount = yield* counter.GetCount() + + console.log(`Count after increment: ${afterIncrement}, current count: ${currentCount}`) +}) + +// `SingleRunner.layer` is useful for local development / tests where you still +// want the cluster entity runtime model. +declare const SqlClientLayer: Layer.Layer + +// Create the cluster layer using `NodeClusterSocket.layer` +const ClusterLayer = NodeClusterSocket.layer().pipe( + Layer.provide(SqlClientLayer) +) + +// You can also use `TestRunner.layer` to run your entities in a single process, +// without any network communication and in-memory storage. This is useful for testing and +// development. +const ClusterLayerTest = TestRunner.layer + +// Merge all your entity layers together and provide the cluster layer to run +// them in a cluster. +const EntitiesLayer = Layer.mergeAll( + CounterEntityLayer +) + +const ProductionLayer = EntitiesLayer.pipe( + Layer.provide(ClusterLayer) +) + +export const TestLayer = EntitiesLayer.pipe( + // For testing, we can use `Layer.provideMerge` to tests can access storage + // and other cluster services directly. + Layer.provideMerge(ClusterLayerTest) +) + +// Finally, run your app with the entities layer. +Layer.launch(ProductionLayer).pipe( + NodeRuntime.runMain +) diff --git a/.repos/effect/ai-docs/src/80_cluster/index.md b/.repos/effect/ai-docs/src/80_cluster/index.md new file mode 100644 index 000000000..1cc42cb54 --- /dev/null +++ b/.repos/effect/ai-docs/src/80_cluster/index.md @@ -0,0 +1,4 @@ +## Building distributed applications with cluster + +The cluster modules let you model stateful services as entities and distribute +them across multiple machines. diff --git a/.repos/effect/ai-docs/src/index.md b/.repos/effect/ai-docs/src/index.md new file mode 100644 index 000000000..0a0213bf7 --- /dev/null +++ b/.repos/effect/ai-docs/src/index.md @@ -0,0 +1,10 @@ +# Effect library documentation + +This documentation covers the Effect library and its related packages. + +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/.repos/effect/ai-docs/tsconfig.json b/.repos/effect/ai-docs/tsconfig.json new file mode 100644 index 000000000..e5be14e9b --- /dev/null +++ b/.repos/effect/ai-docs/tsconfig.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../tsconfig.base.json", + "include": ["src/**/*.ts", "src/**/*.tsx"], + "compilerOptions": { + "rootDir": ".", + "noEmit": true, + "declaration": false, + "declarationMap": false, + "composite": false, + "incremental": false, + "types": ["node"], + "plugins": [ + { + "name": "@effect/language-service", + "namespaceImportPackages": [], + "diagnosticSeverity": { + "floatingEffect": "off", + "preferSchemaOverJson": "off" + } + } + ] + } +} diff --git a/.repos/effect/cookbooks/schedule.md b/.repos/effect/cookbooks/schedule.md new file mode 100644 index 000000000..2ff56da20 --- /dev/null +++ b/.repos/effect/cookbooks/schedule.md @@ -0,0 +1,563 @@ +# 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` to preserve the + latest input, or `Schedule.map` to derive a new output from schedule metadata. +- `Schedule.max` continues only while all schedules continue and outputs the slowest delay. +- `Schedule.min` continues while any schedule can continue and outputs the fastest delay. +- `Schedule.jittered` spreads callers out. It does not add a recurrence limit. +- `Schedule.addDelay` adds extra delay based on schedule metadata. +- `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` | +| Adapt delay from metadata | `Schedule.addDelay` | +| Replace or cap selected delay | `Schedule.modifyDelay` | +| Run phases in sequence | `Schedule.concat` | +| Preserve phase in output | `Schedule.concatResult` | +| Continue while all policies continue | `Schedule.max` | +| Continue while any policy continues | `Schedule.min` | +| Shape output from metadata | `Schedule.map` | +| Observe decisions without changing output | `Schedule.tap` | + +## 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. [Adapt Delays](#adapt-delays) +6. [Observe Schedule Decisions](#observe-schedule-decisions) +7. [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.upTo({ times: 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.upTo({ times: 3 }) +) +``` + +### Label Retry Attempts + +Goal: Create a retry-attempt schedule that turns recurrence count metadata into +labels such as `attempt-1`, `attempt-2`, and `attempt-3`. + +```ts +import { Schedule } from "effect" + +const retryAttemptLabels = Schedule.recurs(3).pipe( + Schedule.map(({ output: count }) => `attempt-${count + 1}`) +) +``` + +Explanation: `Schedule.map` receives the full step metadata. Destructure +`output` when you only need the schedule output, or use fields such as `input`, +`attempt`, `duration`, and `elapsed` when the new output needs more context. + +## Combine Policies + +### Add Jitter To Webhook Backoff + +Goal: Create a webhook retry backoff that starts at 200 milliseconds, adds +jitter, and takes 3 outputs. + +```ts +import { Schedule } from "effect" + +const jitteredWebhookBackoff = Schedule.exponential("200 millis").pipe( + Schedule.jittered, + Schedule.upTo({ times: 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.max([ + Schedule.exponential("200 millis").pipe(Schedule.jittered), + Schedule.recurs(5), + Schedule.during("20 seconds") +]) +``` + +Explanation: `Schedule.max` stops when any schedule stops and outputs the +slowest selected delay for each recurrence. + +### Continue While Any 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, +using the fastest selected delay. + +```ts +import { Schedule } from "effect" + +const readinessWarmupOrSlowProbe = Schedule.min([ + Schedule.recurs(2), + Schedule.spaced("500 millis").pipe(Schedule.upTo({ times: 5 })) +]) +``` + +Explanation: `Schedule.min` keeps recurring while at least one schedule can +continue and outputs the fastest selected delay among schedules that are still +recurring. + +### 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.upTo({ times: 2 }), + Schedule.concat(Schedule.spaced("30 seconds").pipe(Schedule.upTo({ times: 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.upTo({ times: 2 }), + Schedule.concatResult(Schedule.fibonacci("500 millis").pipe(Schedule.upTo({ times: 3 }))), + Schedule.map(({ output: result }) => + Result.match(result, { + onFailure: (delay) => ({ phase: "fast", delay }), + onSuccess: (delay) => ({ phase: "steady", delay }) + }) + ) +) +``` + +Explanation: `Schedule.concatResult` keeps phase information in the output. +The first schedule is represented by the failure side, and the second schedule +is represented by the success 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. + +## 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(({ output: snapshot }) => Effect.succeed(snapshot.depth > 1000 ? "5 seconds" : "500 millis")), + Schedule.upTo({ times: 10 }) +) +``` + +Explanation: `Schedule.addDelay` receives the full step metadata and adds the +returned delay to the selected delay. 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.max([ + Schedule.exponential("100 millis").pipe( + Schedule.jittered, + Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.min(duration, Duration.seconds(5)))) + ), + Schedule.recurs(8) +]) +``` + +Explanation: `Schedule.modifyDelay` receives the full step metadata, including +the selected delay as `duration`, 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.tap(({ input }) => Console.log(`heartbeat:${input.id}`)), + Schedule.passthrough, + Schedule.upTo({ times: 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.upTo({ times: 5 }), + Schedule.tap(({ output: 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.upTo({ times: 5 }), + Schedule.tap(({ attempt, output }) => Console.log(`attempt-${attempt}: ${Duration.toMillis(output)}ms`)) +) +``` + +Explanation: use `Schedule.tap` to observe inputs, outputs, and metadata such as +attempt number or selected duration without changing the schedule output. + +## 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.max([ + Schedule.exponential("100 millis").pipe( + Schedule.jittered, + Schedule.setInputType(), + Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.min(duration, Duration.seconds(2)))) + ), + Schedule.recurs(6) +]).pipe( + Schedule.while(({ input }) => isRetryableGraphqlGatewayError(input)) +) +``` + +### 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 { Duration, Schedule } from "effect" + +type RolloutStatus = { + readonly state: "running" | "succeeded" | "failed" +} + +const rolloutStatusWatcher = Schedule.fixed("1 second").pipe( + Schedule.setInputType(), + Schedule.passthrough, + Schedule.jittered, + Schedule.while(({ input, elapsed }) => + input.state === "running" && + Duration.isLessThanOrEqualTo(Duration.millis(elapsed), Duration.minutes(2)) + ) +) +``` + +### 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.max([ + Schedule.exponential("1 second").pipe( + Schedule.setInputType(), + Schedule.passthrough, + Schedule.modifyDelay(({ output: response, duration }) => + Effect.succeed( + Duration.min( + response.retryAfter === undefined + ? duration + : Duration.max(duration, response.retryAfter), + Duration.minutes(1) + ) + ) + ) + ), + Schedule.recurs(6) +]).pipe( + Schedule.while(({ input }) => input.status === 429 || input.status === 500 || input.status === 503) +) +``` + +### 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 { Duration, 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(({ output: status }) => Effect.succeed(status.error === "slow_down" ? "5 seconds" : "0 millis")), + Schedule.while(({ input, elapsed }) => + (input.error === "authorization_pending" || input.error === "slow_down") && + Duration.isLessThanOrEqualTo(Duration.millis(elapsed), Duration.minutes(15)) + ) +) +``` + +### 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.upTo({ times: 3 }), + Schedule.concat(Schedule.spaced("5 minutes").pipe(Schedule.upTo({ times: 3 }))), + Schedule.concat(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.concat(Schedule.cron("0 3 * * *")) +) +``` diff --git a/.repos/effect/deno.json b/.repos/effect/deno.json new file mode 100644 index 000000000..38de0651a --- /dev/null +++ b/.repos/effect/deno.json @@ -0,0 +1,35 @@ +{ + "$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/*", "./packages/platform/*"], + "exclude": [ + "**/*.mjs", + "**/*.cjs", + "**/*.js", + "**/*.d.ts", + "**/vitest.*.ts", + "**/.tsbuildinfo/", + "**/node_modules/", + "**/dist/", + "ai-docs/", + "bundle/", + "examples/", + "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/tools/", + "packages/sql" + ] +} diff --git a/.repos/effect/docker-compose.yaml b/.repos/effect/docker-compose.yaml new file mode 100644 index 000000000..1a41e02f1 --- /dev/null +++ b/.repos/effect/docker-compose.yaml @@ -0,0 +1,18 @@ +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/.repos/effect/dprint.json b/.repos/effect/dprint.json new file mode 100644 index 000000000..a47e1f94a --- /dev/null +++ b/.repos/effect/dprint.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://dprint.dev/schemas/v0.json", + "incremental": false, + "includes": ["**/*.{ts,tsx,js,jsx,json,md}"], + "indentWidth": 2, + "lineWidth": 120, + "newLineKind": "lf", + "typescript": { + "semiColons": "asi", + "quoteStyle": "alwaysDouble", + "trailingCommas": "never", + "operatorPosition": "maintain", + "arrowFunction.useParentheses": "force" + }, + "excludes": [ + "LLMS.md", + "**/dist", + "**/build", + "**/docs", + "**/coverage", + "packages/**/CHANGELOG.md", + "packages/effect/src/StandardSchema.ts", + "!scratchpad/**/*", + ".changeset", + ".agents", + ".context", + ".specs" + ], + "plugins": [ + "https://plugins.dprint.dev/typescript-0.93.4.wasm", + "https://plugins.dprint.dev/markdown-0.20.0.wasm", + "https://plugins.dprint.dev/json-0.21.1.wasm" + ] +} diff --git a/.repos/effect/flake.lock b/.repos/effect/flake.lock new file mode 100644 index 000000000..de3033b7a --- /dev/null +++ b/.repos/effect/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1786534138, + "narHash": "sha256-fBJMdnKUTUDtfi/BYLr71HLaC9dG382arxLF2Egg2uo=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "044bfe75bfe4c7bbe043dc17b5e42ea823b84a09", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/.repos/effect/flake.nix b/.repos/effect/flake.nix new file mode 100644 index 000000000..664d14810 --- /dev/null +++ b/.repos/effect/flake.nix @@ -0,0 +1,24 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + }; + outputs = {nixpkgs, ...}: let + forAllSystems = function: + nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed ( + system: function nixpkgs.legacyPackages.${system} + ); + in { + formatter = forAllSystems (pkgs: pkgs.alejandra); + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + packages = with pkgs; [ + bun + deno + nodejs_latest + pnpm + python3 + ]; + }; + }); + }; +} diff --git a/.repos/effect/jsdocs.config.json b/.repos/effect/jsdocs.config.json new file mode 100644 index 000000000..f31bd337c --- /dev/null +++ b/.repos/effect/jsdocs.config.json @@ -0,0 +1,17 @@ +{ + "tsconfig": "tsconfig.packages.json", + "include": [ + "packages/**/src/*.ts", + "packages/**/src/**/*.ts" + ], + "exclude": [ + "**/node_modules/**", + "packages/tools/**", + "packages/**/src/index.ts", + "packages/effect/src/StandardSchema.ts", + "packages/**/src/*Generated.ts", + "packages/**/src/internal/**", + "packages/**/src/**/internal/**" + ], + "output": ".data/jsdocs.json" +} diff --git a/.repos/effect/migration/annotations/README.md b/.repos/effect/migration/annotations/README.md new file mode 100644 index 000000000..d78e65871 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Arbitrary.yaml b/.repos/effect/migration/annotations/effect__Arbitrary.yaml new file mode 100644 index 000000000..8410a4a18 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__Arbitrary.yaml @@ -0,0 +1,17 @@ +"effect/Arbitrary#ArbitraryAnnotation": + replacement: "Schema.Annotations.ToArbitrary.Declaration" + note: "Arbitrary derivation annotations now live in Schema.Annotations and use the toArbitrary key." +"effect/Arbitrary#ArbitraryGenerationContext": + replacement: "Schema.Annotations.ToArbitrary.Context" + note: "Use the v4 arbitrary-derivation context type from Schema.Annotations." +"effect/Arbitrary#LazyArbitrary": + replacement: "Schema.Arbitrary" + note: "The arbitrary factory type moved onto Schema." +"effect/Arbitrary#make": + replacement: "Schema.toArbitrary" + note: "Arbitrary derivation is now exposed directly by Schema." + example: "Schema.toArbitrary(schema)(FastCheck)" +"effect/Arbitrary#makeLazy": + replacement: "Schema.toArbitrary" + note: "Lazy arbitrary derivation is now exposed directly by Schema." + example: "Schema.toArbitrary(schema)" diff --git a/.repos/effect/migration/annotations/effect__Array.yaml b/.repos/effect/migration/annotations/effect__Array.yaml new file mode 100644 index 000000000..930975b29 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__BigDecimal.yaml b/.repos/effect/migration/annotations/effect__BigDecimal.yaml new file mode 100644 index 000000000..19b9fe387 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__BigInt.yaml b/.repos/effect/migration/annotations/effect__BigInt.yaml new file mode 100644 index 000000000..a0fc52841 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Brand.yaml b/.repos/effect/migration/annotations/effect__Brand.yaml new file mode 100644 index 000000000..9d07fa7aa --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Cache.yaml b/.repos/effect/migration/annotations/effect__Cache.yaml new file mode 100644 index 000000000..c696acbc9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Cause.yaml b/.repos/effect/migration/annotations/effect__Cause.yaml new file mode 100644 index 000000000..cc4611e8a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Channel.yaml b/.repos/effect/migration/annotations/effect__Channel.yaml new file mode 100644 index 000000000..2b8ea573b --- /dev/null +++ b/.repos/effect/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.runDone" + note: "Renamed to runDone for an inputless, outputless channel. Use runDrain if emitted elements should be discarded." +"effect/Channel#runScoped": + replacement: "Channel.toPull" + note: "No direct scoped runner remains. Use toPull in the caller scope and recover Cause.Done; use runDone or 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/.repos/effect/migration/annotations/effect__ChildExecutorDecision.yaml b/.repos/effect/migration/annotations/effect__ChildExecutorDecision.yaml new file mode 100644 index 000000000..e8bfb105b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Chunk.yaml b/.repos/effect/migration/annotations/effect__Chunk.yaml new file mode 100644 index 000000000..2771afb04 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Clock.yaml b/.repos/effect/migration/annotations/effect__Clock.yaml new file mode 100644 index 000000000..510486a47 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Config.yaml b/.repos/effect/migration/annotations/effect__Config.yaml new file mode 100644 index 000000000..122eb5371 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__Config.yaml @@ -0,0 +1,111 @@ +"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.schema(Config.Array(valueSchema), path)" + note: "Array parsing is schema-based in v4; rebuild the element Config as a Schema and pass the optional path to Config.schema." +"effect/Config#boolean": + replacement: "Config.boolean" + note: "Unchanged." +"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: "Unchanged." +"effect/Config#duration": + replacement: "Config.duration" + note: "Unchanged." +"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." +"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: "Unchanged." +"effect/Config#mapAttempt": + replacement: "Config.mapOrFail" + note: "Catch exceptions explicitly and return an Effect failure containing Config.ConfigError; mapOrFail is Effect-based in v4." +"effect/Config#nonEmptyString": + replacement: "Config.nonEmptyString" + note: "Unchanged." +"effect/Config#number": + replacement: "Config.number" + note: "Unchanged; 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: "Unchanged." +"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.schema(Config.Array(valueSchema), path)" + note: "Repeated values are represented by an array Schema in v4; Config.Array also accepts flat separated input." +"effect/Config#secret": + replacement: "Config.redacted" + note: "Secret was removed in favor of Redacted; this constructor already returns Redacted." +"effect/Config#string": + replacement: "Config.string" + note: "Unchanged." +"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: "Unchanged." +"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/.repos/effect/migration/annotations/effect__ConfigError.yaml b/.repos/effect/migration/annotations/effect__ConfigError.yaml new file mode 100644 index 000000000..51146f3ab --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ConfigProvider.yaml b/.repos/effect/migration/annotations/effect__ConfigProvider.yaml new file mode 100644 index 000000000..7da7c04bb --- /dev/null +++ b/.repos/effect/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 and sequence delimiters moved to provider path transforms and Config.Array/Config.Record schemas." +"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/.repos/effect/migration/annotations/effect__ConfigProviderPathPatch.yaml b/.repos/effect/migration/annotations/effect__ConfigProviderPathPatch.yaml new file mode 100644 index 000000000..049419589 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Console.yaml b/.repos/effect/migration/annotations/effect__Console.yaml new file mode 100644 index 000000000..77030318d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Context.yaml b/.repos/effect/migration/annotations/effect__Context.yaml new file mode 100644 index 000000000..af34f9dfe --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Cron.yaml b/.repos/effect/migration/annotations/effect__Cron.yaml new file mode 100644 index 000000000..94c388d36 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Data.yaml b/.repos/effect/migration/annotations/effect__Data.yaml new file mode 100644 index 000000000..e6f0d5b62 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__DateTime.yaml b/.repos/effect/migration/annotations/effect__DateTime.yaml new file mode 100644 index 000000000..f6c65c930 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__DefaultServices.yaml b/.repos/effect/migration/annotations/effect__DefaultServices.yaml new file mode 100644 index 000000000..f916ca2cc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Deferred.yaml b/.repos/effect/migration/annotations/effect__Deferred.yaml new file mode 100644 index 000000000..844e3029b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Differ.yaml b/.repos/effect/migration/annotations/effect__Differ.yaml new file mode 100644 index 000000000..c780a8413 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Duration.yaml b/.repos/effect/migration/annotations/effect__Duration.yaml new file mode 100644 index 000000000..6f9b67b73 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Effect.yaml b/.repos/effect/migration/annotations/effect__Effect.yaml new file mode 100644 index 000000000..4a7ee9816 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__Effect.yaml @@ -0,0 +1,657 @@ +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: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +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." diff --git a/.repos/effect/migration/annotations/effect__Effectable.yaml b/.repos/effect/migration/annotations/effect__Effectable.yaml new file mode 100644 index 000000000..a90d6864a --- /dev/null +++ b/.repos/effect/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 override property or getter returning the Effect." +"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 override; 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/.repos/effect/migration/annotations/effect__Either.yaml b/.repos/effect/migration/annotations/effect__Either.yaml new file mode 100644 index 000000000..02c6510a1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Encoding.yaml b/.repos/effect/migration/annotations/effect__Encoding.yaml new file mode 100644 index 000000000..9b1508e3e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Equal.yaml b/.repos/effect/migration/annotations/effect__Equal.yaml new file mode 100644 index 000000000..34601828d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Equivalence.yaml b/.repos/effect/migration/annotations/effect__Equivalence.yaml new file mode 100644 index 000000000..7b898a5b0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ExecutionPlan.yaml b/.repos/effect/migration/annotations/effect__ExecutionPlan.yaml new file mode 100644 index 000000000..bd73e986f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ExecutionStrategy.yaml b/.repos/effect/migration/annotations/effect__ExecutionStrategy.yaml new file mode 100644 index 000000000..831f31afa --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Exit.yaml b/.repos/effect/migration/annotations/effect__Exit.yaml new file mode 100644 index 000000000..5644d4368 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FastCheck.yaml b/.repos/effect/migration/annotations/effect__FastCheck.yaml new file mode 100644 index 000000000..0a1868b0a --- /dev/null +++ b/.repos/effect/migration/annotations/effect__FastCheck.yaml @@ -0,0 +1,88 @@ +"effect/FastCheck#ascii": + replacement: "FastCheck.string" + note: "Import FastCheck from effect/testing. 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: "Import FastCheck from effect/testing. Use the binary-ascii string unit." + example: "FastCheck.string({ ...constraints, unit: \"binary-ascii\" })" +"effect/FastCheck#base64": + replacement: "FastCheck.constantFrom" + note: "Import FastCheck from effect/testing. Generate one base64 alphabet character; base64String remains for complete encoded strings." + example: "FastCheck.constantFrom(...\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/\")" +"effect/FastCheck#bigIntN": + replacement: "FastCheck.bigInt" + note: "Import FastCheck from effect/testing. Express the signed bit range with min and max constraints." +"effect/FastCheck#bigUint": + replacement: "FastCheck.bigInt" + note: "Import FastCheck from effect/testing. Use a minimum of 0n and the previous maximum." + example: "FastCheck.bigInt({ min: 0n, max })" +"effect/FastCheck#BigUintConstraints": + replacement: "FastCheck.BigIntConstraints" + note: "Import FastCheck from effect/testing. Unsigned bigint constraints were consolidated into BigIntConstraints with min: 0n." +"effect/FastCheck#bigUintN": + replacement: "FastCheck.bigInt" + note: "Import FastCheck from effect/testing. 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: "Import FastCheck from effect/testing. Use a one-unit printable ASCII string." + example: "FastCheck.string({ unit: \"grapheme-ascii\", minLength: 1, maxLength: 1 })" +"effect/FastCheck#char16bits": + replacement: "FastCheck.nat" + note: "Import FastCheck from effect/testing. Map a 16-bit natural number through String.fromCharCode." + example: "FastCheck.nat({ max: 0xffff }).map(String.fromCharCode)" +"effect/FastCheck#check": + replacement: "FastCheck.check" + note: "Import FastCheck from effect/testing. The runner remains, but RunDetails.error was replaced by errorInstance in fast-check v4." +"effect/FastCheck#constant": + replacement: "FastCheck.constant" + note: "Import FastCheck from effect/testing. The API remains; v4 infers literal types by default." +"effect/FastCheck#context": + replacement: "FastCheck.context" + note: "Import FastCheck from effect/testing. The API is otherwise unchanged." +"effect/FastCheck#fullUnicode": + replacement: "FastCheck.string" + note: "Import FastCheck from effect/testing. Use a one-unit binary Unicode string." + example: "FastCheck.string({ unit: \"binary\", minLength: 1, maxLength: 1 })" +"effect/FastCheck#fullUnicodeString": + replacement: "FastCheck.string" + note: "Import FastCheck from effect/testing. Use the binary string unit." + example: "FastCheck.string({ ...constraints, unit: \"binary\" })" +"effect/FastCheck#hexa": + replacement: "FastCheck.integer" + note: "Import FastCheck from effect/testing. Map an integer from 0 through 15 to a hexadecimal character." +"effect/FastCheck#hexaString": + replacement: "FastCheck.string" + note: "Import FastCheck from effect/testing. Pass a hexadecimal-character arbitrary as the string unit." +"effect/FastCheck#stream": + replacement: "FastCheck.stream" + note: "Import FastCheck from effect/testing. The API remains; update custom generator and Random implementations for fast-check v4 typings." +"effect/FastCheck#string16bits": + replacement: "FastCheck.string" + note: "Import FastCheck from effect/testing. Pass a char16bits-compatible arbitrary as the string unit." +"effect/FastCheck#stringOf": + replacement: "FastCheck.string" + note: "Import FastCheck from effect/testing. Pass the former character arbitrary as the unit constraint." + example: "FastCheck.string({ ...constraints, unit: arbitrary })" +"effect/FastCheck#unicode": + replacement: "FastCheck.integer" + note: "Import FastCheck from effect/testing. Map BMP code points while excluding surrogate code points; prefer the binary string unit for full Unicode." +"effect/FastCheck#unicodeJson": + replacement: "FastCheck.json" + note: "Import FastCheck from effect/testing. Select binary or grapheme strings with stringUnit." + example: "FastCheck.json({ stringUnit: \"binary\" })" +"effect/FastCheck#UnicodeJsonSharedConstraints": + replacement: "FastCheck.JsonSharedConstraints" + note: "Import FastCheck from effect/testing. Unicode JSON generation was consolidated into JsonSharedConstraints.stringUnit." +"effect/FastCheck#unicodeJsonValue": + replacement: "FastCheck.jsonValue" + note: "Import FastCheck from effect/testing. Select binary or grapheme strings with stringUnit." + example: "FastCheck.jsonValue({ stringUnit: \"binary\" })" +"effect/FastCheck#unicodeString": + replacement: "FastCheck.string" + note: "Import FastCheck from effect/testing. Pass a BMP-code-point arbitrary as the unit constraint; prefer unit: binary for full Unicode." +"effect/FastCheck#uuidV": + replacement: "FastCheck.uuid" + note: "Import FastCheck from effect/testing. Specify the UUID version through constraints." + example: "FastCheck.uuid({ version: 4 })" diff --git a/.repos/effect/migration/annotations/effect__Fiber.yaml b/.repos/effect/migration/annotations/effect__Fiber.yaml new file mode 100644 index 000000000..659066b25 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberHandle.yaml b/.repos/effect/migration/annotations/effect__FiberHandle.yaml new file mode 100644 index 000000000..bd7bdb5c7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberId.yaml b/.repos/effect/migration/annotations/effect__FiberId.yaml new file mode 100644 index 000000000..91e0fc410 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberMap.yaml b/.repos/effect/migration/annotations/effect__FiberMap.yaml new file mode 100644 index 000000000..dd3039256 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberRef.yaml b/.repos/effect/migration/annotations/effect__FiberRef.yaml new file mode 100644 index 000000000..403eb277f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberRefs.yaml b/.repos/effect/migration/annotations/effect__FiberRefs.yaml new file mode 100644 index 000000000..63a8ce255 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberRefsPatch.yaml b/.repos/effect/migration/annotations/effect__FiberRefsPatch.yaml new file mode 100644 index 000000000..4ba815541 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberSet.yaml b/.repos/effect/migration/annotations/effect__FiberSet.yaml new file mode 100644 index 000000000..ec0d81253 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__FiberStatus.yaml b/.repos/effect/migration/annotations/effect__FiberStatus.yaml new file mode 100644 index 000000000..752128b4f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Function.yaml b/.repos/effect/migration/annotations/effect__Function.yaml new file mode 100644 index 000000000..b2c548335 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__GlobalValue.yaml b/.repos/effect/migration/annotations/effect__GlobalValue.yaml new file mode 100644 index 000000000..414a1bbe4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Graph.yaml b/.repos/effect/migration/annotations/effect__Graph.yaml new file mode 100644 index 000000000..4b1997f9d --- /dev/null +++ b/.repos/effect/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 extends Graph.Proto; obtain it through Graph.mutate or Graph.beginMutation and use public mutation/query functions." +"effect/Graph#Proto": + replacement: "Graph.Proto" + note: "The name remains as the opaque immutable graph protocol; it no longer exposes storage and is no longer the base of MutableGraph." +"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/.repos/effect/migration/annotations/effect__GroupBy.yaml b/.repos/effect/migration/annotations/effect__GroupBy.yaml new file mode 100644 index 000000000..a98cb79e9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Hash.yaml b/.repos/effect/migration/annotations/effect__Hash.yaml new file mode 100644 index 000000000..69f676887 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__HashMap.yaml b/.repos/effect/migration/annotations/effect__HashMap.yaml new file mode 100644 index 000000000..e35bc6fa9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__HashSet.yaml b/.repos/effect/migration/annotations/effect__HashSet.yaml new file mode 100644 index 000000000..adaa83fd2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Inspectable.yaml b/.repos/effect/migration/annotations/effect__Inspectable.yaml new file mode 100644 index 000000000..3d3ae012b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Iterable.yaml b/.repos/effect/migration/annotations/effect__Iterable.yaml new file mode 100644 index 000000000..b25d8798c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__JSONSchema.yaml b/.repos/effect/migration/annotations/effect__JSONSchema.yaml new file mode 100644 index 000000000..34d61b8f0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__KeyedPool.yaml b/.repos/effect/migration/annotations/effect__KeyedPool.yaml new file mode 100644 index 000000000..f3b27dc52 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Layer.yaml b/.repos/effect/migration/annotations/effect__Layer.yaml new file mode 100644 index 000000000..def772ee9 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__Layer.yaml @@ -0,0 +1,189 @@ +"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." +"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." diff --git a/.repos/effect/migration/annotations/effect__LayerMap.yaml b/.repos/effect/migration/annotations/effect__LayerMap.yaml new file mode 100644 index 000000000..a87d5e2f5 --- /dev/null +++ b/.repos/effect/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." +"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/.repos/effect/migration/annotations/effect__List.yaml b/.repos/effect/migration/annotations/effect__List.yaml new file mode 100644 index 000000000..ce1ec1d1e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__LogLevel.yaml b/.repos/effect/migration/annotations/effect__LogLevel.yaml new file mode 100644 index 000000000..f0a4995d3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__LogSpan.yaml b/.repos/effect/migration/annotations/effect__LogSpan.yaml new file mode 100644 index 000000000..8060c93d2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Logger.yaml b/.repos/effect/migration/annotations/effect__Logger.yaml new file mode 100644 index 000000000..b8a4a8cf4 --- /dev/null +++ b/.repos/effect/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: "Direct constructor rename; call it with the same 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.currentSpan" + note: "No transparent generic equivalent remains. Read span identity from options.fiber.currentSpan 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/.repos/effect/migration/annotations/effect__Mailbox.yaml b/.repos/effect/migration/annotations/effect__Mailbox.yaml new file mode 100644 index 000000000..795891c68 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ManagedRuntime.yaml b/.repos/effect/migration/annotations/effect__ManagedRuntime.yaml new file mode 100644 index 000000000..5daa23e94 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Match.yaml b/.repos/effect/migration/annotations/effect__Match.yaml new file mode 100644 index 000000000..2ed399401 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MergeDecision.yaml b/.repos/effect/migration/annotations/effect__MergeDecision.yaml new file mode 100644 index 000000000..c2606ba0a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MergeState.yaml b/.repos/effect/migration/annotations/effect__MergeState.yaml new file mode 100644 index 000000000..beb3f5811 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MergeStrategy.yaml b/.repos/effect/migration/annotations/effect__MergeStrategy.yaml new file mode 100644 index 000000000..de08cbd87 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Metric.yaml b/.repos/effect/migration/annotations/effect__Metric.yaml new file mode 100644 index 000000000..67a83b2e3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricBoundaries.yaml b/.repos/effect/migration/annotations/effect__MetricBoundaries.yaml new file mode 100644 index 000000000..cc3273029 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricHook.yaml b/.repos/effect/migration/annotations/effect__MetricHook.yaml new file mode 100644 index 000000000..9350c36c5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricKey.yaml b/.repos/effect/migration/annotations/effect__MetricKey.yaml new file mode 100644 index 000000000..512d460e5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricKeyType.yaml b/.repos/effect/migration/annotations/effect__MetricKeyType.yaml new file mode 100644 index 000000000..e4288478c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricLabel.yaml b/.repos/effect/migration/annotations/effect__MetricLabel.yaml new file mode 100644 index 000000000..27cc1bbbf --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricPair.yaml b/.repos/effect/migration/annotations/effect__MetricPair.yaml new file mode 100644 index 000000000..14cebe395 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricPolling.yaml b/.repos/effect/migration/annotations/effect__MetricPolling.yaml new file mode 100644 index 000000000..9a5d183bd --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricRegistry.yaml b/.repos/effect/migration/annotations/effect__MetricRegistry.yaml new file mode 100644 index 000000000..48b0f2c6f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MetricState.yaml b/.repos/effect/migration/annotations/effect__MetricState.yaml new file mode 100644 index 000000000..25a25f98d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Micro.yaml b/.repos/effect/migration/annotations/effect__Micro.yaml new file mode 100644 index 000000000..562cd279b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ModuleVersion.yaml b/.repos/effect/migration/annotations/effect__ModuleVersion.yaml new file mode 100644 index 000000000..07ea5d5e6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MutableHashMap.yaml b/.repos/effect/migration/annotations/effect__MutableHashMap.yaml new file mode 100644 index 000000000..201890730 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MutableHashSet.yaml b/.repos/effect/migration/annotations/effect__MutableHashSet.yaml new file mode 100644 index 000000000..fcc4cef0e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MutableList.yaml b/.repos/effect/migration/annotations/effect__MutableList.yaml new file mode 100644 index 000000000..c9cd12126 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MutableQueue.yaml b/.repos/effect/migration/annotations/effect__MutableQueue.yaml new file mode 100644 index 000000000..6ba01f209 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__MutableRef.yaml b/.repos/effect/migration/annotations/effect__MutableRef.yaml new file mode 100644 index 000000000..e41e4d192 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Number.yaml b/.repos/effect/migration/annotations/effect__Number.yaml new file mode 100644 index 000000000..b1e77db36 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Option.yaml b/.repos/effect/migration/annotations/effect__Option.yaml new file mode 100644 index 000000000..75f930ac9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Order.yaml b/.repos/effect/migration/annotations/effect__Order.yaml new file mode 100644 index 000000000..365449cb7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Ordering.yaml b/.repos/effect/migration/annotations/effect__Ordering.yaml new file mode 100644 index 000000000..68af19108 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ParseResult.yaml b/.repos/effect/migration/annotations/effect__ParseResult.yaml new file mode 100644 index 000000000..3feefd473 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__ParseResult.yaml @@ -0,0 +1,125 @@ +"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#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#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#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/.repos/effect/migration/annotations/effect__PartitionedSemaphore.yaml b/.repos/effect/migration/annotations/effect__PartitionedSemaphore.yaml new file mode 100644 index 000000000..f7ae84b95 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Pipeable.yaml b/.repos/effect/migration/annotations/effect__Pipeable.yaml new file mode 100644 index 000000000..b4a746b9e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Pool.yaml b/.repos/effect/migration/annotations/effect__Pool.yaml new file mode 100644 index 000000000..6cd929382 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Predicate.yaml b/.repos/effect/migration/annotations/effect__Predicate.yaml new file mode 100644 index 000000000..5649916c9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Pretty.yaml b/.repos/effect/migration/annotations/effect__Pretty.yaml new file mode 100644 index 000000000..1a5c14d41 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__PubSub.yaml b/.repos/effect/migration/annotations/effect__PubSub.yaml new file mode 100644 index 000000000..cee33218f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Queue.yaml b/.repos/effect/migration/annotations/effect__Queue.yaml new file mode 100644 index 000000000..d0eab5568 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Random.yaml b/.repos/effect/migration/annotations/effect__Random.yaml new file mode 100644 index 000000000..74bee66bf --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RateLimiter.yaml b/.repos/effect/migration/annotations/effect__RateLimiter.yaml new file mode 100644 index 000000000..77dd05c4f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RcMap.yaml b/.repos/effect/migration/annotations/effect__RcMap.yaml new file mode 100644 index 000000000..8c56ece36 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RcRef.yaml b/.repos/effect/migration/annotations/effect__RcRef.yaml new file mode 100644 index 000000000..b77f8783e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Readable.yaml b/.repos/effect/migration/annotations/effect__Readable.yaml new file mode 100644 index 000000000..2e5c5dd0e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Record.yaml b/.repos/effect/migration/annotations/effect__Record.yaml new file mode 100644 index 000000000..4e86dff11 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RedBlackTree.yaml b/.repos/effect/migration/annotations/effect__RedBlackTree.yaml new file mode 100644 index 000000000..31085e816 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Redacted.yaml b/.repos/effect/migration/annotations/effect__Redacted.yaml new file mode 100644 index 000000000..0334c40d5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Ref.yaml b/.repos/effect/migration/annotations/effect__Ref.yaml new file mode 100644 index 000000000..3abdf00fb --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Reloadable.yaml b/.repos/effect/migration/annotations/effect__Reloadable.yaml new file mode 100644 index 000000000..a91751596 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Request.yaml b/.repos/effect/migration/annotations/effect__Request.yaml new file mode 100644 index 000000000..b8e0ab8fe --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RequestBlock.yaml b/.repos/effect/migration/annotations/effect__RequestBlock.yaml new file mode 100644 index 000000000..bc9b172eb --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RequestResolver.yaml b/.repos/effect/migration/annotations/effect__RequestResolver.yaml new file mode 100644 index 000000000..647e69250 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Resource.yaml b/.repos/effect/migration/annotations/effect__Resource.yaml new file mode 100644 index 000000000..e26fcaee7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Runtime.yaml b/.repos/effect/migration/annotations/effect__Runtime.yaml new file mode 100644 index 000000000..89779d17b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RuntimeFlags.yaml b/.repos/effect/migration/annotations/effect__RuntimeFlags.yaml new file mode 100644 index 000000000..eb6d3bcd3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__RuntimeFlagsPatch.yaml b/.repos/effect/migration/annotations/effect__RuntimeFlagsPatch.yaml new file mode 100644 index 000000000..915423f68 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__STM.yaml b/.repos/effect/migration/annotations/effect__STM.yaml new file mode 100644 index 000000000..d11b88e98 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Schedule.yaml b/.repos/effect/migration/annotations/effect__Schedule.yaml new file mode 100644 index 000000000..00770ca6c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ScheduleDecision.yaml b/.repos/effect/migration/annotations/effect__ScheduleDecision.yaml new file mode 100644 index 000000000..bce4dad16 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ScheduleInterval.yaml b/.repos/effect/migration/annotations/effect__ScheduleInterval.yaml new file mode 100644 index 000000000..2b9a211ba --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ScheduleIntervals.yaml b/.repos/effect/migration/annotations/effect__ScheduleIntervals.yaml new file mode 100644 index 000000000..69985bca3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Scheduler.yaml b/.repos/effect/migration/annotations/effect__Scheduler.yaml new file mode 100644 index 000000000..06e3b0191 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Schema.yaml b/.repos/effect/migration/annotations/effect__Schema.yaml new file mode 100644 index 000000000..69c83c30e --- /dev/null +++ b/.repos/effect/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. +effect/Schema#TemplateLiteralParser: + replacement: Schema.TemplateLiteralParser(schema.parts) + note: Create the template schema first and pass its `parts` property. +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/.repos/effect/migration/annotations/effect__SchemaAST.yaml b/.repos/effect/migration/annotations/effect__SchemaAST.yaml new file mode 100644 index 000000000..00a8f970d --- /dev/null +++ b/.repos/effect/migration/annotations/effect__SchemaAST.yaml @@ -0,0 +1,441 @@ +"effect/SchemaAST#Annotated": + replacement: "SchemaAST.Base" + note: "All v4 AST nodes extend Base, which owns 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; use the toArbitrary annotation key and its Schema.Annotations types." +"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#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." +"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/.repos/effect/migration/annotations/effect__Scope.yaml b/.repos/effect/migration/annotations/effect__Scope.yaml new file mode 100644 index 000000000..e27e3e680 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ScopedCache.yaml b/.repos/effect/migration/annotations/effect__ScopedCache.yaml new file mode 100644 index 000000000..25b6de9b5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ScopedRef.yaml b/.repos/effect/migration/annotations/effect__ScopedRef.yaml new file mode 100644 index 000000000..42581a204 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Secret.yaml b/.repos/effect/migration/annotations/effect__Secret.yaml new file mode 100644 index 000000000..475273e88 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__SingleProducerAsyncInput.yaml b/.repos/effect/migration/annotations/effect__SingleProducerAsyncInput.yaml new file mode 100644 index 000000000..3801646de --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Sink.yaml b/.repos/effect/migration/annotations/effect__Sink.yaml new file mode 100644 index 000000000..636836d09 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__SortedMap.yaml b/.repos/effect/migration/annotations/effect__SortedMap.yaml new file mode 100644 index 000000000..5139e02e9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__SortedSet.yaml b/.repos/effect/migration/annotations/effect__SortedSet.yaml new file mode 100644 index 000000000..cc0e6e0c8 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__SortedSet.yaml @@ -0,0 +1,60 @@ +"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." diff --git a/.repos/effect/migration/annotations/effect__Stream.yaml b/.repos/effect/migration/annotations/effect__Stream.yaml new file mode 100644 index 000000000..c2d861cd5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__StreamEmit.yaml b/.repos/effect/migration/annotations/effect__StreamEmit.yaml new file mode 100644 index 000000000..6d76942ef --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__StreamHaltStrategy.yaml b/.repos/effect/migration/annotations/effect__StreamHaltStrategy.yaml new file mode 100644 index 000000000..598ffa565 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Streamable.yaml b/.repos/effect/migration/annotations/effect__Streamable.yaml new file mode 100644 index 000000000..03e95d4b5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Struct.yaml b/.repos/effect/migration/annotations/effect__Struct.yaml new file mode 100644 index 000000000..acdcf2042 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Subscribable.yaml b/.repos/effect/migration/annotations/effect__Subscribable.yaml new file mode 100644 index 000000000..94bb39b4a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__SubscriptionRef.yaml b/.repos/effect/migration/annotations/effect__SubscriptionRef.yaml new file mode 100644 index 000000000..dbaf54439 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Supervisor.yaml b/.repos/effect/migration/annotations/effect__Supervisor.yaml new file mode 100644 index 000000000..3399df360 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Symbol.yaml b/.repos/effect/migration/annotations/effect__Symbol.yaml new file mode 100644 index 000000000..1dbd674fe --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__SynchronizedRef.yaml b/.repos/effect/migration/annotations/effect__SynchronizedRef.yaml new file mode 100644 index 000000000..5fef8a050 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__SynchronizedRef.yaml @@ -0,0 +1,18 @@ +effect/SynchronizedRef#SynchronizedRef: + replacement: "SynchronizedRef.SynchronizedRef" + note: "The model remains, now extends the v4 Ref model, and is read or updated through explicit SynchronizedRef operations." +effect/SynchronizedRef#SynchronizedRef.Variance: + replacement: "Ref.Ref.Variance" + note: "SynchronizedRef now inherits Ref variance instead of declaring a separate public variance marker." +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/.repos/effect/migration/annotations/effect__TArray.yaml b/.repos/effect/migration/annotations/effect__TArray.yaml new file mode 100644 index 000000000..dd77149a5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TDeferred.yaml b/.repos/effect/migration/annotations/effect__TDeferred.yaml new file mode 100644 index 000000000..8641fa49a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TMap.yaml b/.repos/effect/migration/annotations/effect__TMap.yaml new file mode 100644 index 000000000..1bb30050d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TPriorityQueue.yaml b/.repos/effect/migration/annotations/effect__TPriorityQueue.yaml new file mode 100644 index 000000000..cf9205645 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TPubSub.yaml b/.repos/effect/migration/annotations/effect__TPubSub.yaml new file mode 100644 index 000000000..487f84c0b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TQueue.yaml b/.repos/effect/migration/annotations/effect__TQueue.yaml new file mode 100644 index 000000000..ef7de6f07 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TRandom.yaml b/.repos/effect/migration/annotations/effect__TRandom.yaml new file mode 100644 index 000000000..3bc6b1955 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TReentrantLock.yaml b/.repos/effect/migration/annotations/effect__TReentrantLock.yaml new file mode 100644 index 000000000..399bcd9a1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TRef.yaml b/.repos/effect/migration/annotations/effect__TRef.yaml new file mode 100644 index 000000000..eed0c896c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TSemaphore.yaml b/.repos/effect/migration/annotations/effect__TSemaphore.yaml new file mode 100644 index 000000000..ac4becb2b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TSet.yaml b/.repos/effect/migration/annotations/effect__TSet.yaml new file mode 100644 index 000000000..51d649588 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TSubscriptionRef.yaml b/.repos/effect/migration/annotations/effect__TSubscriptionRef.yaml new file mode 100644 index 000000000..ccb654ec9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Take.yaml b/.repos/effect/migration/annotations/effect__Take.yaml new file mode 100644 index 000000000..0c8cfe1ac --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TestAnnotation.yaml b/.repos/effect/migration/annotations/effect__TestAnnotation.yaml new file mode 100644 index 000000000..b8d69241d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TestAnnotationMap.yaml b/.repos/effect/migration/annotations/effect__TestAnnotationMap.yaml new file mode 100644 index 000000000..407cb77ea --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TestAnnotations.yaml b/.repos/effect/migration/annotations/effect__TestAnnotations.yaml new file mode 100644 index 000000000..2faf243d5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TestClock.yaml b/.repos/effect/migration/annotations/effect__TestClock.yaml new file mode 100644 index 000000000..e95f504bd --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TestConfig.yaml b/.repos/effect/migration/annotations/effect__TestConfig.yaml new file mode 100644 index 000000000..39470247c --- /dev/null +++ b/.repos/effect/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 FastCheck options, or define an application-specific Context.Reference if runtime access is needed." diff --git a/.repos/effect/migration/annotations/effect__TestContext.yaml b/.repos/effect/migration/annotations/effect__TestContext.yaml new file mode 100644 index 000000000..6738380cb --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TestLive.yaml b/.repos/effect/migration/annotations/effect__TestLive.yaml new file mode 100644 index 000000000..e12b07c39 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__TestServices.yaml b/.repos/effect/migration/annotations/effect__TestServices.yaml new file mode 100644 index 000000000..231075734 --- /dev/null +++ b/.repos/effect/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: "{ fastCheck: { numRuns } }" + note: "Pass the run count through @effect/vitest property-test options, for example it.effect.prop(..., { fastCheck: { numRuns: samples } })." +"effect/TestServices#shrinks": + replacement: "none" + note: "The legacy maximum-shrinks service setting was removed; @effect/vitest forwards FastCheck.Parameters, which has no equivalent service value." +"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 fastCheck 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 fastCheck 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 fastCheck 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 fastCheck 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 fastCheck 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/.repos/effect/migration/annotations/effect__TestSized.yaml b/.repos/effect/migration/annotations/effect__TestSized.yaml new file mode 100644 index 000000000..0e146dd2e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Tracer.yaml b/.repos/effect/migration/annotations/effect__Tracer.yaml new file mode 100644 index 000000000..e14547f37 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Trie.yaml b/.repos/effect/migration/annotations/effect__Trie.yaml new file mode 100644 index 000000000..38b40cb2c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Tuple.yaml b/.repos/effect/migration/annotations/effect__Tuple.yaml new file mode 100644 index 000000000..89ec37e3d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Types.yaml b/.repos/effect/migration/annotations/effect__Types.yaml new file mode 100644 index 000000000..cbd7331fa --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__UpstreamPullRequest.yaml b/.repos/effect/migration/annotations/effect__UpstreamPullRequest.yaml new file mode 100644 index 000000000..31a3882a9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__UpstreamPullStrategy.yaml b/.repos/effect/migration/annotations/effect__UpstreamPullStrategy.yaml new file mode 100644 index 000000000..af0ea8399 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__Utils.yaml b/.repos/effect/migration/annotations/effect__Utils.yaml new file mode 100644 index 000000000..40387ec46 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock.yaml new file mode 100644 index 000000000..e9b3b9352 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockClient.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockClient.yaml new file mode 100644 index 000000000..626bc2eb3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockConfig.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockConfig.yaml new file mode 100644 index 000000000..9c49ec99f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockLanguageModel.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockLanguageModel.yaml new file mode 100644 index 000000000..bc1365d26 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockSchema.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockSchema.yaml new file mode 100644 index 000000000..c55353c86 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockTool.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockTool.yaml new file mode 100644 index 000000000..bc5ed3ea7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__EventStreamEncoding.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__EventStreamEncoding.yaml new file mode 100644 index 000000000..7553bfbb2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__index.yaml b/.repos/effect/migration/annotations/effect__ai-amazon-bedrock__index.yaml new file mode 100644 index 000000000..d0ecd9e74 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicClient.yaml b/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicClient.yaml new file mode 100644 index 000000000..6fa9c33d3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicConfig.yaml b/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicConfig.yaml new file mode 100644 index 000000000..068f85afe --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicLanguageModel.yaml b/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicLanguageModel.yaml new file mode 100644 index 000000000..d624ede50 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicTokenizer.yaml b/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicTokenizer.yaml new file mode 100644 index 000000000..a2cb45a97 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicTool.yaml b/.repos/effect/migration/annotations/effect__ai-anthropic__AnthropicTool.yaml new file mode 100644 index 000000000..5cf62d776 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-anthropic__Generated.yaml b/.repos/effect/migration/annotations/effect__ai-anthropic__Generated.yaml new file mode 100644 index 000000000..76e5ff293 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-anthropic__index.yaml b/.repos/effect/migration/annotations/effect__ai-anthropic__index.yaml new file mode 100644 index 000000000..f8fa2bd85 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-google.yaml b/.repos/effect/migration/annotations/effect__ai-google.yaml new file mode 100644 index 000000000..9de246d44 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-google__Generated.yaml b/.repos/effect/migration/annotations/effect__ai-google__Generated.yaml new file mode 100644 index 000000000..0b6ed2397 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-google__GoogleClient.yaml b/.repos/effect/migration/annotations/effect__ai-google__GoogleClient.yaml new file mode 100644 index 000000000..f344b4f94 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-google__GoogleConfig.yaml b/.repos/effect/migration/annotations/effect__ai-google__GoogleConfig.yaml new file mode 100644 index 000000000..175622d5e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-google__GoogleLanguageModel.yaml b/.repos/effect/migration/annotations/effect__ai-google__GoogleLanguageModel.yaml new file mode 100644 index 000000000..0cda6708c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-google__GoogleTool.yaml b/.repos/effect/migration/annotations/effect__ai-google__GoogleTool.yaml new file mode 100644 index 000000000..cd91c242c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-google__index.yaml b/.repos/effect/migration/annotations/effect__ai-google__index.yaml new file mode 100644 index 000000000..a5cfecfc0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__Generated.yaml b/.repos/effect/migration/annotations/effect__ai-openai__Generated.yaml new file mode 100644 index 000000000..5f96a15c4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__OpenAiClient.yaml b/.repos/effect/migration/annotations/effect__ai-openai__OpenAiClient.yaml new file mode 100644 index 000000000..2395a07f4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__OpenAiConfig.yaml b/.repos/effect/migration/annotations/effect__ai-openai__OpenAiConfig.yaml new file mode 100644 index 000000000..b9a58fd0e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__OpenAiEmbeddingModel.yaml b/.repos/effect/migration/annotations/effect__ai-openai__OpenAiEmbeddingModel.yaml new file mode 100644 index 000000000..7152df239 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__OpenAiLanguageModel.yaml b/.repos/effect/migration/annotations/effect__ai-openai__OpenAiLanguageModel.yaml new file mode 100644 index 000000000..21af9e0d6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__OpenAiTelemetry.yaml b/.repos/effect/migration/annotations/effect__ai-openai__OpenAiTelemetry.yaml new file mode 100644 index 000000000..52fb66673 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__OpenAiTokenizer.yaml b/.repos/effect/migration/annotations/effect__ai-openai__OpenAiTokenizer.yaml new file mode 100644 index 000000000..9c459de0c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openai__index.yaml b/.repos/effect/migration/annotations/effect__ai-openai__index.yaml new file mode 100644 index 000000000..5f0446a2b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openrouter__Generated.yaml b/.repos/effect/migration/annotations/effect__ai-openrouter__Generated.yaml new file mode 100644 index 000000000..611408a13 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openrouter__OpenRouterClient.yaml b/.repos/effect/migration/annotations/effect__ai-openrouter__OpenRouterClient.yaml new file mode 100644 index 000000000..81023018b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openrouter__OpenRouterConfig.yaml b/.repos/effect/migration/annotations/effect__ai-openrouter__OpenRouterConfig.yaml new file mode 100644 index 000000000..5ec7a2e8a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openrouter__OpenRouterLanguageModel.yaml b/.repos/effect/migration/annotations/effect__ai-openrouter__OpenRouterLanguageModel.yaml new file mode 100644 index 000000000..18fdc4ef1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai-openrouter__index.yaml b/.repos/effect/migration/annotations/effect__ai-openrouter__index.yaml new file mode 100644 index 000000000..49daa878e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai.yaml b/.repos/effect/migration/annotations/effect__ai.yaml new file mode 100644 index 000000000..2a4a65299 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__AiError.yaml b/.repos/effect/migration/annotations/effect__ai__AiError.yaml new file mode 100644 index 000000000..d906cf83a --- /dev/null +++ b/.repos/effect/migration/annotations/effect__ai__AiError.yaml @@ -0,0 +1,21 @@ +"@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." diff --git a/.repos/effect/migration/annotations/effect__ai__EmbeddingModel.yaml b/.repos/effect/migration/annotations/effect__ai__EmbeddingModel.yaml new file mode 100644 index 000000000..a4c065c0d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__IdGenerator.yaml b/.repos/effect/migration/annotations/effect__ai__IdGenerator.yaml new file mode 100644 index 000000000..8079a2375 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__LanguageModel.yaml b/.repos/effect/migration/annotations/effect__ai__LanguageModel.yaml new file mode 100644 index 000000000..7436f1bf5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__McpSchema.yaml b/.repos/effect/migration/annotations/effect__ai__McpSchema.yaml new file mode 100644 index 000000000..2c7a565d1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__McpServer.yaml b/.repos/effect/migration/annotations/effect__ai__McpServer.yaml new file mode 100644 index 000000000..7ded9767a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__Model.yaml b/.repos/effect/migration/annotations/effect__ai__Model.yaml new file mode 100644 index 000000000..a09b05774 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__Prompt.yaml b/.repos/effect/migration/annotations/effect__ai__Prompt.yaml new file mode 100644 index 000000000..d08583bc6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__Response.yaml b/.repos/effect/migration/annotations/effect__ai__Response.yaml new file mode 100644 index 000000000..2a1351c12 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__ai__Response.yaml @@ -0,0 +1,69 @@ +"@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." diff --git a/.repos/effect/migration/annotations/effect__ai__Tool.yaml b/.repos/effect/migration/annotations/effect__ai__Tool.yaml new file mode 100644 index 000000000..5bd660841 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__ai__Tool.yaml @@ -0,0 +1,57 @@ +"@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." diff --git a/.repos/effect/migration/annotations/effect__ai__Toolkit.yaml b/.repos/effect/migration/annotations/effect__ai__Toolkit.yaml new file mode 100644 index 000000000..7a07a0369 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__ai__index.yaml b/.repos/effect/migration/annotations/effect__ai__index.yaml new file mode 100644 index 000000000..c8787f31a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli.yaml b/.repos/effect/migration/annotations/effect__cli.yaml new file mode 100644 index 000000000..a52d95b5a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__Args.yaml b/.repos/effect/migration/annotations/effect__cli__Args.yaml new file mode 100644 index 000000000..f95db093c --- /dev/null +++ b/.repos/effect/migration/annotations/effect__cli__Args.yaml @@ -0,0 +1,90 @@ +"@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#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#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#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#optional": + replacement: "Argument.optional" + note: "Use the moved combinator; it still returns Option." +"@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/.repos/effect/migration/annotations/effect__cli__AutoCorrect.yaml b/.repos/effect/migration/annotations/effect__cli__AutoCorrect.yaml new file mode 100644 index 000000000..7bdf4dd13 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__BuiltInOptions.yaml b/.repos/effect/migration/annotations/effect__cli__BuiltInOptions.yaml new file mode 100644 index 000000000..66c0860dd --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__CliApp.yaml b/.repos/effect/migration/annotations/effect__cli__CliApp.yaml new file mode 100644 index 000000000..50985d4b8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__CliConfig.yaml b/.repos/effect/migration/annotations/effect__cli__CliConfig.yaml new file mode 100644 index 000000000..ad5e73f6d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__Command.yaml b/.repos/effect/migration/annotations/effect__cli__Command.yaml new file mode 100644 index 000000000..295f9e1ac --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__CommandDescriptor.yaml b/.repos/effect/migration/annotations/effect__cli__CommandDescriptor.yaml new file mode 100644 index 000000000..6875422f4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__CommandDirective.yaml b/.repos/effect/migration/annotations/effect__cli__CommandDirective.yaml new file mode 100644 index 000000000..3c157a9c8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__ConfigFile.yaml b/.repos/effect/migration/annotations/effect__cli__ConfigFile.yaml new file mode 100644 index 000000000..707bf62b8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__HelpDoc.yaml b/.repos/effect/migration/annotations/effect__cli__HelpDoc.yaml new file mode 100644 index 000000000..950aca332 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__HelpDoc__Span.yaml b/.repos/effect/migration/annotations/effect__cli__HelpDoc__Span.yaml new file mode 100644 index 000000000..3ee5b3125 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__Options.yaml b/.repos/effect/migration/annotations/effect__cli__Options.yaml new file mode 100644 index 000000000..22a76c724 --- /dev/null +++ b/.repos/effect/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.choice" + 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.float" + 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.integer" + 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.none 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/.repos/effect/migration/annotations/effect__cli__Primitive.yaml b/.repos/effect/migration/annotations/effect__cli__Primitive.yaml new file mode 100644 index 000000000..35082dfc9 --- /dev/null +++ b/.repos/effect/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.float" + note: "Float 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.integer" + note: "Integer 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/.repos/effect/migration/annotations/effect__cli__Prompt.yaml b/.repos/effect/migration/annotations/effect__cli__Prompt.yaml new file mode 100644 index 000000000..8c1f6ffb1 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__cli__Prompt.yaml @@ -0,0 +1,39 @@ +"@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#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.float" + note: "Use the moved constructor; v4 also supports a default value." +"@effect/cli/Prompt#integer": + replacement: "Prompt.integer" + note: "Use the moved constructor; v4 also supports a default value." +"@effect/cli/Prompt#map": + replacement: "Prompt.map" + note: "Use the moved combinator." +"@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#text": + replacement: "Prompt.text" + note: "Use the moved constructor." diff --git a/.repos/effect/migration/annotations/effect__cli__Usage.yaml b/.repos/effect/migration/annotations/effect__cli__Usage.yaml new file mode 100644 index 000000000..aed15625d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__ValidationError.yaml b/.repos/effect/migration/annotations/effect__cli__ValidationError.yaml new file mode 100644 index 000000000..5c9b66be6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cli__index.yaml b/.repos/effect/migration/annotations/effect__cli__index.yaml new file mode 100644 index 000000000..8a416c619 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster.yaml b/.repos/effect/migration/annotations/effect__cluster.yaml new file mode 100644 index 000000000..ecbb12117 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__ClusterCron.yaml b/.repos/effect/migration/annotations/effect__cluster__ClusterCron.yaml new file mode 100644 index 000000000..e32af55c5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__ClusterError.yaml b/.repos/effect/migration/annotations/effect__cluster__ClusterError.yaml new file mode 100644 index 000000000..9dd6c41b1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__ClusterSchema.yaml b/.repos/effect/migration/annotations/effect__cluster__ClusterSchema.yaml new file mode 100644 index 000000000..dc3c5c316 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__ClusterWorkflowEngine.yaml b/.repos/effect/migration/annotations/effect__cluster__ClusterWorkflowEngine.yaml new file mode 100644 index 000000000..d7bf79fc1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__DeliverAt.yaml b/.repos/effect/migration/annotations/effect__cluster__DeliverAt.yaml new file mode 100644 index 000000000..bba0f7b70 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Entity.yaml b/.repos/effect/migration/annotations/effect__cluster__Entity.yaml new file mode 100644 index 000000000..661a7e0f4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__EntityAddress.yaml b/.repos/effect/migration/annotations/effect__cluster__EntityAddress.yaml new file mode 100644 index 000000000..0bc1367ae --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__EntityId.yaml b/.repos/effect/migration/annotations/effect__cluster__EntityId.yaml new file mode 100644 index 000000000..dbc9ad7b0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__EntityProxy.yaml b/.repos/effect/migration/annotations/effect__cluster__EntityProxy.yaml new file mode 100644 index 000000000..f6e0df345 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__EntityProxyServer.yaml b/.repos/effect/migration/annotations/effect__cluster__EntityProxyServer.yaml new file mode 100644 index 000000000..6edba3715 --- /dev/null +++ b/.repos/effect/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 Rpc.ServicesServer requirements." +"@effect/cluster/EntityProxyServer#layerRpcHandlers": + replacement: "effect/unstable/cluster/EntityProxyServer#layerRpcHandlers" + note: "Moved into core Effect; the service requirement is now Rpc.ServicesServer rather than Rpc.Context." +"@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/.repos/effect/migration/annotations/effect__cluster__EntityResource.yaml b/.repos/effect/migration/annotations/effect__cluster__EntityResource.yaml new file mode 100644 index 000000000..19472a5e2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Envelope.yaml b/.repos/effect/migration/annotations/effect__cluster__Envelope.yaml new file mode 100644 index 000000000..2d749b6f7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__HttpRunner.yaml b/.repos/effect/migration/annotations/effect__cluster__HttpRunner.yaml new file mode 100644 index 000000000..77a734675 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__K8sHttpClient.yaml b/.repos/effect/migration/annotations/effect__cluster__K8sHttpClient.yaml new file mode 100644 index 000000000..ea31aef07 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__MachineId.yaml b/.repos/effect/migration/annotations/effect__cluster__MachineId.yaml new file mode 100644 index 000000000..95933caa4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Message.yaml b/.repos/effect/migration/annotations/effect__cluster__Message.yaml new file mode 100644 index 000000000..e41b871a4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__MessageStorage.yaml b/.repos/effect/migration/annotations/effect__cluster__MessageStorage.yaml new file mode 100644 index 000000000..11f3b99c3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Reply.yaml b/.repos/effect/migration/annotations/effect__cluster__Reply.yaml new file mode 100644 index 000000000..aecd2243b --- /dev/null +++ b/.repos/effect/migration/annotations/effect__cluster__Reply.yaml @@ -0,0 +1,9 @@ +"@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." diff --git a/.repos/effect/migration/annotations/effect__cluster__Runner.yaml b/.repos/effect/migration/annotations/effect__cluster__Runner.yaml new file mode 100644 index 000000000..ebc99a826 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__RunnerAddress.yaml b/.repos/effect/migration/annotations/effect__cluster__RunnerAddress.yaml new file mode 100644 index 000000000..33246c9a4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__RunnerHealth.yaml b/.repos/effect/migration/annotations/effect__cluster__RunnerHealth.yaml new file mode 100644 index 000000000..ed573b9b0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__RunnerServer.yaml b/.repos/effect/migration/annotations/effect__cluster__RunnerServer.yaml new file mode 100644 index 000000000..b74c5f1cc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__RunnerStorage.yaml b/.repos/effect/migration/annotations/effect__cluster__RunnerStorage.yaml new file mode 100644 index 000000000..59560ad17 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Runners.yaml b/.repos/effect/migration/annotations/effect__cluster__Runners.yaml new file mode 100644 index 000000000..e380f964d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__ShardId.yaml b/.repos/effect/migration/annotations/effect__cluster__ShardId.yaml new file mode 100644 index 000000000..3f416f62a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Sharding.yaml b/.repos/effect/migration/annotations/effect__cluster__Sharding.yaml new file mode 100644 index 000000000..774b80e24 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__ShardingConfig.yaml b/.repos/effect/migration/annotations/effect__cluster__ShardingConfig.yaml new file mode 100644 index 000000000..f429966f0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__ShardingRegistrationEvent.yaml b/.repos/effect/migration/annotations/effect__cluster__ShardingRegistrationEvent.yaml new file mode 100644 index 000000000..8aeb99b27 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__SingleRunner.yaml b/.repos/effect/migration/annotations/effect__cluster__SingleRunner.yaml new file mode 100644 index 000000000..d8dd53d8d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Singleton.yaml b/.repos/effect/migration/annotations/effect__cluster__Singleton.yaml new file mode 100644 index 000000000..dd9745911 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__SingletonAddress.yaml b/.repos/effect/migration/annotations/effect__cluster__SingletonAddress.yaml new file mode 100644 index 000000000..6f81f8f00 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__Snowflake.yaml b/.repos/effect/migration/annotations/effect__cluster__Snowflake.yaml new file mode 100644 index 000000000..67e87e5d4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__SocketRunner.yaml b/.repos/effect/migration/annotations/effect__cluster__SocketRunner.yaml new file mode 100644 index 000000000..76e9b2481 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__SqlMessageStorage.yaml b/.repos/effect/migration/annotations/effect__cluster__SqlMessageStorage.yaml new file mode 100644 index 000000000..833229bae --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__SqlRunnerStorage.yaml b/.repos/effect/migration/annotations/effect__cluster__SqlRunnerStorage.yaml new file mode 100644 index 000000000..39c6b0c37 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__TestRunner.yaml b/.repos/effect/migration/annotations/effect__cluster__TestRunner.yaml new file mode 100644 index 000000000..521f937d8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__cluster__index.yaml b/.repos/effect/migration/annotations/effect__cluster__index.yaml new file mode 100644 index 000000000..00d9e4089 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental.yaml b/.repos/effect/migration/annotations/effect__experimental.yaml new file mode 100644 index 000000000..8f4c3ffa2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__DevTools.yaml b/.repos/effect/migration/annotations/effect__experimental__DevTools.yaml new file mode 100644 index 000000000..44ce415b7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__DevTools__Client.yaml b/.repos/effect/migration/annotations/effect__experimental__DevTools__Client.yaml new file mode 100644 index 000000000..6009364c9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__DevTools__Domain.yaml b/.repos/effect/migration/annotations/effect__experimental__DevTools__Domain.yaml new file mode 100644 index 000000000..71f36dcb2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__DevTools__Server.yaml b/.repos/effect/migration/annotations/effect__experimental__DevTools__Server.yaml new file mode 100644 index 000000000..15396b28a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Event.yaml b/.repos/effect/migration/annotations/effect__experimental__Event.yaml new file mode 100644 index 000000000..89e43d33b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__EventGroup.yaml b/.repos/effect/migration/annotations/effect__experimental__EventGroup.yaml new file mode 100644 index 000000000..75f54efe3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__EventJournal.yaml b/.repos/effect/migration/annotations/effect__experimental__EventJournal.yaml new file mode 100644 index 000000000..d7ad705bc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__EventLog.yaml b/.repos/effect/migration/annotations/effect__experimental__EventLog.yaml new file mode 100644 index 000000000..869753522 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__EventLogRemote.yaml b/.repos/effect/migration/annotations/effect__experimental__EventLogRemote.yaml new file mode 100644 index 000000000..281f01ae7 --- /dev/null +++ b/.repos/effect/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.layerMsgPack replace the module-specific request decoder. +"@effect/experimental/EventLogRemote#decodeResponse": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: Generic RPC framing and RpcSerialization.layerMsgPack replace the module-specific response decoder. +"@effect/experimental/EventLogRemote#encodeRequest": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: Generic RPC framing and RpcSerialization.layerMsgPack replace the module-specific request encoder. +"@effect/experimental/EventLogRemote#encodeResponse": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: Generic RPC framing and RpcSerialization.layerMsgPack 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, MsgPack 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#layerMsgPack + note: Use the generic MsgPack 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#layerMsgPack + note: Use the generic MsgPack 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/.repos/effect/migration/annotations/effect__experimental__EventLogServer.yaml b/.repos/effect/migration/annotations/effect__experimental__EventLogServer.yaml new file mode 100644 index 000000000..8a044978f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__EventLogServer__Cloudflare.yaml b/.repos/effect/migration/annotations/effect__experimental__EventLogServer__Cloudflare.yaml new file mode 100644 index 000000000..bec876f63 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Machine.yaml b/.repos/effect/migration/annotations/effect__experimental__Machine.yaml new file mode 100644 index 000000000..d4d737f3d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Machine__Procedure.yaml b/.repos/effect/migration/annotations/effect__experimental__Machine__Procedure.yaml new file mode 100644 index 000000000..2a62aeb1e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Machine__ProcedureList.yaml b/.repos/effect/migration/annotations/effect__experimental__Machine__ProcedureList.yaml new file mode 100644 index 000000000..6dd50fbfb --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Machine__SerializableProcedureList.yaml b/.repos/effect/migration/annotations/effect__experimental__Machine__SerializableProcedureList.yaml new file mode 100644 index 000000000..3dcce020f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__PersistedCache.yaml b/.repos/effect/migration/annotations/effect__experimental__PersistedCache.yaml new file mode 100644 index 000000000..d766b9c6b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__PersistedQueue.yaml b/.repos/effect/migration/annotations/effect__experimental__PersistedQueue.yaml new file mode 100644 index 000000000..bd53323f6 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__experimental__PersistedQueue.yaml @@ -0,0 +1,12 @@ +"@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#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/.repos/effect/migration/annotations/effect__experimental__PersistedQueue__Redis.yaml b/.repos/effect/migration/annotations/effect__experimental__PersistedQueue__Redis.yaml new file mode 100644 index 000000000..cc9dd54be --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Persistence.yaml b/.repos/effect/migration/annotations/effect__experimental__Persistence.yaml new file mode 100644 index 000000000..44ee17247 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Persistence__Lmdb.yaml b/.repos/effect/migration/annotations/effect__experimental__Persistence__Lmdb.yaml new file mode 100644 index 000000000..16ac3dfd3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Persistence__Redis.yaml b/.repos/effect/migration/annotations/effect__experimental__Persistence__Redis.yaml new file mode 100644 index 000000000..dfc6b6e41 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__RateLimiter.yaml b/.repos/effect/migration/annotations/effect__experimental__RateLimiter.yaml new file mode 100644 index 000000000..4ca45b2d7 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__experimental__RateLimiter.yaml @@ -0,0 +1,15 @@ +"@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#TypeId": + replacement: effect/unstable/persistence/RateLimiter#TypeId + note: Import TypeId from the v4 unstable RateLimiter module; it is now a string brand. diff --git a/.repos/effect/migration/annotations/effect__experimental__RateLimiter__Redis.yaml b/.repos/effect/migration/annotations/effect__experimental__RateLimiter__Redis.yaml new file mode 100644 index 000000000..27592db8a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Reactivity.yaml b/.repos/effect/migration/annotations/effect__experimental__Reactivity.yaml new file mode 100644 index 000000000..621a7f78d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__RequestResolver.yaml b/.repos/effect/migration/annotations/effect__experimental__RequestResolver.yaml new file mode 100644 index 000000000..69fe62a7b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__Sse.yaml b/.repos/effect/migration/annotations/effect__experimental__Sse.yaml new file mode 100644 index 000000000..5f233fe6c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__VariantSchema.yaml b/.repos/effect/migration/annotations/effect__experimental__VariantSchema.yaml new file mode 100644 index 000000000..865c3f4d0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__experimental__index.yaml b/.repos/effect/migration/annotations/effect__experimental__index.yaml new file mode 100644 index 000000000..ca42fdd69 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__index.yaml b/.repos/effect/migration/annotations/effect__index.yaml new file mode 100644 index 000000000..1f52497db --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__Logger.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__Logger.yaml new file mode 100644 index 000000000..ea049f648 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__Metrics.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__Metrics.yaml new file mode 100644 index 000000000..ad26cfa0d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__NodeSdk.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__NodeSdk.yaml new file mode 100644 index 000000000..4c9ab8a47 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__Otlp.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__Otlp.yaml new file mode 100644 index 000000000..6737fd2d4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__OtlpLogger.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__OtlpLogger.yaml new file mode 100644 index 000000000..abe3f8563 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__OtlpMetrics.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__OtlpMetrics.yaml new file mode 100644 index 000000000..c6b7b28b8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__OtlpResource.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__OtlpResource.yaml new file mode 100644 index 000000000..a649a5fb4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__OtlpTracer.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__OtlpTracer.yaml new file mode 100644 index 000000000..2487741a9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__Resource.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__Resource.yaml new file mode 100644 index 000000000..efe4a6026 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__Tracer.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__Tracer.yaml new file mode 100644 index 000000000..c6448494f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__WebSdk.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__WebSdk.yaml new file mode 100644 index 000000000..5b6fe7aa7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__opentelemetry__index.yaml b/.repos/effect/migration/annotations/effect__opentelemetry__index.yaml new file mode 100644 index 000000000..7ae5d345b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-browser__BrowserHttpClient.yaml b/.repos/effect/migration/annotations/effect__platform-browser__BrowserHttpClient.yaml new file mode 100644 index 000000000..f27bb5e68 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-browser__BrowserWorker.yaml b/.repos/effect/migration/annotations/effect__platform-browser__BrowserWorker.yaml new file mode 100644 index 000000000..1d91ac92e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-browser__BrowserWorkerRunner.yaml b/.repos/effect/migration/annotations/effect__platform-browser__BrowserWorkerRunner.yaml new file mode 100644 index 000000000..c0b9d7bdc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-browser__Clipboard.yaml b/.repos/effect/migration/annotations/effect__platform-browser__Clipboard.yaml new file mode 100644 index 000000000..f9e8846c0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-browser__Geolocation.yaml b/.repos/effect/migration/annotations/effect__platform-browser__Geolocation.yaml new file mode 100644 index 000000000..f7e226d97 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-browser__Permissions.yaml b/.repos/effect/migration/annotations/effect__platform-browser__Permissions.yaml new file mode 100644 index 000000000..9d87f2777 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-browser__index.yaml b/.repos/effect/migration/annotations/effect__platform-browser__index.yaml new file mode 100644 index 000000000..0def0b900 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunCommandExecutor.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunCommandExecutor.yaml new file mode 100644 index 000000000..ac93b76bd --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunContext.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunContext.yaml new file mode 100644 index 000000000..c4cc6a98e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunFileSystem__ParcelWatcher.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunFileSystem__ParcelWatcher.yaml new file mode 100644 index 000000000..a90b9e375 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunHttpPlatform.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunHttpPlatform.yaml new file mode 100644 index 000000000..2fdea4a15 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunHttpServer.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunHttpServer.yaml new file mode 100644 index 000000000..95b7882e1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunHttpServerRequest.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunHttpServerRequest.yaml new file mode 100644 index 000000000..9f720a621 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunKeyValueStore.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunKeyValueStore.yaml new file mode 100644 index 000000000..000285be3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunSink.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunSink.yaml new file mode 100644 index 000000000..3d2ed68a6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunSocket.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunSocket.yaml new file mode 100644 index 000000000..49ab6a192 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunSocketServer.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunSocketServer.yaml new file mode 100644 index 000000000..8d2679ede --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__BunStream.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunStream.yaml new file mode 100644 index 000000000..4b343a35c --- /dev/null +++ b/.repos/effect/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, onError, and bufferSize." +"@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/.repos/effect/migration/annotations/effect__platform-bun__BunWorker.yaml b/.repos/effect/migration/annotations/effect__platform-bun__BunWorker.yaml new file mode 100644 index 000000000..9757ec982 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-bun__index.yaml b/.repos/effect/migration/annotations/effect__platform-bun__index.yaml new file mode 100644 index 000000000..ad9ef5d50 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node-shared__NodeCommandExecutor.yaml b/.repos/effect/migration/annotations/effect__platform-node-shared__NodeCommandExecutor.yaml new file mode 100644 index 000000000..87da42189 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node-shared__NodeFileSystem__ParcelWatcher.yaml b/.repos/effect/migration/annotations/effect__platform-node-shared__NodeFileSystem__ParcelWatcher.yaml new file mode 100644 index 000000000..cf2eb77b4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node-shared__NodeKeyValueStore.yaml b/.repos/effect/migration/annotations/effect__platform-node-shared__NodeKeyValueStore.yaml new file mode 100644 index 000000000..8215e91d1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node-shared__NodeMultipart.yaml b/.repos/effect/migration/annotations/effect__platform-node-shared__NodeMultipart.yaml new file mode 100644 index 000000000..d130beed3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node-shared__NodeSink.yaml b/.repos/effect/migration/annotations/effect__platform-node-shared__NodeSink.yaml new file mode 100644 index 000000000..2f59e6685 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node-shared__NodeSocket.yaml b/.repos/effect/migration/annotations/effect__platform-node-shared__NodeSocket.yaml new file mode 100644 index 000000000..9994d65cd --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node-shared__NodeStream.yaml b/.repos/effect/migration/annotations/effect__platform-node-shared__NodeStream.yaml new file mode 100644 index 000000000..8ee6af441 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__NodeCommandExecutor.yaml b/.repos/effect/migration/annotations/effect__platform-node__NodeCommandExecutor.yaml new file mode 100644 index 000000000..20a52d962 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__NodeContext.yaml b/.repos/effect/migration/annotations/effect__platform-node__NodeContext.yaml new file mode 100644 index 000000000..670e8ac4c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__NodeFileSystem__ParcelWatcher.yaml b/.repos/effect/migration/annotations/effect__platform-node__NodeFileSystem__ParcelWatcher.yaml new file mode 100644 index 000000000..55d29969b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__NodeHttpClient.yaml b/.repos/effect/migration/annotations/effect__platform-node__NodeHttpClient.yaml new file mode 100644 index 000000000..a706f503b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__NodeHttpServer.yaml b/.repos/effect/migration/annotations/effect__platform-node__NodeHttpServer.yaml new file mode 100644 index 000000000..7de86ada6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__NodeKeyValueStore.yaml b/.repos/effect/migration/annotations/effect__platform-node__NodeKeyValueStore.yaml new file mode 100644 index 000000000..64cc6a8b5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__NodeWorker.yaml b/.repos/effect/migration/annotations/effect__platform-node__NodeWorker.yaml new file mode 100644 index 000000000..95a7a7fe3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__Undici.yaml b/.repos/effect/migration/annotations/effect__platform-node__Undici.yaml new file mode 100644 index 000000000..04d45d0fc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform-node__index.yaml b/.repos/effect/migration/annotations/effect__platform-node__index.yaml new file mode 100644 index 000000000..d2e0c66e9 --- /dev/null +++ b/.repos/effect/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 the same namespaces from the @effect/platform-node package root or import specific modules directly." diff --git a/.repos/effect/migration/annotations/effect__platform.yaml b/.repos/effect/migration/annotations/effect__platform.yaml new file mode 100644 index 000000000..4653d0b89 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__ChannelSchema.yaml b/.repos/effect/migration/annotations/effect__platform__ChannelSchema.yaml new file mode 100644 index 000000000..7a48fbcb2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Command.yaml b/.repos/effect/migration/annotations/effect__platform__Command.yaml new file mode 100644 index 000000000..af9a59b08 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__CommandExecutor.yaml b/.repos/effect/migration/annotations/effect__platform__CommandExecutor.yaml new file mode 100644 index 000000000..976d72eb2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Cookies.yaml b/.repos/effect/migration/annotations/effect__platform__Cookies.yaml new file mode 100644 index 000000000..0cf3cfa23 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__Cookies.yaml @@ -0,0 +1,21 @@ +"@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." diff --git a/.repos/effect/migration/annotations/effect__platform__Effectify.yaml b/.repos/effect/migration/annotations/effect__platform__Effectify.yaml new file mode 100644 index 000000000..ca0d6fe34 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Error.yaml b/.repos/effect/migration/annotations/effect__platform__Error.yaml new file mode 100644 index 000000000..61763d683 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Etag.yaml b/.repos/effect/migration/annotations/effect__platform__Etag.yaml new file mode 100644 index 000000000..4c3f0b6ce --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__FetchHttpClient.yaml b/.repos/effect/migration/annotations/effect__platform__FetchHttpClient.yaml new file mode 100644 index 000000000..1186c7d05 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__FileSystem.yaml b/.repos/effect/migration/annotations/effect__platform__FileSystem.yaml new file mode 100644 index 000000000..ddd7a88d3 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__FileSystem.yaml @@ -0,0 +1,72 @@ +"@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#isFile": + replacement: "FileSystem.isFile" + note: "The guard remains after moving the module to effect/FileSystem." +"@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#OpenFileOptions": + replacement: "NonNullable[1]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@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: "FileSystem.Size" + note: "The branded bigint size type remains after moving the module to effect/FileSystem." +"@effect/platform/FileSystem#StreamOptions": + replacement: "NonNullable[1]>" + note: "Stream options are inline; bufferSize was removed while bytesToRead, chunkSize, and offset remain." +"@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/.repos/effect/migration/annotations/effect__platform__Headers.yaml b/.repos/effect/migration/annotations/effect__platform__Headers.yaml new file mode 100644 index 000000000..ef95fbf84 --- /dev/null +++ b/.repos/effect/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: "Headers.HeadersSchema" + note: "The encoded-record and self schemas were consolidated into HeadersSchema." +"@effect/platform/Headers#schemaFromSelf": + replacement: "Headers.HeadersSchema" + note: "The encoded-record and self schemas were consolidated into HeadersSchema." +"@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/.repos/effect/migration/annotations/effect__platform__HttpApi.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApi.yaml new file mode 100644 index 000000000..d761c45e2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiBuilder.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiBuilder.yaml new file mode 100644 index 000000000..3742d27e5 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__HttpApiBuilder.yaml @@ -0,0 +1,48 @@ +"@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#endpoint" + note: "Use endpoint for a standalone typed endpoint implementation; inside a group pass callbacks 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#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/.repos/effect/migration/annotations/effect__platform__HttpApiClient.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiClient.yaml new file mode 100644 index 000000000..92cf124c3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiEndpoint.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiEndpoint.yaml new file mode 100644 index 000000000..5c8f3b0be --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiError.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiError.yaml new file mode 100644 index 000000000..2d3532777 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiGroup.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiGroup.yaml new file mode 100644 index 000000000..5297e522d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiMiddleware.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiMiddleware.yaml new file mode 100644 index 000000000..d0389a554 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiScalar.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiScalar.yaml new file mode 100644 index 000000000..a6853fcaf --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiSchema.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiSchema.yaml new file mode 100644 index 000000000..3f0cc5009 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiSecurity.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiSecurity.yaml new file mode 100644 index 000000000..f29b17c5a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApiSwagger.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApiSwagger.yaml new file mode 100644 index 000000000..170c2c414 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpApp.yaml b/.repos/effect/migration/annotations/effect__platform__HttpApp.yaml new file mode 100644 index 000000000..774f5823c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpBody.yaml b/.repos/effect/migration/annotations/effect__platform__HttpBody.yaml new file mode 100644 index 000000000..4e8e46f1b --- /dev/null +++ b/.repos/effect/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 chunkSize and the other file options remain." +"@effect/platform/HttpBody#fileInfo": + replacement: "HttpBody.fileFromInfo" + note: "Renamed; it still uses supplied File.Info for content length and requires FileSystem." +"@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/.repos/effect/migration/annotations/effect__platform__HttpClient.yaml b/.repos/effect/migration/annotations/effect__platform__HttpClient.yaml new file mode 100644 index 000000000..6b7033dbb --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpClientError.yaml b/.repos/effect/migration/annotations/effect__platform__HttpClientError.yaml new file mode 100644 index 000000000..37baa2d15 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpClientRequest.yaml b/.repos/effect/migration/annotations/effect__platform__HttpClientRequest.yaml new file mode 100644 index 000000000..e6eba3580 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpClientResponse.yaml b/.repos/effect/migration/annotations/effect__platform__HttpClientResponse.yaml new file mode 100644 index 000000000..cc48c14c8 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__HttpClientResponse.yaml @@ -0,0 +1,27 @@ +"@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." diff --git a/.repos/effect/migration/annotations/effect__platform__HttpIncomingMessage.yaml b/.repos/effect/migration/annotations/effect__platform__HttpIncomingMessage.yaml new file mode 100644 index 000000000..f720fb5d1 --- /dev/null +++ b/.repos/effect/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 FileSystem.Size(input) or undefined directly." diff --git a/.repos/effect/migration/annotations/effect__platform__HttpLayerRouter.yaml b/.repos/effect/migration/annotations/effect__platform__HttpLayerRouter.yaml new file mode 100644 index 000000000..ef12c88d0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpMethod.yaml b/.repos/effect/migration/annotations/effect__platform__HttpMethod.yaml new file mode 100644 index 000000000..73edb9d95 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpMiddleware.yaml b/.repos/effect/migration/annotations/effect__platform__HttpMiddleware.yaml new file mode 100644 index 000000000..dd8f50df1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpMultiplex.yaml b/.repos/effect/migration/annotations/effect__platform__HttpMultiplex.yaml new file mode 100644 index 000000000..d11372f2a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpPlatform.yaml b/.repos/effect/migration/annotations/effect__platform__HttpPlatform.yaml new file mode 100644 index 000000000..ba4d67380 --- /dev/null +++ b/.repos/effect/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." +"@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 and uses updated file stream options." +"@effect/platform/HttpPlatform#TypeId": + replacement: "none" + note: "The public type id was removed; use the HttpPlatform Context.Service class." diff --git a/.repos/effect/migration/annotations/effect__platform__HttpRouter.yaml b/.repos/effect/migration/annotations/effect__platform__HttpRouter.yaml new file mode 100644 index 000000000..7fdd24d99 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpServer.yaml b/.repos/effect/migration/annotations/effect__platform__HttpServer.yaml new file mode 100644 index 000000000..be98b6d5d --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__HttpServer.yaml @@ -0,0 +1,27 @@ +"@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: "HttpServer.TcpAddress" + note: "Moved unchanged." +"@effect/platform/HttpServer#TypeId": + replacement: "none" + note: "The public TypeId was removed; HttpServer is now a Context.Service class." +"@effect/platform/HttpServer#UnixAddress": + replacement: "HttpServer.UnixAddress" + note: "Moved unchanged." +"@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/.repos/effect/migration/annotations/effect__platform__HttpServerError.yaml b/.repos/effect/migration/annotations/effect__platform__HttpServerError.yaml new file mode 100644 index 000000000..60470daf8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpServerRequest.yaml b/.repos/effect/migration/annotations/effect__platform__HttpServerRequest.yaml new file mode 100644 index 000000000..82ea35001 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpServerRespondable.yaml b/.repos/effect/migration/annotations/effect__platform__HttpServerRespondable.yaml new file mode 100644 index 000000000..306afb813 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__HttpServerResponse.yaml b/.repos/effect/migration/annotations/effect__platform__HttpServerResponse.yaml new file mode 100644 index 000000000..86d7dfd14 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__HttpServerResponse.yaml @@ -0,0 +1,63 @@ +"@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 with updated FileSystem stream options." +"@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." diff --git a/.repos/effect/migration/annotations/effect__platform__KeyValueStore.yaml b/.repos/effect/migration/annotations/effect__platform__KeyValueStore.yaml new file mode 100644 index 000000000..ba36c699f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__MsgPack.yaml b/.repos/effect/migration/annotations/effect__platform__MsgPack.yaml new file mode 100644 index 000000000..3c4d47b6b --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__MsgPack.yaml @@ -0,0 +1,24 @@ +"@effect/platform/MsgPack#duplex": + replacement: "Msgpack.duplex" + note: "The API moved to effect/unstable/encoding/Msgpack." +"@effect/platform/MsgPack#duplexSchema": + replacement: "Msgpack.duplexSchema" + note: "The API moved to effect/unstable/encoding/Msgpack and uses v4 Schema constraints." +"@effect/platform/MsgPack#ErrorTypeId": + replacement: "Msgpack.MsgPackError" + note: "The public error type-id alias was removed; use the MsgPackError class." +"@effect/platform/MsgPack#pack": + replacement: "Msgpack.encode" + note: "The MessagePack channel constructor was renamed from pack to encode." +"@effect/platform/MsgPack#packSchema": + replacement: "Msgpack.encodeSchema" + note: "The schema-aware pack channel was renamed to encodeSchema." +"@effect/platform/MsgPack#schema": + replacement: "Msgpack.schema" + note: "The schema helper remains in the moved module and uses the v4 Schema model." +"@effect/platform/MsgPack#unpack": + replacement: "Msgpack.decode" + note: "The MessagePack channel constructor was renamed from unpack to decode." +"@effect/platform/MsgPack#unpackSchema": + replacement: "Msgpack.decodeSchema" + note: "The schema-aware unpack channel was renamed to decodeSchema." diff --git a/.repos/effect/migration/annotations/effect__platform__Multipart.yaml b/.repos/effect/migration/annotations/effect__platform__Multipart.yaml new file mode 100644 index 000000000..e5f43102e --- /dev/null +++ b/.repos/effect/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: "The setting remains but is now a Context.Reference." +"@effect/platform/Multipart#MaxFileSize": + replacement: "Multipart.MaxFileSize" + note: "The setting remains as a Context.Reference; use undefined rather than Option.none 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 the v4 Context.Reference around the effect." +"@effect/platform/Multipart#withMaxFileSize": + replacement: "Effect.provideService(Multipart.MaxFileSize, size)" + note: "Provide the v4 Context.Reference around the effect, converting Option.none to undefined." +"@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 SizeInput values; convert Option.none to undefined and Option.some(value) to value." diff --git a/.repos/effect/migration/annotations/effect__platform__Ndjson.yaml b/.repos/effect/migration/annotations/effect__platform__Ndjson.yaml new file mode 100644 index 000000000..9786ff676 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__OpenApi.yaml b/.repos/effect/migration/annotations/effect__platform__OpenApi.yaml new file mode 100644 index 000000000..fe9cb44cf --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__OpenApiJsonSchema.yaml b/.repos/effect/migration/annotations/effect__platform__OpenApiJsonSchema.yaml new file mode 100644 index 000000000..683b937f3 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__OpenApiJsonSchema.yaml @@ -0,0 +1,45 @@ +"@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#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#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." diff --git a/.repos/effect/migration/annotations/effect__platform__Path.yaml b/.repos/effect/migration/annotations/effect__platform__Path.yaml new file mode 100644 index 000000000..c80325ae5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__PlatformConfigProvider.yaml b/.repos/effect/migration/annotations/effect__platform__PlatformConfigProvider.yaml new file mode 100644 index 000000000..82b29e988 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__PlatformLogger.yaml b/.repos/effect/migration/annotations/effect__platform__PlatformLogger.yaml new file mode 100644 index 000000000..6e418cd39 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Runtime.yaml b/.repos/effect/migration/annotations/effect__platform__Runtime.yaml new file mode 100644 index 000000000..750590e00 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Socket.yaml b/.repos/effect/migration/annotations/effect__platform__Socket.yaml new file mode 100644 index 000000000..cb71a5141 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__Socket.yaml @@ -0,0 +1,27 @@ +"@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: "Socket.SendQueueCapacity" + note: "The FiberRef was replaced by a defaulted Context.Reference." +"@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." diff --git a/.repos/effect/migration/annotations/effect__platform__SocketServer.yaml b/.repos/effect/migration/annotations/effect__platform__SocketServer.yaml new file mode 100644 index 000000000..b7a20dbd8 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__SocketServer.yaml @@ -0,0 +1,9 @@ +"@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: "SocketServer.TcpAddress" + note: "The API moved to effect/unstable/socket/SocketServer and retains this name." +"@effect/platform/SocketServer#UnixAddress": + replacement: "SocketServer.UnixAddress" + note: "The API moved to effect/unstable/socket/SocketServer and retains this name." diff --git a/.repos/effect/migration/annotations/effect__platform__Template.yaml b/.repos/effect/migration/annotations/effect__platform__Template.yaml new file mode 100644 index 000000000..92bdf007f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Terminal.yaml b/.repos/effect/migration/annotations/effect__platform__Terminal.yaml new file mode 100644 index 000000000..9750e3f2a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Transferable.yaml b/.repos/effect/migration/annotations/effect__platform__Transferable.yaml new file mode 100644 index 000000000..02a5415ee --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__Url.yaml b/.repos/effect/migration/annotations/effect__platform__Url.yaml new file mode 100644 index 000000000..951df8c57 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__Url.yaml @@ -0,0 +1,6 @@ +"@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/.repos/effect/migration/annotations/effect__platform__UrlParams.yaml b/.repos/effect/migration/annotations/effect__platform__UrlParams.yaml new file mode 100644 index 000000000..a799f823a --- /dev/null +++ b/.repos/effect/migration/annotations/effect__platform__UrlParams.yaml @@ -0,0 +1,51 @@ +"@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: "UrlParams.UrlParamsSchema" + note: "Renamed to the declaration schema for the v4 wrapper." +"@effect/platform/UrlParams#schemaFromString": + replacement: "Schema.String.pipe(Schema.decodeTo(UrlParams.UrlParamsSchema, { 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: "UrlParams.schemaJsonField(field).pipe(Schema.decodeTo(schema), Schema.decodeEffect)" + note: "Compose the field codec with the target schema, then decode it." +"@effect/platform/UrlParams#schemaParse": + replacement: "UrlParamsFromString.pipe(Schema.decodeTo(UrlParams.schemaRecord.pipe(Schema.decodeTo(schema))))" + note: "Recreate the removed helper by composing the string, record, and target codecs." +"@effect/platform/UrlParams#schemaRecord": + replacement: "UrlParams.schemaRecord.pipe(Schema.decodeTo(schema))" + note: "schemaRecord is now a base codec value; compose it with the target schema." +"@effect/platform/UrlParams#schemaStruct": + replacement: "UrlParams.schemaRecord.pipe(Schema.decodeTo(schema), Schema.decodeEffect)" + note: "Compose the record codec 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." diff --git a/.repos/effect/migration/annotations/effect__platform__Worker.yaml b/.repos/effect/migration/annotations/effect__platform__Worker.yaml new file mode 100644 index 000000000..21e8d6ae2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__WorkerError.yaml b/.repos/effect/migration/annotations/effect__platform__WorkerError.yaml new file mode 100644 index 000000000..52956590e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__WorkerRunner.yaml b/.repos/effect/migration/annotations/effect__platform__WorkerRunner.yaml new file mode 100644 index 000000000..8991b6766 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__platform__index.yaml b/.repos/effect/migration/annotations/effect__platform__index.yaml new file mode 100644 index 000000000..ae474455a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer-ansi.yaml b/.repos/effect/migration/annotations/effect__printer-ansi.yaml new file mode 100644 index 000000000..26e03671d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer-ansi__Ansi.yaml b/.repos/effect/migration/annotations/effect__printer-ansi__Ansi.yaml new file mode 100644 index 000000000..08a09ef88 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer-ansi__AnsiDoc.yaml b/.repos/effect/migration/annotations/effect__printer-ansi__AnsiDoc.yaml new file mode 100644 index 000000000..6b8aa9c5c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer-ansi__Color.yaml b/.repos/effect/migration/annotations/effect__printer-ansi__Color.yaml new file mode 100644 index 000000000..aab454ff4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer-ansi__index.yaml b/.repos/effect/migration/annotations/effect__printer-ansi__index.yaml new file mode 100644 index 000000000..a29ae873a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer.yaml b/.repos/effect/migration/annotations/effect__printer.yaml new file mode 100644 index 000000000..2f14c60c4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__Doc.yaml b/.repos/effect/migration/annotations/effect__printer__Doc.yaml new file mode 100644 index 000000000..7d407cdc9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__DocStream.yaml b/.repos/effect/migration/annotations/effect__printer__DocStream.yaml new file mode 100644 index 000000000..af84c3873 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__DocTree.yaml b/.repos/effect/migration/annotations/effect__printer__DocTree.yaml new file mode 100644 index 000000000..64e960263 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__Flatten.yaml b/.repos/effect/migration/annotations/effect__printer__Flatten.yaml new file mode 100644 index 000000000..4e63575e8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__Layout.yaml b/.repos/effect/migration/annotations/effect__printer__Layout.yaml new file mode 100644 index 000000000..a393f185b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__Optimize.yaml b/.repos/effect/migration/annotations/effect__printer__Optimize.yaml new file mode 100644 index 000000000..74cb297f3 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__PageWidth.yaml b/.repos/effect/migration/annotations/effect__printer__PageWidth.yaml new file mode 100644 index 000000000..d549449d4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__printer__index.yaml b/.repos/effect/migration/annotations/effect__printer__index.yaml new file mode 100644 index 000000000..aaa28f6ad --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc.yaml b/.repos/effect/migration/annotations/effect__rpc.yaml new file mode 100644 index 000000000..c20087b59 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__Rpc.yaml b/.repos/effect/migration/annotations/effect__rpc__Rpc.yaml new file mode 100644 index 000000000..afbdd05e9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcClient.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcClient.yaml new file mode 100644 index 000000000..99a872332 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcClientError.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcClientError.yaml new file mode 100644 index 000000000..db32a74ef --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcGroup.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcGroup.yaml new file mode 100644 index 000000000..b26b2af22 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcMessage.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcMessage.yaml new file mode 100644 index 000000000..6694182b5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcMiddleware.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcMiddleware.yaml new file mode 100644 index 000000000..3fad622b6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcSchema.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcSchema.yaml new file mode 100644 index 000000000..6164045df --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcServer.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcServer.yaml new file mode 100644 index 000000000..fdba78685 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__RpcTest.yaml b/.repos/effect/migration/annotations/effect__rpc__RpcTest.yaml new file mode 100644 index 000000000..caa66472b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__rpc__index.yaml b/.repos/effect/migration/annotations/effect__rpc__index.yaml new file mode 100644 index 000000000..3387ce048 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-clickhouse__ClickhouseClient.yaml b/.repos/effect/migration/annotations/effect__sql-clickhouse__ClickhouseClient.yaml new file mode 100644 index 000000000..2453212dd --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-clickhouse__ClickhouseMigrator.yaml b/.repos/effect/migration/annotations/effect__sql-clickhouse__ClickhouseMigrator.yaml new file mode 100644 index 000000000..c773d443f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-clickhouse__index.yaml b/.repos/effect/migration/annotations/effect__sql-clickhouse__index.yaml new file mode 100644 index 000000000..61fd8dd3f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-d1__D1Client.yaml b/.repos/effect/migration/annotations/effect__sql-d1__D1Client.yaml new file mode 100644 index 000000000..a73f16358 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-d1__index.yaml b/.repos/effect/migration/annotations/effect__sql-d1__index.yaml new file mode 100644 index 000000000..a0299ad51 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-drizzle__Mysql.yaml b/.repos/effect/migration/annotations/effect__sql-drizzle__Mysql.yaml new file mode 100644 index 000000000..963da1e61 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-drizzle__Pg.yaml b/.repos/effect/migration/annotations/effect__sql-drizzle__Pg.yaml new file mode 100644 index 000000000..a435b5544 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-drizzle__Sqlite.yaml b/.repos/effect/migration/annotations/effect__sql-drizzle__Sqlite.yaml new file mode 100644 index 000000000..7a1f5fc35 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-kysely__Kysely.yaml b/.repos/effect/migration/annotations/effect__sql-kysely__Kysely.yaml new file mode 100644 index 000000000..6846ace99 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-kysely__Mssql.yaml b/.repos/effect/migration/annotations/effect__sql-kysely__Mssql.yaml new file mode 100644 index 000000000..1c8b0f2e4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-kysely__Mysql.yaml b/.repos/effect/migration/annotations/effect__sql-kysely__Mysql.yaml new file mode 100644 index 000000000..40b3de2d2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-kysely__Pg.yaml b/.repos/effect/migration/annotations/effect__sql-kysely__Pg.yaml new file mode 100644 index 000000000..b0caeee56 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-kysely__Sqlite.yaml b/.repos/effect/migration/annotations/effect__sql-kysely__Sqlite.yaml new file mode 100644 index 000000000..868b4e6d7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-kysely__patch.types.yaml b/.repos/effect/migration/annotations/effect__sql-kysely__patch.types.yaml new file mode 100644 index 000000000..7d18812ba --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-libsql__index.yaml b/.repos/effect/migration/annotations/effect__sql-libsql__index.yaml new file mode 100644 index 000000000..e2efef269 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-mssql__MssqlClient.yaml b/.repos/effect/migration/annotations/effect__sql-mssql__MssqlClient.yaml new file mode 100644 index 000000000..31b4626f0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-mssql__Parameter.yaml b/.repos/effect/migration/annotations/effect__sql-mssql__Parameter.yaml new file mode 100644 index 000000000..12e910afc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-mssql__Procedure.yaml b/.repos/effect/migration/annotations/effect__sql-mssql__Procedure.yaml new file mode 100644 index 000000000..611b275ee --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-mssql__index.yaml b/.repos/effect/migration/annotations/effect__sql-mssql__index.yaml new file mode 100644 index 000000000..56292676b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-mysql2__MysqlClient.yaml b/.repos/effect/migration/annotations/effect__sql-mysql2__MysqlClient.yaml new file mode 100644 index 000000000..817cc7ed0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-mysql2__index.yaml b/.repos/effect/migration/annotations/effect__sql-mysql2__index.yaml new file mode 100644 index 000000000..81c416a1d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-pg__PgClient.yaml b/.repos/effect/migration/annotations/effect__sql-pg__PgClient.yaml new file mode 100644 index 000000000..f74fad15a --- /dev/null +++ b/.repos/effect/migration/annotations/effect__sql-pg__PgClient.yaml @@ -0,0 +1,12 @@ +"@effect/sql-pg/PgClient#layerFromPool": + replacement: "PgClient.layerFrom(PgClient.fromPool(options))" + note: "Compose fromPool with layerFrom; layerFrom now accepts an Effect acquiring a PgClient rather than pool options." +"@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: "Parameters[0]" + note: "The named type was removed; derive the inline fromPool option type. PgPoolConfig is for creating a managed pool and is not equivalent." diff --git a/.repos/effect/migration/annotations/effect__sql-pg__index.yaml b/.repos/effect/migration/annotations/effect__sql-pg__index.yaml new file mode 100644 index 000000000..ad7fcc5a6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-bun__SqliteClient.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-bun__SqliteClient.yaml new file mode 100644 index 000000000..d936bcb8c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-bun__index.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-bun__index.yaml new file mode 100644 index 000000000..2104c4ec6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-do__SqliteClient.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-do__SqliteClient.yaml new file mode 100644 index 000000000..aabf0cd66 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-do__index.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-do__index.yaml new file mode 100644 index 000000000..1ce99cfef --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-node__SqliteClient.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-node__SqliteClient.yaml new file mode 100644 index 000000000..5bbf9cfbe --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-node__index.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-node__index.yaml new file mode 100644 index 000000000..415dbb0d0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-react-native__SqliteClient.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-react-native__SqliteClient.yaml new file mode 100644 index 000000000..d46613df6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-react-native__index.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-react-native__index.yaml new file mode 100644 index 000000000..45a5fa68c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-wasm__SqliteClient.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-wasm__SqliteClient.yaml new file mode 100644 index 000000000..64cf9a2ff --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql-sqlite-wasm__index.yaml b/.repos/effect/migration/annotations/effect__sql-sqlite-wasm__index.yaml new file mode 100644 index 000000000..a649b2af9 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql.yaml b/.repos/effect/migration/annotations/effect__sql.yaml new file mode 100644 index 000000000..613890f1d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__Migrator.yaml b/.repos/effect/migration/annotations/effect__sql__Migrator.yaml new file mode 100644 index 000000000..3ca711ee6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__Migrator__FileSystem.yaml b/.repos/effect/migration/annotations/effect__sql__Migrator__FileSystem.yaml new file mode 100644 index 000000000..10a624a97 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__Model.yaml b/.repos/effect/migration/annotations/effect__sql__Model.yaml new file mode 100644 index 000000000..00db99efc --- /dev/null +++ b/.repos/effect/migration/annotations/effect__sql__Model.yaml @@ -0,0 +1,33 @@ +"@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#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." +"@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/.repos/effect/migration/annotations/effect__sql__SqlClient.yaml b/.repos/effect/migration/annotations/effect__sql__SqlClient.yaml new file mode 100644 index 000000000..e97901a03 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__SqlConnection.yaml b/.repos/effect/migration/annotations/effect__sql__SqlConnection.yaml new file mode 100644 index 000000000..56a7889ac --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__SqlError.yaml b/.repos/effect/migration/annotations/effect__sql__SqlError.yaml new file mode 100644 index 000000000..50b119fac --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__SqlEventJournal.yaml b/.repos/effect/migration/annotations/effect__sql__SqlEventJournal.yaml new file mode 100644 index 000000000..60b3b2d96 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__SqlEventLogServer.yaml b/.repos/effect/migration/annotations/effect__sql__SqlEventLogServer.yaml new file mode 100644 index 000000000..53e298176 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__SqlPersistedQueue.yaml b/.repos/effect/migration/annotations/effect__sql__SqlPersistedQueue.yaml new file mode 100644 index 000000000..fadb08336 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__SqlResolver.yaml b/.repos/effect/migration/annotations/effect__sql__SqlResolver.yaml new file mode 100644 index 000000000..a87b23afd --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__SqlSchema.yaml b/.repos/effect/migration/annotations/effect__sql__SqlSchema.yaml new file mode 100644 index 000000000..4159f622b --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__Statement.yaml b/.repos/effect/migration/annotations/effect__sql__Statement.yaml new file mode 100644 index 000000000..353d499c6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__sql__index.yaml b/.repos/effect/migration/annotations/effect__sql__index.yaml new file mode 100644 index 000000000..045e8637e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass.yaml b/.repos/effect/migration/annotations/effect__typeclass.yaml new file mode 100644 index 000000000..9ebe60ebe --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Alternative.yaml b/.repos/effect/migration/annotations/effect__typeclass__Alternative.yaml new file mode 100644 index 000000000..b50c13482 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Applicative.yaml b/.repos/effect/migration/annotations/effect__typeclass__Applicative.yaml new file mode 100644 index 000000000..a7a8ebd98 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Bicovariant.yaml b/.repos/effect/migration/annotations/effect__typeclass__Bicovariant.yaml new file mode 100644 index 000000000..1fabb3fd7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Bounded.yaml b/.repos/effect/migration/annotations/effect__typeclass__Bounded.yaml new file mode 100644 index 000000000..f8389cbb3 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__typeclass__Bounded.yaml @@ -0,0 +1,15 @@ +"@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#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/.repos/effect/migration/annotations/effect__typeclass__Chainable.yaml b/.repos/effect/migration/annotations/effect__typeclass__Chainable.yaml new file mode 100644 index 000000000..3f6c02a19 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Contravariant.yaml b/.repos/effect/migration/annotations/effect__typeclass__Contravariant.yaml new file mode 100644 index 000000000..62039ee4e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Coproduct.yaml b/.repos/effect/migration/annotations/effect__typeclass__Coproduct.yaml new file mode 100644 index 000000000..86f2652a1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Covariant.yaml b/.repos/effect/migration/annotations/effect__typeclass__Covariant.yaml new file mode 100644 index 000000000..9d8f80180 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Filterable.yaml b/.repos/effect/migration/annotations/effect__typeclass__Filterable.yaml new file mode 100644 index 000000000..b64d96c4f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__FlatMap.yaml b/.repos/effect/migration/annotations/effect__typeclass__FlatMap.yaml new file mode 100644 index 000000000..e5cfb09a6 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Foldable.yaml b/.repos/effect/migration/annotations/effect__typeclass__Foldable.yaml new file mode 100644 index 000000000..3c3d180b2 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Invariant.yaml b/.repos/effect/migration/annotations/effect__typeclass__Invariant.yaml new file mode 100644 index 000000000..25129fe1f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Monad.yaml b/.repos/effect/migration/annotations/effect__typeclass__Monad.yaml new file mode 100644 index 000000000..0ce29d2cc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Monoid.yaml b/.repos/effect/migration/annotations/effect__typeclass__Monoid.yaml new file mode 100644 index 000000000..5d3e62d0a --- /dev/null +++ b/.repos/effect/migration/annotations/effect__typeclass__Monoid.yaml @@ -0,0 +1,18 @@ +"@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#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/.repos/effect/migration/annotations/effect__typeclass__Of.yaml b/.repos/effect/migration/annotations/effect__typeclass__Of.yaml new file mode 100644 index 000000000..3afd034cc --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Pointed.yaml b/.repos/effect/migration/annotations/effect__typeclass__Pointed.yaml new file mode 100644 index 000000000..d03ceb668 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Product.yaml b/.repos/effect/migration/annotations/effect__typeclass__Product.yaml new file mode 100644 index 000000000..6c3fd5e16 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__SemiAlternative.yaml b/.repos/effect/migration/annotations/effect__typeclass__SemiAlternative.yaml new file mode 100644 index 000000000..16d84e90f --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__SemiApplicative.yaml b/.repos/effect/migration/annotations/effect__typeclass__SemiApplicative.yaml new file mode 100644 index 000000000..a5f0a5917 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__SemiCoproduct.yaml b/.repos/effect/migration/annotations/effect__typeclass__SemiCoproduct.yaml new file mode 100644 index 000000000..8ac86cb92 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__SemiProduct.yaml b/.repos/effect/migration/annotations/effect__typeclass__SemiProduct.yaml new file mode 100644 index 000000000..fd13e924d --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__Semigroup.yaml b/.repos/effect/migration/annotations/effect__typeclass__Semigroup.yaml new file mode 100644 index 000000000..eaab3e72a --- /dev/null +++ b/.repos/effect/migration/annotations/effect__typeclass__Semigroup.yaml @@ -0,0 +1,45 @@ +"@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#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/.repos/effect/migration/annotations/effect__typeclass__Traversable.yaml b/.repos/effect/migration/annotations/effect__typeclass__Traversable.yaml new file mode 100644 index 000000000..a939a1622 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__TraversableFilterable.yaml b/.repos/effect/migration/annotations/effect__typeclass__TraversableFilterable.yaml new file mode 100644 index 000000000..add881f13 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Array.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Array.yaml new file mode 100644 index 000000000..e20b9559e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__BigInt.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__BigInt.yaml new file mode 100644 index 000000000..e94aed7f5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Boolean.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Boolean.yaml new file mode 100644 index 000000000..560fdb587 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Duration.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Duration.yaml new file mode 100644 index 000000000..0a6b7dbc0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Effect.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Effect.yaml new file mode 100644 index 000000000..29c7fe373 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Either.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Either.yaml new file mode 100644 index 000000000..ad27bb0d7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Identity.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Identity.yaml new file mode 100644 index 000000000..6f3f4349e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Micro.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Micro.yaml new file mode 100644 index 000000000..03a02542a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Number.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Number.yaml new file mode 100644 index 000000000..14d0a9a9e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Option.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Option.yaml new file mode 100644 index 000000000..bc1a02fd4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Ordering.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Ordering.yaml new file mode 100644 index 000000000..0410c651e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Predicate.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Predicate.yaml new file mode 100644 index 000000000..551003298 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Record.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Record.yaml new file mode 100644 index 000000000..b10996128 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__String.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__String.yaml new file mode 100644 index 000000000..c79396fdf --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__data__Tuple.yaml b/.repos/effect/migration/annotations/effect__typeclass__data__Tuple.yaml new file mode 100644 index 000000000..8f1e2c205 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__typeclass__index.yaml b/.repos/effect/migration/annotations/effect__typeclass__index.yaml new file mode 100644 index 000000000..ff85bec6c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__vitest__index.yaml b/.repos/effect/migration/annotations/effect__vitest__index.yaml new file mode 100644 index 000000000..a40e32ca2 --- /dev/null +++ b/.repos/effect/migration/annotations/effect__vitest__index.yaml @@ -0,0 +1,234 @@ +"@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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 uses TestContext, which includes the current task and lifecycle methods." +"@effect/vitest/index#Environment": + replacement: "vitest/environments#Environment" + note: "This was a deprecated root re-export. Import it from vitest/environments; Vitest 4 custom environments use Vite environments." +"@effect/vitest/index#EnvironmentReturn": + replacement: "vitest/environments#EnvironmentReturn" + note: "This was a deprecated root re-export. Import it from vitest/environments; Vitest 4 custom environments use Vite environments." +"@effect/vitest/index#VmEnvironmentReturn": + replacement: "vitest/environments#VmEnvironmentReturn" + note: "This was a deprecated root re-export. Import it from vitest/environments; Vitest 4 custom environments use Vite environments." +"@effect/vitest/index#HappyDOMOptions": + replacement: "NonNullable" + note: "Vitest 4 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 4 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/reporters#Reporter" + note: "Import Reporter from the public plural vitest/reporters entrypoint; its lifecycle methods changed in Vitest 4." +"@effect/vitest/index#UserConfig": + replacement: "vitest/config#TestUserConfig" + note: "Vitest 4 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 4 top-level config such as maxWorkers and vmMemoryLimit; vitest/node PoolOptions is a different custom-pool API." +"@effect/vitest/index#RuntimeContext": + replacement: "@vitest/runner#RuntimeContext" + note: "Custom-runner code can add an explicit @vitest/runner dependency; ordinary tests should avoid this internal state type." +"@effect/vitest/index#SuiteHooks": + replacement: "@vitest/runner#SuiteHooks" + note: "Custom-runner code can add an explicit @vitest/runner dependency; 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 4 export replaces this alias. Let the hook return type infer, or type the cleanup function locally." +"@effect/vitest/index#HookListener": + replacement: "none" + note: "Use the matching @vitest/runner hook-specific type such as BeforeAllListener, AfterAllListener, BeforeEachListener, or AfterEachListener for custom runner code." +"@effect/vitest/index#ModuleCache": + replacement: "none" + note: "Vitest 3 marked this unused internal cache shape deprecated; Vitest 4 has no public replacement." +"@effect/vitest/index#ResolvedTestEnvironment": + replacement: "none" + note: "Vitest 3 marked this type unsupported. Use Environment from vitest/environments 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." diff --git a/.repos/effect/migration/annotations/effect__vitest__utils.yaml b/.repos/effect/migration/annotations/effect__vitest__utils.yaml new file mode 100644 index 000000000..240ffe3da --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow.yaml b/.repos/effect/migration/annotations/effect__workflow.yaml new file mode 100644 index 000000000..0995bdb5e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__Activity.yaml b/.repos/effect/migration/annotations/effect__workflow__Activity.yaml new file mode 100644 index 000000000..cc4b67596 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__DurableClock.yaml b/.repos/effect/migration/annotations/effect__workflow__DurableClock.yaml new file mode 100644 index 000000000..adb1bf416 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__DurableDeferred.yaml b/.repos/effect/migration/annotations/effect__workflow__DurableDeferred.yaml new file mode 100644 index 000000000..f30fc24b4 --- /dev/null +++ b/.repos/effect/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." +"@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/.repos/effect/migration/annotations/effect__workflow__DurableQueue.yaml b/.repos/effect/migration/annotations/effect__workflow__DurableQueue.yaml new file mode 100644 index 000000000..5c2dbb0f0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__DurableRateLimiter.yaml b/.repos/effect/migration/annotations/effect__workflow__DurableRateLimiter.yaml new file mode 100644 index 000000000..a0261367e --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__Workflow.yaml b/.repos/effect/migration/annotations/effect__workflow__Workflow.yaml new file mode 100644 index 000000000..941cb9f25 --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__WorkflowEngine.yaml b/.repos/effect/migration/annotations/effect__workflow__WorkflowEngine.yaml new file mode 100644 index 000000000..543c7002c --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__WorkflowProxy.yaml b/.repos/effect/migration/annotations/effect__workflow__WorkflowProxy.yaml new file mode 100644 index 000000000..bc1d27b4a --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/annotations/effect__workflow__WorkflowProxyServer.yaml b/.repos/effect/migration/annotations/effect__workflow__WorkflowProxyServer.yaml new file mode 100644 index 000000000..b604847ef --- /dev/null +++ b/.repos/effect/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/.repos/effect/migration/cause.md b/.repos/effect/migration/cause.md new file mode 100644 index 000000000..0c223cdb0 --- /dev/null +++ b/.repos/effect/migration/cause.md @@ -0,0 +1,163 @@ +# Cause: Flattened Structure + +In v3, `Cause` was a recursive tree with six variants: + +``` +Empty | Fail | Die | Interrupt | Sequential | Parallel +``` + +The `Sequential` and `Parallel` variants composed causes into a tree to +represent errors from finalizers or concurrent operations. + +In v4, `Cause` has been flattened to a simple wrapper around an array of +`Reason` values: + +```ts +interface Cause { + readonly reasons: ReadonlyArray> +} + +type Reason = Fail | Die | Interrupt +``` + +There are only three reason variants — `Fail`, `Die`, and `Interrupt`. The +`Empty`, `Sequential`, and `Parallel` variants have been removed. An empty +cause is represented by an empty `reasons` array. Multiple failures (from +concurrent or sequential composition) are collected into a flat array. + +## Accessing Reasons + +**v3** — pattern match on the recursive tree structure: + +```ts +import { Cause } from "effect" + +const handle = (cause: Cause.Cause) => { + switch (cause._tag) { + case "Fail": + return cause.error + case "Die": + return cause.defect + case "Empty": + return undefined + case "Sequential": + return handle(cause.left) + case "Parallel": + return handle(cause.left) + case "Interrupt": + return cause.fiberId + } +} +``` + +**v4** — iterate over the flat `reasons` array: + +```ts +import { Cause } from "effect" + +const handle = (cause: Cause.Cause) => { + for (const reason of cause.reasons) { + switch (reason._tag) { + case "Fail": + return reason.error + case "Die": + return reason.defect + case "Interrupt": + return reason.fiberId + } + } +} +``` + +## Reason Guards + +The v3 type-level guards (`isFailType`, `isDieType`, `isInterruptType`, etc.) +have been replaced by reason-level guards: + +| v3 | v4 | +| ------------------------------- | --------------------------------- | +| `Cause.isEmptyType(cause)` | `cause.reasons.length === 0` | +| `Cause.isFailType(cause)` | `Cause.isFailReason(reason)` | +| `Cause.isDieType(cause)` | `Cause.isDieReason(reason)` | +| `Cause.isInterruptType(cause)` | `Cause.isInterruptReason(reason)` | +| `Cause.isSequentialType(cause)` | Removed | +| `Cause.isParallelType(cause)` | Removed | + +## Cause-Level Predicates + +| v3 | v4 | +| -------------------------------- | -------------------------------- | +| `Cause.isFailure(cause)` | `Cause.hasFails(cause)` | +| `Cause.isDie(cause)` | `Cause.hasDies(cause)` | +| `Cause.isInterrupted(cause)` | `Cause.hasInterrupts(cause)` | +| `Cause.isInterruptedOnly(cause)` | `Cause.hasInterruptsOnly(cause)` | + +## Constructors + +| v3 | v4 | +| ------------------------------- | ---------------------------- | +| `Cause.empty` | `Cause.empty` | +| `Cause.fail(error)` | `Cause.fail(error)` | +| `Cause.die(defect)` | `Cause.die(defect)` | +| `Cause.interrupt(fiberId)` | `Cause.interrupt(fiberId)` | +| `Cause.sequential(left, right)` | `Cause.combine(left, right)` | +| `Cause.parallel(left, right)` | `Cause.combine(left, right)` | + +In v4, `Cause.combine` concatenates the `reasons` arrays of two causes. The +distinction between sequential and parallel composition is no longer +represented in the data structure. + +## Extractors + +| v3 | v4 | +| ------------------------------ | ------------------------------------------ | +| `Cause.failureOption(cause)` | `Cause.findErrorOption(cause)` | +| `Cause.failureOrCause(cause)` | `Cause.findError(cause)` | +| `Cause.dieOption(cause)` | `Cause.findDefect(cause)` | +| `Cause.interruptOption(cause)` | `Cause.findInterrupt(cause)` | +| `Cause.failures(cause)` | `cause.reasons.filter(Cause.isFailReason)` | +| `Cause.defects(cause)` | `cause.reasons.filter(Cause.isDieReason)` | +| `Cause.interruptors(cause)` | `Cause.interruptors(cause)` | + +Note: `findError` and `findDefect` return `Result.Result` instead of `Option`. +Use `findErrorOption` for the `Option`-based variant. + +## Error Classes + +All `*Exception` classes have been renamed to `*Error`: + +| v3 | v4 | +| -------------------------------------- | ----------------------------- | +| `Cause.NoSuchElementException` | `Cause.NoSuchElementError` | +| `Cause.TimeoutException` | `Cause.TimeoutError` | +| `Cause.IllegalArgumentException` | `Cause.IllegalArgumentError` | +| `Cause.ExceededCapacityException` | `Cause.ExceededCapacityError` | +| `Cause.UnknownException` | `Cause.UnknownError` | +| `Cause.RuntimeException` | Removed | +| `Cause.InterruptedException` | Removed | +| `Cause.InvalidPubSubCapacityException` | Removed | + +The corresponding guards follow the same pattern: + +| v3 | v4 | +| -------------------------------------- | ---------------------------------- | +| `Cause.isNoSuchElementException(u)` | `Cause.isNoSuchElementError(u)` | +| `Cause.isTimeoutException(u)` | `Cause.isTimeoutError(u)` | +| `Cause.isIllegalArgumentException(u)` | `Cause.isIllegalArgumentError(u)` | +| `Cause.isExceededCapacityException(u)` | `Cause.isExceededCapacityError(u)` | +| `Cause.isUnknownException(u)` | `Cause.isUnknownError(u)` | + +## New in v4 + +- **`Cause.fromReasons(reasons)`** — construct a `Cause` from an array of + `Reason` values. +- **`Cause.makeFailReason(error)`**, **`Cause.makeDieReason(defect)`**, + **`Cause.makeInterruptReason(fiberId)`** — construct individual `Reason` + values. +- **`Cause.annotate(cause, annotations)`** — attach annotations to a `Cause`. +- **`Cause.findFail(cause)`**, **`Cause.findDie(cause)`**, + **`Cause.findInterrupt(cause)`** — extract specific reason types using + the `Result` module. +- **`Cause.filterInterruptors(cause)`** — extract interrupting fiber IDs as + a `Result`. +- **`Cause.Done`** — a graceful completion signal for queues and streams. diff --git a/.repos/effect/migration/equality.md b/.repos/effect/migration/equality.md new file mode 100644 index 000000000..d5e2352ea --- /dev/null +++ b/.repos/effect/migration/equality.md @@ -0,0 +1,72 @@ +# Equality + +## Structural Equality by Default + +In v3, `Equal.equals` used **reference equality** for plain objects and arrays. +Structural comparison was only available inside a `structuralRegion`, which +temporarily enabled deep comparison. Outside a structural region, two distinct +objects with identical contents were not considered equal: + +```ts +// v3 +import { Equal } from "effect" + +Equal.equals({ a: 1 }, { a: 1 }) // false — reference equality +Equal.equals([1, 2], [1, 2]) // false — reference equality +``` + +In v4, `Equal.equals` uses **structural equality** by default. Plain objects, +arrays, `Map`s, `Set`s, `Date`s, and `RegExp`s are compared by value without +opting in: + +```ts +// v4 +import { Equal } from "effect" + +Equal.equals({ a: 1 }, { a: 1 }) // true +Equal.equals([1, [2, 3]], [1, [2, 3]]) // true +Equal.equals(new Map([["a", 1]]), new Map([["a", 1]])) // true +Equal.equals(new Set([1, 2]), new Set([1, 2])) // true +``` + +Objects that implement the `Equal` interface continue to use their custom +equality logic, same as v3. + +## Opting Out: `byReference` + +If you need reference equality for a specific object, v4 provides +`Equal.byReference` and `Equal.byReferenceUnsafe`: + +```ts +import { Equal } from "effect" + +const obj = Equal.byReference({ a: 1 }) +Equal.equals(obj, { a: 1 }) // false — reference equality +``` + +- **`byReference(obj)`** — creates a `Proxy` that uses reference equality, + leaving the original object unchanged. +- **`byReferenceUnsafe(obj)`** — marks the object itself for reference + equality without creating a proxy. More performant but permanently changes + how the object is compared. + +## `NaN` Equality + +In v3, `Equal.equals(NaN, NaN)` returned `false` (following IEEE 754). +In v4, `NaN` is considered equal to `NaN`: + +```ts +Equal.equals(NaN, NaN) // v3: false, v4: true +``` + +## `equivalence` → `asEquivalence` + +The function that wraps `equals` as an `Equivalence` has been renamed: + +```ts +// v3 +Equal.equivalence() + +// v4 +Equal.asEquivalence() +``` diff --git a/.repos/effect/migration/error-handling.md b/.repos/effect/migration/error-handling.md new file mode 100644 index 000000000..93ab919ee --- /dev/null +++ b/.repos/effect/migration/error-handling.md @@ -0,0 +1,106 @@ +# Error Handling: `catch*` Renamings + +The `catch` combinators on `Effect` have been renamed in v4. The general +pattern: `catchAll*` is shortened to `catch*`, and the `catchSome*` family is +replaced by `catchFilter` / `catchCauseFilter`. + +## Renamings + +| v3 | v4 | +| ------------------------ | ------------------------------ | +| `Effect.catchAll` | `Effect.catch` | +| `Effect.catchAllCause` | `Effect.catchCause` | +| `Effect.catchAllDefect` | `Effect.catchDefect` | +| `Effect.catchTag` | `Effect.catchTag` (unchanged) | +| `Effect.catchTags` | `Effect.catchTags` (unchanged) | +| `Effect.catchIf` | `Effect.catchIf` (unchanged) | +| `Effect.catchSome` | `Effect.catchFilter` | +| `Effect.catchSomeCause` | `Effect.catchCauseFilter` | +| `Effect.catchSomeDefect` | Removed | + +## `Effect.catchAll` → `Effect.catch` + +**v3** + +```ts +import { Effect } from "effect" + +const program = Effect.fail("error").pipe( + Effect.catchAll((error) => Effect.succeed(`recovered: ${error}`)) +) +``` + +**v4** + +```ts +import { Effect } from "effect" + +const program = Effect.fail("error").pipe( + Effect.catch((error) => Effect.succeed(`recovered: ${error}`)) +) +``` + +## `Effect.catchAllCause` → `Effect.catchCause` + +**v3** + +```ts +import { Effect } from "effect" + +const program = Effect.die("defect").pipe( + Effect.catchAllCause((cause) => Effect.succeed("recovered")) +) +``` + +**v4** + +```ts +import { Cause, Effect } from "effect" + +const program = Effect.die("defect").pipe( + Effect.catchCause((cause) => Effect.succeed("recovered")) +) +``` + +## `Effect.catchSome` → `Effect.catchFilter` + +In v3, `catchSome` took a function returning `Option`. In v4, +`catchFilter` uses the `Filter` module instead. + +**v3** + +```ts +import { Effect, Option } from "effect" + +const program = Effect.fail(42).pipe( + Effect.catchSome((error) => + error === 42 + ? Option.some(Effect.succeed("caught")) + : Option.none() + ) +) +``` + +**v4** + +```ts +import { Effect, Filter } from "effect" + +const program = Effect.fail(42).pipe( + Effect.catchFilter( + Filter.fromPredicate((error: number) => error === 42), + (error) => Effect.succeed("caught") + ) +) +``` + +## New in v4 + +- **`Effect.catchReason(errorTag, reasonTag, handler)`** — catches a specific + `reason` within a tagged error without removing the parent error from the + error channel. Useful for handling nested error causes (e.g. an `AiError` + with a `reason: RateLimitError | QuotaExceededError`). +- **`Effect.catchReasons(errorTag, cases)`** — like `catchReason` but handles + multiple reason tags at once via an object of handlers. +- **`Effect.catchEager(handler)`** — an optimization variant of `catch` that + evaluates synchronous recovery effects immediately. diff --git a/.repos/effect/migration/fiber-keep-alive.md b/.repos/effect/migration/fiber-keep-alive.md new file mode 100644 index 000000000..fe8efa3c4 --- /dev/null +++ b/.repos/effect/migration/fiber-keep-alive.md @@ -0,0 +1,74 @@ +# Fiber Keep-Alive: Automatic Process Lifetime Management + +In v3, the core `effect` runtime did **not** keep the Node.js process alive while +fibers were suspended on certain asynchronous operations. If a fiber was waiting on +something like `Deferred.await` and there was no other work scheduled on the +event loop, the process would exit immediately — the fiber's suspension did not +register as pending work from Node.js's perspective. + +The only way to prevent this was to use `runMain` from `@effect/platform-node` +(or `@effect/platform-bun`), which installed a long-lived `setInterval` timer +to hold the process open until the root fiber completed. + +In v4, **the keep-alive mechanism is built into the core runtime**. + +## The Problem in v3 + +Consider the following program: + +```ts +import { Deferred, Effect } from "effect" + +const program = Effect.gen(function*() { + const deferred = yield* Deferred.make() + + yield* Deferred.await(deferred) +}) + +Effect.runPromise(program) +``` + +In v3, when the main fiber reached `yield* Deferred.await(deferred)`, it suspended +while waiting for the worker fiber to complete the deferred. However, from the +JavaScript runtime's perspective, the event loop had no more work to do. Thus, +the process would exit. + +The workaround was to use `runMain` from the platform package, which installs +a timer that holds the process open until the root fiber completes: + +```ts +import { NodeRuntime } from "@effect/platform-node" + +NodeRuntime.runMain(program) +``` + +## What Changed in v4 + +In v4, the Effect fiber runtime automatically manages a reference-counted +keep-alive timer. + +This means the following program works in v4 **without** `runMain`: + +```ts +import { Deferred, Effect, Fiber } from "effect" + +const program = Effect.gen(function*() { + const deferred = yield* Deferred.make() + + // The process stays alive while waiting — no runMain needed + yield* Deferred.await(deferred) +}) + +Effect.runPromise(program) +``` + +## `runMain` Is Still Recommended + +Even though the core runtime now handles keep-alive, `runMain` from the platform +packages is still the recommended way to run Effect programs. It provides: + +- **Signal handling** — listens for `SIGINT` / `SIGTERM` and interrupts the + root fiber gracefully. +- **Exit code management** — calls `process.exit(code)` when the program fails + or receives a signal. +- **Error reporting** — reports unhandled errors to the console. diff --git a/.repos/effect/migration/fiberref.md b/.repos/effect/migration/fiberref.md new file mode 100644 index 000000000..abfc17fa6 --- /dev/null +++ b/.repos/effect/migration/fiberref.md @@ -0,0 +1,108 @@ +# FiberRef: `FiberRef` → `Context.Reference` + +In v4, `FiberRef`, `FiberRefs`, `FiberRefsPatch`, and `Differ` have been removed. +Fiber-local state is now handled by `Context.Reference` — the same mechanism +used for services with default values. + +## Built-in References + +v3's built-in `FiberRef` values are now `Context.Reference` values exported +from `References` and related modules. + +| v3 FiberRef | v4 Reference | +| ----------------------------------- | ---------------------------------- | +| `FiberRef.currentLogLevel` | `References.CurrentLogLevel` | +| `FiberRef.currentMinimumLogLevel` | `References.MinimumLogLevel` | +| `FiberRef.currentLogAnnotations` | `References.CurrentLogAnnotations` | +| `FiberRef.currentLogSpan` | `References.CurrentLogSpans` | +| `FiberRef.currentScheduler` | `References.Scheduler` | +| `FiberRef.currentMaxOpsBeforeYield` | `References.MaxOpsBeforeYield` | +| `FiberRef.currentTracerEnabled` | `References.TracerEnabled` | +| `FiberRef.unhandledErrorLogLevel` | `References.UnhandledLogLevel` | + +## Reading References + +In v3, `FiberRef.get` retrieved the current value. In v4, references are +services — `yield*` them directly. + +**v3** + +```ts +import { Effect, FiberRef } from "effect" + +const program = Effect.gen(function*() { + const level = yield* FiberRef.get(FiberRef.currentLogLevel) + console.log(level) +}) +``` + +**v4** + +```ts +import { Effect, References } from "effect" + +const program = Effect.gen(function*() { + const level = yield* References.CurrentLogLevel + console.log(level) // "Info" (default) +}) +``` + +## Scoped Updates (`Effect.locally` → `Effect.provideService`) + +v3's `Effect.locally` set a `FiberRef` value for the duration of an effect. In +v4, use `Effect.provideService` with the reference. + +**v3** + +```ts +import { Effect, FiberRef, LogLevel } from "effect" + +const program = Effect.locally( + myEffect, + FiberRef.currentLogLevel, + LogLevel.Debug +) +``` + +**v4** + +```ts +import { Effect, References } from "effect" + +const program = Effect.provideService( + myEffect, + References.CurrentLogLevel, + "Debug" +) +``` + +## Writing References + +v3's `FiberRef.set` mutated the current fiber's ref value. In v4, references are +set via `Effect.provideService`, which scopes the value to the provided effect. + +**v3** + +```ts +import { Effect, FiberRef } from "effect" + +const program = Effect.gen(function*() { + yield* FiberRef.set(FiberRef.currentMaxOpsBeforeYield, 500) + // subsequent code sees maxOpsBeforeYield = 500 +}) +``` + +**v4** + +```ts +import { Effect, References } from "effect" + +const program = Effect.provideService( + Effect.gen(function*() { + const maxOps = yield* References.MaxOpsBeforeYield + console.log(maxOps) // 500 + }), + References.MaxOpsBeforeYield, + 500 +) +``` diff --git a/.repos/effect/migration/forking.md b/.repos/effect/migration/forking.md new file mode 100644 index 000000000..d924272bb --- /dev/null +++ b/.repos/effect/migration/forking.md @@ -0,0 +1,94 @@ +# Forking: Renamed Combinators and New Options + +The `fork*` family of combinators has been renamed in v4 for clarity, and all +variants now accept an options object for controlling fiber startup behavior. + +## Renamings + +| v3 | v4 | Description | +| ----------------------------- | ------------------- | -------------------------------------------- | +| `Effect.fork` | `Effect.forkChild` | Fork as a child of the current fiber | +| `Effect.forkDaemon` | `Effect.forkDetach` | Fork detached from parent lifecycle | +| `Effect.forkScoped` | `Effect.forkScoped` | Fork tied to the current `Scope` (unchanged) | +| `Effect.forkIn` | `Effect.forkIn` | Fork in a specific `Scope` (unchanged) | +| `Effect.forkAll` | — | Removed | +| `Effect.forkWithErrorHandler` | — | Removed | + +## `Effect.fork` → `Effect.forkChild` + +**v3** + +```ts +import { Effect } from "effect" + +const fiber = Effect.fork(myEffect) +``` + +**v4** + +```ts +import { Effect } from "effect" + +const fiber = Effect.forkChild(myEffect) +``` + +## `Effect.forkDaemon` → `Effect.forkDetach` + +**v3** + +```ts +import { Effect } from "effect" + +const fiber = Effect.forkDaemon(myEffect) +``` + +**v4** + +```ts +import { Effect } from "effect" + +const fiber = Effect.forkDetach(myEffect) +``` + +## Fork Options + +In v4, `forkChild`, `forkDetach`, `forkScoped`, and `forkIn` all accept an +optional options object with the following fields: + +```ts +{ + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined +} +``` + +- **`startImmediately`** — When `true`, the forked fiber begins executing + immediately rather than being deferred. Defaults to `undefined` (deferred). +- **`uninterruptible`** — Controls whether the forked fiber can be interrupted. + `true` makes it uninterruptible, `"inherit"` inherits the parent's + interruptibility, and `undefined` uses the default behavior. + +**Usage as data-last (curried)** + +```ts +import { Effect } from "effect" + +const fiber = myEffect.pipe( + Effect.forkChild({ startImmediately: true }) +) +``` + +**Usage as data-first** + +```ts +import { Effect } from "effect" + +const fiber = Effect.forkChild(myEffect, { startImmediately: true }) +``` + +## Removed Combinators + +**`Effect.forkAll`** and **`Effect.forkWithErrorHandler`** have been removed in +v4. For `forkAll`, fork effects individually with `forkChild` or use +higher-level concurrency combinators. For error handling on forked fibers, +observe the fiber's result via `Fiber.join` or `Fiber.await`. diff --git a/.repos/effect/migration/generators.md b/.repos/effect/migration/generators.md new file mode 100644 index 000000000..363f7158a --- /dev/null +++ b/.repos/effect/migration/generators.md @@ -0,0 +1,32 @@ +# Generators + +## `Effect.gen`: Passing `this` + +In v3, you could pass a `self` value directly as the first argument to +`Effect.gen`. In v4, `self` must be wrapped in an options object. + +**v3** + +```ts +import { Effect } from "effect" + +class MyService { + readonly local = 1 + compute = Effect.gen(this, function*() { + return yield* Effect.succeed(this.local + 1) + }) +} +``` + +**v4** + +```ts +import { Effect } from "effect" + +class MyService { + readonly local = 1 + compute = Effect.gen({ self: this }, function*() { + return yield* Effect.succeed(this.local + 1) + }) +} +``` diff --git a/.repos/effect/migration/layer-memoization.md b/.repos/effect/migration/layer-memoization.md new file mode 100644 index 000000000..a67cc6e74 --- /dev/null +++ b/.repos/effect/migration/layer-memoization.md @@ -0,0 +1,98 @@ +# Layer Memoization + +In v3, each call to `Effect.provide` created its own memoization scope. Layers +were memoized / deduplicated within a single `Effect.provide` call, but would +**not** be shared across separate calls — so two `Effect.provide` calls with +overlapping layers would silently build those layers twice. + +In v4, the underlying `MemoMap` data structure which facilitates memoization of +`Layer`s is shared between `Effect.provide` calls (unless explicitly disabled +via the `{ local: true }` option). Thus, layers are automatically memoized / +deduplicated across `Effect.provide` calls. + +## Example + +```ts +import { Console, Context, Effect, Layer } from "effect" + +const MyService = Context.Service<{ readonly value: string }>("MyService") + +const MyServiceLayer = Layer.effect( + MyService, + Effect.gen(function*() { + yield* Console.log("Building MyService") + return { value: "hello" } + }) +) + +const program = Effect.gen(function*() { + const a = yield* MyService + return a.value +}) + +// Same layer provided twice in separate provide calls +const main = program.pipe( + Effect.provide(MyServiceLayer), + Effect.provide(MyServiceLayer) +) + +// Effect v3: "Building MyService" is logged TWICE +// Effect v4: "Building MyService" is logged ONCE +Effect.runPromise(main) +``` + +## Prefer Layer Composition Over Multipl Provides + +Even though v4 memoizes across `provide` calls, **composing layers before +providing is still the recommended pattern**. Layer composition makes your +dependency graph explicit and lets you see the full structure in one place: + +```ts +// Preferred — provide once +const main = program.pipe(Effect.provide(MyServiceLayer)) +``` + +The auto-memoization feature is a safety net to avoid the footguns associated +with multiple `Effect.provide` calls present in v3. It is **NOT** a substitute +for proper layer composition. + +## Opting Out of Shared Memoization + +There are cases where you **want** a layer to be built fresh — for example, +test isolation or creating independent resource pools. v4 provides two +mechanisms: + +### `Layer.fresh` + +Wraps a layer so it always builds with a fresh memo map, bypassing the shared +cache. This existed in v3 as well. + +```ts +import { Effect, Layer } from "effect" + +const main = program.pipe( + Effect.provide(MyServiceLayer), + Effect.provide(Layer.fresh(MyServiceLayer)) +) +// "Building MyService" is logged TWICE — fresh bypasses the shared cache +``` + +### `Effect.provide` with `{ local: true }` + +New in v4. Builds the provided layer with a **local memo map** instead of the +fiber's shared one. The layer and all its sublayers are built from scratch and +are not shared with other `provide` calls. + +```ts +import { Effect } from "effect" + +const main = program.pipe( + Effect.provide(MyServiceLayer), + Effect.provide(MyServiceLayer, { local: true }) +) +// "Building MyService" is logged TWICE — local creates its own memo map +``` + +Use `local: true` when you need an entire layer subtree to be isolated — for +example, when providing layers in a test harness where each test should get +independent resources. diff --git a/.repos/effect/migration/runtime.md b/.repos/effect/migration/runtime.md new file mode 100644 index 000000000..11b64ffcc --- /dev/null +++ b/.repos/effect/migration/runtime.md @@ -0,0 +1,88 @@ +# Runtime: `Runtime` Removed + +In v3, `Runtime` bundled a `Context`, `RuntimeFlags`, and `FiberRefs` +into a single value used to execute effects: + +```ts +// v3 +interface Runtime { + readonly context: Context.Context + readonly runtimeFlags: RuntimeFlags + readonly fiberRefs: FiberRefs +} +``` + +In v4, this type no longer exists and you can use `Context` instead. +Run functions live directly on `Effect`, and the `Runtime` module is reduced to +process lifecycle utilities. + +## `Runtime.runFork(runtime)` -> `Effect.runForkWith(services)` + +In v3, running an effect with dependencies usually meant pulling the current +runtime from `Effect.runtime()` and calling `Runtime.runFork(runtime)` inside +the main effect. + +**v3** + +```ts +import { Context, Effect, Runtime } from "effect" + +class Logger extends Context.Tag("Logger") void +}>() {} + +const program = Effect.gen(function*() { + const logger = yield* Logger + logger.log("Hello from Logger") +}) + +const main = Effect.gen(function*() { + const runtime = yield* Effect.runtime() + return Runtime.runFork(runtime)(program) +}).pipe( + Effect.provideService(Logger, { + log: (message) => console.log(message) + }) +) + +const fiber = Effect.runFork(main) +``` + +In v4, use the same pattern with `Effect.context()`, then run with +`Effect.runForkWith(services)`: + +**v4** + +```ts +import { Context, Effect } from "effect" + +class Logger extends Context.Service void +}>()("Logger") {} + +const program = Effect.gen(function*() { + const logger = yield* Logger + logger.log("Hello from Logger") +}) + +const main = Effect.gen(function*() { + const services = yield* Effect.context() + return Effect.runForkWith(services)(program) +}).pipe( + Effect.provideContext(Context.make(Logger, { + log: (message) => console.log(message) + })) +) + +const fiber = Effect.runFork(main) +``` + +If your effect has no service requirements, use `Effect.runFork(effect)`. + +## `Runtime` Module Contents + +The `Runtime` module now only contains: + +- `Teardown` — interface for handling process exit +- `defaultTeardown` — default teardown implementation +- `makeRunMain` — creates platform-specific main runners diff --git a/.repos/effect/migration/schema.md b/.repos/effect/migration/schema.md new file mode 100644 index 000000000..bc5ead367 --- /dev/null +++ b/.repos/effect/migration/schema.md @@ -0,0 +1,1095 @@ +# Schema: Migration from v3 + +This document maps v3 Schema APIs to their v4 equivalents. Simple renames and argument changes are covered in the summary table below. More complex migrations have dedicated sections with code examples. + +## Migration types + +- **auto** — mechanical find-and-replace, safe to auto-apply +- **semi-auto** — follows a clear pattern but needs structural changes +- **manual** — requires case-by-case decisions, flag for human review +- **removed** — no v4 equivalent + +## Summary table + +| v3 API | v4 API | Migration type | +| ----------------------------------------------- | ----------------------------------------------------------------------------- | ----------------- | +| `asSchema(schema)` | `revealCodec(schema)` | rename | +| `encodedSchema(schema)` | `toEncoded(schema)` | rename | +| `typeSchema(schema)` | `toType(schema)` | rename | +| `compose(schemaB)` | `decodeTo(schemaB)` | rename | +| `annotations(ann)` | `annotate(ann)` | rename | +| `decodingFallback` annotation | `catchDecoding(...)` | rename | +| `parseJson()` | `UnknownFromJsonString` | rename | +| `parseJson(schema)` | `fromJsonString(schema)` | rename | +| `pattern(regex)` | `check(isPattern(regex))` | rename | +| `nonEmptyString` | `isNonEmpty` | rename | +| `BigIntFromSelf` | `BigInt` | rename | +| `SymbolFromSelf` | `Symbol` | rename | +| `URLFromSelf` | `URL` | rename | +| `RedactedFromSelf` | `Redacted` | rename | +| `Redacted` | `RedactedFromValue` | rename | +| `EitherFromSelf` | `Result` | rename | +| `DateFromNumber` | `DateFromMillis` | rename | +| `Date` | `DateFromString` | restructure | +| `decodeUnknown` | `decodeUnknownEffect` | rename | +| `decode` | `decodeEffect` | rename | +| `decodeUnknownEither` | `decodeUnknownExit` | rename | +| `decodeEither` | `decodeExit` | rename | +| `encodeUnknown` | `encodeUnknownEffect` | rename | +| `encode` | `encodeEffect` | rename | +| `encodeUnknownEither` | `encodeUnknownExit` | rename | +| `encodeEither` | `encodeExit` | rename | +| `asserts(schema)(input)` | `asserts(schema, input)` | semi-auto | +| `Literal(null)` | `Null` | restructure | +| `Literal("a", "b")` | `Literals(["a", "b"])` | variadic-to-array | +| `pickLiteral("a", "b")` | `Literals(...).pick(["a", "b"])` | restructure | +| `Union(A, B)` | `Union([A, B])` | variadic-to-array | +| `Tuple(A, B)` | `Tuple([A, B])` | variadic-to-array | +| `TemplateLiteral(A, B)` | `TemplateLiteral([A, B])` | variadic-to-array | +| `TemplateLiteralParser(A, B)` | `TemplateLiteralParser(schema.parts)` | restructure | +| `Record({ key, value })` | `Record(key, value)` | restructure | +| `filter(predicate)` | `check(makeFilter(predicate))` | restructure | +| `filter(refinement)` | `refine(refinement)` | restructure | +| `UUID` | `String.check(isUUID())` | restructure | +| `ULID` | `String.check(isULID())` | restructure | +| `pick("a")` | `mapFields(Struct.pick(["a"]))` | restructure | +| `omit("a")` | `mapFields(Struct.omit(["a"]))` | restructure | +| `partial` | `mapFields(Struct.map(Schema.optional))` | restructure | +| `partialWith({ exact: true })` | `mapFields(Struct.map(Schema.optionalKey))` | restructure | +| `required(schema)` | `schema.mapFields(Struct.map(Schema.requiredKey))` | restructure | +| `extend(structB)` | `mapFields(Struct.assign(fieldsB))` or `fieldsAssign(fieldsB)` | restructure | +| `transform(from, to, { decode, encode })` | `from.pipe(decodeTo(to, SchemaTransformation.transform({ decode, encode })))` | restructure | +| `transformOrFail(from, to, { decode, encode })` | `from.pipe(decodeTo(to, { decode: SchemaGetter.transformOrFail(...), ... }))` | restructure | +| `transformLiteral(from, to)` | `Literal(from).transform(to)` | restructure | +| `transformLiterals([0,"a"], [1,"b"])` | `Literals([0, 1]).transform(["a", "b"])` | restructure | +| `attachPropertySignature("k", "v")` | `mapFields(f => ({...f, k: tagDefaultOmit("v")}))` | restructure | +| `validate*` | removed (use `decode*` + `toType`) | removed | +| `keyof` | — | removed | +| `NonEmptyArrayEnsure` | — | removed | +| `withDefaults` | — | removed | +| `Data(schema)` | — | removed | +| `optionalWith(schema, opts)` | varies by options (see [optionalWith](#optionalwith)) | manual | +| `optionalToOptional` | see [optional field transformations](#optional-field-transformations) | manual | +| `optionalToRequired` | see [optional field transformations](#optional-field-transformations) | manual | +| `requiredToOptional` | see [optional field transformations](#optional-field-transformations) | manual | +| `filterEffect` | see [filterEffect](#filtereffect) | manual | +| `fromKey` | see [rename](#rename) | manual | +| `rename({ a: "c" })` | see [rename](#rename) | manual | +| `format(schema)` | see [format](#format) | manual | +| `ParseResult.ArrayFormatter.formatError(error)` | see [ParseResult formatters](#parseresult-formatters) | manual | +| `declare` | see [declare](#declare) | manual | + +## Additional rename notes + +### `*FromSelf` renames + +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(...))`: + +`greaterThan` → `isGreaterThan`, `greaterThanOrEqualTo` → `isGreaterThanOrEqualTo`, `lessThan` → `isLessThan`, `lessThanOrEqualTo` → `isLessThanOrEqualTo`, `between` → `isBetween`, `int` → `isInt`, `multipleOf` → `isMultipleOf`, `finite` → `isFinite`, `minLength` → `isMinLength`, `maxLength` → `isMaxLength`, `length` → `isLengthBetween` + +Note: `positive`, `negative`, `nonNegative`, `nonPositive` have been removed in v4. + +### Utility renames + +`equivalence` → `toEquivalence`, `arbitrary` → `toArbitrary`, `pretty` → `toFormatter`, `standardSchemaV1` → `toStandardSchemaV1` + +## Detailed migrations + +### Redacted + +**Migration: rename with behavior distinction** + +In v3, `Schema.Redacted(value)` decoded the raw encoded value and wrapped the decoded value in `Redacted`. + +In v4, that behavior is named `Schema.RedactedFromValue(value)`. + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Redacted(Schema.String) +const decode = Schema.decodeSync(schema) + +decode("secret") +``` + +v4 + +```ts +import { Redacted, Schema } from "effect" + +const schema = Schema.RedactedFromValue(Schema.String) +const decode = Schema.decodeSync(schema) + +const redacted = decode("secret") +console.log(Redacted.value(redacted)) +// secret +``` + +`Schema.Redacted(value)` in v4 is the replacement for v3 `Schema.RedactedFromSelf(value)`: it expects the input to already be a `Redacted` value, so both `Type` and `Encoded` are `Redacted<...>`. + +### asserts signature + +**Migration: semi-auto** + +`Schema.asserts` now asserts an input directly instead of returning an assertion function. + +v3 + +```ts +import { Schema } from "effect" + +const assertString = Schema.asserts(Schema.String) +assertString(input) +``` + +v4 + +```ts +import { Schema } from "effect" + +Schema.asserts(Schema.String, input) +``` + +### validate* removal + +**Migration: removed** + +The `validate`, `validateEither`, `validatePromise`, `validateSync`, and `validateOption` APIs have been removed. Use `Schema.decode*` + `Schema.toType` instead. + +```ts +import { Schema } from "effect" + +// v3: Schema.validateSync(Schema.String)(input) +// v4: +const validateSync = Schema.decodeSync(Schema.toType(Schema.String)) +``` + +### Data removal + +**Migration: removed** + +`Schema.Data` has no v4 equivalent. Remove it. `Equal.equals` performs deep structural comparison on objects by default in v4, so `Schema.Data` is unnecessary. + +### pickLiterals + +**Migration: auto** + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Literal("a", "b", "c").pipe(Schema.pickLiteral("a", "b")) +``` + +v4 + +```ts +import { Schema } from "effect" + +const schema = Schema.Literals(["a", "b", "c"]).pick(["a", "b"]) +``` + +### TemplateLiteralParser + +**Migration: semi-auto** + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.TemplateLiteral(Schema.String, ".", Schema.String) +const parser = Schema.TemplateLiteralParser(Schema.String, ".", Schema.String) +``` + +v4 + +```ts +import { Schema } from "effect" + +const schema = Schema.TemplateLiteral([Schema.String, ".", Schema.String]) +// use the `parts` property instead of repeating the template parts +const parser = Schema.TemplateLiteralParser(schema.parts) +``` + +Behavior note: `TemplateLiteral` and `TemplateLiteralParser` match parts semantically. Checks on string, number, and bigint schema parts are applied while matching each segment, so refined parts can reject strings that would match the broader primitive shape. + +### format + +**Migration: manual** + +**New imports:** `SchemaRepresentation` + +v3 + +```ts +import { Schema } from "effect" + +console.log(Schema.format(Schema.String)) +// string +``` + +v4 + +```ts +import { Schema, SchemaRepresentation } from "effect" + +const doc = SchemaRepresentation.toRepresentation(Schema.String.ast) +const multi = SchemaRepresentation.toMultiDocument(doc) +const codeDoc = SchemaRepresentation.toCodeDocument(multi) +console.log(codeDoc.codes[0].Type) +// string +``` + +### ParseResult formatters + +**Migration: manual** + +**New imports:** `SchemaIssue` + +In v4, schema parsing fails with `Schema.SchemaError`, which contains a nested `SchemaIssue` in its `issue` field. + +Use `SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues` for the v3 `ParseResult.ArrayFormatter.formatError(error)` equivalent. + +v3 + +```ts +import { Either, ParseResult, Schema } from "effect" + +const Person = Schema.Struct({ + name: Schema.String, + age: Schema.Number +}) + +const decode = Schema.decodeUnknownEither(Person) + +const result = decode({}) +if (Either.isLeft(result)) { + console.error("Decoding failed:") + console.error(ParseResult.ArrayFormatter.formatErrorSync(result.left)) +} +/* +Decoding failed: +[ { _tag: 'Missing', path: [ 'name' ], message: 'is missing' } ] +*/ +``` + +v4 + +```ts +import { Schema, SchemaIssue } from "effect" + +const Person = Schema.Struct({ + name: Schema.String, + age: Schema.Number +}) + +const decode = Schema.decodeUnknownSync(Person) + +try { + decode({}) +} catch (error) { + if (error instanceof Error) { + console.error("Decoding failed:") + if (SchemaIssue.isIssue(error.cause)) { + console.error(SchemaIssue.makeFormatterStandardSchemaV1()(error.cause).issues) + } + } +} +/* +Decoding failed: +[ { path: [ 'name' ], message: 'Missing key' } ] +*/ +``` + +### Record + +**Migration: auto** + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Record({ key: Schema.String, value: Schema.Number }) +``` + +v4 + +```ts +import { Schema } from "effect" + +const schema = Schema.Record(Schema.String, Schema.Number) +``` + +Behavior note: dynamic record key schemas select matching own properties before the value schema is applied. Refined key schemas such as `Schema.String.check(...)`, `Schema.Int`, or checked template literals ignore properties that do not match the key schema; they do not validate the value at those ignored keys. For transformed key schemas, selection is based on encoded property names before selected keys are decoded. + +### pick / omit + +**Migration: semi-auto** + +**New imports:** `Struct` + +v3 + +```ts +import { Schema } from "effect" + +const picked = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(Schema.pick("a")) +const omitted = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(Schema.omit("b")) +``` + +v4 + +```ts +import { Schema, Struct } from "effect" + +const picked = Schema.Struct({ a: Schema.String, b: Schema.Number }).mapFields(Struct.pick(["a"])) +const omitted = Schema.Struct({ a: Schema.String, b: Schema.Number }).mapFields(Struct.omit(["b"])) +``` + +### partial / partialWith + +**Migration: semi-auto** + +**New imports:** `Struct` + +- `Schema.partial` → `mapFields(Struct.map(Schema.optional))` (allows `undefined`) +- `Schema.partialWith({ exact: true })` → `mapFields(Struct.map(Schema.optionalKey))` (exact) + +```ts +import { Schema, Struct } from "effect" + +const struct = Schema.Struct({ a: Schema.String, b: Schema.Number }) + +// v3: struct.pipe(Schema.partial) +const withUndefined = struct.mapFields(Struct.map(Schema.optional)) + +// v3: struct.pipe(Schema.partialWith({ exact: true })) +const exact = struct.mapFields(Struct.map(Schema.optionalKey)) +``` + +You can also make a subset of fields partial: + +```ts +import { Schema, Struct } from "effect" + +const schema = Schema.Struct({ a: Schema.String, b: Schema.Number }) + .mapFields(Struct.mapPick(["a"], Schema.optional)) +``` + +### required + +**Migration: semi-auto** + +**New imports:** `Struct` + +- `Schema.requiredKey`: makes `optionalKey` fields required; makes `optional` fields required as `T | undefined` +- `Schema.required`: makes `optional` fields required (removes `undefined`) + +```ts +import { Schema, Struct } from "effect" + +const original = Schema.Struct({ + a: Schema.optionalKey(Schema.String), + b: Schema.optionalKey(Schema.Number) +}) + +// v3: Schema.required(original) +const schema = original.mapFields(Struct.map(Schema.requiredKey)) +// { readonly a: string; readonly b: number; } +``` + +### optional field transformations + +**Migration: manual** + +**New imports:** `SchemaGetter` + +`optionalToOptional`, `optionalToRequired`, and `requiredToOptional` are all replaced by `Schema.decodeTo` + `SchemaGetter.transformOptional`. + +The pattern: start with the encoded optionality (`optionalKey` or required), pipe to `decodeTo` with the decoded optionality, and provide `transformOptional` functions for decode/encode. + +**Example** (v3 `optionalToRequired`: setting `null` as default for missing field) + +v3 + +```ts +import { Option, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalToRequired(Schema.String, Schema.NullOr(Schema.String), { + decode: Option.getOrElse(() => null), + encode: Option.liftPredicate((value) => value !== null) + }) +}) +``` + +v4 + +```ts +import { Option, Schema, SchemaGetter } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalKey(Schema.String).pipe( + Schema.decodeTo(Schema.NullOr(Schema.String), { + decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)), + encode: SchemaGetter.transformOptional(Option.filter((value) => value !== null)) + }) + ) +}) +``` + +**Example** (v3 `requiredToOptional`: empty string as missing value) + +v3 + +```ts +import { Option, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.requiredToOptional(Schema.String, Schema.String, { + decode: Option.liftPredicate((s) => s !== ""), + encode: Option.getOrElse(() => "") + }) +}) +``` + +v4 + +```ts +import { Option, Schema, SchemaGetter } from "effect" + +const schema = Schema.Struct({ + a: Schema.String.pipe( + Schema.decodeTo(Schema.optionalKey(Schema.String), { + decode: SchemaGetter.transformOptional(Option.filter((value) => value !== "")), + encode: SchemaGetter.transformOptional(Option.orElseSome(() => "")) + }) + ) +}) +``` + +### optionalWith + +**Migration: manual** + +**New imports:** `SchemaGetter`, `Predicate` (for nullable variants) + +#### Decision tree + +| v3 options | v4 pattern | +| ------------------------------------------ | ----------------------------------------------------------------------- | +| `{ exact: true }` | `optionalKey(schema)` | +| `{ default }` | `schema.pipe(withDecodingDefaultType(...))` | +| `{ exact: true, default }` | `schema.pipe(withDecodingDefaultTypeKey(...))` | +| `{ nullable: true }` | `optional(NullOr(schema))` + `decodeTo` + filter null | +| `{ nullable: true, exact: true }` | `optionalKey(NullOr(schema))` + `decodeTo` + filter null | +| `{ nullable: true, default }` | `optional(NullOr(schema))` + `decodeTo` + filter null + `orElseSome` | +| `{ nullable: true, exact: true, default }` | `optionalKey(NullOr(schema))` + `decodeTo` + filter null + `orElseSome` | + +Key rules: + +- `exact: true` → use `optionalKey` instead of `optional` +- `nullable: true` → wrap inner schema in `NullOr` and filter nulls via `Option.filter(Predicate.isNotNull)` +- `default` → use `withDecodingDefaultType` (or `withDecodingDefaultTypeKey` with `exact: true`) + +#### Example: `{ exact: true }` (simplest case) + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalWith(Schema.NumberFromString, { exact: true }) +}) +``` + +v4 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalKey(Schema.NumberFromString) +}) +``` + +#### Example: `{ default }` + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalWith(Schema.String, { default: () => "" }) +}) +``` + +v4 + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String.pipe(Schema.withDecodingDefaultType(Effect.succeed(""))) +}) +``` + +#### Example: `{ exact: true, default }` + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalWith(Schema.String, { exact: true, default: () => "" }) +}) +``` + +v4 + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String.pipe(Schema.withDecodingDefaultTypeKey(Effect.succeed(""))) +}) +``` + +#### Example: `{ nullable: true, exact: true, default }` (most complex case) + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalWith(Schema.NumberFromString, { nullable: true, default: () => -1, exact: true }) +}) +``` + +v4 + +```ts +import { Option, Predicate, Schema, SchemaGetter } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalKey(Schema.NullOr(Schema.NumberFromString)).pipe( + Schema.decodeTo(Schema.Number, { + decode: SchemaGetter.transformOptional((o) => + o.pipe(Option.filter(Predicate.isNotNull), Option.orElseSome(() => -1)) + ), + encode: SchemaGetter.required() + }) + ) +}) +``` + +### pluck + +**Migration: manual** + +**New imports:** `SchemaGetter`, `Struct` + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(Schema.pluck("a")) +``` + +v4 + +```ts +import { Schema, SchemaGetter, Struct } from "effect" + +function pluck

(key: P) { + return ( + schema: Schema.Struct<{ [K in P]: S }> + ): Schema.decodeTo, Schema.Struct<{ [K in P]: S }>> => { + return schema.mapFields(Struct.pick([key])).pipe( + Schema.decodeTo(Schema.toType(schema.fields[key]), { + decode: SchemaGetter.transform((whole: any) => whole[key]), + encode: SchemaGetter.transform((value) => ({ [key]: value } as any)) + }) + ) + } +} + +const schema = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(pluck("a")) +``` + +### extend + +**Migration: semi-auto** + +**New imports:** `Struct` (Struct case), `Tuple` (Union case) + +#### Struct extends Struct + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).pipe(Schema.extend(Schema.Struct({ c: Schema.Number }))) +``` + +v4 + +```ts +import { Schema, Struct } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields(Struct.assign({ c: Schema.Number })) + +// or more succinctly +const schema2 = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).pipe(Schema.fieldsAssign({ c: Schema.Number })) +``` + +#### Union extends Struct + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Union( + Schema.Struct({ a: Schema.String }), + Schema.Struct({ b: Schema.Number }) +).pipe(Schema.extend(Schema.Struct({ c: Schema.Boolean }))) +``` + +v4 + +```ts +import { Schema, Tuple } from "effect" + +const schema = Schema.Union([ + Schema.Struct({ a: Schema.String }), + Schema.Struct({ b: Schema.Number }) +]).mapMembers(Tuple.map(Schema.fieldsAssign({ c: Schema.Number }))) +``` + +### filter + +**Migration: semi-auto** + +v3 + +```ts +import { Schema } from "effect" + +// inline filter +const a = Schema.String.pipe(Schema.filter((s) => s.length > 0)) + +// refinement +const b = Schema.Option(Schema.String).pipe(Schema.filter(Option.isSome)) +``` + +v4 + +```ts +import { Option, Schema } from "effect" + +// inline filter +const a = Schema.String.check(Schema.makeFilter((s) => s.length > 0)) + +// refinement +const b = Schema.Option(Schema.String).pipe(Schema.refine(Option.isSome)) +``` + +In v4, a `makeFilter` predicate can return any of the shapes described by `Schema.FilterOutput`: + +- `undefined` / `true` — success +- `false` — generic failure +- `string` — failure with that message +- `SchemaIssue.Issue` — a fully-formed issue +- `{ path, issue }` — failure at a nested path (`issue` is a `string` or `SchemaIssue.Issue`) +- `ReadonlyArray` — several failures reported together (empty array = success, single element is unwrapped, otherwise grouped into an `Issue.Composite`) + +**Example** (Failure at a nested path) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ password: Schema.String, confirmPassword: Schema.String }).check( + Schema.makeFilter((o) => + o.password === o.confirmPassword + ? undefined + : { path: ["password"], issue: "password and confirmPassword must match" } + ) +) + +console.log(String(Schema.decodeUnknownExit(schema)({ password: "123456", confirmPassword: "1234567" }))) +// Failure(Cause([Fail(SchemaError: password and confirmPassword must match +// at ["password"])])) +``` + +**Example** (Reporting multiple failures at once) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ a: Schema.Finite, b: Schema.Finite, c: Schema.Finite }).check( + Schema.makeFilter((o) => { + const issues: Array = [] + if (o.a > 0) { + if (o.b <= 0) issues.push({ path: ["b"], issue: "b must be greater than 0" }) + if (o.c <= 0) issues.push({ path: ["c"], issue: "c must be greater than 0" }) + } + return issues + }) +) + +console.log(String(Schema.decodeUnknownExit(schema)({ a: 1, b: 0, c: 0 }))) +// Failure(Cause([Fail(SchemaError: b must be greater than 0 +// at ["b"] +// c must be greater than 0 +// at ["c"])])) +``` + +### filterEffect + +**Migration: manual** + +**New imports:** `SchemaGetter`, `Result` + +v3 + +```ts +import { Effect, Schema } from "effect" + +async function validateUsername(username: string) { + return Promise.resolve(username === "gcanti") +} + +const ValidUsername = Schema.String.pipe( + Schema.filterEffect((username) => + Effect.promise(() => validateUsername(username).then((valid) => valid || "Invalid username")) + ) +) +``` + +v4 + +```ts +import { Effect, Result, Schema, SchemaGetter } from "effect" + +async function validateUsername(username: string) { + return Promise.resolve(username === "gcanti") +} + +const ValidUsername = Schema.String.pipe( + Schema.decode({ + decode: SchemaGetter.checkEffect((username) => + Effect.promise(() => validateUsername(username).then((valid) => valid || "Invalid username")) + ), + encode: SchemaGetter.passthrough() + }) +) +``` + +### transform + +**Migration: semi-auto** + +**New imports:** `SchemaTransformation` + +v3 + +```ts +import { Schema } from "effect" + +const BooleanFromString = Schema.transform(Schema.Literal("on", "off"), Schema.Boolean, { + strict: true, + decode: (literal) => literal === "on", + encode: (bool) => (bool ? "on" : "off") +}) +``` + +v4 + +```ts +import { Schema, SchemaTransformation } from "effect" + +const BooleanFromString = Schema.Literals(["on", "off"]).pipe( + Schema.decodeTo( + Schema.Boolean, + SchemaTransformation.transform({ + decode: (literal) => literal === "on", + encode: (bool) => (bool ? "on" : "off") + }) + ) +) +``` + +### transformOrFail + +**Migration: semi-auto** + +**New imports:** `SchemaGetter`, `SchemaIssue` + +v3 + +```ts +import { ParseResult, Schema } from "effect" + +const NumberFromString = Schema.transformOrFail(Schema.String, Schema.Number, { + strict: true, + decode: (input, _, ast) => { + const parsed = parseFloat(input) + if (isNaN(parsed)) { + return ParseResult.fail(new ParseResult.Type(ast, input, "Failed to convert string to number")) + } + return ParseResult.succeed(parsed) + }, + encode: (input) => ParseResult.succeed(input.toString()) +}) +``` + +v4 + +```ts +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()) + } + return Effect.succeed(n) + }), + encode: SchemaGetter.String() + }) +) +``` + +### transformLiteral / transformLiterals + +**Migration: auto** + +v3 + +```ts +import { Schema } from "effect" + +const a = Schema.transformLiteral(0, "a") +const b = Schema.transformLiterals([0, "a"], [1, "b"], [2, "c"]) +``` + +v4 + +```ts +import { Schema } from "effect" + +const a = Schema.Literal(0).transform("a") +const b = Schema.Literals([0, 1, 2]).transform(["a", "b", "c"]) +``` + +### attachPropertySignature + +**Migration: semi-auto** + +v3 + +```ts +import { Schema } from "effect" + +const Circle = Schema.Struct({ radius: Schema.Number }) +const Square = Schema.Struct({ sideLength: Schema.Number }) + +const DiscriminatedShape = Schema.Union( + Circle.pipe(Schema.attachPropertySignature("kind", "circle")), + Square.pipe(Schema.attachPropertySignature("kind", "square")) +) +``` + +v4 + +```ts +import { Schema } from "effect" + +const Circle = Schema.Struct({ radius: Schema.Number }) +const Square = Schema.Struct({ sideLength: Schema.Number }) + +const DiscriminatedShape = Schema.Union([ + Circle.mapFields((fields) => ({ ...fields, kind: Schema.tagDefaultOmit("circle") })), + Square.mapFields((fields) => ({ ...fields, kind: Schema.tagDefaultOmit("square") })) +]) +``` + +### decodingFallback + +**Migration: auto** + +v3 + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.String.annotations({ + decodingFallback: () => Effect.succeed("a") +}) +``` + +v4 + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.String.pipe(Schema.catchDecoding(() => Effect.succeedSome("a"))) +``` + +### rename + +**Migration: manual** + +**New imports:** `SchemaTransformation` + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).pipe(Schema.rename({ a: "c" })) +``` + +v4 + +```ts +import { Schema } from "effect" + +// experimental API +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).pipe(Schema.encodeKeys({ a: "c" })) +``` + +### Capitalize / Lowercase / Uppercase / Uncapitalize + +**Migration: semi-auto** + +**New imports:** `SchemaTransformation` + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.Capitalize +``` + +v4 + +```ts +import { Schema, SchemaTransformation } from "effect" + +const schema = Schema.String.pipe( + Schema.decodeTo(Schema.String.check(Schema.isCapitalized()), SchemaTransformation.capitalize()) +) +``` + +### NonEmptyTrimmedString + +**Migration: semi-auto** + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.NonEmptyTrimmedString +``` + +v4 + +```ts +import { Schema } from "effect" + +const schema = Schema.Trimmed.check(Schema.isNonEmpty()) +``` + +### split + +**Migration: manual** + +**New imports:** `SchemaTransformation` + +v3 + +```ts +import { Schema } from "effect" + +const schema = Schema.split(",") +``` + +v4 + +```ts +import { Schema, SchemaTransformation } from "effect" + +function split(separator: string) { + return Schema.String.pipe( + Schema.decodeTo( + Schema.Array(Schema.String), + SchemaTransformation.transform({ + decode: (s) => s.split(separator) as ReadonlyArray, + encode: (as) => as.join(separator) + }) + ) + ) +} +``` diff --git a/.repos/effect/migration/scope.md b/.repos/effect/migration/scope.md new file mode 100644 index 000000000..4dd5f1305 --- /dev/null +++ b/.repos/effect/migration/scope.md @@ -0,0 +1,50 @@ +# Scope + +## `Scope.extend` → `Scope.provide` + +`Scope.extend` has been renamed to `Scope.provide` in v4. The behavior is +identical: it provides a `Scope` to an effect that requires one, removing +`Scope` from the effect's requirements without closing the scope when the +effect completes. + +The new name better reflects the operation — you are providing a service (the +`Scope`) to an effect, consistent with how other services are provided in +Effect. + +**v3** + +```ts +import { Effect, Scope } from "effect" + +const program = Effect.gen(function*() { + const scope = yield* Scope.make() + yield* Scope.extend(myEffect, scope) +}) +``` + +**v4** + +```ts +import { Effect, Scope } from "effect" + +const program = Effect.gen(function*() { + const scope = yield* Scope.make() + yield* Scope.provide(scope)(myEffect) +}) +``` + +Both data-first and data-last (curried) forms are supported: + +```ts +// data-first +Scope.provide(myEffect, scope) + +// data-last (curried) +myEffect.pipe(Scope.provide(scope)) +``` + +## Quick Reference + +| v3 | v4 | +| -------------- | --------------- | +| `Scope.extend` | `Scope.provide` | diff --git a/.repos/effect/migration/services.md b/.repos/effect/migration/services.md new file mode 100644 index 000000000..9ee34d63e --- /dev/null +++ b/.repos/effect/migration/services.md @@ -0,0 +1,235 @@ +# Services: `Context.Tag` → `Context.Service` + +In v3, services were defined using `Context.Tag`, `Context.GenericTag`, +`Effect.Tag`, or `Effect.Service`. In v4, all of these have been replaced by +`Context.Service`. + +The underlying runtime data structure is a typed map from service identifiers to +their implementations. + +## Defining Services + +**v3: `Context.GenericTag`** + +```ts +import { Context } from "effect" + +interface Database { + readonly query: (sql: string) => string +} + +const Database = Context.GenericTag("Database") +``` + +**v4: `Context.Service` (function syntax)** + +```ts +import { Context } from "effect" + +interface Database { + readonly query: (sql: string) => string +} + +const Database = Context.Service("Database") +``` + +## Class-Based Services + +**v3: `Context.Tag` class syntax** + +```ts +import { Context } from "effect" + +class Database extends Context.Tag("Database") string +}>() {} +``` + +**v4: `Context.Service` class syntax** + +```ts +import { Context } from "effect" + +class Database extends Context.Service string +}>()("Database") {} +``` + +Note the difference in argument order: in v3, the identifier string is passed to +`Context.Tag(id)` before the type parameters. In v4, the type parameters come +first via `Context.Service()` and the identifier string is +passed to the returned constructor `(id)`. + +## `Effect.Tag` Accessors → `Context.Service` with `use` + +v3's `Effect.Tag` provided proxy access to service methods as static properties +on the tag class (accessors). This allowed calling service methods directly +without first yielding the service: + +```ts +// v3 — static accessor proxy +const program = Notifications.notify("hello") +``` + +This pattern had significant limitations. The proxy was implemented via mapped +types over the service shape, which meant **generic methods lost their type +parameters**. A service method like `get(key: string): Effect` would +have its generic erased when accessed through the proxy, collapsing to +`get(key: string): Effect`. For the same reason, overloaded signatures +were not preserved. + +In v4, accessors are removed. The most direct replacement is `Service.use`, +which receives the service instance and runs a callback: + +**v3** + +```ts +import { Effect } from "effect" + +class Notifications extends Effect.Tag("Notifications") Effect.Effect +}>() {} + +// Static proxy access +const program = Notifications.notify("hello") +``` + +**v4 — `use`** + +```ts +import { Context, Effect } from "effect" + +class Notifications extends Context.Service Effect.Effect +}>()("Notifications") {} + +// use: access the service and call a method in one step +const program = Notifications.use((n) => n.notify("hello")) +``` + +`use` takes an effectful callback `(service: Shape) => Effect` and +returns an `Effect`. `useSync` takes a pure callback +`(service: Shape) => A` and returns an `Effect`. Both +return Effects — `useSync` just allows the accessor function itself to be +synchronous: + +```ts +// ┌─── Effect +// ▼ +const program = Notifications.use((n) => n.notify("hello")) + +// ┌─── Effect +// ▼ +const port = Config.useSync((c) => c.port) +``` + +**Prefer `yield*` over `use` in most cases.** While `use` is a convenient +one-liner, it makes it easy to accidentally leak service dependencies into +return values. When you call `use`, the service is available inside the +callback but the dependency is not visible at the call site — making it harder +to track which services your code depends on. Using `yield*` in a generator +makes dependencies explicit and keeps service access co-located with the rest +of your effect logic: + +```ts +const program = Effect.gen(function*() { + const notifications = yield* Notifications + yield* notifications.notify("hello") + yield* notifications.notify("world") +}) +``` + +## `Effect.Service` → `Context.Service` with `make` + +v3's `Effect.Service` allowed defining a service with an effectful constructor +and dependencies inline. In v4, use `Context.Service` with a `make` option. + +**v3** + +In v3, `Effect.Service` automatically generated a `.Default` layer from the +provided constructor, and wired `dependencies` into it: + +```ts +import { Effect, Layer } from "effect" + +class Logger extends Effect.Service()("Logger", { + effect: Effect.gen(function*() { + const config = yield* Config + return { log: (msg: string) => Effect.log(`[${config.prefix}] ${msg}`) } + }), + dependencies: [Config.Default] +}) {} + +// Logger.Default is auto-generated: Layer +// (dependencies are already wired in) +const program = Effect.gen(function*() { + const logger = yield* Logger + yield* logger.log("hello") +}).pipe(Effect.provide(Logger.Default)) +``` + +**v4** + +In v4, `Context.Service` with `make` stores the constructor effect on the +class but does **not** auto-generate a layer. Define layers explicitly using +`Layer.effect`: + +```ts +import { Context, Effect, Layer } from "effect" + +class Logger extends Context.Service()("Logger", { + make: Effect.gen(function*() { + const config = yield* Config + return { log: (msg: string) => Effect.log(`[${config.prefix}] ${msg}`) } + }) +}) { + // Build the layer yourself from the make effect + static readonly layer = Layer.effect(this, this.make).pipe( + Layer.provide(Config.layer) + ) +} +``` + +The `dependencies` option no longer exists. Wire dependencies via +`Layer.provide` as shown above. + +Note: v4 adopts the convention of naming layers with `layer` (e.g. +`Logger.layer`) instead of v3's `Default` or `Live`. Use `layer` +for the primary layer and descriptive suffixes for variants (e.g. +`layerTest`, `layerConfig`). + +## References (Services with Defaults) + +**v3: `Context.Reference`** + +```ts +import { Context } from "effect" + +class LogLevel extends Context.Reference()("LogLevel", { + defaultValue: () => "info" as const +}) {} +``` + +**v4: `Context.Reference`** + +```ts +import { Context } from "effect" + +const LogLevel = Context.Reference<"info" | "warn" | "error">("LogLevel", { + defaultValue: () => "info" as const +}) +``` + +## Quick Reference + +| v3 | v4 | +| ------------------------------------- | --------------------------------------- | +| `Context.GenericTag(id)` | `Context.Service(id)` | +| `Context.Tag(id)()` | `Context.Service()(id)` | +| `Effect.Tag(id)()` | `Context.Service()(id)` | +| `Effect.Service()(id, opts)` | `Context.Service()(id, { make })` | +| `Context.Reference()(id, opts)` | `Context.Reference(id, opts)` | +| `Context.make(tag, impl)` | `Context.make(tag, impl)` | +| `Context.get(ctx, tag)` | `Context.get(map, tag)` | +| `Context.add(ctx, tag, impl)` | `Context.add(map, tag, impl)` | +| `Context.mergeAll(...)` | `Context.mergeAll(...)` | diff --git a/.repos/effect/migration/v3-to-v4.md b/.repos/effect/migration/v3-to-v4.md new file mode 100644 index 000000000..1d6c697be --- /dev/null +++ b/.repos/effect/migration/v3-to-v4.md @@ -0,0 +1,16133 @@ + + +# v3 to v4 Migration Reference + +Base: `3d390f232bdbc3f0d3d6a2ae3c775084f494b547` (`3d390f232bdbc3f0d3d6a2ae3c775084f494b547`) + +Head: `origin/main` (`20cb4f260e45d37fa417c292c57be015314efe16`) + +This file is generated from the API diff and `migration/annotations/*.yaml`. + +## Import Map + +Each line is `v3 import -> v4 direct module import`. When a grouped v4 barrel +exists, the suggested barrel import is shown in parentheses. + +```text +@effect/platform/ChannelSchema -> effect/ChannelSchema (barrel: effect) +@effect/platform/FileSystem -> effect/FileSystem (barrel: effect) +effect/JSONSchema -> effect/JsonSchema (barrel: effect) +@effect/platform/Path -> effect/Path (barrel: effect) +@effect/platform/Error -> effect/PlatformError (barrel: effect) +effect/Either -> effect/Result (barrel: effect) +@effect/platform/Terminal -> effect/Terminal (barrel: effect) +effect/TDeferred -> effect/TxDeferred (barrel: effect) +effect/TMap -> effect/TxHashMap (barrel: effect) +effect/TSet -> effect/TxHashSet (barrel: effect) +effect/TPriorityQueue -> effect/TxPriorityQueue (barrel: effect) +effect/TPubSub -> effect/TxPubSub (barrel: effect) +effect/TQueue -> effect/TxQueue (barrel: effect) +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/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) +@effect/cli/Command -> effect/unstable/cli/Command (barrel: effect/unstable/cli) +@effect/cli/CommandDescriptor -> effect/unstable/cli/Completions (barrel: effect/unstable/cli) +@effect/cli/Options -> effect/unstable/cli/Flag (barrel: effect/unstable/cli) +@effect/cli/BuiltInOptions -> effect/unstable/cli/GlobalFlag (barrel: effect/unstable/cli) +@effect/cli/HelpDoc -> effect/unstable/cli/HelpDoc (barrel: effect/unstable/cli) +@effect/cli/Primitive -> effect/unstable/cli/Primitive (barrel: effect/unstable/cli) +@effect/cli/Prompt -> effect/unstable/cli/Prompt (barrel: effect/unstable/cli) +@effect/cluster/ClusterCron -> effect/unstable/cluster/ClusterCron (barrel: effect/unstable/cluster) +@effect/cluster/ClusterError -> effect/unstable/cluster/ClusterError (barrel: effect/unstable/cluster) +@effect/cluster/ClusterMetrics -> effect/unstable/cluster/ClusterMetrics (barrel: effect/unstable/cluster) +@effect/cluster/ClusterSchema -> effect/unstable/cluster/ClusterSchema (barrel: effect/unstable/cluster) +@effect/cluster/ClusterWorkflowEngine -> effect/unstable/cluster/ClusterWorkflowEngine (barrel: effect/unstable/cluster) +@effect/cluster/DeliverAt -> effect/unstable/cluster/DeliverAt (barrel: effect/unstable/cluster) +@effect/cluster/Entity -> effect/unstable/cluster/Entity (barrel: effect/unstable/cluster) +@effect/cluster/EntityAddress -> effect/unstable/cluster/EntityAddress (barrel: effect/unstable/cluster) +@effect/cluster/EntityId -> effect/unstable/cluster/EntityId (barrel: effect/unstable/cluster) +@effect/cluster/EntityProxy -> effect/unstable/cluster/EntityProxy (barrel: effect/unstable/cluster) +@effect/cluster/EntityProxyServer -> effect/unstable/cluster/EntityProxyServer (barrel: effect/unstable/cluster) +@effect/cluster/EntityResource -> effect/unstable/cluster/EntityResource (barrel: effect/unstable/cluster) +@effect/cluster/EntityType -> effect/unstable/cluster/EntityType (barrel: effect/unstable/cluster) +@effect/cluster/Envelope -> effect/unstable/cluster/Envelope (barrel: effect/unstable/cluster) +@effect/cluster/HttpRunner -> effect/unstable/cluster/HttpRunner (barrel: effect/unstable/cluster) +@effect/cluster/K8sHttpClient -> effect/unstable/cluster/K8sHttpClient (barrel: effect/unstable/cluster) +@effect/cluster/MachineId -> effect/unstable/cluster/MachineId (barrel: effect/unstable/cluster) +@effect/cluster/Message -> effect/unstable/cluster/Message (barrel: effect/unstable/cluster) +@effect/cluster/MessageStorage -> effect/unstable/cluster/MessageStorage (barrel: effect/unstable/cluster) +@effect/cluster/Reply -> effect/unstable/cluster/Reply (barrel: effect/unstable/cluster) +@effect/cluster/Runner -> effect/unstable/cluster/Runner (barrel: effect/unstable/cluster) +@effect/cluster/RunnerAddress -> effect/unstable/cluster/RunnerAddress (barrel: effect/unstable/cluster) +@effect/cluster/RunnerHealth -> effect/unstable/cluster/RunnerHealth (barrel: effect/unstable/cluster) +@effect/cluster/RunnerServer -> effect/unstable/cluster/RunnerServer (barrel: effect/unstable/cluster) +@effect/cluster/RunnerStorage -> effect/unstable/cluster/RunnerStorage (barrel: effect/unstable/cluster) +@effect/cluster/Runners -> effect/unstable/cluster/Runners (barrel: effect/unstable/cluster) +@effect/cluster/ShardId -> effect/unstable/cluster/ShardId (barrel: effect/unstable/cluster) +@effect/cluster/Sharding -> effect/unstable/cluster/Sharding (barrel: effect/unstable/cluster) +@effect/cluster/ShardingConfig -> effect/unstable/cluster/ShardingConfig (barrel: effect/unstable/cluster) +@effect/cluster/ShardingRegistrationEvent -> effect/unstable/cluster/ShardingRegistrationEvent (barrel: effect/unstable/cluster) +@effect/cluster/SingleRunner -> effect/unstable/cluster/SingleRunner (barrel: effect/unstable/cluster) +@effect/cluster/Singleton -> effect/unstable/cluster/Singleton (barrel: effect/unstable/cluster) +@effect/cluster/SingletonAddress -> effect/unstable/cluster/SingletonAddress (barrel: effect/unstable/cluster) +@effect/cluster/Snowflake -> effect/unstable/cluster/Snowflake (barrel: effect/unstable/cluster) +@effect/cluster/SocketRunner -> effect/unstable/cluster/SocketRunner (barrel: effect/unstable/cluster) +@effect/cluster/SqlMessageStorage -> effect/unstable/cluster/SqlMessageStorage (barrel: effect/unstable/cluster) +@effect/cluster/SqlRunnerStorage -> effect/unstable/cluster/SqlRunnerStorage (barrel: effect/unstable/cluster) +@effect/cluster/TestRunner -> effect/unstable/cluster/TestRunner (barrel: effect/unstable/cluster) +@effect/experimental/DevTools -> effect/unstable/devtools/DevTools (barrel: effect/unstable/devtools) +@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/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) +@effect/ai/Chat -> effect/unstable/ai/Chat (barrel: effect/unstable/ai) +@effect/ai/EmbeddingModel -> effect/unstable/ai/EmbeddingModel (barrel: effect/unstable/ai) +@effect/ai/IdGenerator -> effect/unstable/ai/IdGenerator (barrel: effect/unstable/ai) +@effect/ai/LanguageModel -> effect/unstable/ai/LanguageModel (barrel: effect/unstable/ai) +@effect/ai/McpSchema -> effect/unstable/ai/McpSchema (barrel: effect/unstable/ai) +@effect/ai/McpServer -> effect/unstable/ai/McpServer (barrel: effect/unstable/ai) +@effect/ai/Model -> effect/unstable/ai/Model (barrel: effect/unstable/ai) +@effect/ai/Prompt -> effect/unstable/ai/Prompt (barrel: effect/unstable/ai) +@effect/ai/Response -> effect/unstable/ai/Response (barrel: effect/unstable/ai) +@effect/ai/Telemetry -> effect/unstable/ai/Telemetry (barrel: effect/unstable/ai) +@effect/ai/Tokenizer -> effect/unstable/ai/Tokenizer (barrel: effect/unstable/ai) +@effect/ai/Tool -> effect/unstable/ai/Tool (barrel: effect/unstable/ai) +@effect/ai/Toolkit -> effect/unstable/ai/Toolkit (barrel: effect/unstable/ai) +@effect/experimental/Event -> effect/unstable/eventlog/Event (barrel: effect/unstable/eventlog) +@effect/experimental/EventGroup -> effect/unstable/eventlog/EventGroup (barrel: effect/unstable/eventlog) +@effect/experimental/EventJournal -> effect/unstable/eventlog/EventJournal (barrel: effect/unstable/eventlog) +@effect/experimental/EventLog -> effect/unstable/eventlog/EventLog (barrel: effect/unstable/eventlog) +@effect/experimental/EventLogEncryption -> effect/unstable/eventlog/EventLogEncryption (barrel: effect/unstable/eventlog) +@effect/experimental/EventLogRemote -> effect/unstable/eventlog/EventLogMessage (barrel: effect/unstable/eventlog) +@effect/experimental/EventLogRemote -> effect/unstable/eventlog/EventLogRemote (barrel: effect/unstable/eventlog) +@effect/experimental/EventLogServer -> effect/unstable/eventlog/EventLogServer (barrel: effect/unstable/eventlog) +@effect/experimental/EventLogServer -> effect/unstable/eventlog/EventLogServerEncrypted (barrel: effect/unstable/eventlog) +@effect/sql/SqlEventJournal -> effect/unstable/eventlog/SqlEventJournal (barrel: effect/unstable/eventlog) +@effect/sql/SqlEventLogServer -> effect/unstable/eventlog/SqlEventLogServerEncrypted (barrel: effect/unstable/eventlog) +@effect/platform/Cookies -> effect/unstable/http/Cookies (barrel: effect/unstable/http) +@effect/platform/Etag -> effect/unstable/http/Etag (barrel: effect/unstable/http) +@effect/platform/FetchHttpClient -> effect/unstable/http/FetchHttpClient (barrel: effect/unstable/http) +@effect/platform/Headers -> effect/unstable/http/Headers (barrel: effect/unstable/http) +@effect/platform/HttpBody -> effect/unstable/http/HttpBody (barrel: effect/unstable/http) +@effect/platform/HttpClient -> effect/unstable/http/HttpClient (barrel: effect/unstable/http) +@effect/platform/HttpClientError -> effect/unstable/http/HttpClientError (barrel: effect/unstable/http) +@effect/platform/HttpClientRequest -> effect/unstable/http/HttpClientRequest (barrel: effect/unstable/http) +@effect/platform/HttpClientResponse -> effect/unstable/http/HttpClientResponse (barrel: effect/unstable/http) +@effect/platform/HttpApp -> effect/unstable/http/HttpEffect (barrel: effect/unstable/http) +@effect/platform/HttpIncomingMessage -> effect/unstable/http/HttpIncomingMessage (barrel: effect/unstable/http) +@effect/platform/HttpMethod -> effect/unstable/http/HttpMethod (barrel: effect/unstable/http) +@effect/platform/HttpMiddleware -> effect/unstable/http/HttpMiddleware (barrel: effect/unstable/http) +@effect/platform/HttpPlatform -> effect/unstable/http/HttpPlatform (barrel: effect/unstable/http) +@effect/platform/HttpRouter -> effect/unstable/http/HttpRouter (barrel: effect/unstable/http) +@effect/platform/HttpServer -> effect/unstable/http/HttpServer (barrel: effect/unstable/http) +@effect/platform/HttpServerError -> effect/unstable/http/HttpServerError (barrel: effect/unstable/http) +@effect/platform/HttpServerRequest -> effect/unstable/http/HttpServerRequest (barrel: effect/unstable/http) +@effect/platform/HttpServerRespondable -> effect/unstable/http/HttpServerRespondable (barrel: effect/unstable/http) +@effect/platform/HttpServerResponse -> effect/unstable/http/HttpServerResponse (barrel: effect/unstable/http) +@effect/platform/HttpTraceContext -> effect/unstable/http/HttpTraceContext (barrel: effect/unstable/http) +@effect/platform/Multipart -> effect/unstable/http/Multipart (barrel: effect/unstable/http) +@effect/platform/Template -> effect/unstable/http/Template (barrel: effect/unstable/http) +@effect/platform/Url -> effect/unstable/http/Url (barrel: effect/unstable/http) +@effect/platform/UrlParams -> effect/unstable/http/UrlParams (barrel: effect/unstable/http) +@effect/platform/HttpApi -> effect/unstable/httpapi/HttpApi (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiBuilder -> effect/unstable/httpapi/HttpApiBuilder (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiClient -> effect/unstable/httpapi/HttpApiClient (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiEndpoint -> effect/unstable/httpapi/HttpApiEndpoint (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiError -> effect/unstable/httpapi/HttpApiError (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiGroup -> effect/unstable/httpapi/HttpApiGroup (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiMiddleware -> effect/unstable/httpapi/HttpApiMiddleware (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiScalar -> effect/unstable/httpapi/HttpApiScalar (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiSchema -> effect/unstable/httpapi/HttpApiSchema (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiSecurity -> effect/unstable/httpapi/HttpApiSecurity (barrel: effect/unstable/httpapi) +@effect/platform/HttpApiSwagger -> effect/unstable/httpapi/HttpApiSwagger (barrel: effect/unstable/httpapi) +@effect/platform/OpenApi -> effect/unstable/httpapi/OpenApi (barrel: effect/unstable/httpapi) +@effect/opentelemetry/Otlp -> effect/unstable/observability/Otlp (barrel: effect/unstable/observability) +@effect/opentelemetry/internal/otlpExporter -> effect/unstable/observability/OtlpExporter (barrel: effect/unstable/observability) +@effect/opentelemetry/OtlpLogger -> effect/unstable/observability/OtlpLogger (barrel: effect/unstable/observability) +@effect/opentelemetry/OtlpMetrics -> effect/unstable/observability/OtlpMetrics (barrel: effect/unstable/observability) +@effect/opentelemetry/OtlpResource -> effect/unstable/observability/OtlpResource (barrel: effect/unstable/observability) +@effect/opentelemetry/OtlpSerialization -> effect/unstable/observability/OtlpSerialization (barrel: effect/unstable/observability) +@effect/opentelemetry/OtlpTracer -> effect/unstable/observability/OtlpTracer (barrel: effect/unstable/observability) +@effect/platform/KeyValueStore -> effect/unstable/persistence/KeyValueStore (barrel: effect/unstable/persistence) +@effect/experimental/Persistence -> effect/unstable/persistence/Persistable (barrel: effect/unstable/persistence) +@effect/experimental/PersistedCache -> effect/unstable/persistence/PersistedCache (barrel: effect/unstable/persistence) +@effect/experimental/PersistedQueue -> effect/unstable/persistence/PersistedQueue (barrel: effect/unstable/persistence) +@effect/experimental/Persistence -> effect/unstable/persistence/Persistence (barrel: effect/unstable/persistence) +@effect/experimental/RateLimiter -> effect/unstable/persistence/RateLimiter (barrel: effect/unstable/persistence) +@effect/platform/Command -> effect/unstable/process/ChildProcess (barrel: effect/unstable/process) +@effect/platform/CommandExecutor -> effect/unstable/process/ChildProcessSpawner (barrel: effect/unstable/process) +@effect/experimental/Reactivity -> effect/unstable/reactivity/Reactivity (barrel: effect/unstable/reactivity) +@effect/rpc/Rpc -> effect/unstable/rpc/Rpc (barrel: effect/unstable/rpc) +@effect/rpc/RpcClient -> effect/unstable/rpc/RpcClient (barrel: effect/unstable/rpc) +@effect/rpc/RpcClientError -> effect/unstable/rpc/RpcClientError (barrel: effect/unstable/rpc) +@effect/rpc/RpcGroup -> effect/unstable/rpc/RpcGroup (barrel: effect/unstable/rpc) +@effect/rpc/RpcMessage -> effect/unstable/rpc/RpcMessage (barrel: effect/unstable/rpc) +@effect/rpc/RpcMiddleware -> effect/unstable/rpc/RpcMiddleware (barrel: effect/unstable/rpc) +@effect/rpc/RpcSchema -> effect/unstable/rpc/RpcSchema (barrel: effect/unstable/rpc) +@effect/rpc/RpcSerialization -> effect/unstable/rpc/RpcSerialization (barrel: effect/unstable/rpc) +@effect/rpc/RpcServer -> effect/unstable/rpc/RpcServer (barrel: effect/unstable/rpc) +@effect/rpc/RpcTest -> effect/unstable/rpc/RpcTest (barrel: effect/unstable/rpc) +@effect/rpc/RpcWorker -> effect/unstable/rpc/RpcWorker (barrel: effect/unstable/rpc) +@effect/sql/Model -> effect/unstable/schema/Model (barrel: effect/unstable/schema) +@effect/experimental/VariantSchema -> effect/unstable/schema/VariantSchema (barrel: effect/unstable/schema) +@effect/platform/Socket -> effect/unstable/socket/Socket (barrel: effect/unstable/socket) +@effect/platform/SocketServer -> effect/unstable/socket/SocketServer (barrel: effect/unstable/socket) +@effect/sql/Migrator -> effect/unstable/sql/Migrator (barrel: effect/unstable/sql) +@effect/sql/SqlClient -> effect/unstable/sql/SqlClient (barrel: effect/unstable/sql) +@effect/sql/SqlConnection -> effect/unstable/sql/SqlConnection (barrel: effect/unstable/sql) +@effect/sql/SqlError -> effect/unstable/sql/SqlError (barrel: effect/unstable/sql) +@effect/sql/Model -> effect/unstable/sql/SqlModel (barrel: effect/unstable/sql) +@effect/sql/SqlResolver -> effect/unstable/sql/SqlResolver (barrel: effect/unstable/sql) +@effect/sql/SqlSchema -> effect/unstable/sql/SqlSchema (barrel: effect/unstable/sql) +@effect/sql/SqlStream -> effect/unstable/sql/SqlStream (barrel: effect/unstable/sql) +@effect/sql/Statement -> effect/unstable/sql/Statement (barrel: effect/unstable/sql) +@effect/platform/Transferable -> effect/unstable/workers/Transferable (barrel: effect/unstable/workers) +@effect/platform/Worker -> effect/unstable/workers/Worker (barrel: effect/unstable/workers) +@effect/platform/WorkerError -> effect/unstable/workers/WorkerError (barrel: effect/unstable/workers) +@effect/platform/WorkerRunner -> effect/unstable/workers/WorkerRunner (barrel: effect/unstable/workers) +@effect/workflow/Activity -> effect/unstable/workflow/Activity (barrel: effect/unstable/workflow) +@effect/workflow/DurableClock -> effect/unstable/workflow/DurableClock (barrel: effect/unstable/workflow) +@effect/workflow/DurableDeferred -> effect/unstable/workflow/DurableDeferred (barrel: effect/unstable/workflow) +@effect/workflow/DurableQueue -> effect/unstable/workflow/DurableQueue (barrel: effect/unstable/workflow) +@effect/workflow/Workflow -> effect/unstable/workflow/Workflow (barrel: effect/unstable/workflow) +@effect/workflow/WorkflowEngine -> effect/unstable/workflow/WorkflowEngine (barrel: effect/unstable/workflow) +@effect/workflow/WorkflowProxy -> effect/unstable/workflow/WorkflowProxy (barrel: effect/unstable/workflow) +@effect/workflow/WorkflowProxyServer -> effect/unstable/workflow/WorkflowProxyServer (barrel: effect/unstable/workflow) +effect/Array -> effect/Array (barrel: effect) +effect/BigDecimal -> effect/BigDecimal (barrel: effect) +effect/BigInt -> effect/BigInt (barrel: effect) +effect/Boolean -> effect/Boolean (barrel: effect) +effect/Brand -> effect/Brand (barrel: effect) +effect/Cache -> effect/Cache (barrel: effect) +effect/Cause -> effect/Cause (barrel: effect) +effect/Channel -> effect/Channel (barrel: effect) +effect/Chunk -> effect/Chunk (barrel: effect) +effect/Clock -> effect/Clock (barrel: effect) +@effect/typeclass/Semigroup -> effect/Combiner (barrel: effect) +effect/Config -> effect/Config (barrel: effect) +effect/ConfigProvider -> effect/ConfigProvider (barrel: effect) +effect/Console -> effect/Console (barrel: effect) +effect/Context -> effect/Context (barrel: effect) +effect/Cron -> effect/Cron (barrel: effect) +effect/Data -> effect/Data (barrel: effect) +effect/DateTime -> effect/DateTime (barrel: effect) +effect/Deferred -> effect/Deferred (barrel: effect) +effect/Differ -> effect/Differ (barrel: effect) +effect/Duration -> effect/Duration (barrel: effect) +effect/Effect -> effect/Effect (barrel: effect) +effect/Effectable -> effect/Effectable (barrel: effect) +effect/Encoding -> effect/Encoding (barrel: effect) +effect/Equal -> effect/Equal (barrel: effect) +effect/Equivalence -> effect/Equivalence (barrel: effect) +effect/ExecutionPlan -> effect/ExecutionPlan (barrel: effect) +effect/Exit -> effect/Exit (barrel: effect) +effect/Fiber -> effect/Fiber (barrel: effect) +effect/FiberHandle -> effect/FiberHandle (barrel: effect) +effect/FiberMap -> effect/FiberMap (barrel: effect) +effect/FiberSet -> effect/FiberSet (barrel: effect) +effect/Inspectable -> effect/Formatter (barrel: effect) +effect/Function -> effect/Function (barrel: effect) +effect/Graph -> effect/Graph (barrel: effect) +effect/HKT -> effect/HKT (barrel: effect) +effect/Hash -> effect/Hash (barrel: effect) +effect/HashMap -> effect/HashMap (barrel: effect) +effect/HashRing -> effect/HashRing (barrel: effect) +effect/HashSet -> effect/HashSet (barrel: effect) +effect/Inspectable -> effect/Inspectable (barrel: effect) +effect/Iterable -> effect/Iterable (barrel: effect) +effect/Layer -> effect/Layer (barrel: effect) +effect/LayerMap -> effect/LayerMap (barrel: effect) +effect/LogLevel -> effect/LogLevel (barrel: effect) +effect/Logger -> effect/Logger (barrel: effect) +effect/ManagedRuntime -> effect/ManagedRuntime (barrel: effect) +effect/Match -> effect/Match (barrel: effect) +effect/Metric -> effect/Metric (barrel: effect) +effect/MutableHashMap -> effect/MutableHashMap (barrel: effect) +effect/MutableHashSet -> effect/MutableHashSet (barrel: effect) +effect/MutableList -> effect/MutableList (barrel: effect) +effect/MutableRef -> effect/MutableRef (barrel: effect) +effect/NonEmptyIterable -> effect/NonEmptyIterable (barrel: effect) +effect/Number -> effect/Number (barrel: effect) +effect/Option -> effect/Option (barrel: effect) +effect/Order -> effect/Order (barrel: effect) +effect/Ordering -> effect/Ordering (barrel: effect) +effect/PartitionedSemaphore -> effect/PartitionedSemaphore (barrel: effect) +effect/Pipeable -> effect/Pipeable (barrel: effect) +effect/Pool -> effect/Pool (barrel: effect) +effect/Predicate -> effect/Predicate (barrel: effect) +effect/PrimaryKey -> effect/PrimaryKey (barrel: effect) +effect/PubSub -> effect/PubSub (barrel: effect) +effect/Queue -> effect/Queue (barrel: effect) +effect/Random -> effect/Random (barrel: effect) +effect/RcMap -> effect/RcMap (barrel: effect) +effect/RcRef -> effect/RcRef (barrel: effect) +effect/Record -> effect/Record (barrel: effect) +effect/Inspectable -> effect/Redactable (barrel: effect) +effect/Redacted -> effect/Redacted (barrel: effect) +@effect/typeclass/Monoid -> effect/Reducer (barrel: effect) +effect/Ref -> effect/Ref (barrel: effect) +effect/FiberRef -> effect/References (barrel: effect) +effect/RegExp -> effect/RegExp (barrel: effect) +effect/Request -> effect/Request (barrel: effect) +effect/RequestResolver -> effect/RequestResolver (barrel: effect) +effect/Resource -> effect/Resource (barrel: effect) +effect/Runtime -> effect/Runtime (barrel: effect) +effect/Schedule -> effect/Schedule (barrel: effect) +effect/Scheduler -> effect/Scheduler (barrel: effect) +effect/Schema -> effect/Schema (barrel: effect) +effect/SchemaAST -> effect/SchemaAST (barrel: effect) +effect/ParseResult -> effect/SchemaIssue (barrel: effect) +effect/ParseResult -> effect/SchemaParser (barrel: effect) +effect/Schema -> effect/SchemaTransformation (barrel: effect) +effect/Scope -> effect/Scope (barrel: effect) +effect/ScopedCache -> effect/ScopedCache (barrel: effect) +effect/ScopedRef -> effect/ScopedRef (barrel: effect) +effect/Sink -> effect/Sink (barrel: effect) +effect/Stream -> effect/Stream (barrel: effect) +effect/String -> effect/String (barrel: effect) +effect/Struct -> effect/Struct (barrel: effect) +effect/SubscriptionRef -> effect/SubscriptionRef (barrel: effect) +effect/Symbol -> effect/Symbol (barrel: effect) +effect/SynchronizedRef -> effect/SynchronizedRef (barrel: effect) +effect/Take -> effect/Take (barrel: effect) +effect/Tracer -> effect/Tracer (barrel: effect) +effect/Trie -> effect/Trie (barrel: effect) +effect/Tuple -> effect/Tuple (barrel: effect) +effect/Types -> effect/Types (barrel: effect) +effect/Unify -> effect/Unify (barrel: effect) +effect/Utils -> effect/Utils (barrel: effect) +``` + +## No Counterpart Imports + +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) +effect/JsonPointer (barrel: effect) +effect/Latch (barrel: effect) +effect/Newtype (barrel: effect) +effect/Optic (barrel: effect) +effect/Pull (barrel: effect) +effect/SchemaGetter (barrel: effect) +effect/SchemaRepresentation (barrel: effect) +effect/Semaphore (barrel: effect) +effect/Stdio (barrel: effect) +effect/TxChunk (barrel: effect) +effect/UndefinedOr (barrel: effect) +effect/testing/TestConsole (barrel: effect/testing) +effect/testing/TestSchema (barrel: effect/testing) +effect/unstable/ai/AnthropicStructuredOutput (barrel: effect/unstable/ai) +effect/unstable/ai/OpenAiStructuredOutput (barrel: effect/unstable/ai) +effect/unstable/ai/ResponseIdTracker (barrel: effect/unstable/ai) +effect/unstable/cli/CliOutput (barrel: effect/unstable/cli) +effect/unstable/cli/Param (barrel: effect/unstable/cli) +effect/unstable/eventlog/EventLogServerUnencrypted (barrel: effect/unstable/eventlog) +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/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) +effect/unstable/reactivity/AsyncResult (barrel: effect/unstable/reactivity) +effect/unstable/reactivity/Atom (barrel: effect/unstable/reactivity) +effect/unstable/reactivity/AtomHttpApi (barrel: effect/unstable/reactivity) +effect/unstable/reactivity/AtomRef (barrel: effect/unstable/reactivity) +effect/unstable/reactivity/AtomRegistry (barrel: effect/unstable/reactivity) +effect/unstable/reactivity/AtomRpc (barrel: effect/unstable/reactivity) +effect/unstable/reactivity/Hydration (barrel: effect/unstable/reactivity) +effect/unstable/rpc/Utils (barrel: effect/unstable/rpc) +``` + +## Removed Modules + +- `@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 the same namespaces from the @effect/platform-node package root or import specific modules directly. +- `@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/Msgpack` +- `@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`: No single module replacement; follow the curated per-API guidance below. +- `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` -> `effect/testing/FastCheck` +- `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. + +## 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.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.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.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.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.fileContent` -> `Argument.file + Argument.mapEffect`: Parse a path and read it with FileSystem.readFile; no binary-content argument constructor remains. + +- `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.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.optional` -> `Argument.optional`: Use the moved combinator; it still returns Option. + +- `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.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.choice`: 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.float`: 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.integer`: 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.none 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.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.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.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.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.float`: Use the moved constructor; v4 also supports a default value. + +- `Prompt.integer` -> `Prompt.integer`: Use the moved constructor; v4 also supports a default value. + +- `Prompt.text` -> `Prompt.text`: Use the moved 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 Rpc.ServicesServer requirements. + +- `EntityProxyServer.layerRpcHandlers` -> `effect/unstable/cluster/EntityProxyServer#layerRpcHandlers`: Moved into core Effect; the service requirement is now Rpc.ServicesServer rather than Rpc.Context. + +### `@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.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.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#layerMsgPack`: Use the generic MsgPack 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#layerMsgPack`: Use the generic MsgPack 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.layerMsgPack replace the module-specific request decoder. + +- `EventLogRemote.decodeResponse` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: Generic RPC framing and RpcSerialization.layerMsgPack replace the module-specific response decoder. + +- `EventLogRemote.encodeRequest` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: Generic RPC framing and RpcSerialization.layerMsgPack replace the module-specific request encoder. + +- `EventLogRemote.encodeResponse` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: Generic RPC framing and RpcSerialization.layerMsgPack 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, MsgPack 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.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.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, onError, and bufferSize. + +- `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.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.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.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.OpenFileOptions` -> `NonNullable[1]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `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.StreamOptions` -> `NonNullable[1]>`: Stream options are inline; bufferSize was removed while bytesToRead, chunkSize, and offset remain. + +- `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` -> `Headers.HeadersSchema`: The encoded-record and self schemas were consolidated into HeadersSchema. + +- `Headers.schemaFromSelf` -> `Headers.HeadersSchema`: The encoded-record and self schemas were consolidated into HeadersSchema. + +- `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.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#endpoint`: Use endpoint for a standalone typed endpoint implementation; inside a group pass callbacks 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 chunkSize and the other file options remain. + +- `HttpBody.fileInfo` -> `HttpBody.fileFromInfo`: Renamed; it still uses supplied File.Info for content length and requires FileSystem. + +- `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.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 FileSystem.Size(input) 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. + +- `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 and uses updated file stream options. + +### `@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.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.TypeId` -> `none`: The public TypeId was removed; HttpServer is now a Context.Service class. + +- `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 with updated FileSystem stream options. + +- `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` -> `Msgpack.MsgPackError`: The public error type-id alias was removed; use the MsgPackError class. + +- `MsgPack.duplex` -> `Msgpack.duplex`: The API moved to effect/unstable/encoding/Msgpack. + +- `MsgPack.duplexSchema` -> `Msgpack.duplexSchema`: The API moved to effect/unstable/encoding/Msgpack and uses v4 Schema constraints. + +- `MsgPack.pack` -> `Msgpack.encode`: The MessagePack channel constructor was renamed from pack to encode. + +- `MsgPack.packSchema` -> `Msgpack.encodeSchema`: The schema-aware pack channel was renamed to encodeSchema. + +- `MsgPack.schema` -> `Msgpack.schema`: The schema helper remains in the moved module and uses the v4 Schema model. + +- `MsgPack.unpack` -> `Msgpack.decode`: The MessagePack channel constructor was renamed from unpack to decode. + +- `MsgPack.unpackSchema` -> `Msgpack.decodeSchema`: The schema-aware unpack channel was renamed to decodeSchema. + +### `@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`: The setting remains but is now a Context.Reference. + +- `Multipart.MaxFileSize` -> `Multipart.MaxFileSize`: The setting remains as a Context.Reference; use undefined rather than Option.none 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 SizeInput values; 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 the v4 Context.Reference around the effect. + +- `Multipart.withMaxFileSize` -> `Effect.provideService(Multipart.MaxFileSize, size)`: Provide the v4 Context.Reference around the effect, converting Option.none to undefined. + +- `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.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.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.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` -> `Socket.SendQueueCapacity`: The FiberRef was replaced by a defaulted Context.Reference. + +- `Socket.layerWebSocket` -> `Socket.layerWebSocket`: The constructor remains in effect/unstable/socket/Socket; its URL may now also be an Effect. + +### `@effect/platform/SocketServer` + +- `SocketServer.ErrorTypeId` -> `SocketServer.ErrorTypeId`: The API moved to effect/unstable/socket/SocketServer and retains this name. + +### `@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.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.makeUrl` -> `Url.make`: Moved to Url, returns Result, and takes string | undefined for the hash. + +- `UrlParams.schemaFromSelf` -> `UrlParams.UrlParamsSchema`: Renamed to the declaration schema for the v4 wrapper. + +- `UrlParams.schemaFromString` -> `Schema.String.pipe(Schema.decodeTo(UrlParams.UrlParamsSchema, { 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` -> `UrlParams.schemaJsonField(field).pipe(Schema.decodeTo(schema), Schema.decodeEffect)`: Compose the field codec with the target schema, then decode it. + +- `UrlParams.schemaParse` -> `UrlParamsFromString.pipe(Schema.decodeTo(UrlParams.schemaRecord.pipe(Schema.decodeTo(schema))))`: Recreate the removed helper by composing the string, record, and target codecs. + +- `UrlParams.schemaRecord` -> `UrlParams.schemaRecord.pipe(Schema.decodeTo(schema))`: schemaRecord is now a base codec value; compose it with the target schema. + +- `UrlParams.schemaStruct` -> `UrlParams.schemaRecord.pipe(Schema.decodeTo(schema), Schema.decodeEffect)`: Compose the record codec 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/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` -> `Parameters[0]`: The named type was removed; derive the inline fromPool option type. PgPoolConfig is for creating a managed pool and is not equivalent. + +- `PgClient.layerFromPool` -> `PgClient.layerFrom(PgClient.fromPool(options))`: Compose fromPool with layerFrom; layerFrom now accepts an Effect acquiring a PgClient rather than pool options. + +### `@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/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.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. + +### `@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.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.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.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 4 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.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 4 shape. + +- `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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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 4 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/environments#Environment`: This was a deprecated root re-export. Import it from vitest/environments; Vitest 4 custom environments use Vite environments. + +- `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 4 shape. + +- `index.EnvironmentReturn` -> `vitest/environments#EnvironmentReturn`: This was a deprecated root re-export. Import it from vitest/environments; Vitest 4 custom environments use Vite environments. + +- `index.ErrorWithDiff` -> `vitest#TestError`: Vitest 3 deprecated ErrorWithDiff in favor of TestError; review the tightened actual, expected, and cause fields. + +- `index.ExtendedContext` -> `vitest#TestContext`: The separate context alias was removed. Vitest 4 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 4 keeps this shape only as a property of EnvironmentOptions; derive it from the public vitest/node type. + +- `index.HookCleanupCallback` -> `none`: No named Vitest 4 export replaces this alias. Let the hook return type infer, or type the cleanup function locally. + +- `index.HookListener` -> `none`: Use the matching @vitest/runner hook-specific type such as BeforeAllListener, AfterAllListener, BeforeEachListener, or AfterEachListener for custom runner code. + +- `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 4 shape. + +- `index.JSDOMOptions` -> `NonNullable`: Vitest 4 keeps this shape only as a property of EnvironmentOptions; derive it from the public vitest/node type. + +- `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 4 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 4 shape. + +- `index.PoolOptions` -> `vitest/config#TestUserConfig`: The v3 built-in poolOptions object was removed. Move its fields to Vitest 4 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 4 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 4 shape. + +- `index.Reporter` -> `vitest/reporters#Reporter`: Import Reporter from the public plural vitest/reporters entrypoint; its lifecycle methods changed in Vitest 4. + +- `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 4 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 4 shape. + +- `index.ResolvedTestEnvironment` -> `none`: Vitest 3 marked this type unsupported. Use Environment from vitest/environments 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` -> `@vitest/runner#RuntimeContext`: Custom-runner code can add an explicit @vitest/runner dependency; ordinary tests should avoid this internal state type. + +- `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 4 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 4 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` -> `@vitest/runner#SuiteHooks`: Custom-runner code can add an explicit @vitest/runner dependency; 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 4 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 4 shape. + +- `index.UserConfig` -> `vitest/config#TestUserConfig`: Vitest 4 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 4 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 4 shape. + +- `index.VmEnvironmentReturn` -> `vitest/environments#VmEnvironmentReturn`: This was a deprecated root re-export. Import it from vitest/environments; Vitest 4 custom environments use Vite environments. + +- `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 4 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.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.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.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. + +### `@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. + +- `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.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. + +### `@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 and use the toArbitrary key. + +- `Arbitrary.ArbitraryGenerationContext` -> `Schema.Annotations.ToArbitrary.Context`: Use the v4 arbitrary-derivation context type from Schema.Annotations. + +- `Arbitrary.LazyArbitrary` -> `Schema.Arbitrary`: The arbitrary factory type moved onto Schema. + +#### `Arbitrary.make` + +**Replacement:** `Schema.toArbitrary` + +Arbitrary derivation is now exposed directly by Schema. + +**Example** + +```ts +Schema.toArbitrary(schema)(FastCheck) +``` + +#### `Arbitrary.makeLazy` + +**Replacement:** `Schema.toArbitrary` + +Lazy arbitrary derivation is now exposed directly by Schema. + +**Example** + +```ts +Schema.toArbitrary(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.runDone`: Renamed to runDone for an inputless, outputless channel. Use runDrain if emitted elements should be discarded. + +- `Channel.runScoped` -> `Channel.toPull`: No direct scoped runner remains. Use toPull in the caller scope and recover Cause.Done; use runDone or 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.schema(Config.Array(valueSchema), path)`: Array parsing is schema-based in v4; rebuild the element Config as a Schema and pass the optional path to Config.schema. + +- `Config.boolean` -> `Config.boolean`: Unchanged. + +- `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`: Unchanged. + +- `Config.duration` -> `Config.duration`: Unchanged. + +- `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. + +- `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`: Unchanged. + +- `Config.mapAttempt` -> `Config.mapOrFail`: Catch exceptions explicitly and return an Effect failure containing Config.ConfigError; mapOrFail is Effect-based in v4. + +- `Config.nonEmptyString` -> `Config.nonEmptyString`: Unchanged. + +- `Config.number` -> `Config.number`: Unchanged; 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`: Unchanged. + +- `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.schema(Config.Array(valueSchema), path)`: Repeated values are represented by an array Schema in v4; Config.Array also accepts flat separated input. + +- `Config.secret` -> `Config.redacted`: Secret was removed in favor of Redacted; this constructor already returns Redacted\. + +- `Config.string` -> `Config.string`: Unchanged. + +- `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`: Unchanged. + +- `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 and sequence delimiters moved to provider path transforms and Config.Array/Config.Record schemas. + +- `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`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `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 override property or getter returning the Effect. + +- `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 override; 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.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.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.BigUintConstraints` -> `FastCheck.BigIntConstraints`: Import FastCheck from effect/testing. Unsigned bigint constraints were consolidated into BigIntConstraints with min: 0n. + +- `FastCheck.UnicodeJsonSharedConstraints` -> `FastCheck.JsonSharedConstraints`: Import FastCheck from effect/testing. Unicode JSON generation was consolidated into JsonSharedConstraints.stringUnit. + +#### `FastCheck.ascii` + +**Replacement:** `FastCheck.string` + +Import FastCheck from effect/testing. 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` + +Import FastCheck from effect/testing. Use the binary-ascii string unit. + +**Example** + +```ts +FastCheck.string({ ...constraints, unit: "binary-ascii" }) +``` + +#### `FastCheck.base64` + +**Replacement:** `FastCheck.constantFrom` + +Import FastCheck from effect/testing. Generate one base64 alphabet character; base64String remains for complete encoded strings. + +**Example** + +```ts +FastCheck.constantFrom(..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/") +``` + +- `FastCheck.bigIntN` -> `FastCheck.bigInt`: Import FastCheck from effect/testing. Express the signed bit range with min and max constraints. + +#### `FastCheck.bigUint` + +**Replacement:** `FastCheck.bigInt` + +Import FastCheck from effect/testing. Use a minimum of 0n and the previous maximum. + +**Example** + +```ts +FastCheck.bigInt({ min: 0n, max }) +``` + +#### `FastCheck.bigUintN` + +**Replacement:** `FastCheck.bigInt` + +Import FastCheck from effect/testing. Express the unsigned bit range with min and max constraints. + +**Example** + +```ts +FastCheck.bigInt({ min: 0n, max: (1n << BigInt(n)) - 1n }) +``` + +#### `FastCheck.char` + +**Replacement:** `FastCheck.string` + +Import FastCheck from effect/testing. Use a one-unit printable ASCII string. + +**Example** + +```ts +FastCheck.string({ unit: "grapheme-ascii", minLength: 1, maxLength: 1 }) +``` + +#### `FastCheck.char16bits` + +**Replacement:** `FastCheck.nat` + +Import FastCheck from effect/testing. Map a 16-bit natural number through String.fromCharCode. + +**Example** + +```ts +FastCheck.nat({ max: 0xffff }).map(String.fromCharCode) +``` + +- `FastCheck.constant` -> `FastCheck.constant`: Import FastCheck from effect/testing. The API remains; v4 infers literal types by default. + +- `FastCheck.context` -> `FastCheck.context`: Import FastCheck from effect/testing. The API is otherwise unchanged. + +#### `FastCheck.fullUnicode` + +**Replacement:** `FastCheck.string` + +Import FastCheck from effect/testing. Use a one-unit binary Unicode string. + +**Example** + +```ts +FastCheck.string({ unit: "binary", minLength: 1, maxLength: 1 }) +``` + +#### `FastCheck.fullUnicodeString` + +**Replacement:** `FastCheck.string` + +Import FastCheck from effect/testing. Use the binary string unit. + +**Example** + +```ts +FastCheck.string({ ...constraints, unit: "binary" }) +``` + +- `FastCheck.hexa` -> `FastCheck.integer`: Import FastCheck from effect/testing. Map an integer from 0 through 15 to a hexadecimal character. + +- `FastCheck.hexaString` -> `FastCheck.string`: Import FastCheck from effect/testing. Pass a hexadecimal-character arbitrary as the string unit. + +- `FastCheck.stream` -> `FastCheck.stream`: Import FastCheck from effect/testing. The API remains; update custom generator and Random implementations for fast-check v4 typings. + +- `FastCheck.string16bits` -> `FastCheck.string`: Import FastCheck from effect/testing. Pass a char16bits-compatible arbitrary as the string unit. + +#### `FastCheck.stringOf` + +**Replacement:** `FastCheck.string` + +Import FastCheck from effect/testing. Pass the former character arbitrary as the unit constraint. + +**Example** + +```ts +FastCheck.string({ ...constraints, unit: arbitrary }) +``` + +- `FastCheck.unicode` -> `FastCheck.integer`: Import FastCheck from effect/testing. Map BMP code points while excluding surrogate code points; prefer the binary string unit for full Unicode. + +#### `FastCheck.unicodeJson` + +**Replacement:** `FastCheck.json` + +Import FastCheck from effect/testing. Select binary or grapheme strings with stringUnit. + +**Example** + +```ts +FastCheck.json({ stringUnit: "binary" }) +``` + +#### `FastCheck.unicodeJsonValue` + +**Replacement:** `FastCheck.jsonValue` + +Import FastCheck from effect/testing. Select binary or grapheme strings with stringUnit. + +**Example** + +```ts +FastCheck.jsonValue({ stringUnit: "binary" }) +``` + +- `FastCheck.unicodeString` -> `FastCheck.string`: Import FastCheck from effect/testing. Pass a BMP-code-point arbitrary as the unit constraint; prefer unit: binary for full Unicode. + +#### `FastCheck.uuidV` + +**Replacement:** `FastCheck.uuid` + +Import FastCheck from effect/testing. Specify the UUID version through constraints. + +**Example** + +```ts +FastCheck.uuid({ version: 4 }) +``` + +### `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 extends Graph.Proto; obtain it through Graph.mutate or Graph.beginMutation and use public mutation/query functions. + +- `Graph.Proto` -> `Graph.Proto`: The name remains as the opaque immutable graph protocol; it no longer exposes storage and is no longer the base of MutableGraph. + +- `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. + +- `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. + +- `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.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`: Direct constructor rename; call it with the same 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.currentSpan`: No transparent generic equivalent remains. Read span identity from options.fiber.currentSpan 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.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.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.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.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.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.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. + +- `Schema.TemplateLiteralParser` -> `Schema.TemplateLiteralParser(schema.parts)`: Create the template schema first and pass its `parts` property. + +- `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.Base`: All v4 AST nodes extend Base, which owns 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; use the toArbitrary annotation key and its Schema.Annotations types. + +- `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. + +- `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.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.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.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, now extends the v4 Ref model, and is read or updated through explicit SynchronizedRef operations. + +- `SynchronizedRef.SynchronizedRef.Variance` -> `Ref.Ref.Variance`: SynchronizedRef now inherits Ref variance instead of declaring a separate public variance marker. + +- `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 FastCheck 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` -> `{ fastCheck: { numRuns } }`: Pass the run count through @effect/vitest property-test options, for example it.effect.prop(..., { fastCheck: { numRuns: samples } }). + +- `TestServices.shrinks` -> `none`: The legacy maximum-shrinks service setting was removed; @effect/vitest forwards FastCheck.Parameters, which has no equivalent service value. + +- `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 fastCheck 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 fastCheck 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 fastCheck 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 fastCheck 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 fastCheck 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/.repos/effect/migration/yieldable.md b/.repos/effect/migration/yieldable.md new file mode 100644 index 000000000..f57781328 --- /dev/null +++ b/.repos/effect/migration/yieldable.md @@ -0,0 +1,173 @@ +# Effect Subtyping (v3) → Yieldable (v4) + +In v3, many types were structural subtypes of `Effect` — they carried the +Effect type ID at runtime and could be used anywhere an `Effect` was expected. +This included `Ref`, `Deferred`, `Fiber`, `FiberRef`, `Config`, `Option`, +`Either`, `Context.Tag`, and others. + +While convenient, this created a class of subtle bugs. Because these types +_were_ Effects, they could be silently passed to Effect combinators when you +intended to pass the value itself. For example, passing a `Ref` where you meant +to pass the value inside the `Ref`, or accidentally mapping over a `Deferred` +as an Effect instead of awaiting it. + +v4 replaces this with the **`Yieldable`** trait: a narrower contract that +allows `yield*` in generators but does **not** make the type assignable to +`Effect`. + +## The `Yieldable` Interface + +```ts +interface Yieldable { + asEffect(): Effect + [Symbol.iterator](): EffectIterator +} +``` + +Some example types that implement `Yieldable`: + +- `Effect` itself +- `Option` — yields the value or fails with `NoSuchElementError` +- `Result` — yields the success or fails with the error +- `Config` — yields the config value or fails with `ConfigError` +- `Context.Service` — yields the service from the environment + +Some example types that are **no longer** Effect subtypes and do **not** +implement `Yieldable`: + +- `Ref` — use `Ref.get(ref)` to read +- `Deferred` — use `Deferred.await(deferred)` to wait +- `Fiber` — use `Fiber.join(fiber)` to await + +## `yield*` Still Works + +`yield*` in `Effect.gen` works with any `Yieldable`. The runtime calls +`.asEffect()` internally when yielding. + +```ts +import { Effect, Option } from "effect" + +// The type of program is `Effect` +const program = Effect.gen(function*() { + // yield* works with Yieldable types — same as v3 + const value = yield* Option.some(42) + return value // 42 +}) +``` + +## Effect Combinators Require `.asEffect()` + +In v3, you could pass a `Yieldable` type directly to Effect combinators because +it was a subtype of `Effect`. In v4, you must explicitly convert with +`.asEffect()`. + +**v3** — Option is an Effect subtype, so this compiles: + +```ts +import { Effect, Option } from "effect" + +// Option is assignable to Effect +const program = Effect.map(Option.some(42), (n) => n + 1) +``` + +**v4** — Option is not an Effect, so you must convert explicitly: + +```ts +import { Effect, Option } from "effect" + +// Option is Yieldable but not Effect — use .asEffect() +const program = Effect.map(Option.some(42).asEffect(), (n) => n + 1) + +// Or more idiomatically, use a generator: +const program2 = Effect.gen(function*() { + const n = yield* Option.some(42) + return n + 1 +}) +``` + +## Types No Longer Subtypes of Effect + +Several types that extended `Effect` in v3 no longer do so in v4. Use the +appropriate module functions instead. + +**v3** — `Ref` extends `Effect`, yielding the current value: + +```ts +import { Effect, Ref } from "effect" + +const program = Effect.gen(function*() { + const ref = yield* Ref.make(0) + const value = yield* ref // Ref is an Effect +}) +``` + +**v4** — `Ref` is a plain value, use `Ref.get`: + +```ts +import { Effect, Ref } from "effect" + +const program = Effect.gen(function*() { + const ref = yield* Ref.make(0) + const value = yield* Ref.get(ref) +}) +``` + +**v3** — `Deferred` extends `Effect`, resolving when completed: + +```ts +import { Deferred, Effect } from "effect" + +const program = Effect.gen(function*() { + const deferred = yield* Deferred.make() + const value = yield* deferred // Deferred is an Effect +}) +``` + +**v4** — `Deferred` is a plain value, use `Deferred.await`: + +```ts +import { Deferred, Effect } from "effect" + +const program = Effect.gen(function*() { + const deferred = yield* Deferred.make() + const value = yield* Deferred.await(deferred) +}) +``` + +**v3** — `Fiber` extends `Effect`, joining on yield: + +```ts +import { Effect, Fiber } from "effect" + +const program = Effect.gen(function*() { + const fiber = yield* Effect.fork(task) + const result = yield* fiber // Fiber is an Effect +}) +``` + +**v4** — `Fiber` is a plain value, use `Fiber.join`: + +```ts +import { Effect, Fiber } from "effect" + +const program = Effect.gen(function*() { + const fiber = yield* Effect.forkChild(task) + const result = yield* Fiber.join(fiber) +}) +``` + +## Why This Changed + +The v3 subtyping approach meant the type system could not distinguish between +"I have a Ref" and "I have an Effect that reads the Ref." This ambiguity led +to bugs that were difficult to diagnose: + +- Passing a `Ref` to `Effect.map` would read the ref's value rather than + transforming the ref itself — often not the intended behavior. +- A `Deferred` in a data structure could silently be treated as an Effect, + causing unexpected awaits. +- Combinators like `Effect.all` would accept an array of `Ref` values and + silently read all of them, instead of producing a type error. + +The `Yieldable` trait preserves the ergonomic `yield*` syntax in generators +while making the conversion to `Effect` explicit everywhere else. diff --git a/.repos/effect/package.json b/.repos/effect/package.json new file mode 100644 index 000000000..8dbc701c7 --- /dev/null +++ b/.repos/effect/package.json @@ -0,0 +1,84 @@ +{ + "private": true, + "type": "module", + "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": "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": "tsc -b tsconfig.json", + "typeperf": "pnpm --dir packages/effect exec node typeperf/run.mjs", + "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": "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", + "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": "node scripts/set-strip-internal.mjs && pnpm codemod && pnpm build && changeset publish" + }, + "devDependencies": { + "@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.0", + "@changesets/cli": "3.0.1", + "@effect/ai-docgen": "workspace:^", + "@effect/bundle": "workspace:^", + "@effect/docgen": "workspace:^", + "@effect/doctest": "workspace:^", + "@effect/jsdocs": "workspace:^", + "@effect/oxc": "workspace:^", + "@effect/tsgo": "^0.36.5", + "@effect/utils": "workspace:^", + "@effect/vitest": "workspace:^", + "@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": "^26.2.0", + "@vitest/browser": "^4.1.11", + "@vitest/coverage-v8": "^4.1.11", + "@vitest/expect": "^4.1.11", + "@vitest/web-worker": "^4.1.11", + "ast-types": "^0.14.2", + "babel-plugin-annotate-pure-calls": "^0.5.0", + "dprint": "^0.56.1", + "glob": "^13.0.6", + "happy-dom": "^20.11.6", + "jscodeshift": "^17.4.0", + "lalph": "^0.3.139", + "madge": "^8.0.0", + "oxlint": "^1.79.0", + "pkg-pr-new": "0.0.88", + "playwright": "^1.62.1", + "rollup": "^4.62.5", + "rollup-plugin-bundle-stats": "^4.22.3", + "rollup-plugin-esbuild": "^6.2.1", + "rollup-plugin-visualizer": "^7.1.1", + "terser": "^5.50.0", + "tstyche": "^7.2.3", + "typescript": "^7.0.2", + "vite": "^8.2.2", + "vitest": "^4.1.11", + "vitest-websocket-mock": "^0.7.0", + "zod": "^4.4.3" + } +} diff --git a/.repos/effect/packages/ai/anthropic/CHANGELOG.md b/.repos/effect/packages/ai/anthropic/CHANGELOG.md new file mode 100644 index 000000000..d1b848563 --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/CHANGELOG.md @@ -0,0 +1,849 @@ +# @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 + +- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]: + - effect@4.0.0-beta.90 + +## 4.0.0-beta.89 + +### Patch Changes + +- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]: + - effect@4.0.0-beta.89 + +## 4.0.0-beta.88 + +### Patch Changes + +- [#2450](https://github.com/Effect-TS/effect-smol/pull/2450) [`7b8a549`](https://github.com/Effect-TS/effect-smol/commit/7b8a5493950aa6f9e20024cd7e588838722218f0) Thanks @DomiR! - Fix non-streaming Anthropic responses throwing when a tool call carries `caller` metadata. The mapper emitted `caller.toolId: undefined`, but `ProviderMetadata` is `Record(String, NullOr(Json))` and `undefined` is not a valid Json value, so decoding the model's own response threw `Expected JSON value`. Emit `null` instead, matching the streaming mappers. + +- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]: + - effect@4.0.0-beta.88 + +## 4.0.0-beta.87 + +### Patch Changes + +- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]: + - effect@4.0.0-beta.87 + +## 4.0.0-beta.86 + +### Patch Changes + +- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]: + - effect@4.0.0-beta.86 + +## 4.0.0-beta.85 + +### Patch Changes + +- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]: + - effect@4.0.0-beta.85 + +## 4.0.0-beta.84 + +### Patch Changes + +- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]: + - effect@4.0.0-beta.84 + +## 4.0.0-beta.83 + +### Patch Changes + +- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]: + - effect@4.0.0-beta.83 + +## 4.0.0-beta.82 + +### Patch Changes + +- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]: + - effect@4.0.0-beta.82 + +## 4.0.0-beta.81 + +### Patch Changes + +- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]: + - effect@4.0.0-beta.81 + +## 4.0.0-beta.80 + +### Patch Changes + +- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]: + - effect@4.0.0-beta.80 + +## 4.0.0-beta.79 + +### Patch Changes + +- [#2357](https://github.com/Effect-TS/effect-smol/pull/2357) [`3efe308`](https://github.com/Effect-TS/effect-smol/commit/3efe308fcc6ac642c65560efbd1b3afc68052553) Thanks @jbmusso! - Mark the Claude 4-6 generation as supporting native structured output in `getModelCapabilities`. `claude-opus-4-6` and `claude-sonnet-4-6` support Anthropic's constrained-decoding structured output (verified against the live API), but were classified as `supportsStructuredOutput: false`, so `generateObject` fell back to a forced JSON tool instead of requesting `output_config.format` (`json_schema`). `claude-opus-4-7` / `claude-opus-4-8` are classified the same way for when the generated `Model` enum picks them up. + +- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]: + - effect@4.0.0-beta.79 + +## 4.0.0-beta.78 + +### Patch Changes + +- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]: + - effect@4.0.0-beta.78 + +## 4.0.0-beta.77 + +### Patch Changes + +- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]: + - effect@4.0.0-beta.77 + +## 4.0.0-beta.76 + +### Patch Changes + +- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]: + - effect@4.0.0-beta.76 + +## 4.0.0-beta.75 + +### Patch Changes + +- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]: + - effect@4.0.0-beta.75 + +## 4.0.0-beta.74 + +### Patch Changes + +- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]: + - effect@4.0.0-beta.74 + +## 4.0.0-beta.73 + +### Patch Changes + +- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]: + - effect@4.0.0-beta.73 + +## 4.0.0-beta.72 + +### Patch Changes + +- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]: + - effect@4.0.0-beta.72 + +## 4.0.0-beta.71 + +### Patch Changes + +- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]: + - effect@4.0.0-beta.71 + +## 4.0.0-beta.70 + +### Patch Changes + +- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]: + - effect@4.0.0-beta.70 + +## 4.0.0-beta.69 + +### Patch Changes + +- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]: + - effect@4.0.0-beta.69 + +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + +## 4.0.0-beta.67 + +### Patch Changes + +- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]: + - effect@4.0.0-beta.67 + +## 4.0.0-beta.66 + +### Patch Changes + +- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]: + - effect@4.0.0-beta.66 + +## 4.0.0-beta.65 + +### Patch Changes + +- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]: + - effect@4.0.0-beta.65 + +## 4.0.0-beta.64 + +### Patch Changes + +- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]: + - effect@4.0.0-beta.64 + +## 4.0.0-beta.63 + +### Patch Changes + +- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]: + - effect@4.0.0-beta.63 + +## 4.0.0-beta.62 + +### Patch Changes + +- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]: + - effect@4.0.0-beta.62 + +## 4.0.0-beta.61 + +### Patch Changes + +- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]: + - effect@4.0.0-beta.61 + +## 4.0.0-beta.60 + +### Patch Changes + +- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]: + - effect@4.0.0-beta.60 + +## 4.0.0-beta.59 + +### Patch Changes + +- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]: + - effect@4.0.0-beta.59 + +## 4.0.0-beta.58 + +### Patch Changes + +- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]: + - effect@4.0.0-beta.58 + +## 4.0.0-beta.57 + +### Patch Changes + +- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]: + - effect@4.0.0-beta.57 + +## 4.0.0-beta.56 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]: + - effect@4.0.0-beta.55 + +## 4.0.0-beta.54 + +### Patch Changes + +- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]: + - effect@4.0.0-beta.54 + +## 4.0.0-beta.53 + +### Patch Changes + +- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]: + - effect@4.0.0-beta.53 + +## 4.0.0-beta.52 + +### Patch Changes + +- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]: + - effect@4.0.0-beta.52 + +## 4.0.0-beta.51 + +### Patch Changes + +- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]: + - effect@4.0.0-beta.51 + +## 4.0.0-beta.50 + +### Patch Changes + +- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]: + - effect@4.0.0-beta.50 + +## 4.0.0-beta.49 + +### Patch Changes + +- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]: + - effect@4.0.0-beta.49 + +## 4.0.0-beta.48 + +### Patch Changes + +- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]: + - effect@4.0.0-beta.48 + +## 4.0.0-beta.47 + +### Patch Changes + +- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]: + - effect@4.0.0-beta.47 + +## 4.0.0-beta.46 + +### Patch Changes + +- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]: + - effect@4.0.0-beta.46 + +## 4.0.0-beta.45 + +### Patch Changes + +- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]: + - effect@4.0.0-beta.45 + +## 4.0.0-beta.44 + +### Patch Changes + +- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename the `ServiceMap` module to `Context` across exports, docs, and tests. + +- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]: + - effect@4.0.0-beta.44 + +## 4.0.0-beta.43 + +### Patch Changes + +- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]: + - effect@4.0.0-beta.43 + +## 4.0.0-beta.42 + +### Patch Changes + +- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]: + - effect@4.0.0-beta.42 + +## 4.0.0-beta.41 + +### Patch Changes + +- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]: + - effect@4.0.0-beta.41 + +## 4.0.0-beta.40 + +### Patch Changes + +- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]: + - effect@4.0.0-beta.40 + +## 4.0.0-beta.39 + +### Patch Changes + +- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]: + - effect@4.0.0-beta.39 + +## 4.0.0-beta.38 + +### Patch Changes + +- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]: + - effect@4.0.0-beta.38 + +## 4.0.0-beta.37 + +### Patch Changes + +- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]: + - effect@4.0.0-beta.37 + +## 4.0.0-beta.36 + +### Patch Changes + +- [#1775](https://github.com/Effect-TS/effect-smol/pull/1775) [`954e6d6`](https://github.com/Effect-TS/effect-smol/commit/954e6d655cd32d329b0bfeb872bb654f88b48a13) Thanks @tim-smart! - Add dynamic tool support to the Anthropic language model provider when preparing tool definitions for requests. + +- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]: + - effect@4.0.0-beta.36 + +## 4.0.0-beta.35 + +### Patch Changes + +- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]: + - effect@4.0.0-beta.35 + +## 4.0.0-beta.34 + +### Patch Changes + +- [#1763](https://github.com/Effect-TS/effect-smol/pull/1763) [`2fa940d`](https://github.com/Effect-TS/effect-smol/commit/2fa940d0709769c5fd1337c1f88400867f91989b) Thanks @teeverc! - Remove duplicate `ToolApprovalResponsePartOptions` from Anthropic package + +- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]: + - effect@4.0.0-beta.34 + +## 4.0.0-beta.33 + +### Patch Changes + +- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]: + - effect@4.0.0-beta.33 + +## 4.0.0-beta.32 + +### Patch Changes + +- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]: + - effect@4.0.0-beta.32 + +## 4.0.0-beta.31 + +### Patch Changes + +- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]: + - effect@4.0.0-beta.31 + +## 4.0.0-beta.30 + +### Patch Changes + +- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]: + - effect@4.0.0-beta.30 + +## 4.0.0-beta.29 + +### Patch Changes + +- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]: + - effect@4.0.0-beta.29 + +## 4.0.0-beta.28 + +### Patch Changes + +- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]: + - effect@4.0.0-beta.28 + +## 4.0.0-beta.27 + +### Patch Changes + +- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]: + - effect@4.0.0-beta.27 + +## 4.0.0-beta.26 + +### Patch Changes + +- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]: + - effect@4.0.0-beta.26 + +## 4.0.0-beta.25 + +### Patch Changes + +- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]: + - effect@4.0.0-beta.25 + +## 4.0.0-beta.24 + +### Patch Changes + +- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]: + - effect@4.0.0-beta.24 + +## 4.0.0-beta.23 + +### Patch Changes + +- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]: + - effect@4.0.0-beta.23 + +## 4.0.0-beta.22 + +### Patch Changes + +- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]: + - effect@4.0.0-beta.22 + +## 4.0.0-beta.21 + +### Patch Changes + +- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]: + - effect@4.0.0-beta.21 + +## 4.0.0-beta.20 + +### Patch Changes + +- [#1529](https://github.com/Effect-TS/effect-smol/pull/1529) [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8) Thanks @tim-smart! - Add dedicated AiError metadata interfaces per reason so provider packages can safely augment metadata without conflicting module declarations. + +- [#1528](https://github.com/Effect-TS/effect-smol/pull/1528) [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34) Thanks @tim-smart! - Add `Model.ModelName` and provide it from AI model constructors. + +- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]: + - effect@4.0.0-beta.20 + +## 4.0.0-beta.19 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Patch Changes + +- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]: + - effect@4.0.0-beta.18 + +## 4.0.0-beta.17 + +### Patch Changes + +- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]: + - effect@4.0.0-beta.17 + +## 4.0.0-beta.16 + +### Patch Changes + +- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]: + - effect@4.0.0-beta.16 + +## 4.0.0-beta.15 + +### Patch Changes + +- [#1502](https://github.com/Effect-TS/effect-smol/pull/1502) [`285b7e6`](https://github.com/Effect-TS/effect-smol/commit/285b7e667167566d5788367d5155b19c79f1bf22) Thanks @tim-smart! - allow undefined for ai config + +- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]: + - effect@4.0.0-beta.15 + +## 4.0.0-beta.14 + +### Patch Changes + +- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]: + - effect@4.0.0-beta.14 + +## 4.0.0-beta.13 + +### Patch Changes + +- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]: + - effect@4.0.0-beta.13 + +## 4.0.0-beta.12 + +### Patch Changes + +- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]: + - effect@4.0.0-beta.12 + +## 4.0.0-beta.11 + +### Patch Changes + +- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]: + - effect@4.0.0-beta.11 + +## 4.0.0-beta.10 + +### Patch Changes + +- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]: + - effect@4.0.0-beta.10 + +## 4.0.0-beta.9 + +### Patch Changes + +- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]: + - effect@4.0.0-beta.9 + +## 4.0.0-beta.8 + +### Patch Changes + +- [#1354](https://github.com/Effect-TS/effect-smol/pull/1354) [`b94962c`](https://github.com/Effect-TS/effect-smol/commit/b94962c249d46cf96cdf2e41188dc9feda41536a) Thanks @IMax153! - Fix the generated schemas for ai providers + +- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]: + - effect@4.0.0-beta.8 + +## 4.0.0-beta.7 + +### Patch Changes + +- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]: + - effect@4.0.0-beta.7 + +## 4.0.0-beta.6 + +### Patch Changes + +- [#1333](https://github.com/Effect-TS/effect-smol/pull/1333) [`96260bf`](https://github.com/Effect-TS/effect-smol/commit/96260bf5331fad6e38d2a79e94d70fd9a502ec29) Thanks @IMax153! - Fix tool calling for the Anthropic Effect AI SDK provider integration + +- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]: + - effect@4.0.0-beta.6 + +## 4.0.0-beta.5 + +### Patch Changes + +- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]: + - effect@4.0.0-beta.5 + +## 4.0.0-beta.4 + +### Patch Changes + +- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]: + - effect@4.0.0-beta.4 + +## 4.0.0-beta.3 + +### Patch Changes + +- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]: + - effect@4.0.0-beta.3 + +## 4.0.0-beta.2 + +### Patch Changes + +- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]: + - effect@4.0.0-beta.2 + +## 4.0.0-beta.1 + +### Patch Changes + +- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]: + - effect@4.0.0-beta.1 + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta + +### Patch Changes + +- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]: + - effect@4.0.0-beta.0 diff --git a/.repos/effect/packages/ai/anthropic/README.md b/.repos/effect/packages/ai/anthropic/README.md new file mode 100644 index 000000000..da2336285 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/anthropic/codegen.yml b/.repos/effect/packages/ai/anthropic/codegen.yml new file mode 100644 index 000000000..dd23c4bcb --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/codegen.yml @@ -0,0 +1,62 @@ +# yaml-language-server: $schema=../../tools/ai-codegen/codegen.schema.json +spec: + type: stainless-stats + statsUrl: https://raw.githubusercontent.com/anthropics/anthropic-sdk-typescript/main/.stats.yml +output: src/Generated.ts +name: AnthropicClient +patches: + # 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: | + /** + * @since 1.0.0 + */ +excludeAnnotations: + - examples +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 }" + # Index signature in struct rest pattern + - 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 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 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 })),' + # 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 })) })' + # 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: "\"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 })),' + diff --git a/.repos/effect/packages/ai/anthropic/package.json b/.repos/effect/packages/ai/anthropic/package.json new file mode 100644 index 000000000..6e60982cb --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/package.json @@ -0,0 +1,69 @@ +{ + "name": "@effect/ai-anthropic", + "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.git", + "directory": "packages/ai/anthropic" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "ai", + "anthropic" + ], + "keywords": [ + "typescript", + "ai", + "anthropic" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./internal/*": null, + "./index": null, + "./*/index": null + } + }, + "scripts": { + "codegen": "effect-utils codegen", + "build": "tsc -b tsconfig.json && pnpm babel", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "devDependencies": { + "effect": "workspace:^" + }, + "peerDependencies": { + "effect": "workspace:^" + } +} diff --git a/.repos/effect/packages/ai/anthropic/src/AnthropicClient.ts b/.repos/effect/packages/ai/anthropic/src/AnthropicClient.ts new file mode 100644 index 000000000..93288554f --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/AnthropicClient.ts @@ -0,0 +1,437 @@ +/** + * The `AnthropicClient` module defines the low-level Effect service for + * Anthropic's Messages API. It builds a generated Anthropic HTTP client with + * authentication headers, API version headers, response decoding, and error + * mapping, then exposes helpers for regular and streaming message requests. + * + * @since 4.0.0 + */ +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 } from "effect/Function" +import * as Layer from "effect/Layer" +import * as Predicate from "effect/Predicate" +import * as Redacted from "effect/Redacted" +import * as Schema from "effect/Schema" +import * as Stream from "effect/Stream" +import type * as AiError from "effect/unstable/ai/AiError" +import * as Sse from "effect/unstable/encoding/Sse" +import * as Headers from "effect/unstable/http/Headers" +import * as HttpBody from "effect/unstable/http/HttpBody" +import * as HttpClient from "effect/unstable/http/HttpClient" +import type * as HttpClientError from "effect/unstable/http/HttpClientError" +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import { AnthropicConfig } from "./AnthropicConfig.ts" +import * as Generated from "./Generated.ts" +import * as Errors from "./internal/errors.ts" + +// ============================================================================= +// Service Interface +// ============================================================================= + +/** + * Represents the Anthropic client service with methods for the Messages API, including regular and streaming message + * creation. + * + * @category services + * @since 4.0.0 + */ +export interface Service { + /** + * The underlying generated Anthropic client that exposes all API endpoints. + */ + readonly client: Generated.AnthropicClient + + /** + * Executes a low-level streaming HTTP request and decodes the Server-Sent Events response using the provided schema. + */ + readonly streamRequest: ( + schema: S + ) => (request: HttpClientRequest.HttpClientRequest) => Stream.Stream< + S["Type"], + HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError, + S["DecodingServices"] + > + + /** + * Creates a message using the Anthropic Messages API and maps all errors to the unified `AiError` type. + */ + readonly createMessage: (options: { + readonly payload: typeof Generated.BetaCreateMessageParams.Encoded + readonly params?: typeof Generated.BetaMessagesPostParams.Encoded | undefined + }) => Effect.Effect< + [body: typeof Generated.BetaMessage.Type, response: HttpClientResponse.HttpClientResponse], + AiError.AiError + > + + /** + * Creates a streaming message using the Anthropic Messages API and maps all errors to the unified `AiError` type. + * + * **Details** + * + * The returned Effect yields the HTTP response and a stream of events as the model generates its response. The stream + * automatically terminates when a `message_stop` event is received. + */ + readonly createMessageStream: (options: { + readonly payload: Omit + readonly params?: typeof Generated.BetaMessagesPostParams.Encoded | undefined + }) => Effect.Effect< + [response: HttpClientResponse.HttpClientResponse, stream: Stream.Stream], + AiError.AiError + > +} + +/** + * Represents an event received from the Anthropic Messages API during a streaming request. + * + * **Details** + * + * Events include: + * - `message_start`: Initial event containing message metadata + * - `message_delta`: Incremental updates to the message (e.g., stop reason) + * - `message_stop`: Final event indicating the message is complete + * - `content_block_start`: Start of a content block + * - `content_block_delta`: Incremental content updates (text, tool use, etc.) + * - `content_block_stop`: End of a content block + * - `error`: Error events with type and message + * + * @category models + * @since 4.0.0 + */ +export type MessageStreamEvent = + | typeof Generated.BetaMessageStartEvent.Type + | typeof Generated.BetaMessageDeltaEvent.Type + | typeof Generated.BetaMessageStopEvent.Type + | typeof Generated.BetaContentBlockStartEvent.Type + | typeof Generated.BetaContentBlockDeltaEvent.Type + | typeof Generated.BetaContentBlockStopEvent.Type + | typeof Generated.BetaErrorResponse.Type + +// ============================================================================= +// Service Identifier +// ============================================================================= + +/** + * Service tag for the Anthropic client. + * + * **When to use** + * + * Use when accessing or providing the Anthropic client service through Effect's + * context. + * + * @see {@link make} for constructing an Anthropic client effectfully + * @see {@link layer} for providing a client from explicit options + * @see {@link layerConfig} for providing a client from `Config` + * + * @category services + * @since 4.0.0 + */ +export class AnthropicClient extends Context.Service()( + "@effect/ai-anthropic/AnthropicClient" +) {} + +// ============================================================================= +// Options +// ============================================================================= + +/** + * Configuration for creating an Anthropic client. + * + * **When to use** + * + * Use when the Anthropic client settings are already available as values and + * should be passed directly to `make` or `layer`. + * + * **Details** + * + * These options configure the base Anthropic URL, the `x-api-key` + * authentication header, the `anthropic-version` header, and an optional + * transformation of the underlying `HttpClient`. + * + * @see {@link make} for constructing an Anthropic client from explicit options + * @see {@link layer} for providing an Anthropic client from explicit options + * @see {@link layerConfig} for loading Anthropic client settings from `Config` + * + * @category options + * @since 4.0.0 + */ +export type Options = { + /** + * The Anthropic API key for authentication. Requests are made without authentication when this is omitted, which is + * useful for proxied setups or testing. + */ + readonly apiKey?: Redacted.Redacted | undefined + + /** + * The base URL for the Anthropic API. Override this to use a proxy or a different API-compatible endpoint. + * + * @default "https://api.anthropic.com" + */ + readonly apiUrl?: string | undefined + + /** + * The Anthropic API version header value. This controls which version of the API to use. + * + * @default "2023-06-01" + */ + readonly apiVersion?: string | undefined + + /** + * Optional transformer for the underlying HTTP client, such as middleware, logging, or custom request/response + * handling. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +} + +// ============================================================================= +// Constructor +// ============================================================================= + +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. + * + * **When to use** + * + * Use when you have explicit configuration values and need an `Effect` that + * constructs the Anthropic client service, rather than providing it as a `Layer`. + * + * **Details** + * + * The client handles API key authentication via the `x-api-key` header, API versioning via the `anthropic-version` + * header, error mapping to the unified `AiError` type, and request/response transformations via `AnthropicConfig`. It + * requires an `HttpClient` in the context. + * + * @see {@link layer} for providing the client as a `Layer` from explicit options + * @see {@link layerConfig} for providing the client as a `Layer` with `Config`-based settings + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced( + function*(options: Options): Effect.fn.Return { + const baseClient = yield* HttpClient.HttpClient + const apiVersion = options.apiVersion ?? "2023-06-01" + + const httpClient = baseClient.pipe( + HttpClient.mapRequest((request) => + request.pipe( + HttpClientRequest.prependUrl(options.apiUrl ?? "https://api.anthropic.com"), + Predicate.isNotUndefined(options.apiKey) + ? HttpClientRequest.setHeader( + RedactedAnthropicHeaders.AnthropicApiKey, + Redacted.value(options.apiKey) + ) + : identity, + HttpClientRequest.setHeader("anthropic-version", apiVersion), + HttpClientRequest.acceptJson + ) + ), + Predicate.isNotUndefined(options.transformClient) + ? options.transformClient + : identity + ) + + const client = Generated.make(httpClient, { + transformClient: Effect.fnUntraced(function*(client) { + const config = yield* AnthropicConfig.getOrUndefined + if (Predicate.isNotUndefined(config?.transformClient)) { + return config.transformClient(client) + } + return client + }) + }) + + const httpClientOk = HttpClient.filterStatusOk(httpClient) + + const streamRequest = + (schema: S) => + (request: HttpClientRequest.HttpClientRequest): Stream.Stream< + S["Type"], + HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError, + S["DecodingServices"] + > => + httpClientOk.execute(request).pipe( + Effect.map((response) => response.stream), + Stream.unwrap, + Stream.decodeText, + Stream.pipeThroughChannel(Sse.decodeSchema(schema)) + ) + + const createMessage = (options: { + readonly payload: typeof Generated.BetaCreateMessageParams.Encoded + readonly params?: typeof Generated.BetaMessagesPostParams.Encoded | undefined + }): Effect.Effect< + [body: typeof Generated.BetaMessage.Type, response: HttpClientResponse.HttpClientResponse], + AiError.AiError + > => + client.betaMessagesPost({ ...options, config: { includeResponse: true } }).pipe( + Effect.catchTags({ + 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({ + type: Schema.Literal("ping") + }) + + const MessageEvent = Schema.Union([ + PingEvent, + Generated.BetaMessageStartEvent, + Generated.BetaMessageDeltaEvent, + Generated.BetaMessageStopEvent, + Generated.BetaContentBlockStartEvent, + Generated.BetaContentBlockDeltaEvent, + Generated.BetaContentBlockStopEvent, + Generated.BetaErrorResponse + ]) + + const buildMessageStream = ( + response: HttpClientResponse.HttpClientResponse + ): [HttpClientResponse.HttpClientResponse, Stream.Stream] => { + const stream = response.stream.pipe( + Stream.decodeText, + Stream.pipeThroughChannel(Sse.decodeDataSchema(MessageEvent)), + Stream.takeUntil((event) => event.data.type === "message_stop"), + Stream.map((event) => event.data), + Stream.filter((event): event is MessageStreamEvent => event.type !== "ping"), + 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")) + }) + ) as any + return [response, stream] + } + + const createMessageStream: Service["createMessageStream"] = (options) => { + const request = HttpClientRequest.post("/v1/messages", { + headers: Headers.fromInput({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? apiVersion + }), + body: HttpBody.jsonUnsafe({ + ...options.payload, + stream: true + }) + }) + return httpClientOk.execute(request).pipe( + Effect.map(buildMessageStream), + Effect.catchTag( + "HttpClientError", + (error) => Errors.mapHttpClientError(error, "createMessageStream") + ), + withRedactedHeaders + ) + } + + return AnthropicClient.of({ + client, + streamRequest, + createMessage, + createMessageStream + }) + }, + withRedactedHeaders +) + +// ============================================================================= +// Layers +// ============================================================================= + +/** + * Creates a layer for the Anthropic client with the given options. + * + * **When to use** + * + * Use when you already have explicit `Options` values, such as an API key or + * custom API URL, and want to provide `AnthropicClient` as a `Layer`. + * + * @see {@link make} for constructing the client service effectfully + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: Options): Layer.Layer => + Layer.effect(AnthropicClient, make(options)) + +/** + * Creates a layer for the Anthropic client, loading the requisite configuration + * via Effect's `Config` module. + * + * **When to use** + * + * Use when you want to provide the Anthropic client as a `Layer` with + * configuration loaded from Effect's `Config` module, such as from environment + * variables or a secrets provider. + * + * @see {@link layer} for providing the client from explicit options instead of `Config` + * @see {@link make} for constructing the client service effectfully + * + * @category layers + * @since 4.0.0 + */ +export const layerConfig = (options?: { + /** + * The Anthropic API key for authentication. Requests are made without authentication when this is omitted, which is + * useful for proxied setups or testing. + */ + readonly apiKey?: Config.Config | undefined> | undefined + + /** + * The base URL for the Anthropic API. Override this to use a proxy or a different API-compatible endpoint. + * + * @default "https://api.anthropic.com" + */ + readonly apiUrl?: Config.Config | undefined + + /** + * The Anthropic API version header value. This controls which version of the API to use. + * + * @default "2023-06-01" + */ + readonly apiVersion?: Config.Config | undefined + + /** + * Optional transformer for the underlying HTTP client, such as middleware, logging, or custom request/response + * handling. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +}): Layer.Layer => + Layer.effect( + AnthropicClient, + Effect.gen(function*() { + const apiKey = Predicate.isNotUndefined(options?.apiKey) + ? yield* options.apiKey : + undefined + const apiUrl = Predicate.isNotUndefined(options?.apiUrl) + ? yield* options.apiUrl : + undefined + const apiVersion = Predicate.isNotUndefined(options?.apiVersion) + ? yield* options.apiVersion : + undefined + return yield* make({ + apiKey, + apiUrl, + apiVersion, + transformClient: options?.transformClient + }) + }) + ) diff --git a/.repos/effect/packages/ai/anthropic/src/AnthropicConfig.ts b/.repos/effect/packages/ai/anthropic/src/AnthropicConfig.ts new file mode 100644 index 000000000..f4578de36 --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/AnthropicConfig.ts @@ -0,0 +1,84 @@ +/** + * The `AnthropicConfig` module lets a workflow temporarily customize the HTTP + * client used by generated Anthropic requests. It is used by + * `AnthropicClient` when request helpers run, so code can add middleware, + * logging, or other client changes without rebuilding the client layer. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import type { HttpClient } from "effect/unstable/http/HttpClient" + +/** + * Service tag for Anthropic client configuration overrides, such as transformations applied to the generated HTTP client. + * + * **When to use** + * + * Use when you need to provide or read Anthropic client configuration through + * Effect's context from a layer or integration. + * + * @see {@link withClientTransform} for scoping an HTTP client transformation + * + * @category services + * @since 4.0.0 + */ +export class AnthropicConfig extends Context.Service< + AnthropicConfig, + AnthropicConfig.Service +>()("@effect/ai-anthropic/AnthropicConfig") { + /** + * Gets the configured Anthropic service from the current context when present. + * + * @since 4.0.0 + */ + static readonly getOrUndefined: Effect.Effect = Effect.map( + Effect.context(), + Context.getOrUndefined(AnthropicConfig) + ) +} + +/** + * Namespace containing types associated with the `AnthropicConfig` service. + * + * @since 4.0.0 + */ +export declare namespace AnthropicConfig { + /** + * Configuration provided through `AnthropicConfig`. + * + * **Details** + * + * Use `transformClient` to wrap or replace the `HttpClient` used by generated Anthropic API requests. + * + * @category services + * @since 4.0.0 + */ + export interface Service { + readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined + } +} + +/** + * Runs an effect with an `AnthropicConfig` override that transforms the underlying `HttpClient` used by generated Anthropic requests. + * + * **When to use** + * + * Use when you need to apply a temporary `HttpClient` transformation, such as adding middleware or logging, to a + * specific scope of an effectful program. + * + * @category configuration + * @since 4.0.0 + */ +export const withClientTransform: { + (transform: (client: HttpClient) => HttpClient): (self: Effect.Effect) => Effect.Effect + (self: Effect.Effect, transform: (client: HttpClient) => HttpClient): Effect.Effect +} = dual(2, ( + self: Effect.Effect, + transformClient: (client: HttpClient) => HttpClient +) => + Effect.flatMap( + AnthropicConfig.getOrUndefined, + (config) => Effect.provideService(self, AnthropicConfig, { ...config, transformClient }) + )) diff --git a/.repos/effect/packages/ai/anthropic/src/AnthropicError.ts b/.repos/effect/packages/ai/anthropic/src/AnthropicError.ts new file mode 100644 index 000000000..590176e2f --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/AnthropicError.ts @@ -0,0 +1,212 @@ +/** + * Anthropic error metadata augmentation. + * + * Provides Anthropic-specific metadata fields for AI error types through module + * augmentation, enabling typed access to Anthropic error details. + * + * @since 4.0.0 + */ + +/** + * Anthropic-specific error metadata fields. + * + * **Details** + * + * Contains the Anthropic error type and request identifier copied from provider + * error responses when available. Either field may be `null` when Anthropic + * does not include it or the response cannot be decoded. + * + * @see {@link AnthropicRateLimitMetadata} for rate-limit responses that also include parsed Anthropic rate-limit headers + * + * @category models + * @since 4.0.0 + */ +export type AnthropicErrorMetadata = { + /** + * The Anthropic error type returned by the API. + */ + readonly errorType: string | null + /** + * The unique request ID for debugging with Anthropic support. + */ + readonly requestId: string | null +} + +/** + * Anthropic-specific rate limit metadata fields. + * + * **Details** + * + * Extends base error metadata with rate limit-specific information from Anthropic's rate limit headers. + * + * @category models + * @since 4.0.0 + */ +export type AnthropicRateLimitMetadata = AnthropicErrorMetadata & { + /** + * Number of requests allowed in the current period. + */ + readonly requestsLimit: number | null + /** + * Number of requests remaining in the current period. + */ + readonly requestsRemaining: number | null + /** + * Time when the request rate limit resets. + */ + readonly requestsReset: string | null + /** + * Number of tokens allowed in the current period. + */ + readonly tokensLimit: number | null + /** + * Number of tokens remaining in the current period. + */ + readonly tokensRemaining: number | null + /** + * Time when the token rate limit resets. + */ + readonly tokensReset: string | null +} + +declare module "effect/unstable/ai/AiError" { + /** + * Anthropic metadata attached to `RateLimitError` values. + * + * **Details** + * + * Includes request identifiers, Anthropic error types, and parsed request or token limit headers when the provider rejects a request due to rate limits. + * + * @category models + * @since 4.0.0 + */ + export interface RateLimitErrorMetadata { + readonly anthropic?: AnthropicRateLimitMetadata | null + } + + /** + * Anthropic metadata attached to `QuotaExhaustedError` values. + * + * **Details** + * + * Captures the Anthropic error type and request identifier for failures where the account or workspace has exhausted its available quota. + * + * @category models + * @since 4.0.0 + */ + export interface QuotaExhaustedErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `AuthenticationError` values. + * + * **Details** + * + * Preserves Anthropic error details for missing, invalid, or unauthorized API credentials while keeping the error in the shared AI error model. + * + * @category models + * @since 4.0.0 + */ + export interface AuthenticationErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `ContentPolicyError` values. + * + * **Details** + * + * Records Anthropic error details returned when a request or response is rejected by Anthropic safety or content policy enforcement. + * + * @category models + * @since 4.0.0 + */ + export interface ContentPolicyErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `InvalidRequestError` values. + * + * **Details** + * + * Provides the Anthropic error type and request identifier for malformed or unsupported requests rejected before model execution. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidRequestErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `InternalProviderError` values. + * + * **Details** + * + * Preserves Anthropic request correlation data for provider-side failures that should be reported or investigated with Anthropic support. + * + * @category models + * @since 4.0.0 + */ + export interface InternalProviderErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `InvalidOutputError` values. + * + * **Details** + * + * Describes Anthropic-specific context for responses that could not be decoded or interpreted as valid AI output. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidOutputErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `StructuredOutputError` values. + * + * **Details** + * + * Captures Anthropic error details for structured-output failures, including request correlation data useful when diagnosing schema-related responses. + * + * @category models + * @since 4.0.0 + */ + export interface StructuredOutputErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `UnsupportedSchemaError` values. + * + * **Details** + * + * Provides Anthropic error details for schemas that cannot be represented by or submitted to the Anthropic API. + * + * @category models + * @since 4.0.0 + */ + export interface UnsupportedSchemaErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } + + /** + * Anthropic metadata attached to `UnknownError` values. + * + * **Details** + * + * Retains the Anthropic error type and request identifier when a provider response cannot be classified as a more specific AI error. + * + * @category models + * @since 4.0.0 + */ + export interface UnknownErrorMetadata { + readonly anthropic?: AnthropicErrorMetadata | null + } +} diff --git a/.repos/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts b/.repos/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts new file mode 100644 index 000000000..7ba02b588 --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts @@ -0,0 +1,3124 @@ +/** + * The `AnthropicLanguageModel` module provides the Anthropic implementation of + * Effect AI's `LanguageModel` service. It translates Effect AI prompts, tools, + * files, reasoning content, and Anthropic-specific options into Messages API + * requests, then converts normal and streaming Anthropic responses back into + * Effect AI response content with provider metadata. + * + * @since 4.0.0 + */ +/** @effect-diagnostics preferSchemaOverJson:skip-file */ +import * as Arr from "effect/Array" +import * as Context from "effect/Context" +import * as DateTime from "effect/DateTime" +import * as Effect from "effect/Effect" +import * as Encoding from "effect/Encoding" +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 Redactable from "effect/Redactable" +import * as Schema from "effect/Schema" +import * as SchemaAST from "effect/SchemaAST" +import * as SchemaIssue from "effect/SchemaIssue" +import * as Stream from "effect/Stream" +import type { Span } from "effect/Tracer" +import type { Mutable, Simplify } from "effect/Types" +import * as AiError from "effect/unstable/ai/AiError" +import { toCodecAnthropic } from "effect/unstable/ai/AnthropicStructuredOutput" +import * as IdGenerator from "effect/unstable/ai/IdGenerator" +import * as LanguageModel from "effect/unstable/ai/LanguageModel" +import * as AiModel from "effect/unstable/ai/Model" +import type * as Prompt from "effect/unstable/ai/Prompt" +import type * as Response from "effect/unstable/ai/Response" +import * as Tool from "effect/unstable/ai/Tool" +import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import { AnthropicClient, type MessageStreamEvent } from "./AnthropicClient.ts" +import { addGenAIAnnotations } from "./AnthropicTelemetry.ts" +import type { AnthropicTool } from "./AnthropicTool.ts" +import type * as Generated from "./Generated.ts" +import * as InternalUtilities from "./internal/utilities.ts" + +const formatIssue = SchemaIssue.makeFormatterDefault() + +/** + * Known Anthropic Claude model identifiers exposed by the generated Anthropic schema. + * + * @category models + * @since 4.0.0 + */ +export type Model = (typeof Generated.Model)["members"][1]["Encoded"] + +// ============================================================================= +// Configuration +// ============================================================================= + +/** + * Context service for Anthropic language model configuration. + * + * **When to use** + * + * Use when you need scoped Anthropic model request defaults or per-operation + * overrides from Effect context. + * + * **Details** + * + * The service stores request fields that are merged into Anthropic Messages API + * requests. Scoped configuration overrides defaults supplied to `model`, + * `make`, or `layer`. + * + * @category services + * @since 4.0.0 + */ +export class Config extends Context.Service< + Config, + Simplify< + & Partial< + Omit< + typeof Generated.BetaCreateMessageParams.Encoded, + "messages" | "output_config" | "tools" | "tool_choice" | "stream" + > + > + & { + readonly output_config?: { + readonly effort?: "low" | "medium" | "high" | null + } + /** + * 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. + * + * **Details** + * + * Only applies to models that support structured outputs. Defaults to + * `true` when structured outputs are supported. + */ + readonly strictJsonSchema?: boolean | undefined + } + > +>()("@effect/ai-anthropic/AnthropicLanguageModel/Config") {} + +// ============================================================================= +// Provider Options / Metadata +// ============================================================================= + +declare module "effect/unstable/ai/Prompt" { + /** + * Anthropic-specific options for system messages. + * + * **Details** + * + * These options are used when translating system messages into Anthropic + * request content. + * + * @category models + * @since 4.0.0 + */ + export interface SystemMessageOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for user messages. + * + * **Details** + * + * These options are used when translating user messages into Anthropic + * request content. + * + * @category models + * @since 4.0.0 + */ + export interface UserMessageOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for assistant messages. + * + * **Details** + * + * These options are used when replaying assistant messages in Anthropic + * conversation history. + * + * @category models + * @since 4.0.0 + */ + export interface AssistantMessageOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for tool messages. + * + * **Details** + * + * These options are used when converting tool results into Anthropic user + * content blocks. + * + * @category models + * @since 4.0.0 + */ + export interface ToolMessageOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for text prompt parts. + * + * **When to use** + * + * Use when you use these options to control how text blocks are sent to Anthropic. + * + * @category models + * @since 4.0.0 + */ + export interface TextPartOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for reasoning prompt parts. + * + * **Details** + * + * Preserves Claude thinking metadata when reasoning content is sent back to + * Anthropic in later turns. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartOptions extends ProviderOptions { + readonly anthropic?: { + readonly info?: { + readonly type: "thinking" + /** + * Thinking content as an encrypted string, which is used to verify + * that thinking content was indeed generated by Anthropic's API. + */ + readonly signature: typeof Generated.ResponseThinkingBlock.fields.thinking.Encoded + } | { + readonly type: "redacted_thinking" + /** + * Thinking content which was flagged by Anthropic's safety systems, and + * was therefore encrypted. + */ + readonly redactedData: typeof Generated.ResponseRedactedThinkingBlock.fields.data.Encoded + } | null + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for file prompt parts. + * + * **Details** + * + * Controls document metadata, citations, and prompt caching for files sent to + * Anthropic. + * + * @category models + * @since 4.0.0 + */ + export interface FilePartOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + /** + * Whether or not citations should be enabled for the file part. + */ + readonly citations?: typeof Generated.RequestCitationsConfig.Encoded | null + /** + * A custom title to provide to the document. If omitted, the file part's + * `fileName` property will be used. + */ + readonly documentTitle?: string | null + /** + * Additional context about the document that will be forwarded to the + * large language model, but will not be used towards cited content. + * + * **When to use** + * + * Use when storing additional document metadata as text or stringified JSON. + */ + readonly documentContext?: string | null + } | null + } + + /** + * Anthropic-specific options for tool call prompt parts. + * + * **Details** + * + * Carries Anthropic tool caller metadata, MCP metadata, and cache control for + * tool use blocks. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartOptions extends ProviderOptions { + readonly anthropic?: { + readonly caller?: { + readonly type: string + readonly toolId?: string | null + } | null + /** + * Contains details about the MCP tool that was called. + */ + 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. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for tool result prompt parts. + * + * **Details** + * + * Carries Anthropic MCP metadata and controls prompt caching for tool result + * content. + * + * @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. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for tool approval request prompt parts. + * + * **Details** + * + * Controls prompt caching for human approval requests in conversations. + * + * @category models + * @since 4.0.0 + */ + export interface ToolApprovalRequestPartOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } + + /** + * Anthropic-specific options for tool approval response prompt parts. + * + * **Details** + * + * Controls prompt caching for human approval responses in conversations. + * + * @category models + * @since 4.0.0 + */ + export interface ToolApprovalResponsePartOptions extends ProviderOptions { + readonly anthropic?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | null + } | null + } +} + +declare module "effect/unstable/ai/Response" { + /** + * Anthropic metadata attached when a reasoning block begins. + * + * **Details** + * + * Includes Claude thinking metadata needed to continue reasoning-aware + * conversations. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningStartPartMetadata extends ProviderMetadata { + readonly anthropic?: { + readonly info?: { + readonly type: "thinking" + /** + * Thinking content as an encrypted string, which is used to verify + * that thinking content was indeed generated by Anthropic's API. + */ + readonly signature: typeof Generated.ResponseThinkingBlock.fields.thinking.Encoded + } | { + readonly type: "redacted_thinking" + /** + * Thinking content which was flagged by Anthropic's safety systems, and + * was therefore encrypted. + */ + readonly redactedData: typeof Generated.ResponseRedactedThinkingBlock.fields.data.Encoded + } | null + } | null + } + + /** + * Anthropic metadata attached to streaming reasoning deltas. + * + * **Details** + * + * Includes the signature for streamed Claude thinking content when available. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningDeltaPartMetadata extends ProviderMetadata { + readonly anthropic?: { + readonly info?: { + readonly type: "thinking" + /** + * Thinking content as an encrypted string, which is used to verify + * that thinking content was indeed generated by Anthropic's API. + */ + readonly signature: typeof Generated.ResponseThinkingBlock.fields.thinking.Encoded + } | null + } | null + } + + /** + * Anthropic metadata attached to completed reasoning parts. + * + * **Details** + * + * Preserves Claude thinking or redacted thinking information for later turns. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartMetadata extends ProviderMetadata { + readonly anthropic?: { + readonly info?: { + readonly type: "thinking" + /** + * Thinking content as an encrypted string, which is used to verify + * that thinking content was indeed generated by Anthropic's API. + */ + readonly signature: typeof Generated.ResponseThinkingBlock.fields.thinking.Encoded + } | { + readonly type: "redacted_thinking" + /** + * Thinking content which was flagged by Anthropic's safety systems, and + * was therefore encrypted. + */ + readonly redactedData: typeof Generated.ResponseRedactedThinkingBlock.fields.data.Encoded + } | null + } | null + } + + /** + * Anthropic metadata attached to tool call response parts. + * + * **Details** + * + * Identifies Anthropic caller details and MCP tool metadata emitted by the + * provider. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartMetadata extends ProviderMetadata { + readonly anthropic?: { + readonly caller?: { + readonly type: string + readonly toolId?: string | null + } | null + /** + * Contains details about the MCP tool that was called. + */ + readonly mcp_tool?: { + /** + * The name of the MCP server + */ + readonly server: string + } | null + } | null + } + + /** + * Anthropic metadata attached to tool result response parts. + * + * **Details** + * + * Identifies MCP tool metadata associated with provider-executed tool + * results. + * + * @category models + * @since 4.0.0 + */ + export interface ToolResultPartMetadata extends ProviderMetadata { + readonly anthropic?: { + /** + * Contains details about the MCP tool that was called. + */ + readonly mcp_tool?: { + /** + * The name of the MCP server + */ + readonly server: string + } | null + } | null + } + + /** + * Anthropic metadata for document citations in model responses. + * + * **Details** + * + * Records the cited document span by character position or page number. + * + * @category models + * @since 4.0.0 + */ + export interface DocumentSourcePartMetadata extends ProviderMetadata { + readonly anthropic?: { + readonly source: "document" + readonly type: "char_location" + /** + * The text that was cited in the response. + */ + readonly citedText: string + /** + * The 0-indexed starting position of the characters that were cited. + */ + readonly startCharIndex: number + /** + * The exclusive ending position of the characters that were cited. + */ + readonly endCharIndex: number + } | { + readonly source: "document" + readonly type: "page_location" + /** + * The text that was cited in the response. + */ + readonly citedText: string + /** + * The 1-indexed starting page of pages that were cited. + */ + readonly startPageNumber: number + /** + * The exclusive ending position of the pages that were cited. + */ + readonly endPageNumber: number + } | null + } + + /** + * Anthropic metadata for URL and web citations in model responses. + * + * **Details** + * + * Records cited URL text or web-search source freshness information. + * + * @category models + * @since 4.0.0 + */ + export interface UrlSourcePartMetadata extends ProviderMetadata { + readonly anthropic?: { + readonly source: "url" + /** + * Up to 150 characters of the text content that was referenced from the + * URL source material. + */ + readonly citedText: string + /** + * An internal reference that must be passed back to the Anthropic API + * during multi-turn conversations. + */ + readonly encryptedIndex: string + } | { + readonly source: "web" + readonly pageAge: string | null + } | null + } + + /** + * Anthropic metadata attached to the finish part of a response. + * + * **Details** + * + * Includes container state, context management information, stop details, and + * token usage reported by Anthropic. + * + * @category models + * @since 4.0.0 + */ + export interface FinishPartMetadata extends ProviderMetadata { + readonly anthropic?: { + readonly container: typeof Generated.BetaContainer.Encoded | null + readonly contextManagement: typeof Generated.BetaResponseContextManagement.Encoded | null + readonly stopSequence: string | null + readonly usage: typeof Generated.BetaMessage.Encoded["usage"] | null + } | null + } + + /** + * Anthropic metadata attached to error response parts. + * + * **Details** + * + * Includes the provider request identifier when Anthropic returns one. + * + * @category models + * @since 4.0.0 + */ + export interface ErrorPartMetadata extends ProviderMetadata { + readonly anthropic?: { + requestId?: string | null + } | null + } +} + +// ============================================================================= +// Language Model +// ============================================================================= + +/** + * Creates an Anthropic model descriptor that can be provided with `Effect.provide`. + * + * **When to use** + * + * Use when you want an Anthropic Claude model value that carries provider and + * model metadata and can be supplied directly to an Effect program. + * + * @see {@link layer} for creating a `LanguageModel.LanguageModel` layer directly + * @see {@link make} for constructing the language model service effectfully + * + * @category constructors + * @since 4.0.0 + */ +export const model = ( + model: (string & {}) | Model, + config?: Omit +): AiModel.Model<"anthropic", LanguageModel.LanguageModel, AnthropicClient> => + AiModel.make("anthropic", model, layer({ model, config })) + +/** + * Creates an Anthropic `LanguageModel` service from a model identifier and optional request defaults. + * + * **When to use** + * + * Use when you need to construct a `LanguageModel.Service` value backed by + * `AnthropicClient` inside an Effect. + * + * **Details** + * + * The returned effect requires `AnthropicClient`. Request defaults from the + * `config` option are merged with any `Config` service in the context, with + * context values taking precedence. + * + * @see {@link layer} for providing the service as a `Layer` + * @see {@link model} for creating a model descriptor for `AiModel.provide` + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { + readonly model: (string & {}) | Model + readonly config?: Omit | undefined +}): Effect.fn.Return { + const client = yield* AnthropicClient + + const makeConfig: Effect.Effect = Effect.contextWith((services) => + Effect.succeed({ + model, + ...providerConfig, + ...Context.getOrUndefined(services, Config) + }) + ) + + const makeRequest = Effect.fnUntraced( + function*>({ config, options, toolNameMapper }: { + readonly config: typeof Config.Service & { readonly model: string } + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return<{ + readonly params: typeof Generated.BetaMessagesPostParams.Encoded + readonly payload: typeof Generated.BetaCreateMessageParams.Encoded + }, AiError.AiError> { + const betas = new Set() + 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 }) + const params: Mutable = {} + if (betas.size > 0) { + params["anthropic-beta"] = Array.from(betas).join(",") + } + const { disableParallelToolCalls: _, output_config, structuredOutputs: _structuredOutputs, ...requestConfig } = + config + const payload: Mutable = { + ...requestConfig, + max_tokens: requestConfig.max_tokens ?? capabilities.maxOutputTokens, + messages, + ...(Predicate.isNotUndefined(system) ? { system } : undefined), + ...(Predicate.isNotUndefined(tools) ? { tools } : undefined), + ...(Predicate.isNotUndefined(toolChoice) ? { tool_choice: toolChoice } : undefined) + } + const outputConfig: Mutable = {} + if (Predicate.isNotUndefined(outputFormat)) { + outputConfig.format = outputFormat + } + if (Predicate.isNotUndefined(output_config?.effort)) { + outputConfig.effort = output_config.effort + } + if (Object.keys(outputConfig).length > 0) { + payload.output_config = outputConfig + } + return { params, payload } + } + ) + + return yield* LanguageModel.make({ + codecTransformer: toCodecAnthropic, + generateText: Effect.fnUntraced(function*(options) { + const config = yield* makeConfig + const toolNameMapper = new Tool.NameMapper(options.tools) + const request = yield* makeRequest({ config, options, toolNameMapper }) + annotateRequest(options.span, request.payload) + const [rawResponse, response] = yield* client.createMessage(request) + annotateResponse(options.span, rawResponse) + return yield* makeResponse({ options, rawResponse, response, toolNameMapper }) + }), + streamText: Effect.fnUntraced(function*(options) { + const config = yield* makeConfig + const toolNameMapper = new Tool.NameMapper(options.tools) + const request = yield* makeRequest({ config, options, toolNameMapper }) + annotateRequest(options.span, request.payload) + const [response, stream] = yield* client.createMessageStream(request) + return yield* makeStreamResponse({ stream, response, options, toolNameMapper }) + }, (effect, options) => + effect.pipe( + Stream.unwrap, + Stream.map((response) => { + annotateStreamResponse(options.span, response) + return response + }) + )) + }) +}) + +/** + * Creates a layer for the Anthropic language model. + * + * **When to use** + * + * Use when composing application layers and you want Anthropic to satisfy + * `LanguageModel.LanguageModel` while supplying `AnthropicClient` from another + * layer. + * + * @see {@link make} for constructing the language model service effectfully + * @see {@link model} for creating a model service directly + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: { + readonly model: (string & {}) | Model + readonly config?: Omit | undefined +}): Layer.Layer => + Layer.effect(LanguageModel.LanguageModel, make(options)) + +/** + * Provides config overrides for Anthropic language model operations. + * + * **When to use** + * + * Use to apply Anthropic request configuration to one effect without changing + * the model's default configuration. + * + * **Details** + * + * The overrides are merged with any existing `Config` service for the duration + * of the supplied effect. Fields in `overrides` take precedence over existing + * config, and the helper supports both `effect.pipe(withConfigOverride(overrides))` + * and `withConfigOverride(effect, overrides)`. + * + * @see {@link Config} for available Anthropic request configuration fields + * + * @category configuration + * @since 4.0.0 + */ +export const withConfigOverride: { + (overrides: typeof Config.Service): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, overrides: typeof Config.Service): Effect.Effect> +} = dual< + ( + overrides: typeof Config.Service + ) => (self: Effect.Effect) => Effect.Effect>, + (self: Effect.Effect, overrides: typeof Config.Service) => Effect.Effect> +>(2, (self, overrides) => + Effect.flatMap( + Effect.serviceOption(Config), + (config) => + Effect.provideService(self, Config, { + ...(config._tag === "Some" ? config.value : {}), + ...overrides + }) + )) + +// ============================================================================= +// Prompt Conversion +// ============================================================================= + +const prepareMessages = Effect.fnUntraced( + function*>({ betas, options, toolNameMapper }: { + readonly betas: Set + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return<{ + readonly system: ReadonlyArray | undefined + readonly messages: ReadonlyArray + }, AiError.AiError> { + const groups = groupMessages(options.prompt) + + let system: Array | undefined = undefined + const messages: Array = [] + + for (let i = 0; i < groups.length; i++) { + const group = groups[i] + const isLastGroup = i === groups.length - 1 + + switch (group.type) { + case "system": { + system = group.messages.map((message) => ({ + type: "text", + text: message.content, + cache_control: getCacheControl(message) + })) + break + } + + case "user": { + const content: Array = [] + + for (const message of group.messages) { + switch (message.role) { + case "user": { + for (let j = 0; j < message.content.length; j++) { + const part = message.content[j] + const isLastPart = j === message.content.length - 1 + + // Attempt to get the cache control from the part first. If + // the part does not have cache control defined and we are + // evaluating the last part for this message, also check the + // message for cache control. + const cacheControl = getCacheControl(part) ?? ( + isLastPart ? getCacheControl(message) : null + ) + + switch (part.type) { + case "text": { + content.push({ + type: "text", + text: part.text, + cache_control: cacheControl + }) + break + } + + case "file": { + if (part.mediaType.startsWith("image/")) { + const mediaType: typeof Generated.Base64ImageSource.Type["media_type"] = + (part.mediaType === "image/*" ? "image/jpeg" : part.mediaType) as any + + 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 + + content.push({ type: "image", source, cache_control: cacheControl }) + } else if (part.mediaType === "application/pdf" || part.mediaType === "text/plain") { + betas.add("pdfs-2024-09-25") + + const enableCitations = areCitationsEnabled(part) + const documentOptions = getDocumentMetadata(part) + + const source = isUrlData(part.data) + ? { + type: "url", + url: getUrlString(part.data) + } as const + : part.mediaType === "application/pdf" + ? { + type: "base64", + media_type: "application/pdf", + data: typeof part.data === "string" ? part.data : Encoding.encodeBase64(part.data) + } as const + : { + type: "text", + media_type: "text/plain", + data: typeof part.data === "string" ? part.data : new TextDecoder().decode(part.data) + } as const + + content.push({ + type: "document", + source, + title: documentOptions?.title ?? part.fileName ?? null, + cache_control: cacheControl, + ...(documentOptions?.context ? { context: documentOptions.context } : undefined), + ...(enableCitations ? { citations: { enabled: true } } : undefined) + }) + } else { + return yield* new AiError.AiError({ + module: "AnthropicLanguageModel", + method: "prepareMessages", + reason: new AiError.InvalidUserInputError({ + description: `Detected unsupported media type for file: '${part.mediaType}'` + }) + }) + } + + break + } + } + } + break + } + + case "tool": { + for (let j = 0; j < message.content.length; j++) { + const part = message.content[j] + + // Skip evaluation of tool approval parts + if (part.type === "tool-approval-response") { + continue + } + + const isLastPart = j === message.content.length - 1 + + // Attempt to get the cache control from the part first. If + // the part does not have cache control defined and we are + // evaluating the last part for this message, also check the + // message for cache control. + const cacheControl = getCacheControl(part) ?? ( + isLastPart ? getCacheControl(message) : null + ) + + content.push({ + type: "tool_result", + tool_use_id: part.id, + content: JSON.stringify(part.result), + is_error: part.isFailure, + cache_control: cacheControl + }) + } + } + } + } + + messages.push({ role: "user", content }) + + break + } + + case "assistant": { + const content: Array = [] + const mcpToolIds = new Set() + + for (let j = 0; j < group.messages.length; j++) { + const message = group.messages[j] + const isLastMessage = j === group.messages.length - 1 + + for (let k = 0; k < message.content.length; k++) { + const part = message.content[k] + + if (part.type === "file" || part.type === "tool-approval-request") { + continue + } + + const isLastPart = k === message.content.length - 1 + + // Attempt to get the cache control from the part first. If + // the part does not have cache control defined and we are + // evaluating the last part for this message, also check the + // message for cache control. + const cacheControl = getCacheControl(part) ?? ( + isLastPart ? getCacheControl(message) : undefined + ) + + // TODO: use cache_control in content blocks + void cacheControl + + switch (part.type) { + case "text": { + content.push({ + type: "text", + // Anthropic does not allow trailing whitespace in assistant + // content blocks + text: isLastGroup && isLastMessage && isLastPart + ? part.text.trim() + : part.text + }) + break + } + case "reasoning": { + // TODO: make sending reasoning configurable + const info = part.options.anthropic?.info + if (Predicate.isNotNullish(info)) { + if (info.type === "thinking") { + content.push({ + type: "thinking", + thinking: part.text, + signature: info.signature + }) + } else { + content.push({ + type: "redacted_thinking", + data: info.redactedData + }) + } + } + break + } + + case "tool-call": { + if (part.providerExecuted) { + const toolName = toolNameMapper.getProviderName(part.name) + + const isMcpTool = Predicate.isNotNullish(part.options.anthropic?.mcp_tool) + + if (isMcpTool) { + const { server } = part.options.anthropic.mcp_tool + + mcpToolIds.add(part.id) + + content.push({ + type: "mcp_tool_use", + id: part.id, + name: part.name, + input: part.params as any, + server_name: server + }) + } else if ( + toolName === "code_execution" && + Predicate.hasProperty(part.params, "type") && + ( + part.params.type === "bash_code_execution" || + part.params.type === "text_editor_code_execution" + ) + ) { + content.push({ + type: "server_tool_use", + id: part.id, + name: part.params.type, + input: part.params as any + }) + } else if ( + // code execution 20250825 programmatic tool calling: + // Strip the fake 'programmatic-tool-call' type before sending to Anthropic + toolName === "code_execution" && + Predicate.hasProperty(part.params, "type") && + part.params.type === "programmatic-tool-call" + ) { + const { type, ...params } = part.params + content.push({ + type: "server_tool_use", + id: part.id, + name: toolName, + input: params as any + }) + } else { + if ( + // code execution 20250522 + toolName === "code_execution" || + toolName === "tool_search_tool_regex" || + toolName === "tool_search_tool_bm25" || + toolName === "web_fetch" || + toolName === "web_search" + ) { + content.push({ + type: "server_tool_use", + id: part.id, + name: toolName, + input: part.params as any + }) + } + } + } else { + // Extract caller info from provider options for programmatic tool calling + const options = part.options.anthropic + const caller = Predicate.isNotNullish(options?.caller) + ? ( + options.caller.type === "code_execution_20250825" && + Predicate.isNotNullish(options.caller.toolId) + ) + ? { + type: "code_execution_20250825", + tool_id: options.caller.toolId + } as const + : options.caller.type === "direct" + ? { + type: "direct" + } as const + : undefined + : undefined + + content.push({ + type: "tool_use", + id: part.id, + name: part.name, + input: part.params as any, + ...(Predicate.isNotUndefined(caller) ? { caller } : undefined) + }) + } + + break + } + + case "tool-result": { + const toolName = toolNameMapper.getProviderName(part.name) + + if (mcpToolIds.has(part.id)) { + content.push({ + type: "mcp_tool_result", + tool_use_id: part.id, + is_error: part.isFailure, + content: part.result as any + }) + break + } + + if (toolName === "code_execution" && Predicate.hasProperty(part.result, "type")) { + if (part.result.type === "code_execution_result") { + content.push({ + type: "code_execution_tool_result", + tool_use_id: part.id, + content: part.result as any + }) + } else if ( + part.result.type === "bash_code_execution_result" || + part.result.type === "bash_code_execution_tool_result_error" + ) { + content.push({ + type: "bash_code_execution_tool_result", + tool_use_id: part.id, + content: part.result as any + }) + } else if ( + part.result.type === "text_editor_code_execution_tool_result" || + part.result.type === "text_editor_code_execution_tool_result_error" + ) { + content.push({ + type: "text_editor_code_execution_tool_result", + tool_use_id: part.id, + content: part.result as any + }) + } + break + } + + if (toolName === "web_fetch") { + content.push({ + type: "web_fetch_tool_result", + tool_use_id: part.id, + content: part.result as any + }) + break + } + + if (toolName === "web_search") { + content.push({ + type: "web_search_tool_result", + tool_use_id: part.id, + content: part.result as any + }) + break + } + + if ( + toolName === "tool_search_tool_regex" || + toolName === "tool_search_tool_bm25" + ) { + content.push({ + type: "tool_search_tool_result", + tool_use_id: part.id, + content: part.result as any + }) + break + } + + break + } + } + } + } + + messages.push({ role: "assistant", content }) + + break + } + } + } + + return { + system, + messages + } + } +) + +// ============================================================================= +// Tool Conversion +// ============================================================================= + +/** + * Encoded Anthropic custom tool definition that can be sent in a Messages API request. + * + * **When to use** + * + * Use when you need to type or inspect the provider-specific request payload for + * a custom Anthropic tool. + * + * **Details** + * + * This type aliases the encoded `Generated.BetaTool` schema used for Effect + * user-defined and dynamic tools after conversion. It contains the tool `name`, + * optional `description`, and `input_schema`, plus Anthropic-specific fields + * such as `strict` and `cache_control`. + * + * @see {@link AnthropicProviderDefinedTool} for the request shape used by Anthropic built-in provider tools + * + * @category tools + * @since 4.0.0 + */ +export type AnthropicUserDefinedTool = typeof Generated.BetaTool.Encoded + +/** + * Represents a provider-defined tool that can be passed to the Anthropic API. + * + * **Details** + * + * These include Anthropic's built-in tools like computer use, code execution, + * web search, and text editing. + * + * @category tools + * @since 4.0.0 + */ +export type AnthropicProviderDefinedTool = + | typeof Generated.BetaBashTool_20241022.Encoded + | typeof Generated.BetaBashTool_20250124.Encoded + | typeof Generated.BetaCodeExecutionTool_20250522.Encoded + | typeof Generated.BetaCodeExecutionTool_20250825.Encoded + | typeof Generated.BetaComputerUseTool_20241022.Encoded + | typeof Generated.BetaComputerUseTool_20250124.Encoded + | typeof Generated.BetaComputerUseTool_20251124.Encoded + | typeof Generated.BetaMemoryTool_20250818.Encoded + | typeof Generated.BetaTextEditor_20241022.Encoded + | typeof Generated.BetaTextEditor_20250124.Encoded + | typeof Generated.BetaTextEditor_20250429.Encoded + | typeof Generated.BetaTextEditor_20250728.Encoded + | typeof Generated.BetaToolSearchToolBM25_20251119.Encoded + | typeof Generated.BetaToolSearchToolRegex_20251119.Encoded + | typeof Generated.BetaWebFetchTool_20250910.Encoded + | typeof Generated.BetaWebSearchTool_20250305.Encoded + +const prepareTools = Effect.fnUntraced( + function*({ betas, capabilities, config, options }: { + readonly betas: Set + readonly capabilities: ModelCapabilities + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions + }): Effect.fn.Return<{ + 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") + const userDescription = SchemaAST.resolveDescription(options.responseFormat.schema.ast) + const description = Predicate.isNotUndefined(userDescription) ? `${userDescription} - ` : "" + return { + tools: [{ + name: options.responseFormat.objectName, + description: `${description}You MUST respond with a JSON object.`, + input_schema: input_schema as any + }], + toolChoice: { + type: "tool", + name: options.responseFormat.objectName, + disable_parallel_tool_use: true + } + } + } + + const userTools: Array = [] + const providerTools: Array = [] + + for (const tool of options.tools) { + if (Tool.isUserDefined(tool) || Tool.isDynamic(tool)) { + const description = Tool.getDescription(tool) + const input_schema = yield* tryToolJsonSchema(tool, "prepareTools") + const toolStrict = Tool.getStrictMode(tool) + const strict = capabilities.supportsStructuredOutput + ? (toolStrict ?? config.strictJsonSchema ?? true) + : undefined + + userTools.push({ + name: tool.name, + input_schema: input_schema as any, + ...(Predicate.isNotUndefined(description) ? { description } : undefined), + ...(Predicate.isNotUndefined(strict) ? { strict } : undefined) + }) + + if (capabilities.supportsStructuredOutput === true) { + betas.add("structured-outputs-2025-11-13") + } + } + + if (Tool.isProviderDefined(tool)) { + const providerTool = tool as AnthropicTool + switch (providerTool.id) { + case "anthropic.bash_20241022": { + betas.add("computer-use-2024-10-22") + providerTools.push({ name: "bash", type: "bash_20241022" }) + break + } + + case "anthropic.bash_20250124": { + betas.add("computer-use-2025-01-24") + providerTools.push({ name: "bash", type: "bash_20250124" }) + break + } + + case "anthropic.code_execution_20250522": { + betas.add("code-execution-2025-05-22") + providerTools.push({ name: "code_execution", type: "code_execution_20250522" }) + break + } + + case "anthropic.code_execution_20250825": { + betas.add("code-execution-2025-08-25") + providerTools.push({ name: "code_execution", type: "code_execution_20250825" }) + break + } + + case "anthropic.computer_use_20241022": { + betas.add("computer-use-2024-10-22") + providerTools.push({ + name: "computer", + type: "computer_20241022", + display_height_px: providerTool.args.displayHeightPx, + display_width_px: providerTool.args.displayWidthPx, + display_number: providerTool.args.displayNumber ?? null + }) + break + } + + case "anthropic.computer_20250124": { + betas.add("computer-use-2025-01-24") + providerTools.push({ + name: "computer", + type: "computer_20250124", + display_height_px: providerTool.args.displayHeightPx, + display_width_px: providerTool.args.displayWidthPx, + display_number: providerTool.args.displayNumber ?? null + }) + break + } + + case "anthropic.computer_20251124": { + betas.add("computer-use-2025-11-24") + providerTools.push({ + name: "computer", + type: "computer_20251124", + display_height_px: providerTool.args.displayHeightPx, + display_width_px: providerTool.args.displayWidthPx, + display_number: providerTool.args.displayNumber ?? null, + enable_zoom: providerTool.args.enableZoom ?? false + }) + break + } + + case "anthropic.memory_20250818": { + betas.add("context-management-2025-06-27") + providerTools.push({ name: "memory", type: "memory_20250818" }) + break + } + + case "anthropic.text_editor_20241022": { + betas.add("computer-use-2024-10-22") + providerTools.push({ name: "str_replace_editor", type: "text_editor_20241022" }) + break + } + + case "anthropic.text_editor_20250124": { + betas.add("computer-use-2025-01-24") + providerTools.push({ name: "str_replace_editor", type: "text_editor_20250124" }) + break + } + + case "anthropic.text_editor_20250429": { + betas.add("computer-use-2025-01-24") + providerTools.push({ name: "str_replace_based_edit_tool", type: "text_editor_20250429" }) + break + } + + case "anthropic.text_editor_20250728": { + providerTools.push({ + name: "str_replace_based_edit_tool", + type: "text_editor_20250728", + max_characters: providerTool.args.max_characters ?? null + }) + break + } + + case "anthropic.tool_search_tool_bm25_20251119": { + betas.add("advanced-tool-use-2025-11-20") + providerTools.push({ name: "tool_search_tool_bm25", type: "tool_search_tool_bm25_20251119" }) + break + } + + case "anthropic.tool_search_tool_regex_20251119": { + providerTools.push({ name: "tool_search_tool_regex", type: "tool_search_tool_regex_20251119" }) + break + } + + case "anthropic.web_search_20250305": { + providerTools.push({ + name: "web_search", + type: "web_search_20250305", + max_uses: providerTool.args.maxUses ?? null, + allowed_domains: providerTool.args.allowedDomains ?? null, + blocked_domains: providerTool.args.blockedDomains ?? null, + user_location: Predicate.isNotUndefined(providerTool.args.userLocation) + ? { + type: providerTool.args.userLocation.type, + region: providerTool.args.userLocation.region ?? null, + city: providerTool.args.userLocation.city ?? null, + country: providerTool.args.userLocation.country ?? null, + timezone: providerTool.args.userLocation.timezone ?? null + } + : null + }) + break + } + + case "anthropic.web_fetch_20250910": { + betas.add("web-fetch-2025-09-10") + providerTools.push({ + name: "web_fetch", + type: "web_fetch_20250910", + max_uses: providerTool.args.maxUses ?? null, + allowed_domains: providerTool.args.allowedDomains ?? null, + blocked_domains: providerTool.args.blockedDomains ?? null, + citations: providerTool.args.citations ?? null, + max_content_tokens: providerTool.args.maxContentTokens ?? null + }) + break + } + + default: { + return yield* AiError.make({ + module: "AnthropicLanguageModel", + method: "prepareTools", + reason: new AiError.InvalidUserInputError({ + description: `Received request to call unknown provider-defined tool '${tool.name}'` + }) + }) + } + } + } + } + + let tools = [...userTools, ...providerTools] + let toolChoice: Mutable | undefined = undefined + + if (options.toolChoice === "auto") { + toolChoice = { type: "auto" } + } else if (options.toolChoice === "required") { + toolChoice = { type: "any" } + } else if ("tool" in options.toolChoice) { + toolChoice = { type: "tool", name: options.toolChoice.tool } + } else { + const allowedTools = new Set(options.toolChoice.oneOf) + tools = tools.filter((tool) => allowedTools.has(tool.name)) + toolChoice = { type: options.toolChoice.mode === "required" ? "any" : "auto" } + } + + if ( + Predicate.isNotUndefined(config.disableParallelToolCalls) && + Predicate.isNotUndefined(toolChoice) && + toolChoice.type !== "none" + ) { + toolChoice.disable_parallel_tool_use = config.disableParallelToolCalls + } + + return { + tools, + toolChoice + } + } +) + +// ============================================================================= +// HTTP Details +// ============================================================================= + +const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +const buildHttpResponseDetails = ( + response: HttpClientResponse.HttpClientResponse +): typeof Response.HttpResponseDetails.Type => ({ + status: response.status, + headers: Redactable.redact(response.headers) as Record +}) + +// ============================================================================= +// Response Conversion +// ============================================================================= + +const makeResponse = Effect.fnUntraced( + function*>({ + options, + rawResponse, + response, + toolNameMapper + }: { + readonly options: LanguageModel.ProviderOptions + readonly rawResponse: Generated.BetaMessage + readonly response: HttpClientResponse.HttpClientResponse + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return< + Array, + AiError.AiError, + IdGenerator.IdGenerator + > { + const parts: Array = [] + const mcpToolCalls: Map = new Map() + const serverToolCalls: Map = new Map() + const citableDocuments = extractCitableDocuments(options.prompt) + + parts.push({ + type: "response-metadata", + id: rawResponse.id, + modelId: rawResponse.model, + timestamp: DateTime.formatIso(yield* DateTime.now), + request: buildHttpRequestDetails(response.request) + }) + + 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. + parts.push({ + type: "text", + text: part.text + }) + + if (Predicate.isNotNullish(part.citations)) { + for (const citation of part.citations) { + const source = yield* processCitation(citation, citableDocuments) + if (Predicate.isNotUndefined(source)) { + parts.push(source) + } + } + } + + break + } + + case "thinking": { + const metadata = { + info: { type: "thinking", signature: part.signature } + } as const + + parts.push({ + type: "reasoning", + text: part.thinking, + metadata: { anthropic: metadata } + }) + break + } + + case "redacted_thinking": { + const metadata = { + info: { type: "redacted_thinking", redactedData: part.data } + } as const + + parts.push({ + type: "reasoning", + text: "", + metadata: { anthropic: metadata } + }) + break + } + + 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") { + parts.push({ + type: "text", + text: JSON.stringify(part.input) + }) + } else { + // Extract caller info if present + const caller = (part as any).caller + const callerInfo = Predicate.isNotNullish(caller) + ? { + type: caller.type, + toolId: "tool_id" in caller ? caller.tool_id : null + } + : 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) + const params = yield* transformToolCallParams(options.tools, toolName, part.input) + + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params, + ...(Predicate.isNotUndefined(callerInfo) + ? { metadata: { anthropic: { caller: callerInfo } } } + : undefined) + }) + } + + break + } + + case "server_tool_use": { + const toolName = toolNameMapper.getCustomName(part.name) + + if ( + part.name === "bash_code_execution" || + part.name === "text_editor_code_execution" + ) { + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params: { type: part.name, ...part.input }, + providerExecuted: true + }) + } else if ( + part.name === "code_execution" || + part.name === "web_fetch" || + part.name === "web_search" + ) { + const toolParams: Record = { ...part.input } + + // Inject `type: "programmatic-tool-call"` when the input parameters + // has the format `{ code: ... }` + if ( + part.name === "code_execution" && + Predicate.hasProperty(part.input, "code") && + !Predicate.hasProperty(part.input, "type") + ) { + toolParams.type = "programmatic-tool-call" + } + + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params: toolParams, + providerExecuted: true + }) + } else if ( + part.name === "tool_search_tool_bm25" || + part.name === "tool_search_tool_regex" + ) { + serverToolCalls.set(part.id, part.name) + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params: part.input, + providerExecuted: true + }) + } + + break + } + + case "mcp_tool_use": { + const toolCall: Response.ToolCallPartEncoded = { + type: "tool-call", + id: part.id, + name: part.name, + params: part.input, + providerExecuted: true, + metadata: { anthropic: { mcp_tool: { server: part.server_name } } } + } + + mcpToolCalls.set(part.id, toolCall) + + parts.push(toolCall) + + break + } + + case "mcp_tool_result": { + const toolCall = mcpToolCalls.get(part.tool_use_id) + const mcpMetadata = toolCall?.metadata?.anthropic?.mcp_tool + + if (Predicate.isNotUndefined(toolCall)) { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolCall.name, + isFailure: part.is_error, + result: part.content, + metadata: { + anthropic: { + ...(Predicate.isNotNullish(mcpMetadata) + ? { mcp_tool: mcpMetadata } : + undefined) + } + } + }) + } + + break + } + + // Code Execution 20250522 + case "code_execution_tool_result": { + const toolName = toolNameMapper.getCustomName("code_execution") + + if (part.content.type === "code_execution_result") { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } + + break + } + + // Code Execution 20250825 + case "bash_code_execution_tool_result": + case "text_editor_code_execution_tool_result": { + const toolName = toolNameMapper.getCustomName("code_execution") + + if ( + part.content.type === "bash_code_execution_tool_result_error" || + part.content.type === "text_editor_code_execution_tool_result_error" + ) { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + } + + break + } + + case "tool_search_tool_result": { + let providerName = serverToolCalls.get(part.tool_use_id) + + if (Predicate.isUndefined(providerName)) { + const bm25Name = toolNameMapper.getCustomName("tool_search_tool_bm25") + const regexName = toolNameMapper.getCustomName("tool_search_tool_regex") + + if (bm25Name !== "tool_search_tool_bm25") { + providerName = "tool_search_tool_bm25" + } else if (regexName !== "tool_search_tool_regex") { + providerName = "tool_search_tool_regex" + } + } + + const toolName = toolNameMapper.getCustomName(providerName!) + + if (part.content.type === "tool_search_tool_search_result") { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content.tool_references + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } + + break + } + + case "web_fetch_tool_result": { + const toolName = toolNameMapper.getCustomName("web_fetch") + + if (part.content.type === "web_fetch_result") { + citableDocuments.push({ + title: part.content.content.title ?? part.content.url, + mediaType: part.content.content.source.media_type + }) + + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } + + break + } + + case "web_search_tool_result": { + const toolName = toolNameMapper.getCustomName("web_search") + + if (Predicate.hasProperty(part.content, "type")) { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } else { + const idGenerator = yield* IdGenerator.IdGenerator + + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + + const content = part.content as ReadonlyArray + + for (const result of content) { + const id = yield* idGenerator.generateId() + + parts.push({ + type: "source", + sourceType: "url", + id, + url: result.url, + title: result.title, + metadata: { anthropic: { source: "web", pageAge: result.page_age } } + }) + } + } + + break + } + } + } + + // Anthropic always returns a non-null `stop_reason` for non-streaming responses + const finishReason = InternalUtilities.resolveFinishReason( + rawResponse.stop_reason!, + options.responseFormat.type === "json" + ) + + const inputTokens = rawResponse.usage.input_tokens + const outputTokens = rawResponse.usage.output_tokens + const cacheWriteTokens = rawResponse.usage.cache_creation_input_tokens ?? 0 + const cacheReadTokens = rawResponse.usage.cache_read_input_tokens ?? 0 + + parts.push({ + type: "finish", + reason: finishReason, + usage: { + inputTokens: { + uncached: inputTokens, + total: inputTokens + cacheWriteTokens + cacheReadTokens, + cacheRead: cacheReadTokens, + cacheWrite: cacheWriteTokens + }, + outputTokens: { + total: outputTokens, + text: undefined, + reasoning: undefined + } + }, + response: buildHttpResponseDetails(response), + metadata: { + anthropic: { + container: rawResponse.container ?? null, + contextManagement: rawResponse.context_management ?? null, + usage: rawResponse.usage, + stopSequence: rawResponse.stop_sequence + } + } + }) + + return parts + } +) + +const makeStreamResponse = Effect.fnUntraced( + function*>({ + stream, + response, + options, + toolNameMapper + }: { + readonly stream: Stream.Stream + readonly response: HttpClientResponse.HttpClientResponse + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return< + Stream.Stream, + AiError.AiError + > { + const citableDocuments = extractCitableDocuments(options.prompt) + + let container: typeof Generated.BetaContainer.Encoded | null = null + let contextManagement: typeof Generated.BetaResponseContextManagement.Encoded | null = null + let finishReason: Response.FinishReason = "unknown" + let stopSequence: string | null = null + let rawUsage: typeof Generated.BetaMessage.Encoded["usage"] | null = null + const mcpToolCalls: Map = new Map() + const serverToolCalls: Map = new Map() + const contentBlocks: Map< + number, + | { + readonly type: "text" + } + | { + readonly type: "reasoning" + } + | { + readonly type: "tool-call" + readonly id: string + readonly name: string + params: string + firstDelta: boolean + readonly providerName?: string | undefined + readonly providerExecuted?: boolean | undefined + readonly caller?: { type: string; toolId: string | null } | undefined + } + > = new Map() + const usage: Mutable<{ + inputTokens: number + outputTokens: number + cacheReadInputTokens: number + cacheWriteInputTokens: number + }> = { + inputTokens: 0, + outputTokens: 0, + cacheReadInputTokens: 0, + cacheWriteInputTokens: 0 + } + + let blockType: typeof Generated.BetaContentBlockStartEvent.Encoded["content_block"]["type"] | undefined = undefined + + return stream.pipe( + Stream.mapEffect(Effect.fnUntraced(function*(event) { + const parts: Array = [] + + switch (event.type) { + case "message_start": { + rawUsage = { ...event.message.usage } + usage.inputTokens = event.message.usage.input_tokens + usage.cacheReadInputTokens = event.message.usage.cache_read_input_tokens ?? 0 + usage.cacheWriteInputTokens = event.message.usage.cache_creation_input_tokens ?? 0 + + if (Predicate.isNotNullish(event.message.container)) { + container = event.message.container + } + + if (Predicate.isNotNull(event.message.stop_sequence)) { + stopSequence = event.message.stop_sequence + } + + if (Predicate.isNotNull(event.message.stop_reason)) { + finishReason = InternalUtilities.resolveFinishReason(event.message.stop_reason) + } + + parts.push({ + type: "response-metadata", + id: event.message.id, + modelId: event.message.model, + timestamp: DateTime.formatIso(yield* DateTime.now), + request: buildHttpRequestDetails(response.request) + }) + + // Process pre-populated content blocks + if (Predicate.isNotNullish(event.message.content) && event.message.content.length > 0) { + for (let i = 0; i < event.message.content.length; i++) { + const part = event.message.content[i] + + if (part.type === "tool_use") { + const callerInfo = Predicate.isNotUndefined(part.caller) + ? { + type: part.caller.type, + toolId: "tool_id" in part.caller ? part.caller.tool_id : null + } + : 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: toolName + }) + + parts.push({ + type: "tool-params-delta", + id: part.id, + delta: JSON.stringify(part.input ?? {}) + }) + + parts.push({ + type: "tool-params-end", + id: part.id + }) + + const params = yield* transformToolCallParams(options.tools, toolName, part.input) + + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params, + ...(Predicate.isNotUndefined(callerInfo) + ? { metadata: { anthropic: { caller: callerInfo } } } + : undefined) + }) + } + } + } + + break + } + + case "message_delta": { + rawUsage = { ...rawUsage, ...event.usage } as any + + if ( + Predicate.isNotNull(event.usage.input_tokens) && + usage.inputTokens !== event.usage.input_tokens + ) { + usage.inputTokens = event.usage.input_tokens + } + usage.outputTokens = event.usage.output_tokens + + if ( + Predicate.isNotNull(event.usage.cache_read_input_tokens) && + usage.cacheReadInputTokens !== event.usage.cache_read_input_tokens + ) { + usage.cacheReadInputTokens = event.usage.cache_read_input_tokens + } + if ( + Predicate.isNotNull(event.usage.cache_creation_input_tokens) && + usage.cacheWriteInputTokens !== event.usage.cache_creation_input_tokens + ) { + usage.cacheWriteInputTokens = event.usage.cache_creation_input_tokens + } + + if (Predicate.isNotNullish(event.delta.container)) { + container = event.delta.container + } + + if (Predicate.isNotNullish(event.context_management)) { + contextManagement = event.context_management + } + + if (Predicate.isNotNull(event.delta.stop_reason)) { + finishReason = InternalUtilities.resolveFinishReason(event.delta.stop_reason) + } + + if (Predicate.isNotNull(event.delta.stop_sequence)) { + stopSequence = event.delta.stop_sequence + } + + break + } + + case "message_stop": { + const metadata: Response.FinishPartMetadata = { + anthropic: { + container, + contextManagement, + stopSequence, + usage: rawUsage + } + } + + parts.push({ + type: "finish", + reason: finishReason, + usage: { + inputTokens: { + uncached: usage.inputTokens, + total: usage.inputTokens + usage.cacheWriteInputTokens + usage.cacheReadInputTokens, + cacheRead: usage.cacheReadInputTokens, + cacheWrite: usage.cacheWriteInputTokens + }, + outputTokens: { + total: usage.outputTokens, + text: undefined, + reasoning: undefined + } + }, + response: buildHttpResponseDetails(response), + metadata + }) + + break + } + + case "content_block_start": { + blockType = event.content_block.type + + switch (event.content_block.type) { + case "text": { + contentBlocks.set(event.index, { type: "text" }) + + parts.push({ + type: "text-start", + id: event.index.toString() + }) + + break + } + + case "thinking": { + contentBlocks.set(event.index, { type: "reasoning" }) + + parts.push({ + type: "reasoning-start", + id: event.index.toString() + }) + + break + } + + case "redacted_thinking": { + contentBlocks.set(event.index, { type: "reasoning" }) + + const metadata: Response.ReasoningStartPartMetadata = { + anthropic: { + info: { + type: "redacted_thinking", + redactedData: event.content_block.data + } + } + } + + parts.push({ + type: "reasoning-start", + id: event.index.toString(), + metadata + }) + + break + } + + case "tool_use": { + const part = event.content_block + + const caller = Predicate.isNotUndefined(part.caller) + ? { + type: part.caller.type, + toolId: "tool_id" in part.caller ? part.caller.tool_id : null + } + : undefined + + 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: toolName, + params: initialParams, + firstDelta: initialParams.length > 0, + ...(Predicate.isNotUndefined(caller) ? { caller } : undefined) + }) + + parts.push({ + type: "tool-params-start", + id: part.id, + name: toolName + }) + + break + } + + case "server_tool_use": { + const part = event.content_block + + if ( + part.name === "code_execution" || + part.name === "bash_code_execution" || + part.name === "text_editor_code_execution" || + part.name === "web_fetch" || + part.name === "web_search" + ) { + const toolName = toolNameMapper.getCustomName( + part.name === "bash_code_execution" || part.name === "text_editor_code_execution" + ? "code_execution" + : part.name + ) + + contentBlocks.set(event.index, { + type: "tool-call", + id: part.id, + name: toolName, + params: "", + firstDelta: true, + providerName: part.name, + providerExecuted: true + }) + + parts.push({ + type: "tool-params-start", + id: part.id, + name: toolName, + providerExecuted: true + }) + } else if ( + part.name === "tool_search_tool_bm25" || + part.name === "tool_search_tool_regex" + ) { + serverToolCalls.set(part.id, part.name) + + const toolName = toolNameMapper.getCustomName(part.name) + + contentBlocks.set(event.index, { + type: "tool-call", + id: part.id, + name: toolName, + params: "", + firstDelta: true, + providerName: part.name, + providerExecuted: true + }) + + parts.push({ + type: "tool-params-start", + id: part.id, + name: toolName, + providerExecuted: true + }) + } + + break + } + + case "web_fetch_tool_result": { + const part = event.content_block + const toolName = toolNameMapper.getCustomName("web_fetch") + + if (part.content.type === "web_fetch_result") { + citableDocuments.push({ + title: part.content.content.title ?? part.content.url, + mediaType: part.content.content.source.media_type + }) + + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } + + break + } + + case "web_search_tool_result": { + const part = event.content_block + const toolName = toolNameMapper.getCustomName("web_search") + + if (Predicate.hasProperty(part.content, "type")) { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } else { + const idGenerator = yield* IdGenerator.IdGenerator + + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + + const content = part.content as ReadonlyArray + + for (const result of content) { + const id = yield* idGenerator.generateId() + + parts.push({ + type: "source", + sourceType: "url", + id, + url: result.url, + title: result.title, + metadata: { anthropic: { source: "web", pageAge: result.page_age } } + }) + } + } + break + } + + case "code_execution_tool_result": { + const part = event.content_block + const toolName = toolNameMapper.getCustomName("code_execution") + + if (part.content.type === "code_execution_result") { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } + break + } + + case "bash_code_execution_tool_result": + case "text_editor_code_execution_tool_result": { + const part = event.content_block + const toolName = toolNameMapper.getCustomName("code_execution") + + if ( + part.content.type === "bash_code_execution_tool_result_error" || + part.content.type === "text_editor_code_execution_tool_result_error" + ) { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content + }) + } + break + } + + case "tool_search_tool_result": { + const part = event.content_block + let providerName = serverToolCalls.get(part.tool_use_id) + + if (Predicate.isUndefined(providerName)) { + const bm25Name = toolNameMapper.getCustomName("tool_search_tool_bm25") + const regexName = toolNameMapper.getCustomName("tool_search_tool_regex") + + if (bm25Name !== "tool_search_tool_bm25") { + providerName = "tool_search_tool_bm25" + } else if (regexName !== "tool_search_tool_regex") { + providerName = "tool_search_tool_regex" + } + } + + const toolName = toolNameMapper.getCustomName(providerName!) + + if (part.content.type === "tool_search_tool_search_result") { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: false, + result: part.content.tool_references + }) + } else { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolName, + isFailure: true, + result: part.content + }) + } + break + } + + case "mcp_tool_use": { + const part = event.content_block + + const toolCall: Response.ToolCallPartEncoded = { + type: "tool-call", + id: part.id, + name: part.name, + params: part.input, + providerExecuted: true, + metadata: { anthropic: { mcp_tool: { server: part.server_name } } } + } + + mcpToolCalls.set(part.id, toolCall) + + parts.push(toolCall) + + break + } + + case "mcp_tool_result": { + const part = event.content_block + const toolCall = mcpToolCalls.get(part.tool_use_id) + const mcpMetadata = toolCall?.metadata?.anthropic?.mcp_tool + + if (Predicate.isNotUndefined(toolCall)) { + parts.push({ + type: "tool-result", + id: part.tool_use_id, + name: toolCall.name, + isFailure: part.is_error, + result: part.content, + metadata: { + anthropic: { + ...(Predicate.isNotNullish(mcpMetadata) + ? { mcp_tool: mcpMetadata } : + undefined) + } + } + }) + } + + break + } + } + + break + } + + case "content_block_delta": { + const part = event.delta + + switch (part.type) { + case "text_delta": { + parts.push({ + type: "text-delta", + id: event.index.toString(), + delta: part.text + }) + + break + } + + case "thinking_delta": { + parts.push({ + type: "reasoning-delta", + id: event.index.toString(), + delta: part.thinking + }) + + break + } + + case "signature_delta": { + if (blockType === "thinking") { + parts.push({ + type: "reasoning-delta", + id: event.index.toString(), + delta: "", + metadata: { + anthropic: { + info: { + type: "thinking", + signature: part.signature + } + } + } + }) + } + + break + } + + case "input_json_delta": { + let delta = part.partial_json + + // Skip empty deltas + if (delta.length === 0) { + break + } + + const contentBlock = contentBlocks.get(event.index) + + // Skip invalid deltas + if (Predicate.isUndefined(contentBlock)) { + break + } + + // Skip non-tool-call deltas + if (contentBlock.type !== "tool-call") { + break + } + + if ( + contentBlock.firstDelta && + (contentBlock.providerName === "bash_code_execution" || + contentBlock.providerName === "text_editor_code_execution") + ) { + delta = `{"type":${JSON.stringify(contentBlock.providerName)},${delta.substring(1)}` + } + + parts.push({ + type: "tool-params-delta", + id: contentBlock.id, + delta + }) + + contentBlock.params += delta + contentBlock.firstDelta = false + + break + } + + case "citations_delta": { + const source = yield* processCitation(part.citation, citableDocuments) + + if (Predicate.isNotUndefined(source)) { + parts.push(source) + } + + break + } + } + + break + } + + case "content_block_stop": { + const contentBlock = contentBlocks.get(event.index) + + if (Predicate.isNotUndefined(contentBlock)) { + switch (contentBlock.type) { + case "text": { + parts.push({ + type: "text-end", + id: event.index.toString() + }) + + break + } + + case "reasoning": { + parts.push({ + type: "reasoning-end", + id: event.index.toString() + }) + + break + } + + case "tool-call": { + parts.push({ + type: "tool-params-end", + id: contentBlock.id + }) + + // For code execution, inject the `programmatic-tool-call` type + // when the input format is `{ code }` + let finalParams = contentBlock.params.length === 0 ? "{}" : contentBlock.params + + if (contentBlock.providerName === "code_execution") { + // @effect-diagnostics-next-line tryCatchInEffectGen:off + try { + const params = Tool.unsafeSecureJsonParse(finalParams) + if (Predicate.hasProperty(params, "code") && !Predicate.hasProperty(params, "type")) { + finalParams = JSON.stringify({ type: "programmatic-tool-call", ...params }) + } + } catch { + // Ignore errors and use original tool call parameters + } + } + + const params = contentBlock.providerExecuted === true + ? Tool.unsafeSecureJsonParse(finalParams) + : yield* transformToolCallParams( + options.tools, + contentBlock.name, + Tool.unsafeSecureJsonParse(finalParams) + ) + + parts.push({ + type: "tool-call", + id: contentBlock.id, + name: contentBlock.name, + params, + ...(Predicate.isNotUndefined(contentBlock.providerExecuted) + ? { providerExecuted: contentBlock.providerExecuted } + : undefined), + ...(Predicate.isNotUndefined(contentBlock.caller) + ? { metadata: { anthropic: { caller: contentBlock.caller } } } + : undefined) + }) + } + } + + contentBlocks.delete(event.index) + } + + blockType = undefined + + break + } + + case "error": { + parts.push({ + type: "error", + error: event.error, + metadata: { anthropic: { requestId: event.request_id } } + }) + + break + } + } + + return parts + })), + Stream.flattenIterable + ) + } +) + +// ============================================================================= +// Telemetry +// ============================================================================= + +const annotateRequest = ( + span: Span, + request: typeof Generated.BetaCreateMessageParams.Encoded +): void => { + addGenAIAnnotations(span, { + system: "anthropic", + operation: { name: "chat" }, + request: { + model: request.model, + temperature: request.temperature, + topK: request.top_k, + topP: request.top_p, + maxTokens: request.max_tokens, + stopSequences: Arr.ensure(request.stop_sequences).filter( + Predicate.isNotNullish + ) + } + }) +} + +const annotateResponse = (span: Span, response: Generated.BetaMessage): void => { + addGenAIAnnotations(span, { + response: { + id: response.id, + model: response.model, + finishReasons: response.stop_reason ? [response.stop_reason] : undefined + }, + usage: { + inputTokens: response.usage.input_tokens, + outputTokens: response.usage.output_tokens + } + }) +} + +const annotateStreamResponse = (span: Span, part: Response.StreamPartEncoded) => { + if (part.type === "response-metadata") { + addGenAIAnnotations(span, { + response: { + id: part.id, + model: part.modelId + } + }) + } + if (part.type === "finish") { + addGenAIAnnotations(span, { + response: { + finishReasons: [part.reason] + }, + usage: { + inputTokens: part.usage.inputTokens.uncached, + outputTokens: part.usage.outputTokens.total + } + }) + } +} + +// ============================================================================= +// Internal Utilities +// ============================================================================= + +type ContentGroup = SystemMessageGroup | AssistantMessageGroup | UserMessageGroup + +interface SystemMessageGroup { + readonly type: "system" + readonly messages: Array +} + +interface AssistantMessageGroup { + readonly type: "assistant" + readonly messages: Array +} + +interface UserMessageGroup { + readonly type: "user" + readonly messages: Array +} + +const groupMessages = (prompt: Prompt.Prompt): Array => { + const messages: Array = [] + let current: ContentGroup | undefined = undefined + for (const message of prompt.content) { + switch (message.role) { + case "system": { + if (current?.type !== "system") { + current = { type: "system", messages: [] } + messages.push(current) + } + current.messages.push(message) + break + } + case "assistant": { + if (current?.type !== "assistant") { + current = { type: "assistant", messages: [] } + messages.push(current) + } + current.messages.push(message) + break + } + case "tool": + case "user": { + if (current?.type !== "user") { + current = { type: "user", messages: [] } + messages.push(current) + } + current.messages.push(message) + break + } + } + } + return messages +} + +/** + * Checks whether data is a URL (either a URL object or a URL string). + */ +const isUrlData = ( + data: typeof Prompt.FilePart.Type["data"] +): data is URL => data instanceof URL || isUrlString(data) + +const isUrlString = (data: typeof Prompt.FilePart.Type["data"]): boolean => + typeof data === "string" && /^https?:\/\//i.test(data) + +const getUrlString = (data: string | URL): string => data instanceof URL ? data.toString() : data + +const getCacheControl = ( + part: + | Prompt.SystemMessage + | Prompt.UserMessage + | Prompt.AssistantMessage + | Prompt.ToolMessage + | Prompt.UserMessagePart + | Prompt.AssistantMessagePart + | Prompt.ToolMessagePart +): typeof Generated.CacheControlEphemeral.Encoded | null => part.options.anthropic?.cacheControl ?? null + +const getDocumentMetadata = (part: Prompt.FilePart): { + readonly title: string | null + readonly context: string | null +} | null => { + const options = part.options.anthropic + if (Predicate.isNotUndefined(options)) { + return { + title: options?.documentTitle ?? null, + context: options?.documentContext ?? null + } + } + return null +} + +const areCitationsEnabled = (part: Prompt.FilePart): boolean => part.options.anthropic?.citations?.enabled ?? false + +const isCitationPart = (part: Prompt.UserMessagePart): part is Prompt.FilePart => + part.type === "file" && (part.mediaType === "application/pdf" || part.mediaType === "text/plain") + ? areCitationsEnabled(part) + : false + +interface CitableDocument { + readonly title: string + readonly fileName?: string | undefined + readonly mediaType: string +} + +const extractCitableDocuments = (prompt: Prompt.Prompt): Array => { + const citableDocuments: Array = [] + + for (const message of prompt.content) { + if (message.role === "user") { + for (const part of message.content) { + if (isCitationPart(part)) { + citableDocuments.push({ + title: part.fileName ?? "Untitled Document", + fileName: part.fileName, + mediaType: part.mediaType + }) + } + } + } + } + + return citableDocuments +} + +const processCitation = Effect.fnUntraced( + function*( + citation: + | Generated.ResponseCharLocationCitation + | Generated.ResponsePageLocationCitation + | Generated.ResponseContentBlockLocationCitation + | Generated.ResponseWebSearchResultLocationCitation + | Generated.ResponseSearchResultLocationCitation, + citableDocuments: ReadonlyArray + ): Effect.fn.Return< + Response.DocumentSourcePartEncoded | Response.UrlSourcePartEncoded | undefined, + never, + IdGenerator.IdGenerator + > { + const idGenerator = yield* IdGenerator.IdGenerator + + if (citation.type === "page_location" || citation.type === "char_location") { + const citedDocument = citableDocuments[citation.document_index] + if (Predicate.isNotUndefined(citedDocument)) { + const id = yield* idGenerator.generateId() + const metadata = citation.type === "char_location" + ? { + source: "document", + type: citation.type, + citedText: citation.cited_text, + startCharIndex: citation.start_char_index, + endCharIndex: citation.end_char_index + } as const + : { + source: "document", + type: citation.type, + citedText: citation.cited_text, + startPageNumber: citation.start_page_number, + endPageNumber: citation.end_page_number + } as const + + return { + type: "source", + sourceType: "document", + id, + mediaType: citedDocument.mediaType, + title: citation.document_title ?? citedDocument.title, + ...(Predicate.isNotUndefined(citedDocument.fileName) + ? { fileName: citedDocument.fileName } + : undefined), + metadata: { anthropic: metadata } + } + } + } + + if (citation.type === "web_search_result_location") { + const id = yield* idGenerator.generateId() + + const metadata = { + source: "url", + citedText: citation.cited_text, + encryptedIndex: citation.encrypted_index + } as const + + return { + type: "source", + sourceType: "url", + id, + url: citation.url, + title: citation.title ?? "Untitled", + metadata: { anthropic: metadata } + } + } + + return undefined + } +) + +interface ModelCapabilities { + readonly maxOutputTokens: number + readonly supportsStructuredOutput: 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 + */ +const getModelCapabilities = (modelId: string): ModelCapabilities => { + if ( + modelId.includes("claude-sonnet-4-5") || + modelId.includes("claude-opus-4-5") || + modelId.includes("claude-haiku-4-5") + ) { + return { + maxOutputTokens: 64000, + supportsStructuredOutput: true + } + } else if (modelId.includes("claude-opus-4-1")) { + return { + maxOutputTokens: 32000, + supportsStructuredOutput: true + } + } else if ( + modelId.includes("claude-sonnet-4-0") || + modelId.includes("claude-sonnet-4-20250514") || + modelId.includes("claude-3-7-sonnet") + ) { + return { + maxOutputTokens: 64000, + supportsStructuredOutput: false + } + } else if ( + modelId.includes("claude-opus-4-0") || + modelId.includes("claude-opus-4-20250514") + ) { + return { + maxOutputTokens: 32000, + supportsStructuredOutput: false + } + } else if (modelId.includes("claude-3-5-haiku")) { + return { + maxOutputTokens: 8192, + supportsStructuredOutput: false + } + } else if (modelId.includes("claude-3-")) { + return { + maxOutputTokens: 4096, + supportsStructuredOutput: false + } + } else { + return { + maxOutputTokens: 128000, + supportsStructuredOutput: true + } + } +} + +const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError => + AiError.make({ + module: "AnthropicLanguageModel", + method, + reason: new AiError.UnsupportedSchemaError({ + description: error instanceof Error ? error.message : String(error) + }) + }) + +const tryCodecTransform = (schema: S, method: string) => + Effect.try({ + try: () => toCodecAnthropic(schema), + catch: (error) => unsupportedSchemaError(error, method) + }) + +const tryJsonSchema = (schema: S, method: string) => + Effect.try({ + try: () => Tool.getJsonSchemaFromSchema(schema, { transformer: toCodecAnthropic }), + catch: (error) => unsupportedSchemaError(error, method) + }) + +const tryToolJsonSchema = (tool: T, method: string) => + Effect.try({ + try: () => Tool.getJsonSchema(tool, { transformer: toCodecAnthropic }), + catch: (error) => unsupportedSchemaError(error, method) + }) + +const getOutputFormat = Effect.fnUntraced(function*({ capabilities, options }: { + readonly capabilities: ModelCapabilities + readonly options: LanguageModel.ProviderOptions +}): Effect.fn.Return { + if (options.responseFormat.type === "json" && capabilities.supportsStructuredOutput) { + const jsonSchema = yield* tryJsonSchema(options.responseFormat.schema, "getOutputFormat") + return { + type: "json_schema", + schema: jsonSchema as any + } + } + return undefined +}) + +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: "AnthropicLanguageModel", + method: "makeResponse", + reason: new AiError.ToolNotFoundError({ + toolName, + availableTools: tools.map((tool) => tool.name) + }) + }) + } + + const { codec } = yield* tryCodecTransform(tool.parametersSchema, "makeResponse") + + const transform = Schema.decodeEffect(codec) + + return yield* ( + transform(toolParams) as Effect.Effect + ).pipe(Effect.mapError((error) => + AiError.make({ + module: "AnthropicLanguageModel", + method: "makeResponse", + reason: new AiError.ToolParameterValidationError({ + toolName, + toolParams, + description: formatIssue(error.issue) + }) + }) + )) +}) diff --git a/.repos/effect/packages/ai/anthropic/src/AnthropicTelemetry.ts b/.repos/effect/packages/ai/anthropic/src/AnthropicTelemetry.ts new file mode 100644 index 000000000..ce99b637b --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/AnthropicTelemetry.ts @@ -0,0 +1,132 @@ +/** + * The `AnthropicTelemetry` module defines Anthropic-specific telemetry + * attributes and a helper for adding them to a tracing span. It keeps the + * standard GenAI telemetry attributes and adds request and response metadata + * under the `gen_ai.anthropic.*` OpenTelemetry namespaces. + * + * @since 4.0.0 + */ +import { dual } from "effect/Function" +import * as String from "effect/String" +import type { Span } from "effect/Tracer" +import type { Simplify } from "effect/Types" +import * as Telemetry from "effect/unstable/ai/Telemetry" + +/** + * The attributes used to describe telemetry in the context of Generative + * Artificial Intelligence (GenAI) Models requests and responses. + * + * **Details** + * + * These attributes follow the OpenTelemetry generative AI semantic + * conventions: + * https://opentelemetry.io/docs/specs/semconv/attributes-registry/gen-ai/ + * + * @category models + * @since 4.0.0 + */ +export type AnthropicTelemetryAttributes = Simplify< + & Telemetry.GenAITelemetryAttributes + & Telemetry.AttributesWithPrefix + & Telemetry.AttributesWithPrefix +> + +/** + * All telemetry attributes which are part of the GenAI specification, + * including the Anthropic-specific attributes. + * + * @category models + * @since 4.0.0 + */ +export type AllAttributes = Telemetry.AllAttributes & RequestAttributes & ResponseAttributes + +/** + * Telemetry attributes which are part of the GenAI specification and are + * namespaced by `gen_ai.anthropic.request`. + * + * @category models + * @since 4.0.0 + */ +export interface RequestAttributes { + /** + * Whether extended thinking is enabled. + */ + readonly extendedThinking?: boolean | null | undefined + /** + * The budget tokens for extended thinking. + */ + readonly thinkingBudgetTokens?: number | null | undefined +} + +/** + * Telemetry attributes which are part of the GenAI specification and are + * namespaced by `gen_ai.anthropic.response`. + * + * @category models + * @since 4.0.0 + */ +export interface ResponseAttributes { + /** + * The stop reason from the response. + */ + readonly stopReason?: string | null | undefined + /** + * Number of cache creation input tokens. + */ + readonly cacheCreationInputTokens?: number | null | undefined + /** + * Number of cache read input tokens. + */ + readonly cacheReadInputTokens?: number | null | undefined +} + +/** + * Options accepted by `addGenAIAnnotations`, combining standard GenAI telemetry attributes with optional Anthropic request and response attributes. + * + * @category options + * @since 4.0.0 + */ +export type AnthropicTelemetryAttributeOptions = Telemetry.GenAITelemetryAttributeOptions & { + anthropic?: { + request?: RequestAttributes | undefined + response?: ResponseAttributes | undefined + } | undefined +} + +const addAnthropicRequestAttributes = Telemetry.addSpanAttributes("gen_ai.anthropic.request", String.camelToSnake)< + RequestAttributes +> +const addAnthropicResponseAttributes = Telemetry.addSpanAttributes("gen_ai.anthropic.response", String.camelToSnake)< + ResponseAttributes +> + +/** + * Applies the specified Anthropic GenAI telemetry attributes to the provided + * `Span`. + * + * **When to use** + * + * Use to annotate an Anthropic model span with standard GenAI telemetry + * attributes and Anthropic-specific request or response metadata. + * + * **Gotchas** + * + * This method mutates the `Span` in place. + * + * @category tracing + * @since 4.0.0 + */ +export const addGenAIAnnotations: { + (options: AnthropicTelemetryAttributeOptions): (span: Span) => void + (span: Span, options: AnthropicTelemetryAttributeOptions): void +} = dual(2, (span: Span, options: AnthropicTelemetryAttributeOptions) => { + Telemetry.addGenAIAnnotations(span, options) + if (options.anthropic != null) { + if (options.anthropic.request != null) { + addAnthropicRequestAttributes(span, options.anthropic.request) + } + if (options.anthropic.response != null) { + addAnthropicResponseAttributes(span, options.anthropic.response) + } + } +}) diff --git a/.repos/effect/packages/ai/anthropic/src/AnthropicTool.ts b/.repos/effect/packages/ai/anthropic/src/AnthropicTool.ts new file mode 100644 index 000000000..e0f975120 --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/AnthropicTool.ts @@ -0,0 +1,2518 @@ +/** + * The `AnthropicTool` module defines Anthropic provider tools and the schemas + * for their inputs and results. It covers Anthropic-owned tools such as Bash, + * Code Execution, Computer Use, Memory, Text Editor, Web Search, Web Fetch, and + * Tool Search, which can be attached to Anthropic-backed Effect AI language + * model requests. + * + * @since 4.0.0 + */ +import * as Schema from "effect/Schema" +import * as Tool from "effect/unstable/ai/Tool" +import * as Generated from "./Generated.ts" + +/** + * Union of all Anthropic provider-defined tool definitions exported by this module. + * + * **When to use** + * + * Use when a helper, collection, or option accepts any Anthropic + * provider-defined tool value created by this module. + * + * **Details** + * + * The union is built from the return types of the exported constructors, + * including Bash, Code Execution, Computer Use, Memory, Text Editor, Tool + * Search, Web Fetch, and Web Search tool versions. + * + * @category models + * @since 4.0.0 + */ +export type AnthropicTool = + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + +// ============================================================================= +// Bash +// ============================================================================= + +/** + * Defines the Anthropic Bash tool (2024-10-22 version). + * + * **When to use** + * + * Use when you want the model to execute bash commands with the 2024-10-22 + * Anthropic computer-use beta. + * + * **Details** + * + * Allows the model to execute bash commands in a sandboxed environment. + * Requires the "computer-use-2024-10-22" beta header. + * + * @see {@link Bash_20250124} for the newer 2025-01-24 version of the bash tool + * + * @category tools + * @since 4.0.0 + */ +export const Bash_20241022 = Tool.providerDefined({ + id: "anthropic.bash_20241022", + customName: "AnthropicBash", + providerName: "bash", + requiresHandler: true, + success: Schema.String, + parameters: Schema.Struct({ + command: Schema.String, + restart: Schema.optionalKey(Schema.Boolean) + }) +}) + +/** + * Defines the Anthropic Bash tool (2025-01-24 version). + * + * **When to use** + * + * Use when you want the model to execute bash commands with the 2025-01-24 + * Anthropic computer-use beta. + * + * **Details** + * + * Allows the model to execute bash commands in a sandboxed environment. + * Requires the "computer-use-2025-01-24" beta header. + * + * @see {@link Bash_20241022} for the older 2024-10-22 version of the bash tool + * + * @category tools + * @since 4.0.0 + */ +export const Bash_20250124 = Tool.providerDefined({ + id: "anthropic.bash_20250124", + customName: "AnthropicBash", + providerName: "bash", + requiresHandler: true, + success: Schema.String, + parameters: Schema.Struct({ + command: Schema.String, + restart: Schema.optionalKey(Schema.Boolean) + }) +}) + +// ============================================================================= +// Code Execution +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Code Execution 20250522 Parameters +// ----------------------------------------------------------------------------- + +/** + * Schema for a code execution request that asks Anthropic to run source code as a programmatic tool call. + * + * **When to use** + * + * Use when constructing or validating a programmatic tool call for the Anthropic + * Code Execution tool. + * + * @see {@link CodeExecution_20250522} for the parent tool definition + * + * @category schemas + * @since 4.0.0 + */ +export const CodeExecutionProgrammaticToolCall = Schema.Struct({ + type: Schema.Literal("programmatic-tool-call"), + /** + * The code to execute. + */ + code: Schema.String +}) +/** + * Input payload for a programmatic code execution tool call, including the source code to execute. + * + * @category models + * @since 4.0.0 + */ +export type CodeExecutionProgrammaticToolCall = typeof CodeExecutionProgrammaticToolCall.Type + +/** + * Schema for the `bash_code_execution` input variant of Anthropic Code Execution. + * + * **When to use** + * + * Use when validating or constructing a bash command request for + * `CodeExecution_20250522`. + * + * **Details** + * + * The schema requires `type` to be `"bash_code_execution"` and `command` to + * contain the bash command sent to Anthropic. + * + * @see {@link CodeExecution_20250522} for the provider-defined tool that consumes this input variant + * + * @category schemas + * @since 4.0.0 + */ +export const CodeExecutionBashCommand = Schema.Struct({ + type: Schema.Literal("bash_code_execution"), + /** + * The bash command to execute. + */ + command: Schema.String +}) +/** + * Input payload for a bash command routed through the Anthropic code execution tool. + * + * **When to use** + * + * Use when representing a provider-executed bash command request for the + * 2025-05-22 code execution tool. + * + * **Details** + * + * The payload uses `type: "bash_code_execution"` to distinguish bash execution + * from programmatic code and text editor operations, and `command` contains the + * bash command to run. + * + * @see {@link CodeExecutionProgrammaticToolCall} for programmatic code execution input + * @see {@link CodeExecutionTextEditorView} for viewing files through text editor code execution + * @see {@link CodeExecutionTextEditorCreate} for creating files through text editor code execution + * @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 models + * @since 4.0.0 + */ +export type CodeExecutionBashCommand = typeof CodeExecutionBashCommand.Type + +/** + * Schema for a code execution text editor request that views a file by path. + * + * **When to use** + * + * Use when you need the schema for provider-bound code-execution view requests + * before distinguishing them from create or replace text-editor commands. + * + * **Details** + * + * The encoded payload uses `type: "text_editor_code_execution"`, + * `command: "view"`, and a `path` string. + * + * @see {@link CodeExecutionTextEditorCreate} for the command that creates a file + * @see {@link CodeExecutionTextEditorStrReplace} for the command that replaces text in a file + * + * @category schemas + * @since 4.0.0 + */ +export const CodeExecutionTextEditorView = Schema.Struct({ + type: Schema.Literal("text_editor_code_execution"), + command: Schema.Literal("view"), + /** + * Path to the file to view. + */ + path: Schema.String +}) +/** + * Input payload for the `view` command of Anthropic's text editor code execution tool. + * + * **When to use** + * + * Use when working at the Anthropic protocol boundary and the code-execution + * view request must be distinguished from standalone text-editor view requests. + * + * **Details** + * + * The payload is discriminated by `type: "text_editor_code_execution"` and + * `command: "view"`. The `path` field identifies the file to view. + * + * **Gotchas** + * + * This code execution view payload does not include `view_range`; line ranges + * are part of the standalone text editor view payload, not this code execution + * payload. + * + * @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 models + * @since 4.0.0 + */ +export type CodeExecutionTextEditorView = typeof CodeExecutionTextEditorView.Type + +/** + * Schema for a text editor code execution request that creates a file at a path. + * + * **When to use** + * + * Use when validating or constructing an Anthropic `text_editor_code_execution` + * tool call that should create a file. + * + * **Details** + * + * The request is discriminated by `type: "text_editor_code_execution"` and + * `command: "create"`. It requires `path` and accepts optional `file_text`; the + * schema allows `file_text` to be omitted, `null`, or a string. + * + * @see {@link CodeExecution_20250522} for the provider-defined tool that consumes this request + * @see {@link CodeExecutionTextEditorView} for the matching view request + * @see {@link CodeExecutionTextEditorStrReplace} for the matching replace request + * + * @category schemas + * @since 4.0.0 + */ +export const CodeExecutionTextEditorCreate = Schema.Struct({ + type: Schema.Literal("text_editor_code_execution"), + command: Schema.Literal("create"), + /** + * Path where the file should be created. + */ + path: Schema.String, + /** + * The content to write to the new file. + */ + file_text: Schema.optional(Schema.NullOr(Schema.String)) +}) +/** + * Input payload for creating a file through the text editor code execution tool, optionally including initial file text. + * + * @category models + * @since 4.0.0 + */ +export type CodeExecutionTextEditorCreate = typeof CodeExecutionTextEditorCreate.Type + +/** + * Schema for a code execution text editor request that replaces one exact string in a file. + * + * **When to use** + * + * Use when validating or constructing the `str_replace` text editor operation + * for the 2025-05-22 Anthropic code execution tool. + * + * **Gotchas** + * + * The `old_str` must match the file contents exactly, including whitespace and + * indentation, and must identify a single occurrence. + * + * @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 schemas + * @since 4.0.0 + */ +export const CodeExecutionTextEditorStrReplace = Schema.Struct({ + type: Schema.Literal("text_editor_code_execution"), + command: Schema.Literal("str_replace"), + /** + * Path to the file to modify. + */ + path: Schema.String, + /** + * The text to replace. + */ + old_str: Schema.String, + /** + * The replacement text. + */ + new_str: Schema.String +}) +/** + * Input payload for replacing text in a file through the text editor code execution tool. + * + * @category models + * @since 4.0.0 + */ +export type CodeExecutionTextEditorStrReplace = typeof CodeExecutionTextEditorStrReplace.Type + +const CodeExecution_20250522_Parameters = Schema.Union([ + CodeExecutionProgrammaticToolCall, + CodeExecutionBashCommand, + CodeExecutionTextEditorView, + CodeExecutionTextEditorCreate, + CodeExecutionTextEditorStrReplace +]) + +// ----------------------------------------------------------------------------- +// Code Execution 20250825 Parameters +// ----------------------------------------------------------------------------- + +/** + * Schema for the 2025-08-25 code execution tool input, containing the code to execute. + * + * **When to use** + * + * Use when you need the schema for code-execution input at the Anthropic + * protocol boundary before sending source code to the 2025-08-25 tool. + * + * @see {@link CodeExecution_20250825} for the provider-defined tool that consumes this schema + * + * @category schemas + * @since 4.0.0 + */ +export const CodeExecution_20250825_Parameters = Schema.Struct({ + /** + * The code to execute. + */ + code: Schema.String +}) +/** + * Input payload for the 2025-08-25 Anthropic code execution tool. + * + * **When to use** + * + * Use when exposing the 2025-08-25 code-execution payload separately from the + * provider tool definition, such as at a transport or persistence boundary. + * + * **Details** + * + * The payload has a single `code` field containing the source code string to + * execute. + * + * @see {@link CodeExecution_20250825} for the provider-defined tool that consumes this payload + * + * @category models + * @since 4.0.0 + */ +export type CodeExecution_20250825_Parameters = typeof CodeExecution_20250825_Parameters.Type + +// ----------------------------------------------------------------------------- +// Code Execution Tool Definitions +// ----------------------------------------------------------------------------- + +/** + * Defines the Anthropic Code Execution tool (2025-05-22 version). + * + * **When to use** + * + * Use when you want the model to execute code in a sandboxed environment with + * the 2025-05-22 Anthropic code-execution beta. + * + * **Details** + * + * Allows the model to execute code in a sandboxed environment with support + * for multiple execution types including programmatic tool calls, bash + * execution, and text editor operations. + * + * @see {@link CodeExecutionProgrammaticToolCall} for the programmatic tool call schema + * + * @category tools + * @since 4.0.0 + */ +export const CodeExecution_20250522 = Tool.providerDefined({ + id: "anthropic.code_execution_20250522", + customName: "AnthropicCodeExecution", + providerName: "code_execution", + parameters: CodeExecution_20250522_Parameters, + success: Generated.BetaResponseCodeExecutionResultBlock, + failure: Generated.BetaResponseCodeExecutionToolResultError +}) + +/** + * Defines the Anthropic Code Execution tool (2025-08-25 version). + * + * **When to use** + * + * Use when you want the model to execute code in a sandboxed environment with + * the 2025-08-25 Anthropic code-execution beta. + * + * **Details** + * + * Requires the `code-execution-2025-08-25` beta header and uses + * `CodeExecution_20250825_Parameters` as its input schema. + * + * @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 tools + * @since 4.0.0 + */ +export const CodeExecution_20250825 = Tool.providerDefined({ + id: "anthropic.code_execution_20250825", + customName: "AnthropicCodeExecution", + providerName: "code_execution", + parameters: CodeExecution_20250825_Parameters, + success: Schema.Union([ + Generated.BetaResponseCodeExecutionResultBlock, + Generated.BetaResponseBashCodeExecutionResultBlock, + Generated.BetaResponseTextEditorCodeExecutionViewResultBlock, + Generated.BetaResponseTextEditorCodeExecutionCreateResultBlock, + Generated.BetaResponseTextEditorCodeExecutionStrReplaceResultBlock + ]), + failure: Schema.Union([ + Generated.BetaResponseCodeExecutionToolResultError, + Generated.BetaResponseBashCodeExecutionToolResultError, + Generated.BetaResponseTextEditorCodeExecutionToolResultError + ]) +}) + +// ============================================================================= +// Computer Use +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Common Types +// ----------------------------------------------------------------------------- + +/** + * Schema for an `[x, y]` screen coordinate in pixels. + * + * **When to use** + * + * Use when validating computer-use action payloads that carry a single screen + * position and provider-side bounds checks remain acceptable. + * + * **Details** + * + * This is a two-number tuple used by computer-use actions that accept screen + * positions. + * + * **Gotchas** + * + * This schema validates tuple shape only and does not check display bounds. + * + * @category schemas + * @since 4.0.0 + */ +export const Coordinate = Schema.Tuple([Schema.Int, Schema.Int]) +/** + * An `[x, y]` screen coordinate in pixels. + * + * @category models + * @since 4.0.0 + */ +export type Coordinate = typeof Coordinate.Type + +/** + * Schema for an `[x1, y1, x2, y2]` screen region in pixels. + * + * **When to use** + * + * Use when validating computer-use action payloads that carry a rectangular + * screen region and provider-side bounds checks remain acceptable. + * + * **Details** + * + * The tuple represents top-left and bottom-right corners. + * + * **Gotchas** + * + * This schema validates four numbers only and does not check coordinate ordering + * or display bounds. + * + * @category schemas + * @since 4.0.0 + */ +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 models + * @since 4.0.0 + */ +export type Region = typeof Region.Type + +/** + * Schema for scroll direction literals: `"up"`, `"down"`, `"left"`, or `"right"`. + * + * @see {@link ComputerUseScrollAction} for the action payload that consumes this schema + * + * @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 models + * @since 4.0.0 + */ +export type ScrollDirection = typeof ScrollDirection.Type + +/** + * Schema for modifier key literals. + * + * **Details** + * + * Allowed values are `"alt"`, `"ctrl"`, `"meta"`, and `"shift"`. + * + * @category schemas + * @since 4.0.0 + */ +export const ModifierKey = Schema.Literals(["alt", "ctrl", "meta", "shift"]) +/** + * Modifier key literals. + * + * **Details** + * + * Allowed values are `"alt"`, `"ctrl"`, `"meta"`, and `"shift"`. + * + * @category models + * @since 4.0.0 + */ +export type ModifierKey = typeof ModifierKey.Type + +// ----------------------------------------------------------------------------- +// ComputerUse_20241022_Args +// ----------------------------------------------------------------------------- + +const ComputerUse_20241022_Args = Schema.Struct({ + /** + * The width of the display being controlled by the model in pixels. + */ + displayWidthPx: Schema.Int, + + /** + * The height of the display being controlled by the model in pixels. + */ + 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.Int) +}) + +const ComputerUse_20251124_Args = Schema.Struct({ + ...ComputerUse_20241022_Args.fields, + enableZoom: Schema.optional(Schema.Boolean) +}) + +// ----------------------------------------------------------------------------- +// Computer Use 20241022 Actions +// ----------------------------------------------------------------------------- + +/** + * Schema for a computer-use action that presses a key or key combination, such + * as `"Return"`, `"ctrl+c"`, or `"ctrl+s"`. + * + * **When to use** + * + * Use when validating or constructing a computer-use action for keyboard + * shortcuts or non-text key presses. + * + * @see {@link TypeAction} for entering ordinary text strings + * @see {@link ComputerUseHoldKeyAction} for holding a key for a duration + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseKeyAction = Schema.Struct({ + action: Schema.Literal("key"), + /** + * The key to press. + */ + text: Schema.String +}) +/** + * Computer-use action payload for pressing a key or key combination. + * + * **When to use** + * + * Use when typing parsed computer-use key action payloads after schema + * validation, where provider-specific key-name validation is handled outside + * TypeScript. + * + * **Details** + * + * The payload uses `action: "key"` and stores the key or key combination to + * press in `text`, such as `"Return"`, `"ctrl+c"`, or `"ctrl+s"`. + * + * **Gotchas** + * + * `text` is typed as `string`; the paired schema does not validate + * provider-specific key names or key combinations. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseKeyAction = typeof ComputerUseKeyAction.Type + +/** + * Schema for a computer-use action that performs a left click. + * + * **When to use** + * + * Use to validate or construct an Anthropic computer-use payload for clicking + * once at the current mouse position or at a specific screen coordinate. + * + * **Details** + * + * The encoded payload uses `action: "left_click"`. The optional `coordinate` + * field supplies the `[x, y]` pixel position; when omitted, the action uses the + * current mouse position. + * + * **Gotchas** + * + * The coordinate schema only checks that the value is a two-number tuple. It + * does not validate that the point falls within the configured display + * dimensions. + * + * @see {@link ComputerUseDoubleClickAction} for performing a double click + * @see {@link ComputerUseMouseMoveAction} for moving the mouse without clicking + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseLeftClickAction = Schema.Struct({ + action: Schema.Literal("left_click"), + /** + * The `[x, y]` coordinate on the screen to left click (defaults to the current + * mouse position if omitted). + */ + coordinate: Schema.optionalKey(Coordinate) +}) +/** + * Computer-use action payload for performing a left click, optionally at a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseLeftClickAction = typeof ComputerUseLeftClickAction.Type + +/** + * Schema for a computer-use action that moves the mouse cursor to a required + * `[x, y]` screen coordinate. + * + * **When to use** + * + * Use to validate or construct a mouse movement action for an Anthropic + * computer-use tool call. + * + * **Details** + * + * The encoded payload has action `"mouse_move"` and a required `coordinate` + * field containing the target `[x, y]` pixel position. + * + * **Gotchas** + * + * The coordinate schema only checks that the value is a two-number tuple. It + * does not validate that the point falls within the configured display + * dimensions. + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseMouseMoveAction = Schema.Struct({ + action: Schema.Literal("mouse_move"), + /** + * The `[x, y]` coordinate on the screen to move to. + */ + coordinate: Coordinate +}) +/** + * Computer-use action payload for moving the mouse cursor to a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseMouseMoveAction = typeof ComputerUseMouseMoveAction.Type + +/** + * Schema for a computer-use action that requests a screenshot of the current display. + * + * **When to use** + * + * Use to validate or construct a computer-use tool action that asks the handler + * to capture the full current display. + * + * **Details** + * + * The payload contains only `action: "screenshot"` and does not include + * coordinates or other options. + * + * @see {@link ComputerUseZoomAction} for requesting a zoomed-in screenshot of a specific screen region with the 2025-11-24 computer-use tool + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseScreenshotAction = Schema.Struct({ + action: Schema.Literal("screenshot") +}) +/** + * Computer-use action payload for capturing the current display. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseScreenshotAction = typeof ComputerUseScreenshotAction.Type + +/** + * Schema for a computer-use action that enters text. + * + * **When to use** + * + * Use to validate or construct a computer-use action for entering ordinary text + * strings. + * + * **Details** + * + * The payload uses `action: "type"` and a `text` string containing the text to + * enter. + * + * @see {@link ComputerUseKeyAction} for key presses and keyboard shortcuts + * + * @category schemas + * @since 4.0.0 + */ +export const TypeAction = Schema.Struct({ + action: Schema.Literal("type"), + /** + * The text to type. + */ + text: Schema.String +}) +/** + * Computer-use action payload for typing a text string. + * + * **Details** + * + * The payload uses `action: "type"` and a `text` string containing the text to + * enter. + * + * @category models + * @since 4.0.0 + */ +export type TypeAction = typeof TypeAction.Type + +const ComputerUse_20241022_Actions = Schema.Union([ + ComputerUseKeyAction, + ComputerUseLeftClickAction, + ComputerUseMouseMoveAction, + ComputerUseScreenshotAction, + TypeAction +]) + +// ----------------------------------------------------------------------------- +// Computer Use 20250124 Actions +// ----------------------------------------------------------------------------- + +/** + * Schema for a computer-use action that performs a double click. + * + * **When to use** + * + * Use to validate or construct an Anthropic computer-use payload for double + * clicking at the current mouse position or at a specific screen coordinate. + * + * **Details** + * + * The encoded payload uses `action: "double_click"`. The optional + * `coordinate` field supplies the `[x, y]` pixel position; when omitted, the + * action uses the current mouse position. + * + * **Gotchas** + * + * The coordinate schema only checks that the value is a two-number tuple. It + * does not validate that the point falls within the configured display + * dimensions. + * + * @see {@link ComputerUseLeftClickAction} for performing a single left click + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseDoubleClickAction = Schema.Struct({ + action: Schema.Literal("double_click"), + /** + * The coordinate to double click (defaults to the current mouse position if + * omitted). + */ + coordinate: Schema.optionalKey(Coordinate) +}) +/** + * Computer-use action payload for performing a double click, optionally at a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseDoubleClickAction = typeof ComputerUseDoubleClickAction.Type + +/** + * Keeps a key pressed for a specified duration during computer-use execution. + * + * **When to use** + * + * Use to keep a keyboard key depressed for a fixed number of seconds in a + * computer-use action sequence. + * + * **Details** + * + * The schema describes objects with `action: "hold_key"`, a `text` field + * containing the key to hold, and a `duration` field containing the number of + * seconds to hold it. + * + * **Gotchas** + * + * The schema only checks that `duration` is a number; it does not require a + * positive value. + * + * @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 schemas + * @since 4.0.0 + */ +export const ComputerUseHoldKeyAction = Schema.Struct({ + action: Schema.Literal("hold_key"), + /** + * The key to hold (e.g. `"shift"`, `"ctrl"`). + */ + text: Schema.String, + /** + * The number of seconds to hold the key. + */ + duration: Schema.Finite +}) +/** + * Computer-use action payload for holding a key for a specified duration. + * + * **When to use** + * + * Use to represent a key that should remain pressed for a measured interval. + * + * **Details** + * + * Set `action` to `"hold_key"`, `text` to the key to hold, and `duration` to + * the number of seconds to hold it. + * + * @see {@link ComputerUseKeyAction} for a single key press or key combination without a hold duration + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseHoldKeyAction = typeof ComputerUseHoldKeyAction.Type + +/** + * Schema for a computer-use action that drags with the left mouse button. + * + * **When to use** + * + * Use to validate or construct an Anthropic computer-use payload for dragging + * from one screen coordinate to another in a single action. + * + * **Details** + * + * The encoded payload uses `action: "left_click_drag"` and requires both + * `start_coordinate` and `coordinate` as `[x, y]` pixel positions. + * + * **Gotchas** + * + * The coordinate schema only checks that each value is a two-number tuple. It + * does not validate that either point falls within the configured display + * dimensions. + * + * @see {@link ComputerUseLeftMouseDownAction} for starting a manual drag sequence + * @see {@link ComputerUseLeftMouseUpAction} for ending a manual drag sequence + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseLeftClickDragAction = Schema.Struct({ + action: Schema.Literal("left_click_drag"), + /** + * The `[x, y]` coordinate to start dragging from. + */ + start_coordinate: Coordinate, + /** + * The `[x, y]` coordinate to drag to. + */ + coordinate: Coordinate +}) +/** + * Computer-use action payload for dragging from a start coordinate to an end coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseLeftClickDragAction = typeof ComputerUseLeftClickDragAction.Type + +/** + * Starts a left mouse button press without releasing it. + * + * **When to use** + * + * Use when constructing a manual click or drag sequence that should press and + * hold the left mouse button before a later release. + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseLeftMouseDownAction = Schema.Struct({ + action: Schema.Literal("left_mouse_down"), + /** + * The coordinate at which the left mouse button should be held down (defaults + * to the current mouse position if omitted). + */ + coordinate: Schema.optionalKey(Coordinate) +}) +/** + * Computer-use action payload for pressing and holding the left mouse button, optionally at a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseLeftMouseDownAction = typeof ComputerUseLeftMouseDownAction.Type + +/** + * Releases the left mouse button. + * + * **When to use** + * + * Use when constructing a manual click or drag sequence that should release the + * left mouse button after it was previously held down. + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseLeftMouseUpAction = Schema.Struct({ + action: Schema.Literal("left_mouse_up"), + /** + * The coordinate at which the left mouse button should be released (defaults + * to the current mouse position if omitted). + */ + coordinate: Schema.optionalKey(Coordinate) +}) +/** + * Computer-use action payload for releasing the left mouse button, optionally at a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseLeftMouseUpAction = typeof ComputerUseLeftMouseUpAction.Type + +/** + * Schema for a computer-use action that performs a middle click. + * + * **When to use** + * + * Use to validate or construct a middle-button click action for Anthropic + * computer use, optionally targeting a specific screen coordinate. + * + * **Details** + * + * The payload must use `action: "middle_click"`. When `coordinate` is omitted, + * the click occurs at the current mouse position. + * + * **Gotchas** + * + * This action is available in the 2025-01-24 computer-use action set and later; + * it is not part of `ComputerUse_20241022`. + * + * @see {@link ComputerUse_20250124} for the provider-defined tool version that first accepts this action + * @see {@link ComputerUseLeftClickAction} for primary-button clicks + * @see {@link ComputerUseRightClickAction} for secondary-button clicks + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseMiddleClickAction = Schema.Struct({ + action: Schema.Literal("middle_click"), + /** + * The coordinate to middle click (defaults to the current mouse position if + * omitted). + */ + coordinate: Schema.optionalKey(Coordinate) +}) +/** + * Computer-use action payload for performing a middle click, optionally at a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseMiddleClickAction = typeof ComputerUseMiddleClickAction.Type + +/** + * Schema for a computer-use action that performs a right click, optionally at a + * specific screen coordinate. + * + * **When to use** + * + * Use to validate or construct the `right_click` action for an Anthropic + * computer-use tool call. + * + * **Details** + * + * The optional `coordinate` field is an `[x, y]` screen coordinate in pixels. + * When omitted, the right click is performed at the current mouse position. + * + * @see {@link ComputerUse_20250124} for the provider-defined computer-use tool version that introduced this action + * @see {@link ComputerUseLeftClickAction} for the corresponding left-click action + * @see {@link ComputerUseMiddleClickAction} for the corresponding middle-click action + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseRightClickAction = Schema.Struct({ + action: Schema.Literal("right_click"), + /** + * The coordinate to right click (defaults to the current mouse position if + * omitted). + */ + coordinate: Schema.optionalKey(Coordinate) +}) +/** + * Computer-use action payload for performing a right click, optionally at a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseRightClickAction = typeof ComputerUseRightClickAction.Type + +/** + * Schema for a computer-use scroll action. + * + * **When to use** + * + * Use when validating or constructing Anthropic computer-use scroll payloads. + * + * **Details** + * + * The encoded payload uses `action: "scroll"`, an optional `coordinate`, + * `scroll_direction`, and `scroll_amount`. + * + * **Gotchas** + * + * `coordinate` only checks a two-number tuple, and `scroll_amount` is only + * `Schema.Int`. + * + * @see {@link ComputerUse_20250124} for the tool version that accepts this action + * @see {@link ScrollDirection} for the accepted direction literals + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseScrollAction = Schema.Struct({ + action: Schema.Literal("scroll"), + /** + * The coordinate to start scrolling from (defaults to the current mouse + * position if omitted). + */ + coordinate: Schema.optionalKey(Coordinate), + /** + * The direction to scroll. + */ + scroll_direction: ScrollDirection, + /** + * The amount to scroll (in pixels or scroll units). + */ + scroll_amount: Schema.Int +}) +/** + * Computer-use action payload for scrolling by a specified amount in a specified direction, optionally from a coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseScrollAction = typeof ComputerUseScrollAction.Type + +/** + * Schema for a computer-use triple-click action. + * + * **When to use** + * + * Use when validating or constructing Anthropic computer-use triple-click + * payloads at the current pointer position or an optional coordinate. + * + * **Details** + * + * The encoded payload uses `action: "triple_click"` and an optional + * `coordinate`. + * + * **Gotchas** + * + * `coordinate` only validates as a two-number tuple and does not check display + * bounds. + * + * @see {@link ComputerUse_20250124} for the tool version that accepts this action + * @see {@link ComputerUseDoubleClickAction} for the two-click variant + * @see {@link ComputerUseLeftClickAction} for a single left click + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseTripleClickAction = Schema.Struct({ + action: Schema.Literal("triple_click"), + /** + * The coordinate to triple click (defaults to the current mouse position if + * omitted). + */ + coordinate: Schema.optionalKey(Coordinate) +}) +/** + * Computer-use action payload for performing a triple click, optionally at a specific coordinate. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseTripleClickAction = typeof ComputerUseTripleClickAction.Type + +/** + * Schema for a computer-use wait action. + * + * **When to use** + * + * Use when validating or constructing Anthropic computer-use payloads that pause + * between actions. + * + * **Details** + * + * The encoded payload uses `action: "wait"` and a required `duration` in + * seconds. + * + * **Gotchas** + * + * `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 schemas + * @since 4.0.0 + */ +export const ComputerUseWaitAction = Schema.Struct({ + action: Schema.Literal("wait"), + /** + * The number of seconds to wait. + */ + duration: Schema.Finite +}) +/** + * Computer-use action payload for pausing for a specified duration. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseWaitAction = typeof ComputerUseWaitAction.Type + +const ComputerUse_20250124_Actions = Schema.Union([ + ...ComputerUse_20241022_Actions.members, + ComputerUseDoubleClickAction, + ComputerUseHoldKeyAction, + ComputerUseLeftClickDragAction, + ComputerUseLeftMouseDownAction, + ComputerUseLeftMouseUpAction, + ComputerUseMiddleClickAction, + ComputerUseRightClickAction, + ComputerUseScrollAction, + ComputerUseTripleClickAction, + ComputerUseWaitAction +]) + +// ----------------------------------------------------------------------------- +// Computer Use 20251124 Actions +// ----------------------------------------------------------------------------- + +/** + * Zooms into a specific region of the screen at full resolution. + * + * **When to use** + * + * Use when building or validating the 2025-11-24 computer-use action for a + * zoom-enabled tool definition. + * + * **Details** + * + * The encoded payload uses `action: "zoom"` and a `region` tuple. + * + * **Gotchas** + * + * Requires `enableZoom: true` in the tool definition. `region` is only a + * four-number tuple and does not validate corner ordering or display bounds. + * + * @see {@link ComputerUse_20251124} for the tool version that accepts this action + * @see {@link ComputerUseScreenshotAction} for capturing the full screen instead + * + * @category schemas + * @since 4.0.0 + */ +export const ComputerUseZoomAction = Schema.Struct({ + action: Schema.Literal("zoom"), + /** + * Region to zoom into, defined as `[x1, y1, x2, y2]` coordinates where + * `(x1, y1)` is the top-left corner and `(x2, y2)` is the bottom-right corner. + */ + region: Region +}) +/** + * Computer-use action payload for zooming into a specific screen region. + * + * **Gotchas** + * + * The enclosing computer-use tool must be configured with `enableZoom: true`. + * `region` is only a four-number tuple and does not validate corner ordering or + * display bounds. + * + * @category models + * @since 4.0.0 + */ +export type ComputerUseZoomAction = typeof ComputerUseZoomAction.Type + +const ComputerUse_20251124_Actions = Schema.Union([ + ...ComputerUse_20250124_Actions.members, + ComputerUseZoomAction +]) + +// ----------------------------------------------------------------------------- +// Computer Use Tool Definitions +// ----------------------------------------------------------------------------- + +/** + * Defines the deprecated computer-use tool for Claude 3.5 Sonnet v2. + * + * **Details** + * + * Requires the "computer-use-2024-10-22" beta header. + * Basic actions only: screenshot, left_click, type, key, mouse_move. + * + * @category tools + * @since 4.0.0 + */ +export const ComputerUse_20241022 = Tool.providerDefined({ + id: "anthropic.computer_use_20241022", + customName: "AnthropicComputerUse", + providerName: "computer_use", + requiresHandler: true, + args: ComputerUse_20241022_Args, + parameters: ComputerUse_20241022_Actions, + success: Schema.String +}) + +/** + * Defines the computer-use tool for Claude 4 models and Claude Sonnet 3.7. + * + * **When to use** + * + * Use when you need Anthropic computer use for Claude 4 models or Claude + * Sonnet 3.7 with the 2025-01-24 action set. + * + * **Details** + * + * Requires the "computer-use-2025-01-24" beta header. + * Includes basic actions plus enhanced actions: scroll, left_click_drag, + * right_click, middle_click, double_click, triple_click, left_mouse_down, + * left_mouse_up, hold_key, wait. + * + * @see {@link ComputerUse_20241022} for the older basic action set + * @see {@link ComputerUse_20251124} for the newer zoom-capable version + * + * @category tools + * @since 4.0.0 + */ +export const ComputerUse_20250124 = Tool.providerDefined({ + id: "anthropic.computer_20250124", + customName: "AnthropicComputerUse", + providerName: "computer", + requiresHandler: true, + args: ComputerUse_20241022_Args, + parameters: ComputerUse_20250124_Actions, + success: Schema.String +}) + +/** + * Defines the computer-use tool for Claude Opus 4.5 only. + * + * **When to use** + * + * Use when you need Anthropic computer use for Claude Opus 4.5 with the + * 2025-11-24 action set and zoom-capable screen inspection. + * + * **Details** + * + * Requires the "computer-use-2025-11-24" beta header. + * Includes all actions from computer_20250124 plus the zoom action for + * detailed screen region inspection. + * + * **Gotchas** + * + * Zoom actions require `enableZoom: true` in args. + * + * @see {@link ComputerUse_20250124} for the previous action set without zoom + * @see {@link ComputerUseZoomAction} for the zoom action payload + * + * @category tools + * @since 4.0.0 + */ +export const ComputerUse_20251124 = Tool.providerDefined({ + id: "anthropic.computer_20251124", + customName: "AnthropicComputerUse", + providerName: "computer", + requiresHandler: true, + args: ComputerUse_20251124_Args, + parameters: ComputerUse_20251124_Actions, + success: Schema.String +}) + +// ============================================================================= +// Memory +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Common Types +// ----------------------------------------------------------------------------- + +/** + * Defines a `[start, end]` line range for viewing file contents. + * + * **When to use** + * + * Use when constructing or validating `view_range` for memory or text editor + * view commands. + * + * **Details** + * + * Lines are 1-indexed. Use `-1` for end to read to the end of the file. For + * example, `[1, 50]` views lines 1-50 and `[100, -1]` views from line 100 to + * the end of the file. + * + * @see {@link MemoryViewCommand} for memory view payloads that use this range + * @see {@link TextEditorViewCommand} for text editor view payloads that use this range + * + * @category schemas + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use when typing `view_range` for memory or text editor view commands. + * + * @category models + * @since 4.0.0 + */ +export type ViewRange = typeof ViewRange.Type + +// ----------------------------------------------------------------------------- +// Memory 20250818 Commands +// ----------------------------------------------------------------------------- + +/** + * Schema for the memory tool command that creates a new file at a path. + * + * **Details** + * + * The payload contains `command: "create"`, a `path` string, and the + * `file_text` content to write to the file. + * + * @category schemas + * @since 4.0.0 + */ +export const MemoryCreateCommand = Schema.Struct({ + command: Schema.Literal("create"), + /** + * The path to the file that should be created. + */ + 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 models + * @since 4.0.0 + */ +export type MemoryCreateCommand = typeof MemoryCreateCommand.Type + +/** + * Schema for a memory command that deletes a file or directory. + * + * @category schemas + * @since 4.0.0 + */ +export const MemoryDeleteCommand = Schema.Struct({ + command: Schema.Literal("delete"), + /** + * The path to the file or directory to delete. + */ + path: Schema.String +}) +/** + * Memory tool command payload for deleting a file or directory at a path. + * + * @category models + * @since 4.0.0 + */ +export type MemoryDeleteCommand = typeof MemoryDeleteCommand.Type + +/** + * Schema for the memory `insert` command. + * + * **When to use** + * + * Use when validating or constructing `insert` payloads for `Memory_20250818`. + * + * **Details** + * + * The payload is discriminated by `command: "insert"` and requires `path`, + * `insert_line`, and `insert_text`. + * + * @see {@link Memory_20250818} for the provider-defined tool that consumes this command + * @see {@link MemoryStrReplaceCommand} for replacing existing text instead + * + * @category schemas + * @since 4.0.0 + */ +export const MemoryInsertCommand = Schema.Struct({ + command: Schema.Literal("insert"), + /** + * The path to the file to insert text into. + */ + path: Schema.String, + /** + * The line at which the text should be inserted. + */ + insert_line: Schema.Int, + /** + * The text to insert. + */ + insert_text: Schema.String +}) +/** + * Memory tool command payload for inserting text at a specific line in a file. + * + * @category models + * @since 4.0.0 + */ +export type MemoryInsertCommand = typeof MemoryInsertCommand.Type + +/** + * Schema for the memory command that renames or moves a file or directory. + * + * **Details** + * + * The payload uses `command: "rename"` and requires `old_path` as the current + * path plus `new_path` as the new destination path. + * + * @category schemas + * @since 4.0.0 + */ +export const MemoryRenameCommand = Schema.Struct({ + command: Schema.Literal("rename"), + /** + * The old path to the file or directory. + */ + old_path: Schema.String, + /** + * The new path to the file or directory. + */ + new_path: Schema.String +}) +/** + * Memory tool command payload for renaming or moving a file or directory. + * + * @category models + * @since 4.0.0 + */ +export type MemoryRenameCommand = typeof MemoryRenameCommand.Type + +/** + * Schema for the memory `str_replace` command. + * + * **When to use** + * + * Use when validating or constructing `str_replace` payloads for + * `Memory_20250818`. + * + * **Details** + * + * The payload is discriminated by `command: "str_replace"` and requires `path`, + * `old_str`, and `new_str`. + * + * @see {@link Memory_20250818} for the provider-defined tool that consumes this command + * + * @category schemas + * @since 4.0.0 + */ +export const MemoryStrReplaceCommand = Schema.Struct({ + command: Schema.Literal("str_replace"), + /** + * The path to the file in which the replacement should occur. + */ + path: Schema.String, + /** + * The text to replace. + */ + old_str: Schema.String, + /** + * The replacement text. + */ + new_str: Schema.String +}) +/** + * Memory tool command payload for replacing text in a file. + * + * @category models + * @since 4.0.0 + */ +export type MemoryStrReplaceCommand = typeof MemoryStrReplaceCommand.Type + +/** + * Shows directory contents or file contents with optional line ranges. + * + * **Details** + * + * When used on a file, returns file contents optionally limited by `view_range`. + * When used on a directory, lists contents. + * + * @category schemas + * @since 4.0.0 + */ +export const MemoryViewCommand = Schema.Struct({ + command: Schema.Literal("view"), + /** + * The path to the file or directory to view. + */ + path: Schema.String, + /** + * The specific lines to view. + */ + view_range: Schema.optionalKey(ViewRange) +}) +/** + * Memory tool command payload for viewing a file or directory, optionally with a file line range. + * + * @category models + * @since 4.0.0 + */ +export type MemoryViewCommand = typeof MemoryViewCommand.Type + +const Memory_20250818_Commands = Schema.Union([ + MemoryCreateCommand, + MemoryDeleteCommand, + MemoryInsertCommand, + MemoryRenameCommand, + MemoryStrReplaceCommand, + MemoryViewCommand +]) + +// ----------------------------------------------------------------------------- +// Memory Tool Definitions +// ----------------------------------------------------------------------------- + +/** + * Defines the memory tool for persistent file operations across conversations. + * + * **Details** + * + * Provides commands for creating, viewing, editing, renaming, and deleting + * files within the model's memory space. + * + * @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 +}) + +// ============================================================================= +// Text Editor +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Text Editor Commands +// ----------------------------------------------------------------------------- + +/** + * Reads the contents of a file or lists directory contents. + * + * **When to use** + * + * Use when validating or constructing the standalone Anthropic Text Editor + * `view` command. + * + * **Details** + * + * When used on a file, returns the file contents, optionally limited to a line + * range. When used on a directory, lists all files and subdirectories. + * `view_range` is a 1-indexed `[start, end]` tuple where `-1` means through + * the end of the file. + * + * @see {@link CodeExecutionTextEditorView} for the code-execution variant without `view_range` + * + * @category schemas + * @since 4.0.0 + */ +export const TextEditorViewCommand = Schema.Struct({ + command: Schema.Literal("view"), + /** + * Absolute or relative path to the file or directory to view. + */ + path: Schema.String, + /** + * 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.optionalKey(ViewRange) +}) +/** + * Text editor command payload for viewing file contents or listing directory contents. + * + * **Details** + * + * `view_range` is a 1-indexed `[start, end]` tuple where `-1` means through + * the end of the file. + * + * @category models + * @since 4.0.0 + */ +export type TextEditorViewCommand = typeof TextEditorViewCommand.Type + +/** + * Create a new file with specified content. + * + * **When to use** + * + * Use when validating or constructing an Anthropic text editor `create` + * command. + * + * **Details** + * + * The payload is discriminated by `command: "create"` and requires both `path` + * and `file_text`. + * + * **Gotchas** + * + * Fails if the file already exists. Parent directories must exist. + * + * @category schemas + * @since 4.0.0 + */ +export const TextEditorCreateCommand = Schema.Struct({ + command: Schema.Literal("create"), + /** + * Path where the file should be created. + */ + path: Schema.String, + /** + * The content to write to the new file. + */ + file_text: Schema.String +}) +/** + * Text editor command payload for creating a new file with the specified content. + * + * **When to use** + * + * Use when typing parsed text-editor create command payloads after schema + * validation and before dispatching to Anthropic tool handlers. + * + * **Gotchas** + * + * The command fails if the file already exists or if parent directories are missing. + * + * @category models + * @since 4.0.0 + */ +export type TextEditorCreateCommand = typeof TextEditorCreateCommand.Type + +/** + * Replaces a specific string in a file with a new string. + * + * **When to use** + * + * Use when validating or constructing standalone Anthropic text editor + * `str_replace` commands. + * + * **Details** + * + * The payload uses `command: "str_replace"`, `path`, `old_str`, and `new_str`. + * `new_str` may be empty to delete text. + * + * **Gotchas** + * + * The `old_str` must match exactly (including whitespace and indentation) + * and must be unique in the file. + * + * @see {@link TextEditorViewCommand} for reading contents before choosing `old_str` + * @see {@link CodeExecutionTextEditorStrReplace} for the code-execution variant + * + * @category schemas + * @since 4.0.0 + */ +export const TextEditorStrReplaceCommand = Schema.Struct({ + command: Schema.Literal("str_replace"), + /** + * Path to the file to modify. + */ + path: Schema.String, + /** + * The exact string to find and replace (must be unique in the file). + */ + old_str: Schema.String, + /** + * The string to replace old_str with (can be empty to delete). + */ + new_str: Schema.String +}) +/** + * Text editor command payload for replacing one exact, unique string in a file. + * + * **When to use** + * + * Use when typing parsed text-editor replace command payloads that must carry + * one exact `old_str` match. + * + * **Gotchas** + * + * The `old_str` must match exactly, including whitespace and indentation, and + * must be unique in the file. + * + * @category models + * @since 4.0.0 + */ +export type TextEditorStrReplaceCommand = typeof TextEditorStrReplaceCommand.Type + +/** + * Inserts text at a specific line number in a file. + * + * **Details** + * + * 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 schemas + * @since 4.0.0 + */ +export const TextEditorInsertCommand = Schema.Struct({ + command: Schema.Literal("insert"), + /** + * Path to the file to modify. + */ + path: Schema.String, + /** + * The line number after which to insert (0 = beginning, 1-indexed). + */ + insert_line: Schema.Int, + /** + * The text to insert. + */ + new_str: Schema.String +}) +/** + * Text editor command payload for inserting text after a specific line number in a file. + * + * @category models + * @since 4.0.0 + */ +export type TextEditorInsertCommand = typeof TextEditorInsertCommand.Type + +/** + * Undoes the last edit made to a file. + * + * **Details** + * + * Reverts the most recent `str_replace`, `insert`, or `create` operation on the + * file. + * + * **Gotchas** + * + * This command is available in `text_editor_20241022` and + * `text_editor_20250124`, but not in `text_editor_20250429` or + * `text_editor_20250728`. + * + * @category schemas + * @since 4.0.0 + */ +export const TextEditorUndoEditCommand = Schema.Struct({ + command: Schema.Literal("undo_edit"), + /** + * Path to the file to undo the last edit on. + */ + path: Schema.String +}) +/** + * Text editor command payload for undoing the most recent edit to a file. + * + * **Gotchas** + * + * Available for `text_editor_20241022` and `text_editor_20250124`, but not for + * `text_editor_20250429` or `text_editor_20250728`. + * + * @category models + * @since 4.0.0 + */ +export type TextEditorUndoEditCommand = typeof TextEditorUndoEditCommand.Type + +const TextEditor_StrReplaceEditor_Commands = Schema.Union([ + TextEditorViewCommand, + TextEditorCreateCommand, + TextEditorStrReplaceCommand, + TextEditorInsertCommand, + TextEditorUndoEditCommand +]) + +const TextEditor_StrReplaceBasedEdit_Commands = Schema.Union([ + TextEditorViewCommand, + TextEditorCreateCommand, + TextEditorStrReplaceCommand, + TextEditorInsertCommand +]) + +// ----------------------------------------------------------------------------- +// Text Editor Args +// ----------------------------------------------------------------------------- + +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.Int) +}) + +// ----------------------------------------------------------------------------- +// Text Editor Tool Definitions +// ----------------------------------------------------------------------------- + +/** + * Defines the deprecated text editor tool for Claude 3.5 Sonnet. + * + * **When to use** + * + * Use when you need the 2024-10-22 `str_replace_editor` compatibility path for + * Claude 3.5 Sonnet. + * + * **Details** + * + * Requires the "computer-use-2024-10-22" beta header and supports `view`, + * `create`, `str_replace`, `insert`, and `undo_edit` commands. + * + * @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 tools + * @since 4.0.0 + */ +export const TextEditor_20241022 = Tool.providerDefined({ + id: "anthropic.text_editor_20241022", + customName: "AnthropicTextEditor", + providerName: "str_replace_editor", + requiresHandler: true, + parameters: TextEditor_StrReplaceEditor_Commands, + success: Schema.String +}) + +/** + * Defines the text editor tool for deprecated Claude Sonnet 3.7. + * + * **When to use** + * + * Use when you need the 2025-01-24 Claude Sonnet 3.7 text editor tool using + * `str_replace_editor`. + * + * **Details** + * + * Requires the "computer-use-2025-01-24" beta header, requires a handler, and + * supports `view`, `create`, `str_replace`, `insert`, and `undo_edit` commands. + * + * @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 tools + * @since 4.0.0 + */ +export const TextEditor_20250124 = Tool.providerDefined({ + id: "anthropic.text_editor_20250124", + customName: "AnthropicTextEditor", + providerName: "str_replace_editor", + requiresHandler: true, + parameters: TextEditor_StrReplaceEditor_Commands, + success: Schema.String +}) + +/** + * Defines the text editor tool for Claude 4 models using Anthropic's `str_replace_based_edit_tool`. + * + * **When to use** + * + * Use when you need the 2025-04-29 Claude 4 `str_replace_based_edit_tool` + * version. + * + * **Details** + * + * Requires the "computer-use-2025-01-24" beta header. + * + * **Gotchas** + * + * This version does not support the `undo_edit` command. + * + * @see {@link TextEditor_20250124} for the previous `str_replace_editor` version + * @see {@link TextEditor_20250728} for the later Claude 4 text editor version + * + * @category tools + * @since 4.0.0 + */ +export const TextEditor_20250429 = Tool.providerDefined({ + id: "anthropic.text_editor_20250429", + customName: "AnthropicTextEditor", + providerName: "str_replace_based_edit_tool", + requiresHandler: true, + args: TextEditor_StrReplaceBasedEdit_Args, + parameters: TextEditor_StrReplaceBasedEdit_Commands, + success: Schema.String +}) + +/** + * Defines the text editor tool for Claude 4 models. + * + * **Details** + * + * Uses Anthropic's `str_replace_based_edit_tool`. `max_characters` can limit + * file-view output for this version. + * + * **Gotchas** + * + * This version does not support the `undo_edit` command. + * + * @category tools + * @since 4.0.0 + */ +export const TextEditor_20250728 = Tool.providerDefined({ + id: "anthropic.text_editor_20250728", + customName: "AnthropicTextEditor", + providerName: "str_replace_based_edit_tool", + requiresHandler: true, + args: TextEditor_StrReplaceBasedEdit_Args, + parameters: TextEditor_StrReplaceBasedEdit_Commands, + success: Schema.String +}) + +// ============================================================================= +// Web Search +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Web Search Types +// ----------------------------------------------------------------------------- + +/** + * Describes user location for localizing search results. + * + * **When to use** + * + * Use when you need to localize search results for location-dependent queries + * like weather, local businesses, or events. + * + * **Details** + * + * The schema uses `type: "approximate"` plus optional `city`, `region`, + * `country`, and `timezone`. `country` is an ISO 3166-1 alpha-2 code, and + * `timezone` is an IANA time zone identifier. + * + * @see {@link WebSearch_20250305_Args} for the argument schema that consumes this location + * + * @category schemas + * @since 4.0.0 + */ +export const WebSearchUserLocation = Schema.Struct({ + /** + * Location type - currently only "approximate" is supported. + */ + type: Schema.Literal("approximate"), + /** + * City name. + */ + city: Schema.optional(Schema.String), + /** + * Region/state/province name. + */ + region: Schema.optional(Schema.String), + /** + * ISO 3166-1 alpha-2 country code. + */ + country: Schema.optional(Schema.String), + /** + * IANA timezone identifier. + */ + timezone: Schema.optional(Schema.String) +}) + +// ----------------------------------------------------------------------------- +// Web Search Args +// ----------------------------------------------------------------------------- + +/** + * Defines configuration arguments for the web search tool. + * + * **When to use** + * + * Use when you need to configure `WebSearch_20250305` with search limits, + * domain filters, or user location. + * + * **Details** + * + * The payload can set `maxUses`, `allowedDomains`, `blockedDomains`, and + * `userLocation`. + * + * **Gotchas** + * + * `allowedDomains` and `blockedDomains` are mutually exclusive. + * + * @see {@link WebSearch_20250305} for the provider-defined tool that consumes these arguments + * @see {@link WebSearchUserLocation} for localizing search results + * + * @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.Int), + /** + * Restrict search results to only these domains. + * + * Cannot be used together with `blockedDomains`. + */ + allowedDomains: Schema.optional(Schema.Array(Schema.String)), + /** + * Exclude results from these domains. + * + * Cannot be used together with `allowedDomains`. + */ + blockedDomains: Schema.optional(Schema.Array(Schema.String)), + /** + * User location for localizing search results. + */ + userLocation: Schema.optional(WebSearchUserLocation) +}) +/** + * Configuration arguments for the Anthropic web search tool, including usage limits, domain filters, and optional user location. + * + * **Gotchas** + * + * `allowedDomains` and `blockedDomains` are mutually exclusive. + * + * @category models + * @since 4.0.0 + */ +export type WebSearch_20250305_Args = typeof WebSearch_20250305_Args.Type + +// ----------------------------------------------------------------------------- +// Web Search Parameters +// ----------------------------------------------------------------------------- + +/** + * Schema for Claude-supplied web search tool parameters. + * + * **Details** + * + * The payload contains the generated `query` string and is consumed by + * `WebSearch_20250305`. + * + * @see {@link WebSearch_20250305} for the provider-defined tool that consumes this payload + * + * @category schemas + * @since 4.0.0 + */ +export const WebSearchParameters = Schema.Struct({ + /** + * The search query generated by Claude. + */ + query: Schema.String +}) +/** + * Type of the parameters Claude supplies when invoking the Anthropic web search tool. + * + * **Details** + * + * Contains the generated search query used by `WebSearch_20250305`. + * + * @see {@link WebSearch_20250305} for the provider-defined tool that consumes this payload + * + * @category models + * @since 4.0.0 + */ +export type WebSearchParameters = typeof WebSearchParameters.Type + +// ----------------------------------------------------------------------------- +// Web Search Tool Definitions +// ----------------------------------------------------------------------------- + +/** + * Defines the web search tool for Claude models. + * + * **When to use** + * + * Use when you want Claude to search the web for real-time information. + * + * **Details** + * + * Enables Claude to search the web for real-time information. This is a + * server-side tool executed by Anthropic's infrastructure. + * Generally available (no beta header required). + * + * @see {@link WebFetch_20250910} for retrieving known URLs after discovery + * + * @category tools + * @since 4.0.0 + */ +export const WebSearch_20250305 = Tool.providerDefined({ + id: "anthropic.web_search_20250305", + customName: "AnthropicWebSearch", + providerName: "web_search", + args: WebSearch_20250305_Args, + parameters: WebSearchParameters, + success: Schema.Array(Generated.BetaResponseWebSearchResultBlock), + failure: Generated.BetaResponseWebSearchToolResultError +}) + +// ============================================================================= +// Web Fetch +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Web Fetch Types +// ----------------------------------------------------------------------------- + +/** + * Defines citation configuration for web fetch. + * + * **When to use** + * + * Use when you need to enable or disable citations on web fetch results. + * + * **Details** + * + * The payload contains the `enabled` flag. `citations` is optional on + * `WebFetch_20250910_Args`, and citations are disabled by default. + * + * @see {@link WebFetch_20250910_Args} for the argument schema that consumes this configuration + * + * @category schemas + * @since 4.0.0 + */ +export const WebFetchCitationsConfig = Schema.Struct({ + /** + * Enable citations for fetched content. + */ + enabled: Schema.Boolean +}) +/** + * Configuration payload for enabling or disabling citations on web fetch results. + * + * **When to use** + * + * Use when typing parsed web-fetch citation configuration shared between + * request arguments and handler code. + * + * **Details** + * + * The payload contains the `enabled` flag. `citations` is optional on + * `WebFetch_20250910_Args`, and citations are disabled by default. + * + * @see {@link WebFetch_20250910_Args} for the argument schema that consumes this configuration + * + * @category models + * @since 4.0.0 + */ +export type WebFetchCitationsConfig = typeof WebFetchCitationsConfig.Type + +// ----------------------------------------------------------------------------- +// Web Fetch Args +// ----------------------------------------------------------------------------- + +/** + * Defines configuration arguments for the web fetch tool. + * + * **When to use** + * + * Use when you need to configure `WebFetch_20250910` with usage limits, domain + * filters, citations, or content token limits. + * + * **Details** + * + * The payload can set `maxUses`, domain filters, `citations`, and + * `maxContentTokens`, which map to Anthropic web fetch request fields. + * + * **Gotchas** + * + * `allowedDomains` and `blockedDomains` are mutually exclusive. + * `maxContentTokens` is approximate and does not apply to binary content such + * as PDFs. + * + * @see {@link WebFetch_20250910} for the provider-defined tool that consumes these arguments + * @see {@link WebFetchCitationsConfig} for configuring citations + * + * @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.Int), + /** + * Restrict fetches to only these domains. + * + * Cannot be used together with `blockedDomains`. + */ + allowedDomains: Schema.optional(Schema.Array(Schema.String)), + /** + * Exclude fetches from these domains. + * + * Cannot be used together with `allowedDomains`. + */ + blockedDomains: Schema.optional(Schema.Array(Schema.String)), + /** + * Enable citations for fetched content. + */ + citations: Schema.optional(WebFetchCitationsConfig), + /** + * Maximum content length in tokens. + */ + maxContentTokens: Schema.optional(Schema.Int) +}) +/** + * Configuration arguments for the Anthropic web fetch tool, including usage limits, domain filters, citation settings, and token limits. + * + * **When to use** + * + * Use when typing parsed web-fetch tool configuration shared by the + * provider-defined tool and request-building code. + * + * **Gotchas** + * + * `allowedDomains` and `blockedDomains` are mutually exclusive. + * `maxContentTokens` is approximate and does not apply to binary content such + * as PDFs. + * + * @category models + * @since 4.0.0 + */ +export type WebFetch_20250910_Args = typeof WebFetch_20250910_Args.Type + +// ----------------------------------------------------------------------------- +// Web Fetch Parameters +// ----------------------------------------------------------------------------- + +/** + * Schema for Claude-supplied web fetch parameters. + * + * **When to use** + * + * Use when validating or constructing the `url` payload consumed by + * `WebFetch_20250910`. + * + * **Details** + * + * The payload contains the single `url` parameter for Anthropic web fetch. + * + * **Gotchas** + * + * The URL must be user-provided or from prior search/fetch results. Maximum URL + * length is 250 characters. + * + * @see {@link WebFetch_20250910} for the provider-defined tool that consumes this payload + * + * @category schemas + * @since 4.0.0 + */ +export const WebFetchParameters = Schema.Struct({ + /** + * URL to fetch. Must be a URL provided by the user or from prior search/fetch + * results. Maximum URL length: 250 characters. + */ + url: Schema.String +}) +/** + * Type of the parameters Claude supplies when invoking the Anthropic web fetch tool. + * + * **When to use** + * + * Use when typing Claude-supplied web-fetch tool parameters after schema + * validation, before enforcing URL provenance or length constraints. + * + * **Details** + * + * The payload contains the single `url` parameter for Anthropic web fetch. + * + * **Gotchas** + * + * The URL must be user-provided or from prior search/fetch results. Maximum URL + * length is 250 characters. + * + * @category models + * @since 4.0.0 + */ +export type WebFetchParameters = typeof WebFetchParameters.Type + +// ----------------------------------------------------------------------------- +// Web Fetch Tool Definitions +// ----------------------------------------------------------------------------- + +/** + * Defines the web fetch tool for Claude models. + * + * **When to use** + * + * Use when you want Claude to retrieve the content of a specific web page or + * PDF. + * + * **Details** + * + * Allows Claude to retrieve full content from web pages and PDF documents. + * This is a server-side tool executed by Anthropic's infrastructure. Selecting + * this tool adds the "web-fetch-2025-09-10" beta header. + * + * @see {@link WebSearch_20250305} for discovering URLs before fetching specific content + * + * @category tools + * @since 4.0.0 + */ +export const WebFetch_20250910 = Tool.providerDefined({ + id: "anthropic.web_fetch_20250910", + customName: "AnthropicWebFetch", + providerName: "web_fetch", + args: WebFetch_20250910_Args, + parameters: WebFetchParameters, + success: Generated.BetaResponseWebFetchResultBlock, + failure: Generated.BetaResponseWebFetchToolResultError +}) + +// ============================================================================= +// Tool Search +// ============================================================================= + +// ----------------------------------------------------------------------------- +// Tool Search Parameters +// ----------------------------------------------------------------------------- + +/** + * Schema for regex-based tool search input parameters. + * + * **Details** + * + * Claude constructs regex patterns using Python's `re.search()` syntax. + * Maximum query length: 200 characters. + * + * @category schemas + * @since 4.0.0 + */ +export const ToolSearchRegexParameters = Schema.Struct({ + /** + * Python regex pattern to search for tools. + */ + query: Schema.String +}) +/** + * Type of the parameters Claude supplies when invoking regex-based Anthropic tool search. + * + * **Details** + * + * Claude constructs regex patterns using Python's `re.search()` syntax. + * Maximum query length: 200 characters. + * + * @category models + * @since 4.0.0 + */ +export type ToolSearchRegexParameters = typeof ToolSearchRegexParameters.Type + +/** + * Defines input parameters for BM25/natural language tool search. + * + * **When to use** + * + * Use when validating or constructing the natural-language query payload for + * `ToolSearchBM25_20251119`. + * + * **Details** + * + * The payload contains Claude's natural-language `query`. BM25 searches tool + * names, descriptions, argument names, and argument descriptions. + * + * @see {@link ToolSearchBM25_20251119} for the provider-defined tool that consumes these parameters + * + * @category schemas + * @since 4.0.0 + */ +export const ToolSearchBM25Parameters = Schema.Struct({ + /** + * Natural language query to search for tools. + */ + query: Schema.String +}) +/** + * Type of the parameters Claude supplies when invoking BM25 natural-language Anthropic tool search. + * + * @category models + * @since 4.0.0 + */ +export type ToolSearchBM25Parameters = typeof ToolSearchBM25Parameters.Type + +// ----------------------------------------------------------------------------- +// Tool Search Tool Definitions +// ----------------------------------------------------------------------------- + +/** + * Defines regex-based tool search for Claude models. + * + * **Details** + * + * Claude constructs regex patterns using Python's `re.search()` syntax to + * find tools. The regex is matched against tool names, descriptions, + * argument names, and argument descriptions. + * Requires the "advanced-tool-use-2025-11-20" beta header. + * + * @category tools + * @since 4.0.0 + */ +export const ToolSearchRegex_20251119 = Tool.providerDefined({ + id: "anthropic.tool_search_tool_regex_20251119", + customName: "AnthropicToolSearchRegex", + providerName: "tool_search_tool_regex", + parameters: ToolSearchRegexParameters, + success: Schema.Array(Generated.BetaRequestToolReferenceBlock), + failure: Generated.BetaResponseToolSearchToolResultError +}) + +/** + * Defines BM25/natural language tool search for Claude models. + * + * **When to use** + * + * Use when you want Claude to find relevant tools from a natural-language query + * instead of a regex pattern. + * + * **Details** + * + * Claude uses natural language queries to search for tools using the + * BM25 algorithm. The search is performed against tool names, descriptions, + * argument names, and argument descriptions. + * Requires the "advanced-tool-use-2025-11-20" beta header. + * + * @see {@link ToolSearchRegex_20251119} for the regex-pattern alternative + * + * @category tools + * @since 4.0.0 + */ +export const ToolSearchBM25_20251119 = Tool.providerDefined({ + id: "anthropic.tool_search_tool_bm25_20251119", + customName: "AnthropicToolSearchBM25", + providerName: "tool_search_tool_bm25", + parameters: ToolSearchBM25Parameters, + success: Schema.Array(Generated.BetaRequestToolReferenceBlock), + failure: Generated.BetaResponseToolSearchToolResultError +}) diff --git a/.repos/effect/packages/ai/anthropic/src/Generated.ts b/.repos/effect/packages/ai/anthropic/src/Generated.ts new file mode 100644 index 000000000..319aeb25c --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/Generated.ts @@ -0,0 +1,12592 @@ +/** + * @since 4.0.0 + */ + +import * as Data from "effect/Data" +import * as Effect from "effect/Effect" +import type { SchemaError } from "effect/Schema" +import * as Schema from "effect/Schema" +import * as Stream from "effect/Stream" +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" +// non-recursive definitions +export type APIError = { readonly "message": string; readonly "type": "api_error" } +export const APIError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Internal server error" }), + "type": Schema.Literal("api_error").annotate({ "title": "Type", "default": "api_error" }) +}).annotate({ "title": "APIError" }) +export type AuthenticationError = { readonly "message": string; readonly "type": "authentication_error" } +export const AuthenticationError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Authentication error" }), + "type": Schema.Literal("authentication_error").annotate({ "title": "Type", "default": "authentication_error" }) +}).annotate({ "title": "AuthenticationError" }) +export type Base64ImageSource = { + readonly "data": string + readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" + readonly "type": "base64" +} +export const Base64ImageSource = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data", "format": "byte" }), + "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]).annotate({ + "title": "Media Type" + }), + "type": Schema.Literal("base64").annotate({ "title": "Type" }) +}).annotate({ "title": "Base64ImageSource" }) +export type Base64PDFSource = { + readonly "data": string + readonly "media_type": "application/pdf" + readonly "type": "base64" +} +export const Base64PDFSource = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data", "format": "byte" }), + "media_type": Schema.Literal("application/pdf").annotate({ "title": "Media Type" }), + "type": Schema.Literal("base64").annotate({ "title": "Type" }) +}).annotate({ "title": "Base64PDFSource" }) +export type BashCodeExecutionToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "output_file_too_large" +export const BashCodeExecutionToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "output_file_too_large" +]).annotate({ "title": "BashCodeExecutionToolResultErrorCode" }) +export type BetaAPIError = { readonly "message": string; readonly "type": "api_error" } +export const BetaAPIError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Internal server error" }), + "type": Schema.Literal("api_error").annotate({ "title": "Type", "default": "api_error" }) +}).annotate({ "title": "APIError" }) +export type BetaAllThinkingTurns = { readonly "type": "all" } +export const BetaAllThinkingTurns = Schema.Struct({ "type": Schema.Literal("all").annotate({ "title": "Type" }) }) + .annotate({ "title": "AllThinkingTurns" }) +export type BetaAuthenticationError = { readonly "message": string; readonly "type": "authentication_error" } +export const BetaAuthenticationError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Authentication error" }), + "type": Schema.Literal("authentication_error").annotate({ "title": "Type", "default": "authentication_error" }) +}).annotate({ "title": "AuthenticationError" }) +export type BetaBase64ImageSource = { + readonly "data": string + readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" + readonly "type": "base64" +} +export const BetaBase64ImageSource = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data", "format": "byte" }), + "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]).annotate({ + "title": "Media Type" + }), + "type": Schema.Literal("base64").annotate({ "title": "Type" }) +}).annotate({ "title": "Base64ImageSource" }) +export type BetaBase64PDFSource = { + readonly "data": string + readonly "media_type": "application/pdf" + readonly "type": "base64" +} +export const BetaBase64PDFSource = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data", "format": "byte" }), + "media_type": Schema.Literal("application/pdf").annotate({ "title": "Media Type" }), + "type": Schema.Literal("base64").annotate({ "title": "Type" }) +}).annotate({ "title": "Base64PDFSource" }) +export type BetaBashCodeExecutionToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "output_file_too_large" +export const BetaBashCodeExecutionToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "output_file_too_large" +]).annotate({ "title": "BashCodeExecutionToolResultErrorCode" }) +export type BetaBillingError = { readonly "message": string; readonly "type": "billing_error" } +export const BetaBillingError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Billing error" }), + "type": Schema.Literal("billing_error").annotate({ "title": "Type", "default": "billing_error" }) +}).annotate({ "title": "BillingError" }) +export type BetaBody_create_skill_v1_skills_post = { + readonly "display_title"?: string | null + readonly "files"?: ReadonlyArray | null +} +export const BetaBody_create_skill_v1_skills_post = Schema.Struct({ + "display_title": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }) + ), + "files": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String.annotate({ "format": "binary" })), Schema.Null]).annotate({ + "title": "Files", + "description": + "Files to upload for the skill.\n\nAll files must be in the same top-level directory and must include a SKILL.md file at the root of that directory." + }) + ) +}).annotate({ "title": "Body_create_skill_v1_skills_post" }) +export type BetaBody_create_skill_version_v1_skills__skill_id__versions_post = { + readonly "files"?: ReadonlyArray | null +} +export const BetaBody_create_skill_version_v1_skills__skill_id__versions_post = Schema.Struct({ + "files": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String.annotate({ "format": "binary" })), Schema.Null]).annotate({ + "title": "Files", + "description": + "Files to upload for the skill.\n\nAll files must be in the same top-level directory and must include a SKILL.md file at the root of that directory." + }) + ) +}).annotate({ "title": "Body_create_skill_version_v1_skills__skill_id__versions_post" }) +export type BetaCacheControlEphemeral = { readonly "ttl"?: "5m" | "1h"; readonly "type": "ephemeral" } +export const BetaCacheControlEphemeral = Schema.Struct({ + "ttl": Schema.optionalKey( + Schema.Literals(["5m", "1h"]).annotate({ + "title": "Ttl", + "description": + "The time-to-live for the cache control breakpoint.\n\nThis may be one the following values:\n- `5m`: 5 minutes\n- `1h`: 1 hour\n\nDefaults to `5m`." + }) + ), + "type": Schema.Literal("ephemeral").annotate({ "title": "Type" }) +}).annotate({ "title": "CacheControlEphemeral" }) +export type BetaCacheCreation = { + readonly "ephemeral_1h_input_tokens": number + readonly "ephemeral_5m_input_tokens": number +} +export const BetaCacheCreation = Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.Number.annotate({ + "title": "Ephemeral 1H Input Tokens", + "description": "The number of input tokens used to create the 1 hour cache entry.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "ephemeral_5m_input_tokens": Schema.Number.annotate({ + "title": "Ephemeral 5M Input Tokens", + "description": "The number of input tokens used to create the 5 minute cache entry.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ "title": "CacheCreation" }) +export type BetaCanceledResult = { readonly "type": "canceled" } +export const BetaCanceledResult = Schema.Struct({ + "type": Schema.Literal("canceled").annotate({ "title": "Type", "default": "canceled" }) +}).annotate({ "title": "CanceledResult" }) +export type BetaCodeExecutionToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" +export const BetaCodeExecutionToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded" +]).annotate({ "title": "CodeExecutionToolResultErrorCode" }) +export type BetaCompactionContentBlockDelta = { readonly "content": string | null; readonly "type": "compaction_delta" } +export const BetaCompactionContentBlockDelta = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Content" }), + "type": Schema.Literal("compaction_delta").annotate({ "title": "Type", "default": "compaction_delta" }) +}).annotate({ "title": "CompactionContentBlockDelta" }) +export type BetaContentBlockStopEvent = { readonly "index": number; readonly "type": "content_block_stop" } +export const BetaContentBlockStopEvent = Schema.Struct({ + "index": Schema.Number.annotate({ "title": "Index" }).check(Schema.isInt()), + "type": Schema.Literal("content_block_stop").annotate({ "title": "Type", "default": "content_block_stop" }) +}).annotate({ "title": "ContentBlockStopEvent" }) +export type BetaContextManagementResponse = { readonly "original_input_tokens": number } +export const BetaContextManagementResponse = Schema.Struct({ + "original_input_tokens": Schema.Number.annotate({ + "title": "Original Input Tokens", + "description": "The original token count before context management was applied" + }).check(Schema.isInt()) +}).annotate({ "title": "ContextManagementResponse" }) +export type BetaCreateSkillResponse = { + readonly "created_at": string + readonly "display_title": string | null + readonly "id": string + readonly "latest_version": string | null + readonly "source": string + readonly "type": string + readonly "updated_at": string +} +export const BetaCreateSkillResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill was created." + }), + "display_title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "latest_version": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Latest Version", + "description": + "The latest version identifier for the skill.\n\nThis represents the most recent version of the skill that has been created." + }), + "source": Schema.String.annotate({ + "title": "Source", + "description": + "Source of the skill.\n\nThis may be one of the following values:\n* `\"custom\"`: the skill was created by a user\n* `\"anthropic\"`: the skill was created by Anthropic" + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skills, this is always `\"skill\"`.", + "default": "skill" + }), + "updated_at": Schema.String.annotate({ + "title": "Updated At", + "description": "ISO 8601 timestamp of when the skill was last updated." + }) +}).annotate({ "title": "CreateSkillResponse" }) +export type BetaCreateSkillVersionResponse = { + readonly "created_at": string + readonly "description": string + readonly "directory": string + readonly "id": string + readonly "name": string + readonly "skill_id": string + readonly "type": string + readonly "version": string +} +export const BetaCreateSkillVersionResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill version was created." + }), + "description": Schema.String.annotate({ + "title": "Description", + "description": "Description of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "directory": Schema.String.annotate({ + "title": "Directory", + "description": + "Directory name of the skill version.\n\nThis is the top-level directory name that was extracted from the uploaded files." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill version.\n\nThe format and length of IDs may change over time." + }), + "name": Schema.String.annotate({ + "title": "Name", + "description": + "Human-readable name of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "skill_id": Schema.String.annotate({ + "title": "Skill Id", + "description": "Identifier for the skill that this version belongs to." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skill Versions, this is always `\"skill_version\"`.", + "default": "skill_version" + }), + "version": Schema.String.annotate({ + "title": "Version", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }) +}).annotate({ "title": "CreateSkillVersionResponse" }) +export type BetaDeleteMessageBatchResponse = { readonly "id": string; readonly "type": "message_batch_deleted" } +export const BetaDeleteMessageBatchResponse = Schema.Struct({ + "id": Schema.String.annotate({ "title": "Id", "description": "ID of the Message Batch." }), + "type": Schema.Literal("message_batch_deleted").annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor Message Batches, this is always `\"message_batch_deleted\"`.", + "default": "message_batch_deleted" + }) +}).annotate({ "title": "DeleteMessageBatchResponse" }) +export type BetaDeleteSkillResponse = { readonly "id": string; readonly "type": string } +export const BetaDeleteSkillResponse = Schema.Struct({ + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor Skills, this is always `\"skill_deleted\"`.", + "default": "skill_deleted" + }) +}).annotate({ "title": "DeleteSkillResponse" }) +export type BetaDeleteSkillVersionResponse = { readonly "id": string; readonly "type": string } +export const BetaDeleteSkillVersionResponse = Schema.Struct({ + "id": Schema.String.annotate({ + "title": "Id", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor Skill Versions, this is always `\"skill_version_deleted\"`.", + "default": "skill_version_deleted" + }) +}).annotate({ "title": "DeleteSkillVersionResponse" }) +export type BetaDirectCaller = { readonly "type": "direct" } +export const BetaDirectCaller = Schema.Struct({ "type": Schema.Literal("direct").annotate({ "title": "Type" }) }) + .annotate({ "title": "DirectCaller", "description": "Tool invocation directly from the model." }) +export type BetaEffortLevel = "low" | "medium" | "high" | "max" +export const BetaEffortLevel = Schema.Literals(["low", "medium", "high", "max"]).annotate({ + "title": "EffortLevel", + "description": "All possible effort levels." +}) +export type BetaExpiredResult = { readonly "type": "expired" } +export const BetaExpiredResult = Schema.Struct({ + "type": Schema.Literal("expired").annotate({ "title": "Type", "default": "expired" }) +}).annotate({ "title": "ExpiredResult" }) +export type BetaFileDeleteResponse = { readonly "id": string; readonly "type"?: "file_deleted" } +export const BetaFileDeleteResponse = Schema.Struct({ + "id": Schema.String.annotate({ "title": "Id", "description": "ID of the deleted file." }), + "type": Schema.optionalKey( + Schema.Literal("file_deleted").annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor file deletion, this is always `\"file_deleted\"`.", + "default": "file_deleted" + }) + ) +}).annotate({ "title": "FileDeleteResponse" }) +export type BetaFileDocumentSource = { readonly "file_id": string; readonly "type": "file" } +export const BetaFileDocumentSource = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("file").annotate({ "title": "Type" }) +}).annotate({ "title": "FileDocumentSource" }) +export type BetaFileImageSource = { readonly "file_id": string; readonly "type": "file" } +export const BetaFileImageSource = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("file").annotate({ "title": "Type" }) +}).annotate({ "title": "FileImageSource" }) +export type BetaFileMetadataSchema = { + 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 BetaFileMetadataSchema = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "RFC 3339 datetime string representing when the file was created.", + "format": "date-time" + }), + "downloadable": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Downloadable", + "description": "Whether the file can be downloaded.", + "default": false + }) + ), + "filename": Schema.String.annotate({ "title": "Filename", "description": "Original filename of the uploaded file." }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(500)), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique object identifier.\n\nThe format and length of IDs may change over time." + }), + "mime_type": Schema.String.annotate({ "title": "Mime Type", "description": "MIME type of the file." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(255)), + "size_bytes": Schema.Number.annotate({ "title": "Size Bytes", "description": "Size of the file in bytes." }).check( + Schema.isInt() + ).check(Schema.isGreaterThanOrEqualTo(0)), + "type": Schema.Literal("file").annotate({ + "title": "Type", + "description": "Object type.\n\nFor files, this is always `\"file\"`." + }) +}).annotate({ "title": "FileMetadataSchema" }) +export type BetaGatewayTimeoutError = { readonly "message": string; readonly "type": "timeout_error" } +export const BetaGatewayTimeoutError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Request timeout" }), + "type": Schema.Literal("timeout_error").annotate({ "title": "Type", "default": "timeout_error" }) +}).annotate({ "title": "GatewayTimeoutError" }) +export type BetaGetSkillResponse = { + readonly "created_at": string + readonly "display_title": string | null + readonly "id": string + readonly "latest_version": string | null + readonly "source": string + readonly "type": string + readonly "updated_at": string +} +export const BetaGetSkillResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill was created." + }), + "display_title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "latest_version": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Latest Version", + "description": + "The latest version identifier for the skill.\n\nThis represents the most recent version of the skill that has been created." + }), + "source": Schema.String.annotate({ + "title": "Source", + "description": + "Source of the skill.\n\nThis may be one of the following values:\n* `\"custom\"`: the skill was created by a user\n* `\"anthropic\"`: the skill was created by Anthropic" + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skills, this is always `\"skill\"`.", + "default": "skill" + }), + "updated_at": Schema.String.annotate({ + "title": "Updated At", + "description": "ISO 8601 timestamp of when the skill was last updated." + }) +}).annotate({ "title": "GetSkillResponse" }) +export type BetaGetSkillVersionResponse = { + readonly "created_at": string + readonly "description": string + readonly "directory": string + readonly "id": string + readonly "name": string + readonly "skill_id": string + readonly "type": string + readonly "version": string +} +export const BetaGetSkillVersionResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill version was created." + }), + "description": Schema.String.annotate({ + "title": "Description", + "description": "Description of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "directory": Schema.String.annotate({ + "title": "Directory", + "description": + "Directory name of the skill version.\n\nThis is the top-level directory name that was extracted from the uploaded files." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill version.\n\nThe format and length of IDs may change over time." + }), + "name": Schema.String.annotate({ + "title": "Name", + "description": + "Human-readable name of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "skill_id": Schema.String.annotate({ + "title": "Skill Id", + "description": "Identifier for the skill that this version belongs to." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skill Versions, this is always `\"skill_version\"`.", + "default": "skill_version" + }), + "version": Schema.String.annotate({ + "title": "Version", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }) +}).annotate({ "title": "GetSkillVersionResponse" }) +export type BetaInputJsonContentBlockDelta = { readonly "partial_json": string; readonly "type": "input_json_delta" } +export const BetaInputJsonContentBlockDelta = Schema.Struct({ + "partial_json": Schema.String.annotate({ "title": "Partial Json" }), + "type": Schema.Literal("input_json_delta").annotate({ "title": "Type", "default": "input_json_delta" }) +}).annotate({ "title": "InputJsonContentBlockDelta" }) +export type BetaInputTokensClearAtLeast = { readonly "type": "input_tokens"; readonly "value": number } +export const BetaInputTokensClearAtLeast = Schema.Struct({ + "type": Schema.Literal("input_tokens").annotate({ "title": "Type" }), + "value": Schema.Number.annotate({ "title": "Value" }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ "title": "InputTokensClearAtLeast" }) +export type BetaInputTokensTrigger = { readonly "type": "input_tokens"; readonly "value": number } +export const BetaInputTokensTrigger = Schema.Struct({ + "type": Schema.Literal("input_tokens").annotate({ "title": "Type" }), + "value": Schema.Number.annotate({ "title": "Value" }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)) +}).annotate({ "title": "InputTokensTrigger" }) +export type BetaInvalidRequestError = { readonly "message": string; readonly "type": "invalid_request_error" } +export const BetaInvalidRequestError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Invalid request" }), + "type": Schema.Literal("invalid_request_error").annotate({ "title": "Type", "default": "invalid_request_error" }) +}).annotate({ "title": "InvalidRequestError" }) +export type BetaJsonOutputFormat = { + readonly "schema": { readonly [x: string]: Schema.Json } + readonly "type": "json_schema" +} +export const BetaJsonOutputFormat = Schema.Struct({ + "schema": Schema.Record(Schema.String, Schema.Json).annotate({ + "title": "Schema", + "description": "The JSON schema of the format" + }), + "type": Schema.Literal("json_schema").annotate({ "title": "Type" }) +}).annotate({ "title": "JsonOutputFormat" }) +export type BetaJsonValue = unknown +export const BetaJsonValue = Schema.Unknown +export type BetaMCPToolConfig = { readonly "defer_loading"?: boolean; readonly "enabled"?: boolean } +export const BetaMCPToolConfig = Schema.Struct({ + "defer_loading": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Defer Loading" })), + "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Enabled" })) +}).annotate({ "title": "MCPToolConfig", "description": "Configuration for a specific tool in an MCP toolset." }) +export type BetaMessageBatch = { + readonly "archived_at": string | null + readonly "cancel_initiated_at": string | null + readonly "created_at": string + readonly "ended_at": string | null + readonly "expires_at": string + readonly "id": string + readonly "processing_status": "in_progress" | "canceling" | "ended" + readonly "request_counts": { + readonly "canceled": number + readonly "errored": number + readonly "expired": number + readonly "processing": number + readonly "succeeded": number + } + readonly "results_url": string | null + readonly "type": "message_batch" +} +export const BetaMessageBatch = Schema.Struct({ + "archived_at": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]).annotate({ + "title": "Archived At", + "description": + "RFC 3339 datetime string representing the time at which the Message Batch was archived and its results became unavailable." + }), + "cancel_initiated_at": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]).annotate({ + "title": "Cancel Initiated At", + "description": + "RFC 3339 datetime string representing the time at which cancellation was initiated for the Message Batch. Specified only if cancellation was initiated." + }), + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "RFC 3339 datetime string representing the time at which the Message Batch was created.", + "format": "date-time" + }), + "ended_at": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]).annotate({ + "title": "Ended At", + "description": + "RFC 3339 datetime string representing the time at which processing for the Message Batch ended. Specified only once processing ends.\n\nProcessing ends when every request in a Message Batch has either succeeded, errored, canceled, or expired." + }), + "expires_at": Schema.String.annotate({ + "title": "Expires At", + "description": + "RFC 3339 datetime string representing the time at which the Message Batch will expire and end processing, which is 24 hours after creation.", + "format": "date-time" + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique object identifier.\n\nThe format and length of IDs may change over time." + }), + "processing_status": Schema.Literals(["in_progress", "canceling", "ended"]).annotate({ + "title": "Processing Status", + "description": "Processing status of the Message Batch." + }), + "request_counts": Schema.Struct({ + "canceled": Schema.Number.annotate({ + "title": "Canceled", + "description": + "Number of requests in the Message Batch that have been canceled.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()), + "errored": Schema.Number.annotate({ + "title": "Errored", + "description": + "Number of requests in the Message Batch that encountered an error.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()), + "expired": Schema.Number.annotate({ + "title": "Expired", + "description": + "Number of requests in the Message Batch that have expired.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()), + "processing": Schema.Number.annotate({ + "title": "Processing", + "description": "Number of requests in the Message Batch that are processing.", + "default": 0 + }).check(Schema.isInt()), + "succeeded": Schema.Number.annotate({ + "title": "Succeeded", + "description": + "Number of requests in the Message Batch that have completed successfully.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()) + }).annotate({ + "title": "RequestCounts", + "description": + "Tallies requests within the Message Batch, categorized by their status.\n\nRequests start as `processing` and move to one of the other statuses only once processing of the entire batch ends. The sum of all values always matches the total number of requests in the batch." + }), + "results_url": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Results Url", + "description": + "URL to a `.jsonl` file containing the results of the Message Batch requests. Specified only once processing ends.\n\nResults in the file are not guaranteed to be in the same order as requests. Use the `custom_id` field to match results to requests." + }), + "type": Schema.Literal("message_batch").annotate({ + "title": "Type", + "description": "Object type.\n\nFor Message Batches, this is always `\"message_batch\"`.", + "default": "message_batch" + }) +}).annotate({ "title": "MessageBatch" }) +export type BetaMessageStopEvent = { readonly "type": "message_stop" } +export const BetaMessageStopEvent = Schema.Struct({ + "type": Schema.Literal("message_stop").annotate({ "title": "Type", "default": "message_stop" }) +}).annotate({ "title": "MessageStopEvent" }) +export type BetaModelInfo = { + readonly "created_at": string + readonly "display_name": string + readonly "id": string + readonly "type": "model" +} +export const BetaModelInfo = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": + "RFC 3339 datetime string representing the time at which the model was released. May be set to an epoch value if the release date is unknown.", + "format": "date-time" + }), + "display_name": Schema.String.annotate({ + "title": "Display Name", + "description": "A human-readable name for the model." + }), + "id": Schema.String.annotate({ "title": "Id", "description": "Unique model identifier." }), + "type": Schema.Literal("model").annotate({ + "title": "Type", + "description": "Object type.\n\nFor Models, this is always `\"model\"`.", + "default": "model" + }) +}).annotate({ "title": "ModelInfo" }) +export type BetaNotFoundError = { readonly "message": string; readonly "type": "not_found_error" } +export const BetaNotFoundError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Not found" }), + "type": Schema.Literal("not_found_error").annotate({ "title": "Type", "default": "not_found_error" }) +}).annotate({ "title": "NotFoundError" }) +export type BetaOverloadedError = { readonly "message": string; readonly "type": "overloaded_error" } +export const BetaOverloadedError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Overloaded" }), + "type": Schema.Literal("overloaded_error").annotate({ "title": "Type", "default": "overloaded_error" }) +}).annotate({ "title": "OverloadedError" }) +export type BetaPermissionError = { readonly "message": string; readonly "type": "permission_error" } +export const BetaPermissionError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Permission denied" }), + "type": Schema.Literal("permission_error").annotate({ "title": "Type", "default": "permission_error" }) +}).annotate({ "title": "PermissionError" }) +export type BetaPlainTextSource = { + readonly "data": string + readonly "media_type": "text/plain" + readonly "type": "text" +} +export const BetaPlainTextSource = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data" }), + "media_type": Schema.Literal("text/plain").annotate({ "title": "Media Type" }), + "type": Schema.Literal("text").annotate({ "title": "Type" }) +}).annotate({ "title": "PlainTextSource" }) +export type BetaRateLimitError = { readonly "message": string; readonly "type": "rate_limit_error" } +export const BetaRateLimitError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Rate limited" }), + "type": Schema.Literal("rate_limit_error").annotate({ "title": "Type", "default": "rate_limit_error" }) +}).annotate({ "title": "RateLimitError" }) +export type BetaRequestBashCodeExecutionOutputBlock = { + readonly "file_id": string + readonly "type": "bash_code_execution_output" +} +export const BetaRequestBashCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("bash_code_execution_output").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionOutputBlock" }) +export type BetaRequestCharLocationCitation = { + 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 BetaRequestCharLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([ + Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), + Schema.Null + ]).annotate({ "title": "Document Title" }), + "end_char_index": Schema.Number.annotate({ "title": "End Char Index" }).check(Schema.isInt()), + "start_char_index": Schema.Number.annotate({ "title": "Start Char Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("char_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCharLocationCitation" }) +export type BetaRequestCitationsConfig = { readonly "enabled"?: boolean } +export const BetaRequestCitationsConfig = Schema.Struct({ + "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Enabled" })) +}).annotate({ "title": "RequestCitationsConfig" }) +export type BetaRequestCodeExecutionOutputBlock = { + readonly "file_id": string + readonly "type": "code_execution_output" +} +export const BetaRequestCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("code_execution_output").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCodeExecutionOutputBlock" }) +export type BetaRequestContentBlockLocationCitation = { + 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 BetaRequestContentBlockLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([ + Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), + Schema.Null + ]).annotate({ "title": "Document Title" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("content_block_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestContentBlockLocationCitation" }) +export type BetaRequestMCPServerToolConfiguration = { + readonly "allowed_tools"?: ReadonlyArray | null + readonly "enabled"?: boolean | null +} +export const BetaRequestMCPServerToolConfiguration = Schema.Struct({ + "allowed_tools": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "title": "Allowed Tools" }) + ), + "enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "title": "Enabled" })) +}).annotate({ "title": "RequestMCPServerToolConfiguration" }) +export type BetaRequestPageLocationCitation = { + 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 BetaRequestPageLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([ + Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), + Schema.Null + ]).annotate({ "title": "Document Title" }), + "end_page_number": Schema.Number.annotate({ "title": "End Page Number" }).check(Schema.isInt()), + "start_page_number": Schema.Number.annotate({ "title": "Start Page Number" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(1) + ), + "type": Schema.Literal("page_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestPageLocationCitation" }) +export type BetaRequestSearchResultLocationCitation = { + 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 BetaRequestSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "search_result_index": Schema.Number.annotate({ "title": "Search Result Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "source": Schema.String.annotate({ "title": "Source" }), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Title" }), + "type": Schema.Literal("search_result_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestSearchResultLocationCitation" }) +export type BetaRequestTextEditorCodeExecutionCreateResultBlock = { + readonly "is_file_update": boolean + readonly "type": "text_editor_code_execution_create_result" +} +export const BetaRequestTextEditorCodeExecutionCreateResultBlock = Schema.Struct({ + "is_file_update": Schema.Boolean.annotate({ "title": "Is File Update" }), + "type": Schema.Literal("text_editor_code_execution_create_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionCreateResultBlock" }) +export type BetaRequestTextEditorCodeExecutionStrReplaceResultBlock = { + 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 BetaRequestTextEditorCodeExecutionStrReplaceResultBlock = Schema.Struct({ + "lines": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "title": "Lines" })), + "new_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "New Lines" }) + ), + "new_start": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "New Start" }) + ), + "old_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Old Lines" }) + ), + "old_start": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Old Start" }) + ), + "type": Schema.Literal("text_editor_code_execution_str_replace_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionStrReplaceResultBlock" }) +export type BetaRequestTextEditorCodeExecutionViewResultBlock = { + 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 BetaRequestTextEditorCodeExecutionViewResultBlock = Schema.Struct({ + "content": Schema.String.annotate({ "title": "Content" }), + "file_type": Schema.Literals(["text", "image", "pdf"]).annotate({ "title": "File Type" }), + "num_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Num Lines" }) + ), + "start_line": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Start Line" }) + ), + "total_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Total Lines" }) + ), + "type": Schema.Literal("text_editor_code_execution_view_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionViewResultBlock" }) +export type BetaRequestWebSearchResultBlock = { + readonly "encrypted_content": string + readonly "page_age"?: string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string +} +export const BetaRequestWebSearchResultBlock = Schema.Struct({ + "encrypted_content": Schema.String.annotate({ "title": "Encrypted Content" }), + "page_age": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Page Age" })), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "RequestWebSearchResultBlock" }) +export type BetaRequestWebSearchResultLocationCitation = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" + readonly "url": string +} +export const BetaRequestWebSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "encrypted_index": Schema.String.annotate({ "title": "Encrypted Index" }), + "title": Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(512)), Schema.Null]) + .annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result_location").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2048)) +}).annotate({ "title": "RequestWebSearchResultLocationCitation" }) +export type BetaResponseBashCodeExecutionOutputBlock = { + readonly "file_id": string + readonly "type": "bash_code_execution_output" +} +export const BetaResponseBashCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("bash_code_execution_output").annotate({ + "title": "Type", + "default": "bash_code_execution_output" + }) +}).annotate({ "title": "ResponseBashCodeExecutionOutputBlock" }) +export type BetaResponseCharLocationCitation = { + 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 BetaResponseCharLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Document Title" }), + "end_char_index": Schema.Number.annotate({ "title": "End Char Index" }).check(Schema.isInt()), + "file_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "File Id", "default": null }), + "start_char_index": Schema.Number.annotate({ "title": "Start Char Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("char_location").annotate({ "title": "Type", "default": "char_location" }) +}).annotate({ "title": "ResponseCharLocationCitation" }) +export type BetaResponseCitationsConfig = { readonly "enabled": boolean } +export const BetaResponseCitationsConfig = Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "title": "Enabled", "default": false }) +}).annotate({ "title": "ResponseCitationsConfig" }) +export type BetaResponseClearThinking20251015Edit = { + readonly "cleared_input_tokens": number + readonly "cleared_thinking_turns": number + readonly "type": "clear_thinking_20251015" +} +export const BetaResponseClearThinking20251015Edit = Schema.Struct({ + "cleared_input_tokens": Schema.Number.annotate({ + "title": "Cleared Input Tokens", + "description": "Number of input tokens cleared by this edit." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "cleared_thinking_turns": Schema.Number.annotate({ + "title": "Cleared Thinking Turns", + "description": "Number of thinking turns that were cleared." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "type": Schema.Literal("clear_thinking_20251015").annotate({ + "title": "Type", + "description": "The type of context management edit applied.", + "default": "clear_thinking_20251015" + }) +}).annotate({ "title": "ResponseClearThinking20251015Edit" }) +export type BetaResponseClearToolUses20250919Edit = { + readonly "cleared_input_tokens": number + readonly "cleared_tool_uses": number + readonly "type": "clear_tool_uses_20250919" +} +export const BetaResponseClearToolUses20250919Edit = Schema.Struct({ + "cleared_input_tokens": Schema.Number.annotate({ + "title": "Cleared Input Tokens", + "description": "Number of input tokens cleared by this edit." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "cleared_tool_uses": Schema.Number.annotate({ + "title": "Cleared Tool Uses", + "description": "Number of tool uses that were cleared." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "type": Schema.Literal("clear_tool_uses_20250919").annotate({ + "title": "Type", + "description": "The type of context management edit applied.", + "default": "clear_tool_uses_20250919" + }) +}).annotate({ "title": "ResponseClearToolUses20250919Edit" }) +export type BetaResponseCodeExecutionOutputBlock = { + readonly "file_id": string + readonly "type": "code_execution_output" +} +export const BetaResponseCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("code_execution_output").annotate({ "title": "Type", "default": "code_execution_output" }) +}).annotate({ "title": "ResponseCodeExecutionOutputBlock" }) +export type BetaResponseCompactionBlock = { readonly "content": string | null; readonly "type": "compaction" } +export const BetaResponseCompactionBlock = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Content", + "description": "Summary of compacted content, or null if compaction failed" + }), + "type": Schema.Literal("compaction").annotate({ "title": "Type", "default": "compaction" }) +}).annotate({ + "title": "ResponseCompactionBlock", + "description": + "A compaction block returned when autocompact is triggered.\n\nWhen content is None, it indicates the compaction failed to produce a valid\nsummary (e.g., malformed output from the model). Clients may round-trip\ncompaction blocks with null content; the server treats them as no-ops." +}) +export type BetaResponseContainerUploadBlock = { readonly "file_id": string; readonly "type": "container_upload" } +export const BetaResponseContainerUploadBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("container_upload").annotate({ "title": "Type", "default": "container_upload" }) +}).annotate({ + "title": "ResponseContainerUploadBlock", + "description": "Response model for a file uploaded to the container." +}) +export type BetaResponseContentBlockLocationCitation = { + 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 BetaResponseContentBlockLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Document Title" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "file_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "File Id", "default": null }), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("content_block_location").annotate({ "title": "Type", "default": "content_block_location" }) +}).annotate({ "title": "ResponseContentBlockLocationCitation" }) +export type BetaResponseMCPToolUseBlock = { + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": string + readonly "server_name": string + readonly "type": "mcp_tool_use" +} +export const BetaResponseMCPToolUseBlock = Schema.Struct({ + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.String.annotate({ "title": "Name", "description": "The name of the MCP tool" }), + "server_name": Schema.String.annotate({ "title": "Server Name", "description": "The name of the MCP server" }), + "type": Schema.Literal("mcp_tool_use").annotate({ "title": "Type", "default": "mcp_tool_use" }) +}).annotate({ "title": "ResponseMCPToolUseBlock" }) +export type BetaResponsePageLocationCitation = { + 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 BetaResponsePageLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Document Title" }), + "end_page_number": Schema.Number.annotate({ "title": "End Page Number" }).check(Schema.isInt()), + "file_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "File Id", "default": null }), + "start_page_number": Schema.Number.annotate({ "title": "Start Page Number" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(1) + ), + "type": Schema.Literal("page_location").annotate({ "title": "Type", "default": "page_location" }) +}).annotate({ "title": "ResponsePageLocationCitation" }) +export type BetaResponseRedactedThinkingBlock = { readonly "data": string; readonly "type": "redacted_thinking" } +export const BetaResponseRedactedThinkingBlock = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data" }), + "type": Schema.Literal("redacted_thinking").annotate({ "title": "Type", "default": "redacted_thinking" }) +}).annotate({ "title": "ResponseRedactedThinkingBlock" }) +export type BetaResponseSearchResultLocationCitation = { + 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 BetaResponseSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "search_result_index": Schema.Number.annotate({ "title": "Search Result Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "source": Schema.String.annotate({ "title": "Source" }), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Title" }), + "type": Schema.Literal("search_result_location").annotate({ "title": "Type", "default": "search_result_location" }) +}).annotate({ "title": "ResponseSearchResultLocationCitation" }) +export type BetaResponseTextEditorCodeExecutionCreateResultBlock = { + readonly "is_file_update": boolean + readonly "type": "text_editor_code_execution_create_result" +} +export const BetaResponseTextEditorCodeExecutionCreateResultBlock = Schema.Struct({ + "is_file_update": Schema.Boolean.annotate({ "title": "Is File Update" }), + "type": Schema.Literal("text_editor_code_execution_create_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_create_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionCreateResultBlock" }) +export type BetaResponseTextEditorCodeExecutionStrReplaceResultBlock = { + 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 BetaResponseTextEditorCodeExecutionStrReplaceResultBlock = Schema.Struct({ + "lines": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "title": "Lines", "default": null }), + "new_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "New Lines", + "default": null + }), + "new_start": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "New Start", + "default": null + }), + "old_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Old Lines", + "default": null + }), + "old_start": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Old Start", + "default": null + }), + "type": Schema.Literal("text_editor_code_execution_str_replace_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_str_replace_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionStrReplaceResultBlock" }) +export type BetaResponseTextEditorCodeExecutionViewResultBlock = { + 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 BetaResponseTextEditorCodeExecutionViewResultBlock = Schema.Struct({ + "content": Schema.String.annotate({ "title": "Content" }), + "file_type": Schema.Literals(["text", "image", "pdf"]).annotate({ "title": "File Type" }), + "num_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Num Lines", + "default": null + }), + "start_line": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Start Line", + "default": null + }), + "total_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Total Lines", + "default": null + }), + "type": Schema.Literal("text_editor_code_execution_view_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_view_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionViewResultBlock" }) +export type BetaResponseThinkingBlock = { + readonly "signature": string + readonly "thinking": string + readonly "type": "thinking" +} +export const BetaResponseThinkingBlock = Schema.Struct({ + "signature": Schema.String.annotate({ "title": "Signature" }), + "thinking": Schema.String.annotate({ "title": "Thinking" }), + "type": Schema.Literal("thinking").annotate({ "title": "Type", "default": "thinking" }) +}).annotate({ "title": "ResponseThinkingBlock" }) +export type BetaResponseToolReferenceBlock = { readonly "tool_name": string; readonly "type": "tool_reference" } +export const BetaResponseToolReferenceBlock = Schema.Struct({ + "tool_name": Schema.String.annotate({ "title": "Tool Name" }).check(Schema.isMinLength(1)).check( + Schema.isMaxLength(256) + ).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,256}$"))), + "type": Schema.Literal("tool_reference").annotate({ "title": "Type", "default": "tool_reference" }) +}).annotate({ "title": "ResponseToolReferenceBlock" }) +export type BetaResponseWebSearchResultBlock = { + readonly "encrypted_content": string + readonly "page_age": string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string +} +export const BetaResponseWebSearchResultBlock = Schema.Struct({ + "encrypted_content": Schema.String.annotate({ "title": "Encrypted Content" }), + "page_age": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Page Age", "default": null }), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result").annotate({ "title": "Type", "default": "web_search_result" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "ResponseWebSearchResultBlock" }) +export type BetaResponseWebSearchResultLocationCitation = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" + readonly "url": string +} +export const BetaResponseWebSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "encrypted_index": Schema.String.annotate({ "title": "Encrypted Index" }), + "title": Schema.Union([Schema.String.check(Schema.isMaxLength(512)), Schema.Null]).annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result_location").annotate({ + "title": "Type", + "default": "web_search_result_location" + }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "ResponseWebSearchResultLocationCitation" }) +export type BetaServerToolCaller = { readonly "tool_id": string; readonly "type": "code_execution_20250825" } +export const BetaServerToolCaller = Schema.Struct({ + "tool_id": Schema.String.annotate({ "title": "Tool Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_20250825").annotate({ "title": "Type" }) +}).annotate({ "title": "ServerToolCaller", "description": "Tool invocation generated by a server-side tool." }) +export type BetaServerToolCaller_20260120 = { readonly "tool_id": string; readonly "type": "code_execution_20260120" } +export const BetaServerToolCaller_20260120 = Schema.Struct({ + "tool_id": Schema.String.annotate({ "title": "Tool Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_20260120").annotate({ "title": "Type" }) +}).annotate({ "title": "ServerToolCaller_20260120" }) +export type BetaServerToolUsage = { readonly "web_fetch_requests": number; readonly "web_search_requests": number } +export const BetaServerToolUsage = Schema.Struct({ + "web_fetch_requests": Schema.Number.annotate({ + "title": "Web Fetch Requests", + "description": "The number of web fetch tool requests.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "web_search_requests": Schema.Number.annotate({ + "title": "Web Search Requests", + "description": "The number of web search tool requests.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ "title": "ServerToolUsage" }) +export type BetaSignatureContentBlockDelta = { readonly "signature": string; readonly "type": "signature_delta" } +export const BetaSignatureContentBlockDelta = Schema.Struct({ + "signature": Schema.String.annotate({ "title": "Signature" }), + "type": Schema.Literal("signature_delta").annotate({ "title": "Type", "default": "signature_delta" }) +}).annotate({ "title": "SignatureContentBlockDelta" }) +export type BetaSkill = { + readonly "skill_id": string + readonly "type": "anthropic" | "custom" + readonly "version": string +} +export const BetaSkill = Schema.Struct({ + "skill_id": Schema.String.annotate({ "title": "Skill Id", "description": "Skill ID" }).check(Schema.isMinLength(1)) + .check(Schema.isMaxLength(64)), + "type": Schema.Literals(["anthropic", "custom"]).annotate({ + "title": "Type", + "description": "Type of skill - either 'anthropic' (built-in) or 'custom' (user-defined)" + }), + "version": Schema.String.annotate({ + "title": "Version", + "description": "Skill version or 'latest' for most recent version" + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)) +}).annotate({ "title": "Skill", "description": "A skill that was loaded in a container (response model)." }) +export type BetaSkillParams = { + readonly "skill_id": string + readonly "type": "anthropic" | "custom" + readonly "version"?: string +} +export const BetaSkillParams = Schema.Struct({ + "skill_id": Schema.String.annotate({ "title": "Skill Id", "description": "Skill ID" }).check(Schema.isMinLength(1)) + .check(Schema.isMaxLength(64)), + "type": Schema.Literals(["anthropic", "custom"]).annotate({ + "title": "Type", + "description": "Type of skill - either 'anthropic' (built-in) or 'custom' (user-defined)" + }), + "version": Schema.optionalKey( + Schema.String.annotate({ "title": "Version", "description": "Skill version or 'latest' for most recent version" }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)) + ) +}).annotate({ + "title": "SkillParams", + "description": "Specification for a skill to be loaded in a container (request model)." +}) +export type BetaSkillVersion = { + readonly "created_at": string + readonly "description": string + readonly "directory": string + readonly "id": string + readonly "name": string + readonly "skill_id": string + readonly "type": string + readonly "version": string +} +export const BetaSkillVersion = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill version was created." + }), + "description": Schema.String.annotate({ + "title": "Description", + "description": "Description of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "directory": Schema.String.annotate({ + "title": "Directory", + "description": + "Directory name of the skill version.\n\nThis is the top-level directory name that was extracted from the uploaded files." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill version.\n\nThe format and length of IDs may change over time." + }), + "name": Schema.String.annotate({ + "title": "Name", + "description": + "Human-readable name of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "skill_id": Schema.String.annotate({ + "title": "Skill Id", + "description": "Identifier for the skill that this version belongs to." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skill Versions, this is always `\"skill_version\"`.", + "default": "skill_version" + }), + "version": Schema.String.annotate({ + "title": "Version", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }) +}).annotate({ "title": "SkillVersion" }) +export type BetaSpeed = "standard" | "fast" +export const BetaSpeed = Schema.Literals(["standard", "fast"]).annotate({ "title": "Speed" }) +export type BetaTextContentBlockDelta = { readonly "text": string; readonly "type": "text_delta" } +export const BetaTextContentBlockDelta = Schema.Struct({ + "text": Schema.String.annotate({ "title": "Text" }), + "type": Schema.Literal("text_delta").annotate({ "title": "Type", "default": "text_delta" }) +}).annotate({ "title": "TextContentBlockDelta" }) +export type BetaTextEditorCodeExecutionToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "file_not_found" +export const BetaTextEditorCodeExecutionToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "file_not_found" +]).annotate({ "title": "TextEditorCodeExecutionToolResultErrorCode" }) +export type BetaThinkingConfigAdaptive = { readonly "type": "adaptive" } +export const BetaThinkingConfigAdaptive = Schema.Struct({ + "type": Schema.Literal("adaptive").annotate({ "title": "Type" }) +}).annotate({ "title": "ThinkingConfigAdaptive" }) +export type BetaThinkingConfigDisabled = { readonly "type": "disabled" } +export const BetaThinkingConfigDisabled = Schema.Struct({ + "type": Schema.Literal("disabled").annotate({ "title": "Type" }) +}).annotate({ "title": "ThinkingConfigDisabled" }) +export type BetaThinkingConfigEnabled = { readonly "budget_tokens": number; readonly "type": "enabled" } +export const BetaThinkingConfigEnabled = Schema.Struct({ + "budget_tokens": Schema.Number.annotate({ + "title": "Budget Tokens", + "description": + "Determines how many tokens Claude can use for its internal reasoning process. Larger budgets can enable more thorough analysis for complex problems, improving response quality.\n\nMust be ≥1024 and less than `max_tokens`.\n\nSee [extended thinking](https://docs.claude.com/en/docs/build-with-claude/extended-thinking) for details." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1024)), + "type": Schema.Literal("enabled").annotate({ "title": "Type" }) +}).annotate({ "title": "ThinkingConfigEnabled" }) +export type BetaThinkingContentBlockDelta = { readonly "thinking": string; readonly "type": "thinking_delta" } +export const BetaThinkingContentBlockDelta = Schema.Struct({ + "thinking": Schema.String.annotate({ "title": "Thinking" }), + "type": Schema.Literal("thinking_delta").annotate({ "title": "Type", "default": "thinking_delta" }) +}).annotate({ "title": "ThinkingContentBlockDelta" }) +export type BetaThinkingTurns = { readonly "type": "thinking_turns"; readonly "value": number } +export const BetaThinkingTurns = Schema.Struct({ + "type": Schema.Literal("thinking_turns").annotate({ "title": "Type" }), + "value": Schema.Number.annotate({ "title": "Value" }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)) +}).annotate({ "title": "ThinkingTurns" }) +export type BetaToolChoiceAny = { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "any" } +export const BetaToolChoiceAny = Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Disable Parallel Tool Use", + "description": + "Whether to disable parallel tool use.\n\nDefaults to `false`. If set to `true`, the model will output exactly one tool use." + }) + ), + "type": Schema.Literal("any").annotate({ "title": "Type" }) +}).annotate({ "title": "ToolChoiceAny", "description": "The model will use any available tools." }) +export type BetaToolChoiceAuto = { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "auto" } +export const BetaToolChoiceAuto = Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Disable Parallel Tool Use", + "description": + "Whether to disable parallel tool use.\n\nDefaults to `false`. If set to `true`, the model will output at most one tool use." + }) + ), + "type": Schema.Literal("auto").annotate({ "title": "Type" }) +}).annotate({ "title": "ToolChoiceAuto", "description": "The model will automatically decide whether to use tools." }) +export type BetaToolChoiceNone = { readonly "type": "none" } +export const BetaToolChoiceNone = Schema.Struct({ "type": Schema.Literal("none").annotate({ "title": "Type" }) }) + .annotate({ "title": "ToolChoiceNone", "description": "The model will not be allowed to use tools." }) +export type BetaToolChoiceTool = { + readonly "disable_parallel_tool_use"?: boolean + readonly "name": string + readonly "type": "tool" +} +export const BetaToolChoiceTool = Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Disable Parallel Tool Use", + "description": + "Whether to disable parallel tool use.\n\nDefaults to `false`. If set to `true`, the model will output exactly one tool use." + }) + ), + "name": Schema.String.annotate({ "title": "Name", "description": "The name of the tool to use." }), + "type": Schema.Literal("tool").annotate({ "title": "Type" }) +}).annotate({ + "title": "ToolChoiceTool", + "description": "The model will use the specified tool with `tool_choice.name`." +}) +export type BetaToolSearchToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" +export const BetaToolSearchToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded" +]).annotate({ "title": "ToolSearchToolResultErrorCode" }) +export type BetaToolUsesKeep = { readonly "type": "tool_uses"; readonly "value": number } +export const BetaToolUsesKeep = Schema.Struct({ + "type": Schema.Literal("tool_uses").annotate({ "title": "Type" }), + "value": Schema.Number.annotate({ "title": "Value" }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ "title": "ToolUsesKeep" }) +export type BetaToolUsesTrigger = { readonly "type": "tool_uses"; readonly "value": number } +export const BetaToolUsesTrigger = Schema.Struct({ + "type": Schema.Literal("tool_uses").annotate({ "title": "Type" }), + "value": Schema.Number.annotate({ "title": "Value" }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)) +}).annotate({ "title": "ToolUsesTrigger" }) +export type BetaURLImageSource = { readonly "type": "url"; readonly "url": string } +export const BetaURLImageSource = Schema.Struct({ + "type": Schema.Literal("url").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "URLImageSource" }) +export type BetaURLPDFSource = { readonly "type": "url"; readonly "url": string } +export const BetaURLPDFSource = Schema.Struct({ + "type": Schema.Literal("url").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "URLPDFSource" }) +export type BetaUserLocation = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" +} +export const BetaUserLocation = Schema.Struct({ + "city": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), Schema.Null]).annotate({ + "title": "City", + "description": "The city of the user." + }) + ), + "country": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(2)).check(Schema.isMaxLength(2)), Schema.Null]).annotate({ + "title": "Country", + "description": "The two letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) of the user." + }) + ), + "region": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), Schema.Null]).annotate({ + "title": "Region", + "description": "The region of the user." + }) + ), + "timezone": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), Schema.Null]).annotate({ + "title": "Timezone", + "description": "The [IANA timezone](https://nodatime.org/TimeZones) of the user." + }) + ), + "type": Schema.Literal("approximate").annotate({ "title": "Type" }) +}).annotate({ "title": "UserLocation" }) +export type BetaWebFetchToolResultErrorCode = + | "invalid_tool_input" + | "url_too_long" + | "url_not_allowed" + | "url_not_accessible" + | "unsupported_content_type" + | "too_many_requests" + | "max_uses_exceeded" + | "unavailable" +export const BetaWebFetchToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "url_too_long", + "url_not_allowed", + "url_not_accessible", + "unsupported_content_type", + "too_many_requests", + "max_uses_exceeded", + "unavailable" +]).annotate({ "title": "WebFetchToolResultErrorCode" }) +export type BetaWebSearchToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "max_uses_exceeded" + | "too_many_requests" + | "query_too_long" + | "request_too_large" +export const BetaWebSearchToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "max_uses_exceeded", + "too_many_requests", + "query_too_long", + "request_too_large" +]).annotate({ "title": "WebSearchToolResultErrorCode" }) +export type Betaapi__schemas__skills__Skill = { + readonly "created_at": string + readonly "display_title": string | null + readonly "id": string + readonly "latest_version": string | null + readonly "source": string + readonly "type": string + readonly "updated_at": string +} +export const Betaapi__schemas__skills__Skill = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill was created." + }), + "display_title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "latest_version": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Latest Version", + "description": + "The latest version identifier for the skill.\n\nThis represents the most recent version of the skill that has been created." + }), + "source": Schema.String.annotate({ + "title": "Source", + "description": + "Source of the skill.\n\nThis may be one of the following values:\n* `\"custom\"`: the skill was created by a user\n* `\"anthropic\"`: the skill was created by Anthropic" + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skills, this is always `\"skill\"`.", + "default": "skill" + }), + "updated_at": Schema.String.annotate({ + "title": "Updated At", + "description": "ISO 8601 timestamp of when the skill was last updated." + }) +}).annotate({ "title": "Skill" }) +export type BillingError = { readonly "message": string; readonly "type": "billing_error" } +export const BillingError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Billing error" }), + "type": Schema.Literal("billing_error").annotate({ "title": "Type", "default": "billing_error" }) +}).annotate({ "title": "BillingError" }) +export type Body_create_skill_v1_skills_post = { + readonly "display_title"?: string | null + readonly "files"?: ReadonlyArray | null +} +export const Body_create_skill_v1_skills_post = Schema.Struct({ + "display_title": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }) + ), + "files": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String.annotate({ "format": "binary" })), Schema.Null]).annotate({ + "title": "Files", + "description": + "Files to upload for the skill.\n\nAll files must be in the same top-level directory and must include a SKILL.md file at the root of that directory." + }) + ) +}).annotate({ "title": "Body_create_skill_v1_skills_post" }) +export type Body_create_skill_version_v1_skills__skill_id__versions_post = { + readonly "files"?: ReadonlyArray | null +} +export const Body_create_skill_version_v1_skills__skill_id__versions_post = Schema.Struct({ + "files": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String.annotate({ "format": "binary" })), Schema.Null]).annotate({ + "title": "Files", + "description": + "Files to upload for the skill.\n\nAll files must be in the same top-level directory and must include a SKILL.md file at the root of that directory." + }) + ) +}).annotate({ "title": "Body_create_skill_version_v1_skills__skill_id__versions_post" }) +export type CacheControlEphemeral = { readonly "ttl"?: "5m" | "1h"; readonly "type": "ephemeral" } +export const CacheControlEphemeral = Schema.Struct({ + "ttl": Schema.optionalKey( + Schema.Literals(["5m", "1h"]).annotate({ + "title": "Ttl", + "description": + "The time-to-live for the cache control breakpoint.\n\nThis may be one the following values:\n- `5m`: 5 minutes\n- `1h`: 1 hour\n\nDefaults to `5m`." + }) + ), + "type": Schema.Literal("ephemeral").annotate({ "title": "Type" }) +}).annotate({ "title": "CacheControlEphemeral" }) +export type CacheCreation = { + readonly "ephemeral_1h_input_tokens": number + readonly "ephemeral_5m_input_tokens": number +} +export const CacheCreation = Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.Number.annotate({ + "title": "Ephemeral 1H Input Tokens", + "description": "The number of input tokens used to create the 1 hour cache entry.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "ephemeral_5m_input_tokens": Schema.Number.annotate({ + "title": "Ephemeral 5M Input Tokens", + "description": "The number of input tokens used to create the 5 minute cache entry.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ "title": "CacheCreation" }) +export type CanceledResult = { readonly "type": "canceled" } +export const CanceledResult = Schema.Struct({ + "type": Schema.Literal("canceled").annotate({ "title": "Type", "default": "canceled" }) +}).annotate({ "title": "CanceledResult" }) +export type CodeExecutionToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" +export const CodeExecutionToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded" +]).annotate({ "title": "CodeExecutionToolResultErrorCode" }) +export type Container = { readonly "expires_at": string; readonly "id": string } +export const Container = Schema.Struct({ + "expires_at": Schema.String.annotate({ + "title": "Expires At", + "description": "The time at which the container will expire.", + "format": "date-time" + }), + "id": Schema.String.annotate({ "title": "Id", "description": "Identifier for the container used in this request" }) +}).annotate({ + "title": "Container", + "description": "Information about the container used in the request (for the code execution tool)" +}) +export type ContentBlockStopEvent = { readonly "index": number; readonly "type": "content_block_stop" } +export const ContentBlockStopEvent = Schema.Struct({ + "index": Schema.Number.annotate({ "title": "Index" }).check(Schema.isInt()), + "type": Schema.Literal("content_block_stop").annotate({ "title": "Type", "default": "content_block_stop" }) +}).annotate({ "title": "ContentBlockStopEvent" }) +export type CountMessageTokensResponse = { readonly "input_tokens": number } +export const CountMessageTokensResponse = Schema.Struct({ + "input_tokens": Schema.Number.annotate({ + "title": "Input Tokens", + "description": "The total number of tokens across the provided list of messages, system prompt, and tools." + }).check(Schema.isInt()) +}).annotate({ "title": "CountMessageTokensResponse" }) +export type CreateSkillResponse = { + readonly "created_at": string + readonly "display_title": string | null + readonly "id": string + readonly "latest_version": string | null + readonly "source": string + readonly "type": string + readonly "updated_at": string +} +export const CreateSkillResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill was created." + }), + "display_title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "latest_version": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Latest Version", + "description": + "The latest version identifier for the skill.\n\nThis represents the most recent version of the skill that has been created." + }), + "source": Schema.String.annotate({ + "title": "Source", + "description": + "Source of the skill.\n\nThis may be one of the following values:\n* `\"custom\"`: the skill was created by a user\n* `\"anthropic\"`: the skill was created by Anthropic" + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skills, this is always `\"skill\"`.", + "default": "skill" + }), + "updated_at": Schema.String.annotate({ + "title": "Updated At", + "description": "ISO 8601 timestamp of when the skill was last updated." + }) +}).annotate({ "title": "CreateSkillResponse" }) +export type CreateSkillVersionResponse = { + readonly "created_at": string + readonly "description": string + readonly "directory": string + readonly "id": string + readonly "name": string + readonly "skill_id": string + readonly "type": string + readonly "version": string +} +export const CreateSkillVersionResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill version was created." + }), + "description": Schema.String.annotate({ + "title": "Description", + "description": "Description of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "directory": Schema.String.annotate({ + "title": "Directory", + "description": + "Directory name of the skill version.\n\nThis is the top-level directory name that was extracted from the uploaded files." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill version.\n\nThe format and length of IDs may change over time." + }), + "name": Schema.String.annotate({ + "title": "Name", + "description": + "Human-readable name of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "skill_id": Schema.String.annotate({ + "title": "Skill Id", + "description": "Identifier for the skill that this version belongs to." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skill Versions, this is always `\"skill_version\"`.", + "default": "skill_version" + }), + "version": Schema.String.annotate({ + "title": "Version", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }) +}).annotate({ "title": "CreateSkillVersionResponse" }) +export type DeleteMessageBatchResponse = { readonly "id": string; readonly "type": "message_batch_deleted" } +export const DeleteMessageBatchResponse = Schema.Struct({ + "id": Schema.String.annotate({ "title": "Id", "description": "ID of the Message Batch." }), + "type": Schema.Literal("message_batch_deleted").annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor Message Batches, this is always `\"message_batch_deleted\"`.", + "default": "message_batch_deleted" + }) +}).annotate({ "title": "DeleteMessageBatchResponse" }) +export type DeleteSkillResponse = { readonly "id": string; readonly "type": string } +export const DeleteSkillResponse = Schema.Struct({ + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor Skills, this is always `\"skill_deleted\"`.", + "default": "skill_deleted" + }) +}).annotate({ "title": "DeleteSkillResponse" }) +export type DeleteSkillVersionResponse = { readonly "id": string; readonly "type": string } +export const DeleteSkillVersionResponse = Schema.Struct({ + "id": Schema.String.annotate({ + "title": "Id", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor Skill Versions, this is always `\"skill_version_deleted\"`.", + "default": "skill_version_deleted" + }) +}).annotate({ "title": "DeleteSkillVersionResponse" }) +export type DirectCaller = { readonly "type": "direct" } +export const DirectCaller = Schema.Struct({ "type": Schema.Literal("direct").annotate({ "title": "Type" }) }).annotate({ + "title": "DirectCaller", + "description": "Tool invocation directly from the model." +}) +export type EffortLevel = "low" | "medium" | "high" | "max" +export const EffortLevel = Schema.Literals(["low", "medium", "high", "max"]).annotate({ + "title": "EffortLevel", + "description": "All possible effort levels." +}) +export type ExpiredResult = { readonly "type": "expired" } +export const ExpiredResult = Schema.Struct({ + "type": Schema.Literal("expired").annotate({ "title": "Type", "default": "expired" }) +}).annotate({ "title": "ExpiredResult" }) +export type FileDeleteResponse = { readonly "id": string; readonly "type"?: "file_deleted" } +export const FileDeleteResponse = Schema.Struct({ + "id": Schema.String.annotate({ "title": "Id", "description": "ID of the deleted file." }), + "type": Schema.optionalKey( + Schema.Literal("file_deleted").annotate({ + "title": "Type", + "description": "Deleted object type.\n\nFor file deletion, this is always `\"file_deleted\"`.", + "default": "file_deleted" + }) + ) +}).annotate({ "title": "FileDeleteResponse" }) +export type FileMetadataSchema = { + 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 FileMetadataSchema = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "RFC 3339 datetime string representing when the file was created.", + "format": "date-time" + }), + "downloadable": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Downloadable", + "description": "Whether the file can be downloaded.", + "default": false + }) + ), + "filename": Schema.String.annotate({ "title": "Filename", "description": "Original filename of the uploaded file." }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(500)), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique object identifier.\n\nThe format and length of IDs may change over time." + }), + "mime_type": Schema.String.annotate({ "title": "Mime Type", "description": "MIME type of the file." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(255)), + "size_bytes": Schema.Number.annotate({ "title": "Size Bytes", "description": "Size of the file in bytes." }).check( + Schema.isInt() + ).check(Schema.isGreaterThanOrEqualTo(0)), + "type": Schema.Literal("file").annotate({ + "title": "Type", + "description": "Object type.\n\nFor files, this is always `\"file\"`." + }) +}).annotate({ "title": "FileMetadataSchema" }) +export type GatewayTimeoutError = { readonly "message": string; readonly "type": "timeout_error" } +export const GatewayTimeoutError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Request timeout" }), + "type": Schema.Literal("timeout_error").annotate({ "title": "Type", "default": "timeout_error" }) +}).annotate({ "title": "GatewayTimeoutError" }) +export type GetSkillResponse = { + readonly "created_at": string + readonly "display_title": string | null + readonly "id": string + readonly "latest_version": string | null + readonly "source": string + readonly "type": string + readonly "updated_at": string +} +export const GetSkillResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill was created." + }), + "display_title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "latest_version": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Latest Version", + "description": + "The latest version identifier for the skill.\n\nThis represents the most recent version of the skill that has been created." + }), + "source": Schema.String.annotate({ + "title": "Source", + "description": + "Source of the skill.\n\nThis may be one of the following values:\n* `\"custom\"`: the skill was created by a user\n* `\"anthropic\"`: the skill was created by Anthropic" + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skills, this is always `\"skill\"`.", + "default": "skill" + }), + "updated_at": Schema.String.annotate({ + "title": "Updated At", + "description": "ISO 8601 timestamp of when the skill was last updated." + }) +}).annotate({ "title": "GetSkillResponse" }) +export type GetSkillVersionResponse = { + readonly "created_at": string + readonly "description": string + readonly "directory": string + readonly "id": string + readonly "name": string + readonly "skill_id": string + readonly "type": string + readonly "version": string +} +export const GetSkillVersionResponse = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill version was created." + }), + "description": Schema.String.annotate({ + "title": "Description", + "description": "Description of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "directory": Schema.String.annotate({ + "title": "Directory", + "description": + "Directory name of the skill version.\n\nThis is the top-level directory name that was extracted from the uploaded files." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill version.\n\nThe format and length of IDs may change over time." + }), + "name": Schema.String.annotate({ + "title": "Name", + "description": + "Human-readable name of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "skill_id": Schema.String.annotate({ + "title": "Skill Id", + "description": "Identifier for the skill that this version belongs to." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skill Versions, this is always `\"skill_version\"`.", + "default": "skill_version" + }), + "version": Schema.String.annotate({ + "title": "Version", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }) +}).annotate({ "title": "GetSkillVersionResponse" }) +export type InputJsonContentBlockDelta = { readonly "partial_json": string; readonly "type": "input_json_delta" } +export const InputJsonContentBlockDelta = Schema.Struct({ + "partial_json": Schema.String.annotate({ "title": "Partial Json" }), + "type": Schema.Literal("input_json_delta").annotate({ "title": "Type", "default": "input_json_delta" }) +}).annotate({ "title": "InputJsonContentBlockDelta" }) +export type InvalidRequestError = { readonly "message": string; readonly "type": "invalid_request_error" } +export const InvalidRequestError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Invalid request" }), + "type": Schema.Literal("invalid_request_error").annotate({ "title": "Type", "default": "invalid_request_error" }) +}).annotate({ "title": "InvalidRequestError" }) +export type JsonOutputFormat = { + readonly "schema": { readonly [x: string]: Schema.Json } + readonly "type": "json_schema" +} +export const JsonOutputFormat = Schema.Struct({ + "schema": Schema.Record(Schema.String, Schema.Json).annotate({ + "title": "Schema", + "description": "The JSON schema of the format" + }), + "type": Schema.Literal("json_schema").annotate({ "title": "Type" }) +}).annotate({ "title": "JsonOutputFormat" }) +export type JsonValue = unknown +export const JsonValue = Schema.Unknown +export type MessageBatch = { + readonly "archived_at": string | null + readonly "cancel_initiated_at": string | null + readonly "created_at": string + readonly "ended_at": string | null + readonly "expires_at": string + readonly "id": string + readonly "processing_status": "in_progress" | "canceling" | "ended" + readonly "request_counts": { + readonly "canceled": number + readonly "errored": number + readonly "expired": number + readonly "processing": number + readonly "succeeded": number + } + readonly "results_url": string | null + readonly "type": "message_batch" +} +export const MessageBatch = Schema.Struct({ + "archived_at": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]).annotate({ + "title": "Archived At", + "description": + "RFC 3339 datetime string representing the time at which the Message Batch was archived and its results became unavailable." + }), + "cancel_initiated_at": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]).annotate({ + "title": "Cancel Initiated At", + "description": + "RFC 3339 datetime string representing the time at which cancellation was initiated for the Message Batch. Specified only if cancellation was initiated." + }), + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "RFC 3339 datetime string representing the time at which the Message Batch was created.", + "format": "date-time" + }), + "ended_at": Schema.Union([Schema.String.annotate({ "format": "date-time" }), Schema.Null]).annotate({ + "title": "Ended At", + "description": + "RFC 3339 datetime string representing the time at which processing for the Message Batch ended. Specified only once processing ends.\n\nProcessing ends when every request in a Message Batch has either succeeded, errored, canceled, or expired." + }), + "expires_at": Schema.String.annotate({ + "title": "Expires At", + "description": + "RFC 3339 datetime string representing the time at which the Message Batch will expire and end processing, which is 24 hours after creation.", + "format": "date-time" + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique object identifier.\n\nThe format and length of IDs may change over time." + }), + "processing_status": Schema.Literals(["in_progress", "canceling", "ended"]).annotate({ + "title": "Processing Status", + "description": "Processing status of the Message Batch." + }), + "request_counts": Schema.Struct({ + "canceled": Schema.Number.annotate({ + "title": "Canceled", + "description": + "Number of requests in the Message Batch that have been canceled.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()), + "errored": Schema.Number.annotate({ + "title": "Errored", + "description": + "Number of requests in the Message Batch that encountered an error.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()), + "expired": Schema.Number.annotate({ + "title": "Expired", + "description": + "Number of requests in the Message Batch that have expired.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()), + "processing": Schema.Number.annotate({ + "title": "Processing", + "description": "Number of requests in the Message Batch that are processing.", + "default": 0 + }).check(Schema.isInt()), + "succeeded": Schema.Number.annotate({ + "title": "Succeeded", + "description": + "Number of requests in the Message Batch that have completed successfully.\n\nThis is zero until processing of the entire Message Batch has ended.", + "default": 0 + }).check(Schema.isInt()) + }).annotate({ + "title": "RequestCounts", + "description": + "Tallies requests within the Message Batch, categorized by their status.\n\nRequests start as `processing` and move to one of the other statuses only once processing of the entire batch ends. The sum of all values always matches the total number of requests in the batch." + }), + "results_url": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Results Url", + "description": + "URL to a `.jsonl` file containing the results of the Message Batch requests. Specified only once processing ends.\n\nResults in the file are not guaranteed to be in the same order as requests. Use the `custom_id` field to match results to requests." + }), + "type": Schema.Literal("message_batch").annotate({ + "title": "Type", + "description": "Object type.\n\nFor Message Batches, this is always `\"message_batch\"`.", + "default": "message_batch" + }) +}).annotate({ "title": "MessageBatch" }) +export type MessageStopEvent = { readonly "type": "message_stop" } +export const MessageStopEvent = Schema.Struct({ + "type": Schema.Literal("message_stop").annotate({ "title": "Type", "default": "message_stop" }) +}).annotate({ "title": "MessageStopEvent" }) +export type ModelInfo = { + readonly "created_at": string + readonly "display_name": string + readonly "id": string + readonly "type": "model" +} +export const ModelInfo = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": + "RFC 3339 datetime string representing the time at which the model was released. May be set to an epoch value if the release date is unknown.", + "format": "date-time" + }), + "display_name": Schema.String.annotate({ + "title": "Display Name", + "description": "A human-readable name for the model." + }), + "id": Schema.String.annotate({ "title": "Id", "description": "Unique model identifier." }), + "type": Schema.Literal("model").annotate({ + "title": "Type", + "description": "Object type.\n\nFor Models, this is always `\"model\"`.", + "default": "model" + }) +}).annotate({ "title": "ModelInfo" }) +export type NotFoundError = { readonly "message": string; readonly "type": "not_found_error" } +export const NotFoundError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Not found" }), + "type": Schema.Literal("not_found_error").annotate({ "title": "Type", "default": "not_found_error" }) +}).annotate({ "title": "NotFoundError" }) +export type OverloadedError = { readonly "message": string; readonly "type": "overloaded_error" } +export const OverloadedError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Overloaded" }), + "type": Schema.Literal("overloaded_error").annotate({ "title": "Type", "default": "overloaded_error" }) +}).annotate({ "title": "OverloadedError" }) +export type PermissionError = { readonly "message": string; readonly "type": "permission_error" } +export const PermissionError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Permission denied" }), + "type": Schema.Literal("permission_error").annotate({ "title": "Type", "default": "permission_error" }) +}).annotate({ "title": "PermissionError" }) +export type PlainTextSource = { readonly "data": string; readonly "media_type": "text/plain"; readonly "type": "text" } +export const PlainTextSource = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data" }), + "media_type": Schema.Literal("text/plain").annotate({ "title": "Media Type" }), + "type": Schema.Literal("text").annotate({ "title": "Type" }) +}).annotate({ "title": "PlainTextSource" }) +export type RateLimitError = { readonly "message": string; readonly "type": "rate_limit_error" } +export const RateLimitError = Schema.Struct({ + "message": Schema.String.annotate({ "title": "Message", "default": "Rate limited" }), + "type": Schema.Literal("rate_limit_error").annotate({ "title": "Type", "default": "rate_limit_error" }) +}).annotate({ "title": "RateLimitError" }) +export type RequestBashCodeExecutionOutputBlock = { + readonly "file_id": string + readonly "type": "bash_code_execution_output" +} +export const RequestBashCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("bash_code_execution_output").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionOutputBlock" }) +export type RequestCharLocationCitation = { + 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 RequestCharLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([ + Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), + Schema.Null + ]).annotate({ "title": "Document Title" }), + "end_char_index": Schema.Number.annotate({ "title": "End Char Index" }).check(Schema.isInt()), + "start_char_index": Schema.Number.annotate({ "title": "Start Char Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("char_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCharLocationCitation" }) +export type RequestCitationsConfig = { readonly "enabled"?: boolean } +export const RequestCitationsConfig = Schema.Struct({ + "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Enabled" })) +}).annotate({ "title": "RequestCitationsConfig" }) +export type RequestCodeExecutionOutputBlock = { readonly "file_id": string; readonly "type": "code_execution_output" } +export const RequestCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("code_execution_output").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCodeExecutionOutputBlock" }) +export type RequestContentBlockLocationCitation = { + 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 RequestContentBlockLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([ + Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), + Schema.Null + ]).annotate({ "title": "Document Title" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("content_block_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestContentBlockLocationCitation" }) +export type RequestPageLocationCitation = { + 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 RequestPageLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([ + Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), + Schema.Null + ]).annotate({ "title": "Document Title" }), + "end_page_number": Schema.Number.annotate({ "title": "End Page Number" }).check(Schema.isInt()), + "start_page_number": Schema.Number.annotate({ "title": "Start Page Number" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(1) + ), + "type": Schema.Literal("page_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestPageLocationCitation" }) +export type RequestSearchResultLocationCitation = { + 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 RequestSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "search_result_index": Schema.Number.annotate({ "title": "Search Result Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "source": Schema.String.annotate({ "title": "Source" }), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Title" }), + "type": Schema.Literal("search_result_location").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestSearchResultLocationCitation" }) +export type RequestTextEditorCodeExecutionCreateResultBlock = { + readonly "is_file_update": boolean + readonly "type": "text_editor_code_execution_create_result" +} +export const RequestTextEditorCodeExecutionCreateResultBlock = Schema.Struct({ + "is_file_update": Schema.Boolean.annotate({ "title": "Is File Update" }), + "type": Schema.Literal("text_editor_code_execution_create_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionCreateResultBlock" }) +export type RequestTextEditorCodeExecutionStrReplaceResultBlock = { + 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 RequestTextEditorCodeExecutionStrReplaceResultBlock = Schema.Struct({ + "lines": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "title": "Lines" })), + "new_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "New Lines" }) + ), + "new_start": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "New Start" }) + ), + "old_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Old Lines" }) + ), + "old_start": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Old Start" }) + ), + "type": Schema.Literal("text_editor_code_execution_str_replace_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionStrReplaceResultBlock" }) +export type RequestTextEditorCodeExecutionViewResultBlock = { + 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 RequestTextEditorCodeExecutionViewResultBlock = Schema.Struct({ + "content": Schema.String.annotate({ "title": "Content" }), + "file_type": Schema.Literals(["text", "image", "pdf"]).annotate({ "title": "File Type" }), + "num_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Num Lines" }) + ), + "start_line": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Start Line" }) + ), + "total_lines": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ "title": "Total Lines" }) + ), + "type": Schema.Literal("text_editor_code_execution_view_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionViewResultBlock" }) +export type RequestWebSearchResultBlock = { + readonly "encrypted_content": string + readonly "page_age"?: string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string +} +export const RequestWebSearchResultBlock = Schema.Struct({ + "encrypted_content": Schema.String.annotate({ "title": "Encrypted Content" }), + "page_age": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Page Age" })), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "RequestWebSearchResultBlock" }) +export type RequestWebSearchResultLocationCitation = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" + readonly "url": string +} +export const RequestWebSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "encrypted_index": Schema.String.annotate({ "title": "Encrypted Index" }), + "title": Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(512)), Schema.Null]) + .annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result_location").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2048)) +}).annotate({ "title": "RequestWebSearchResultLocationCitation" }) +export type ResponseBashCodeExecutionOutputBlock = { + readonly "file_id": string + readonly "type": "bash_code_execution_output" +} +export const ResponseBashCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("bash_code_execution_output").annotate({ + "title": "Type", + "default": "bash_code_execution_output" + }) +}).annotate({ "title": "ResponseBashCodeExecutionOutputBlock" }) +export type ResponseCharLocationCitation = { + 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 ResponseCharLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Document Title" }), + "end_char_index": Schema.Number.annotate({ "title": "End Char Index" }).check(Schema.isInt()), + "file_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "File Id", "default": null }), + "start_char_index": Schema.Number.annotate({ "title": "Start Char Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("char_location").annotate({ "title": "Type", "default": "char_location" }) +}).annotate({ "title": "ResponseCharLocationCitation" }) +export type ResponseCitationsConfig = { readonly "enabled": boolean } +export const ResponseCitationsConfig = Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "title": "Enabled", "default": false }) +}).annotate({ "title": "ResponseCitationsConfig" }) +export type ResponseCodeExecutionOutputBlock = { readonly "file_id": string; readonly "type": "code_execution_output" } +export const ResponseCodeExecutionOutputBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("code_execution_output").annotate({ "title": "Type", "default": "code_execution_output" }) +}).annotate({ "title": "ResponseCodeExecutionOutputBlock" }) +export type ResponseContainerUploadBlock = { readonly "file_id": string; readonly "type": "container_upload" } +export const ResponseContainerUploadBlock = Schema.Struct({ + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("container_upload").annotate({ "title": "Type", "default": "container_upload" }) +}).annotate({ + "title": "ResponseContainerUploadBlock", + "description": "Response model for a file uploaded to the container." +}) +export type ResponseContentBlockLocationCitation = { + 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 ResponseContentBlockLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Document Title" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "file_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "File Id", "default": null }), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "type": Schema.Literal("content_block_location").annotate({ "title": "Type", "default": "content_block_location" }) +}).annotate({ "title": "ResponseContentBlockLocationCitation" }) +export type ResponsePageLocationCitation = { + 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 ResponsePageLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "document_index": Schema.Number.annotate({ "title": "Document Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "document_title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Document Title" }), + "end_page_number": Schema.Number.annotate({ "title": "End Page Number" }).check(Schema.isInt()), + "file_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "File Id", "default": null }), + "start_page_number": Schema.Number.annotate({ "title": "Start Page Number" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(1) + ), + "type": Schema.Literal("page_location").annotate({ "title": "Type", "default": "page_location" }) +}).annotate({ "title": "ResponsePageLocationCitation" }) +export type ResponseRedactedThinkingBlock = { readonly "data": string; readonly "type": "redacted_thinking" } +export const ResponseRedactedThinkingBlock = Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data" }), + "type": Schema.Literal("redacted_thinking").annotate({ "title": "Type", "default": "redacted_thinking" }) +}).annotate({ "title": "ResponseRedactedThinkingBlock" }) +export type ResponseSearchResultLocationCitation = { + 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 ResponseSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "end_block_index": Schema.Number.annotate({ "title": "End Block Index" }).check(Schema.isInt()), + "search_result_index": Schema.Number.annotate({ "title": "Search Result Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "source": Schema.String.annotate({ "title": "Source" }), + "start_block_index": Schema.Number.annotate({ "title": "Start Block Index" }).check(Schema.isInt()).check( + Schema.isGreaterThanOrEqualTo(0) + ), + "title": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Title" }), + "type": Schema.Literal("search_result_location").annotate({ "title": "Type", "default": "search_result_location" }) +}).annotate({ "title": "ResponseSearchResultLocationCitation" }) +export type ResponseTextEditorCodeExecutionCreateResultBlock = { + readonly "is_file_update": boolean + readonly "type": "text_editor_code_execution_create_result" +} +export const ResponseTextEditorCodeExecutionCreateResultBlock = Schema.Struct({ + "is_file_update": Schema.Boolean.annotate({ "title": "Is File Update" }), + "type": Schema.Literal("text_editor_code_execution_create_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_create_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionCreateResultBlock" }) +export type ResponseTextEditorCodeExecutionStrReplaceResultBlock = { + 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 ResponseTextEditorCodeExecutionStrReplaceResultBlock = Schema.Struct({ + "lines": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "title": "Lines", "default": null }), + "new_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "New Lines", + "default": null + }), + "new_start": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "New Start", + "default": null + }), + "old_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Old Lines", + "default": null + }), + "old_start": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Old Start", + "default": null + }), + "type": Schema.Literal("text_editor_code_execution_str_replace_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_str_replace_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionStrReplaceResultBlock" }) +export type ResponseTextEditorCodeExecutionViewResultBlock = { + 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 ResponseTextEditorCodeExecutionViewResultBlock = Schema.Struct({ + "content": Schema.String.annotate({ "title": "Content" }), + "file_type": Schema.Literals(["text", "image", "pdf"]).annotate({ "title": "File Type" }), + "num_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Num Lines", + "default": null + }), + "start_line": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Start Line", + "default": null + }), + "total_lines": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Total Lines", + "default": null + }), + "type": Schema.Literal("text_editor_code_execution_view_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_view_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionViewResultBlock" }) +export type ResponseThinkingBlock = { + readonly "signature": string + readonly "thinking": string + readonly "type": "thinking" +} +export const ResponseThinkingBlock = Schema.Struct({ + "signature": Schema.String.annotate({ "title": "Signature" }), + "thinking": Schema.String.annotate({ "title": "Thinking" }), + "type": Schema.Literal("thinking").annotate({ "title": "Type", "default": "thinking" }) +}).annotate({ "title": "ResponseThinkingBlock" }) +export type ResponseToolReferenceBlock = { readonly "tool_name": string; readonly "type": "tool_reference" } +export const ResponseToolReferenceBlock = Schema.Struct({ + "tool_name": Schema.String.annotate({ "title": "Tool Name" }).check(Schema.isMinLength(1)).check( + Schema.isMaxLength(256) + ).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,256}$"))), + "type": Schema.Literal("tool_reference").annotate({ "title": "Type", "default": "tool_reference" }) +}).annotate({ "title": "ResponseToolReferenceBlock" }) +export type ResponseWebSearchResultBlock = { + readonly "encrypted_content": string + readonly "page_age": string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string +} +export const ResponseWebSearchResultBlock = Schema.Struct({ + "encrypted_content": Schema.String.annotate({ "title": "Encrypted Content" }), + "page_age": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Page Age", "default": null }), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result").annotate({ "title": "Type", "default": "web_search_result" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "ResponseWebSearchResultBlock" }) +export type ResponseWebSearchResultLocationCitation = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" + readonly "url": string +} +export const ResponseWebSearchResultLocationCitation = Schema.Struct({ + "cited_text": Schema.String.annotate({ "title": "Cited Text" }), + "encrypted_index": Schema.String.annotate({ "title": "Encrypted Index" }), + "title": Schema.Union([Schema.String.check(Schema.isMaxLength(512)), Schema.Null]).annotate({ "title": "Title" }), + "type": Schema.Literal("web_search_result_location").annotate({ + "title": "Type", + "default": "web_search_result_location" + }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "ResponseWebSearchResultLocationCitation" }) +export type ServerToolCaller = { readonly "tool_id": string; readonly "type": "code_execution_20250825" } +export const ServerToolCaller = Schema.Struct({ + "tool_id": Schema.String.annotate({ "title": "Tool Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_20250825").annotate({ "title": "Type" }) +}).annotate({ "title": "ServerToolCaller", "description": "Tool invocation generated by a server-side tool." }) +export type ServerToolCaller_20260120 = { readonly "tool_id": string; readonly "type": "code_execution_20260120" } +export const ServerToolCaller_20260120 = Schema.Struct({ + "tool_id": Schema.String.annotate({ "title": "Tool Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_20260120").annotate({ "title": "Type" }) +}).annotate({ "title": "ServerToolCaller_20260120" }) +export type ServerToolUsage = { readonly "web_fetch_requests": number; readonly "web_search_requests": number } +export const ServerToolUsage = Schema.Struct({ + "web_fetch_requests": Schema.Number.annotate({ + "title": "Web Fetch Requests", + "description": "The number of web fetch tool requests.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "web_search_requests": Schema.Number.annotate({ + "title": "Web Search Requests", + "description": "The number of web search tool requests.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ "title": "ServerToolUsage" }) +export type SignatureContentBlockDelta = { readonly "signature": string; readonly "type": "signature_delta" } +export const SignatureContentBlockDelta = Schema.Struct({ + "signature": Schema.String.annotate({ "title": "Signature" }), + "type": Schema.Literal("signature_delta").annotate({ "title": "Type", "default": "signature_delta" }) +}).annotate({ "title": "SignatureContentBlockDelta" }) +export type Skill = { + readonly "created_at": string + readonly "display_title": string | null + readonly "id": string + readonly "latest_version": string | null + readonly "source": string + readonly "type": string + readonly "updated_at": string +} +export const Skill = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill was created." + }), + "display_title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Display Title", + "description": + "Display title for the skill.\n\nThis is a human-readable label that is not included in the prompt sent to the model." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill.\n\nThe format and length of IDs may change over time." + }), + "latest_version": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Latest Version", + "description": + "The latest version identifier for the skill.\n\nThis represents the most recent version of the skill that has been created." + }), + "source": Schema.String.annotate({ + "title": "Source", + "description": + "Source of the skill.\n\nThis may be one of the following values:\n* `\"custom\"`: the skill was created by a user\n* `\"anthropic\"`: the skill was created by Anthropic" + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skills, this is always `\"skill\"`.", + "default": "skill" + }), + "updated_at": Schema.String.annotate({ + "title": "Updated At", + "description": "ISO 8601 timestamp of when the skill was last updated." + }) +}).annotate({ "title": "Skill" }) +export type SkillVersion = { + readonly "created_at": string + readonly "description": string + readonly "directory": string + readonly "id": string + readonly "name": string + readonly "skill_id": string + readonly "type": string + readonly "version": string +} +export const SkillVersion = Schema.Struct({ + "created_at": Schema.String.annotate({ + "title": "Created At", + "description": "ISO 8601 timestamp of when the skill version was created." + }), + "description": Schema.String.annotate({ + "title": "Description", + "description": "Description of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "directory": Schema.String.annotate({ + "title": "Directory", + "description": + "Directory name of the skill version.\n\nThis is the top-level directory name that was extracted from the uploaded files." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique identifier for the skill version.\n\nThe format and length of IDs may change over time." + }), + "name": Schema.String.annotate({ + "title": "Name", + "description": + "Human-readable name of the skill version.\n\nThis is extracted from the SKILL.md file in the skill upload." + }), + "skill_id": Schema.String.annotate({ + "title": "Skill Id", + "description": "Identifier for the skill that this version belongs to." + }), + "type": Schema.String.annotate({ + "title": "Type", + "description": "Object type.\n\nFor Skill Versions, this is always `\"skill_version\"`.", + "default": "skill_version" + }), + "version": Schema.String.annotate({ + "title": "Version", + "description": + "Version identifier for the skill.\n\nEach version is identified by a Unix epoch timestamp (e.g., \"1759178010641129\")." + }) +}).annotate({ "title": "SkillVersion" }) +export type TextContentBlockDelta = { readonly "text": string; readonly "type": "text_delta" } +export const TextContentBlockDelta = Schema.Struct({ + "text": Schema.String.annotate({ "title": "Text" }), + "type": Schema.Literal("text_delta").annotate({ "title": "Type", "default": "text_delta" }) +}).annotate({ "title": "TextContentBlockDelta" }) +export type TextEditorCodeExecutionToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "file_not_found" +export const TextEditorCodeExecutionToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "file_not_found" +]).annotate({ "title": "TextEditorCodeExecutionToolResultErrorCode" }) +export type ThinkingConfigAdaptive = { readonly "type": "adaptive" } +export const ThinkingConfigAdaptive = Schema.Struct({ + "type": Schema.Literal("adaptive").annotate({ "title": "Type" }) +}).annotate({ "title": "ThinkingConfigAdaptive" }) +export type ThinkingConfigDisabled = { readonly "type": "disabled" } +export const ThinkingConfigDisabled = Schema.Struct({ + "type": Schema.Literal("disabled").annotate({ "title": "Type" }) +}).annotate({ "title": "ThinkingConfigDisabled" }) +export type ThinkingConfigEnabled = { readonly "budget_tokens": number; readonly "type": "enabled" } +export const ThinkingConfigEnabled = Schema.Struct({ + "budget_tokens": Schema.Number.annotate({ + "title": "Budget Tokens", + "description": + "Determines how many tokens Claude can use for its internal reasoning process. Larger budgets can enable more thorough analysis for complex problems, improving response quality.\n\nMust be ≥1024 and less than `max_tokens`.\n\nSee [extended thinking](https://docs.claude.com/en/docs/build-with-claude/extended-thinking) for details." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1024)), + "type": Schema.Literal("enabled").annotate({ "title": "Type" }) +}).annotate({ "title": "ThinkingConfigEnabled" }) +export type ThinkingContentBlockDelta = { readonly "thinking": string; readonly "type": "thinking_delta" } +export const ThinkingContentBlockDelta = Schema.Struct({ + "thinking": Schema.String.annotate({ "title": "Thinking" }), + "type": Schema.Literal("thinking_delta").annotate({ "title": "Type", "default": "thinking_delta" }) +}).annotate({ "title": "ThinkingContentBlockDelta" }) +export type ToolChoiceAny = { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "any" } +export const ToolChoiceAny = Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Disable Parallel Tool Use", + "description": + "Whether to disable parallel tool use.\n\nDefaults to `false`. If set to `true`, the model will output exactly one tool use." + }) + ), + "type": Schema.Literal("any").annotate({ "title": "Type" }) +}).annotate({ "title": "ToolChoiceAny", "description": "The model will use any available tools." }) +export type ToolChoiceAuto = { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "auto" } +export const ToolChoiceAuto = Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Disable Parallel Tool Use", + "description": + "Whether to disable parallel tool use.\n\nDefaults to `false`. If set to `true`, the model will output at most one tool use." + }) + ), + "type": Schema.Literal("auto").annotate({ "title": "Type" }) +}).annotate({ "title": "ToolChoiceAuto", "description": "The model will automatically decide whether to use tools." }) +export type ToolChoiceNone = { readonly "type": "none" } +export const ToolChoiceNone = Schema.Struct({ "type": Schema.Literal("none").annotate({ "title": "Type" }) }).annotate({ + "title": "ToolChoiceNone", + "description": "The model will not be allowed to use tools." +}) +export type ToolChoiceTool = { + readonly "disable_parallel_tool_use"?: boolean + readonly "name": string + readonly "type": "tool" +} +export const ToolChoiceTool = Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Disable Parallel Tool Use", + "description": + "Whether to disable parallel tool use.\n\nDefaults to `false`. If set to `true`, the model will output exactly one tool use." + }) + ), + "name": Schema.String.annotate({ "title": "Name", "description": "The name of the tool to use." }), + "type": Schema.Literal("tool").annotate({ "title": "Type" }) +}).annotate({ + "title": "ToolChoiceTool", + "description": "The model will use the specified tool with `tool_choice.name`." +}) +export type ToolSearchToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" +export const ToolSearchToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded" +]).annotate({ "title": "ToolSearchToolResultErrorCode" }) +export type URLImageSource = { readonly "type": "url"; readonly "url": string } +export const URLImageSource = Schema.Struct({ + "type": Schema.Literal("url").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "URLImageSource" }) +export type URLPDFSource = { readonly "type": "url"; readonly "url": string } +export const URLPDFSource = Schema.Struct({ + "type": Schema.Literal("url").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "URLPDFSource" }) +export type UserLocation = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" +} +export const UserLocation = Schema.Struct({ + "city": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), Schema.Null]).annotate({ + "title": "City", + "description": "The city of the user." + }) + ), + "country": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(2)).check(Schema.isMaxLength(2)), Schema.Null]).annotate({ + "title": "Country", + "description": "The two letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) of the user." + }) + ), + "region": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), Schema.Null]).annotate({ + "title": "Region", + "description": "The region of the user." + }) + ), + "timezone": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), Schema.Null]).annotate({ + "title": "Timezone", + "description": "The [IANA timezone](https://nodatime.org/TimeZones) of the user." + }) + ), + "type": Schema.Literal("approximate").annotate({ "title": "Type" }) +}).annotate({ "title": "UserLocation" }) +export type WebFetchToolResultErrorCode = + | "invalid_tool_input" + | "url_too_long" + | "url_not_allowed" + | "url_not_accessible" + | "unsupported_content_type" + | "too_many_requests" + | "max_uses_exceeded" + | "unavailable" +export const WebFetchToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "url_too_long", + "url_not_allowed", + "url_not_accessible", + "unsupported_content_type", + "too_many_requests", + "max_uses_exceeded", + "unavailable" +]).annotate({ "title": "WebFetchToolResultErrorCode" }) +export type WebSearchToolResultErrorCode = + | "invalid_tool_input" + | "unavailable" + | "max_uses_exceeded" + | "too_many_requests" + | "query_too_long" + | "request_too_large" +export const WebSearchToolResultErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "max_uses_exceeded", + "too_many_requests", + "query_too_long", + "request_too_large" +]).annotate({ "title": "WebSearchToolResultErrorCode" }) +export type StopReason = "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal" +export const StopReason = Schema.Literals([ + "end_turn", + "max_tokens", + "stop_sequence", + "tool_use", + "pause_turn", + "refusal" +]) +export type BetaStopReason = + | "end_turn" + | "max_tokens" + | "stop_sequence" + | "tool_use" + | "pause_turn" + | "compaction" + | "refusal" + | "model_context_window_exceeded" +export const BetaStopReason = Schema.Literals([ + "end_turn", + "max_tokens", + "stop_sequence", + "tool_use", + "pause_turn", + "compaction", + "refusal", + "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-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" +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." +}) +export type BetaMemoryTool_20250818_ViewCommand = { + readonly "command": "view" + readonly "path": string + readonly "view_range"?: ReadonlyArray +} +export const BetaMemoryTool_20250818_ViewCommand = Schema.Struct({ + "command": Schema.Literal("view").annotate({ "description": "Command type identifier", "default": "view" }), + "path": Schema.String.annotate({ "description": "Path to directory or file to view" }), + "view_range": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": "Optional line range for viewing specific lines" + }).check(Schema.isMinLength(2)).check(Schema.isMaxLength(2)) + ) +}) +export type BetaMemoryTool_20250818_CreateCommand = { + readonly "command": "create" + readonly "path": string + readonly "file_text": string +} +export const BetaMemoryTool_20250818_CreateCommand = Schema.Struct({ + "command": Schema.Literal("create").annotate({ "description": "Command type identifier", "default": "create" }), + "path": Schema.String.annotate({ "description": "Path where the file should be created" }), + "file_text": Schema.String.annotate({ "description": "Content to write to the file" }) +}) +export type BetaMemoryTool_20250818_StrReplaceCommand = { + readonly "command": "str_replace" + readonly "path": string + readonly "old_str": string + readonly "new_str": string +} +export const BetaMemoryTool_20250818_StrReplaceCommand = Schema.Struct({ + "command": Schema.Literal("str_replace").annotate({ + "description": "Command type identifier", + "default": "str_replace" + }), + "path": Schema.String.annotate({ "description": "Path to the file where text should be replaced" }), + "old_str": Schema.String.annotate({ "description": "Text to search for and replace" }), + "new_str": Schema.String.annotate({ "description": "Text to replace with" }) +}) +export type BetaMemoryTool_20250818_InsertCommand = { + readonly "command": "insert" + readonly "path": string + readonly "insert_line": number + readonly "insert_text": string +} +export const BetaMemoryTool_20250818_InsertCommand = Schema.Struct({ + "command": Schema.Literal("insert").annotate({ "description": "Command type identifier", "default": "insert" }), + "path": Schema.String.annotate({ "description": "Path to the file where text should be inserted" }), + "insert_line": Schema.Number.annotate({ "description": "Line number where text should be inserted" }).check( + Schema.isInt() + ).check(Schema.isGreaterThanOrEqualTo(1)), + "insert_text": Schema.String.annotate({ "description": "Text to insert at the specified line" }) +}) +export type BetaMemoryTool_20250818_DeleteCommand = { readonly "command": "delete"; readonly "path": string } +export const BetaMemoryTool_20250818_DeleteCommand = Schema.Struct({ + "command": Schema.Literal("delete").annotate({ "description": "Command type identifier", "default": "delete" }), + "path": Schema.String.annotate({ "description": "Path to the file or directory to delete" }) +}) +export type BetaMemoryTool_20250818_RenameCommand = { + readonly "command": "rename" + readonly "old_path": string + readonly "new_path": string +} +export const BetaMemoryTool_20250818_RenameCommand = Schema.Struct({ + "command": Schema.Literal("rename").annotate({ "description": "Command type identifier", "default": "rename" }), + "old_path": Schema.String.annotate({ "description": "Current path of the file or directory" }), + "new_path": Schema.String.annotate({ "description": "New path for the file or directory" }) +}) +export type RequestBashCodeExecutionToolResultError = { + readonly "error_code": BashCodeExecutionToolResultErrorCode + readonly "type": "bash_code_execution_tool_result_error" +} +export const RequestBashCodeExecutionToolResultError = Schema.Struct({ + "error_code": BashCodeExecutionToolResultErrorCode, + "type": Schema.Literal("bash_code_execution_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionToolResultError" }) +export type ResponseBashCodeExecutionToolResultError = { + readonly "error_code": BashCodeExecutionToolResultErrorCode + readonly "type": "bash_code_execution_tool_result_error" +} +export const ResponseBashCodeExecutionToolResultError = Schema.Struct({ + "error_code": BashCodeExecutionToolResultErrorCode, + "type": Schema.Literal("bash_code_execution_tool_result_error").annotate({ + "title": "Type", + "default": "bash_code_execution_tool_result_error" + }) +}).annotate({ "title": "ResponseBashCodeExecutionToolResultError" }) +export type BetaRequestBashCodeExecutionToolResultError = { + readonly "error_code": BetaBashCodeExecutionToolResultErrorCode + readonly "type": "bash_code_execution_tool_result_error" +} +export const BetaRequestBashCodeExecutionToolResultError = Schema.Struct({ + "error_code": BetaBashCodeExecutionToolResultErrorCode, + "type": Schema.Literal("bash_code_execution_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionToolResultError" }) +export type BetaResponseBashCodeExecutionToolResultError = { + readonly "error_code": BetaBashCodeExecutionToolResultErrorCode + readonly "type": "bash_code_execution_tool_result_error" +} +export const BetaResponseBashCodeExecutionToolResultError = Schema.Struct({ + "error_code": BetaBashCodeExecutionToolResultErrorCode, + "type": Schema.Literal("bash_code_execution_tool_result_error").annotate({ + "title": "Type", + "default": "bash_code_execution_tool_result_error" + }) +}).annotate({ "title": "ResponseBashCodeExecutionToolResultError" }) +export type BetaCodeExecutionTool_20250522 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "name": "code_execution" + readonly "strict"?: boolean + readonly "type": "code_execution_20250522" +} +export const BetaCodeExecutionTool_20250522 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("code_execution").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("code_execution_20250522").annotate({ "title": "Type" }) +}).annotate({ "title": "CodeExecutionTool_20250522" }) +export type BetaCodeExecutionTool_20250825 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "name": "code_execution" + readonly "strict"?: boolean + readonly "type": "code_execution_20250825" +} +export const BetaCodeExecutionTool_20250825 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("code_execution").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("code_execution_20250825").annotate({ "title": "Type" }) +}).annotate({ "title": "CodeExecutionTool_20250825" }) +export type BetaCodeExecutionTool_20260120 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "name": "code_execution" + readonly "strict"?: boolean + readonly "type": "code_execution_20260120" +} +export const BetaCodeExecutionTool_20260120 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("code_execution").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("code_execution_20260120").annotate({ "title": "Type" }) +}).annotate({ + "title": "CodeExecutionTool_20260120", + "description": "Code execution tool with REPL state persistence (daemon mode + gVisor checkpoint)." +}) +export type BetaRequestCompactionBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "content": string | null + readonly "type": "compaction" +} +export const BetaRequestCompactionBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Content", + "description": "Summary of previously compacted content, or null if compaction failed" + }), + "type": Schema.Literal("compaction").annotate({ "title": "Type" }) +}).annotate({ + "title": "RequestCompactionBlock", + "description": + "A compaction block containing summary of previous context.\n\nUsers should round-trip these blocks from responses to subsequent requests\nto maintain context across compaction boundaries.\n\nWhen content is None, the block represents a failed compaction. The server\ntreats these as no-ops. Empty string content is not allowed." +}) +export type BetaRequestContainerUploadBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "file_id": string + readonly "type": "container_upload" +} +export const BetaRequestContainerUploadBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("container_upload").annotate({ "title": "Type" }) +}).annotate({ + "title": "RequestContainerUploadBlock", + "description": + "A content block that represents a file to be uploaded to the container\nFiles uploaded via this block will be available in the container's input directory." +}) +export type BetaRequestMCPToolUseBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": string + readonly "server_name": string + readonly "type": "mcp_tool_use" +} +export const BetaRequestMCPToolUseBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.String.annotate({ "title": "Name" }), + "server_name": Schema.String.annotate({ "title": "Server Name", "description": "The name of the MCP server" }), + "type": Schema.Literal("mcp_tool_use").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestMCPToolUseBlock" }) +export type BetaRequestToolReferenceBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "tool_name": string + readonly "type": "tool_reference" +} +export const BetaRequestToolReferenceBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "tool_name": Schema.String.annotate({ "title": "Tool Name" }).check(Schema.isMinLength(1)).check( + Schema.isMaxLength(256) + ).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,256}$"))), + "type": Schema.Literal("tool_reference").annotate({ "title": "Type" }) +}).annotate({ + "title": "RequestToolReferenceBlock", + "description": "Tool reference block that can be included in tool_result content." +}) +export type BetaToolSearchToolBM25_20251119 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + 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 const BetaToolSearchToolBM25_20251119 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("tool_search_tool_bm25").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literals(["tool_search_tool_bm25_20251119", "tool_search_tool_bm25"]).annotate({ "title": "Type" }) +}).annotate({ "title": "ToolSearchToolBM25_20251119" }) +export type BetaToolSearchToolRegex_20251119 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + 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 BetaToolSearchToolRegex_20251119 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("tool_search_tool_regex").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literals(["tool_search_tool_regex_20251119", "tool_search_tool_regex"]).annotate({ "title": "Type" }) +}).annotate({ "title": "ToolSearchToolRegex_20251119" }) +export type BetaCompactionIterationUsage = { + readonly "cache_creation": BetaCacheCreation | null + 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 BetaCompactionIterationUsage = Schema.Struct({ + "cache_creation": Schema.Union([BetaCacheCreation, Schema.Null]).annotate({ + "description": "Breakdown of cached tokens by TTL", + "default": null + }), + "cache_creation_input_tokens": Schema.Number.annotate({ + "title": "Cache Creation Input Tokens", + "description": "The number of input tokens used to create the cache entry.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "cache_read_input_tokens": Schema.Number.annotate({ + "title": "Cache Read Input Tokens", + "description": "The number of input tokens read from the cache.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "input_tokens": Schema.Number.annotate({ + "title": "Input Tokens", + "description": "The number of input tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "output_tokens": Schema.Number.annotate({ + "title": "Output Tokens", + "description": "The number of output tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "type": Schema.Literal("compaction").annotate({ + "title": "Type", + "description": "Usage for a compaction iteration", + "default": "compaction" + }) +}).annotate({ "title": "CompactionIterationUsage", "description": "Token usage for a compaction iteration." }) +export type BetaMessageIterationUsage = { + readonly "cache_creation": BetaCacheCreation | null + readonly "cache_creation_input_tokens": number + readonly "cache_read_input_tokens": number + readonly "input_tokens": number + readonly "output_tokens": number + readonly "type": "message" +} +export const BetaMessageIterationUsage = Schema.Struct({ + "cache_creation": Schema.Union([BetaCacheCreation, Schema.Null]).annotate({ + "description": "Breakdown of cached tokens by TTL", + "default": null + }), + "cache_creation_input_tokens": Schema.Number.annotate({ + "title": "Cache Creation Input Tokens", + "description": "The number of input tokens used to create the cache entry.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "cache_read_input_tokens": Schema.Number.annotate({ + "title": "Cache Read Input Tokens", + "description": "The number of input tokens read from the cache.", + "default": 0 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "input_tokens": Schema.Number.annotate({ + "title": "Input Tokens", + "description": "The number of input tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "output_tokens": Schema.Number.annotate({ + "title": "Output Tokens", + "description": "The number of output tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "type": Schema.Literal("message").annotate({ + "title": "Type", + "description": "Usage for a sampling iteration", + "default": "message" + }) +}).annotate({ "title": "MessageIterationUsage", "description": "Token usage for a sampling iteration." }) +export type BetaRequestCodeExecutionToolResultError = { + readonly "error_code": BetaCodeExecutionToolResultErrorCode + readonly "type": "code_execution_tool_result_error" +} +export const BetaRequestCodeExecutionToolResultError = Schema.Struct({ + "error_code": BetaCodeExecutionToolResultErrorCode, + "type": Schema.Literal("code_execution_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "Error" }) +export type BetaResponseCodeExecutionToolResultError = { + readonly "error_code": BetaCodeExecutionToolResultErrorCode + readonly "type": "code_execution_tool_result_error" +} +export const BetaResponseCodeExecutionToolResultError = Schema.Struct({ + "error_code": BetaCodeExecutionToolResultErrorCode, + "type": Schema.Literal("code_execution_tool_result_error").annotate({ + "title": "Type", + "default": "code_execution_tool_result_error" + }) +}).annotate({ "title": "ResponseCodeExecutionToolResultError" }) +export type BetaCountMessageTokensResponse = { + readonly "context_management": BetaContextManagementResponse | null + readonly "input_tokens": number +} +export const BetaCountMessageTokensResponse = Schema.Struct({ + "context_management": Schema.Union([BetaContextManagementResponse, Schema.Null]).annotate({ + "description": "Information about context management applied to the message." + }), + "input_tokens": Schema.Number.annotate({ + "title": "Input Tokens", + "description": "The total number of tokens across the provided list of messages, system prompt, and tools." + }).check(Schema.isInt()) +}).annotate({ "title": "CountMessageTokensResponse" }) +export type BetaFileListResponse = { + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "has_more"?: boolean + readonly "last_id"?: string | null +} +export const BetaFileListResponse = Schema.Struct({ + "data": Schema.Array(BetaFileMetadataSchema).annotate({ + "title": "Data", + "description": "List of file metadata objects." + }), + "first_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "First Id", + "description": "ID of the first file in this page of results." + }) + ), + "has_more": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Has More", + "description": "Whether there are more results available.", + "default": false + }) + ), + "last_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Last Id", + "description": "ID of the last file in this page of results." + }) + ) +}).annotate({ "title": "FileListResponse" }) +export type BetaCompact20260112 = { + readonly "instructions"?: string | null + readonly "pause_after_compaction"?: boolean + readonly "trigger"?: BetaInputTokensTrigger | null + readonly "type": "compact_20260112" +} +export const BetaCompact20260112 = Schema.Struct({ + "instructions": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Instructions", + "description": "Additional instructions for summarization." + }) + ), + "pause_after_compaction": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Pause After Compaction", + "description": "Whether to pause after compaction and return the compaction block to the user." + }) + ), + "trigger": Schema.optionalKey( + Schema.Union([BetaInputTokensTrigger, Schema.Null]).annotate({ + "description": "When to trigger compaction. Defaults to 150000 input tokens." + }) + ), + "type": Schema.Literal("compact_20260112").annotate({ "title": "Type" }) +}).annotate({ + "title": "Compact20260112", + "description": "Automatically compact older context when reaching the configured trigger threshold." +}) +export type BetaBashTool_20241022 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "bash" + readonly "strict"?: boolean + readonly "type": "bash_20241022" +} +export const BetaBashTool_20241022 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("bash").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("bash_20241022").annotate({ "title": "Type" }) +}).annotate({ "title": "BashTool_20241022" }) +export type BetaBashTool_20250124 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "bash" + readonly "strict"?: boolean + readonly "type": "bash_20250124" +} +export const BetaBashTool_20250124 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("bash").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("bash_20250124").annotate({ "title": "Type" }) +}).annotate({ "title": "BashTool_20250124" }) +export type BetaComputerUseTool_20241022 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "display_height_px": number + readonly "display_number"?: number | null + readonly "display_width_px": number + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "computer" + readonly "strict"?: boolean + readonly "type": "computer_20241022" +} +export const BetaComputerUseTool_20241022 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "display_height_px": Schema.Number.annotate({ + "title": "Display Height Px", + "description": "The height of the display in pixels." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "display_number": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), Schema.Null]).annotate({ + "title": "Display Number", + "description": "The X11 display number (e.g. 0, 1) for the display." + }) + ), + "display_width_px": Schema.Number.annotate({ + "title": "Display Width Px", + "description": "The width of the display in pixels." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("computer").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("computer_20241022").annotate({ "title": "Type" }) +}).annotate({ "title": "ComputerUseTool_20241022" }) +export type BetaComputerUseTool_20250124 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "display_height_px": number + readonly "display_number"?: number | null + readonly "display_width_px": number + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "computer" + readonly "strict"?: boolean + readonly "type": "computer_20250124" +} +export const BetaComputerUseTool_20250124 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "display_height_px": Schema.Number.annotate({ + "title": "Display Height Px", + "description": "The height of the display in pixels." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "display_number": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), Schema.Null]).annotate({ + "title": "Display Number", + "description": "The X11 display number (e.g. 0, 1) for the display." + }) + ), + "display_width_px": Schema.Number.annotate({ + "title": "Display Width Px", + "description": "The width of the display in pixels." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("computer").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("computer_20250124").annotate({ "title": "Type" }) +}).annotate({ "title": "ComputerUseTool_20250124" }) +export type BetaComputerUseTool_20251124 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "display_height_px": number + readonly "display_number"?: number | null + readonly "display_width_px": number + readonly "enable_zoom"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "computer" + readonly "strict"?: boolean + readonly "type": "computer_20251124" +} +export const BetaComputerUseTool_20251124 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "display_height_px": Schema.Number.annotate({ + "title": "Display Height Px", + "description": "The height of the display in pixels." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "display_number": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), Schema.Null]).annotate({ + "title": "Display Number", + "description": "The X11 display number (e.g. 0, 1) for the display." + }) + ), + "display_width_px": Schema.Number.annotate({ + "title": "Display Width Px", + "description": "The width of the display in pixels." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "enable_zoom": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Enable Zoom", + "description": "Whether to enable an action to take a zoomed-in screenshot of the screen." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("computer").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("computer_20251124").annotate({ "title": "Type" }) +}).annotate({ "title": "ComputerUseTool_20251124" }) +export type BetaMemoryTool_20250818 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "memory" + readonly "strict"?: boolean + readonly "type": "memory_20250818" +} +export const BetaMemoryTool_20250818 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("memory").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("memory_20250818").annotate({ "title": "Type" }) +}).annotate({ "title": "MemoryTool_20250818" }) +export type BetaTextEditor_20241022 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "str_replace_editor" + readonly "strict"?: boolean + readonly "type": "text_editor_20241022" +} +export const BetaTextEditor_20241022 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("str_replace_editor").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("text_editor_20241022").annotate({ "title": "Type" }) +}).annotate({ "title": "TextEditor_20241022" }) +export type BetaTextEditor_20250124 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "str_replace_editor" + readonly "strict"?: boolean + readonly "type": "text_editor_20250124" +} +export const BetaTextEditor_20250124 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("str_replace_editor").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("text_editor_20250124").annotate({ "title": "Type" }) +}).annotate({ "title": "TextEditor_20250124" }) +export type BetaTextEditor_20250429 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "name": "str_replace_based_edit_tool" + readonly "strict"?: boolean + readonly "type": "text_editor_20250429" +} +export const BetaTextEditor_20250429 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("str_replace_based_edit_tool").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("text_editor_20250429").annotate({ "title": "Type" }) +}).annotate({ "title": "TextEditor_20250429" }) +export type BetaTextEditor_20250728 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> + readonly "max_characters"?: number | null + readonly "name": "str_replace_based_edit_tool" + readonly "strict"?: boolean + readonly "type": "text_editor_20250728" +} +export const BetaTextEditor_20250728 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ), + "max_characters": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), Schema.Null]).annotate({ + "title": "Max Characters", + "description": + "Maximum number of characters to display when viewing a file. If not specified, defaults to displaying the full file." + }) + ), + "name": Schema.Literal("str_replace_based_edit_tool").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("text_editor_20250728").annotate({ "title": "Type" }) +}).annotate({ "title": "TextEditor_20250728" }) +export type BetaTool = { + readonly "type"?: null | "custom" + readonly "description"?: string + readonly "name": string + readonly "input_schema": { + readonly "properties"?: { readonly [x: string]: Schema.Json } | null + readonly "required"?: ReadonlyArray | null + readonly "type": "object" + readonly [x: string]: Schema.Json + } + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "strict"?: boolean + readonly "eager_input_streaming"?: boolean | null + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: BetaJsonValue }> +} +export const BetaTool = Schema.Struct({ + "type": Schema.optionalKey(Schema.Union([Schema.Null, Schema.Literal("custom")]).annotate({ "title": "Type" })), + "description": Schema.optionalKey(Schema.String.annotate({ + "title": "Description", + "description": + "Description of what this tool does.\n\nTool descriptions should be as detailed as possible. The more information that the model has about what the tool is and how to use it, the better it will perform. You can use natural language descriptions to reinforce important aspects of the tool input JSON schema." + })), + "name": Schema.String.annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(128)).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,128}$")) + ), + "input_schema": Schema.StructWithRest( + Schema.Struct({ + "properties": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json), Schema.Null]).annotate({ "title": "Properties" }) + ), + "required": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "title": "Required" }) + ), + "type": Schema.Literal("object").annotate({ "title": "Type" }) + }), + [Schema.Record(Schema.String, Schema.Json)] + ).annotate({ + "title": "InputSchema", + "description": + "[JSON schema](https://json-schema.org/draft/2020-12) for this tool's input.\n\nThis defines the shape of the `input` that your tool accepts and that the model will produce." + }), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "eager_input_streaming": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "title": "Eager Input Streaming", + "description": + "Enable eager input streaming for this tool. When true, tool input parameters will be streamed incrementally as they are generated, and types will be inferred on-the-fly rather than buffering the full JSON output. When false, streaming is disabled for this tool even if the fine-grained-tool-streaming beta is active. When null (default), uses the default behavior based on beta headers." + }) + ), + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, BetaJsonValue)).annotate({ "title": "Input Examples" }) + ) +}).annotate({ "title": "Tool" }) +export type BetaMCPToolset = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "configs"?: { readonly [x: string]: BetaMCPToolConfig } | null + readonly "default_config"?: { readonly "defer_loading"?: boolean; readonly "enabled"?: boolean } + readonly "mcp_server_name": string + readonly "type": "mcp_toolset" +} +export const BetaMCPToolset = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "configs": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, BetaMCPToolConfig), Schema.Null]).annotate({ + "title": "Configs", + "description": "Configuration overrides for specific tools, keyed by tool name" + }) + ), + "default_config": Schema.optionalKey( + Schema.Struct({ + "defer_loading": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Defer Loading" })), + "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Enabled" })) + }).annotate({ + "title": "MCPToolDefaultConfig", + "description": "Default configuration applied to all tools from this server" + }) + ), + "mcp_server_name": Schema.String.annotate({ + "title": "Mcp Server Name", + "description": "Name of the MCP server to configure tools for" + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(255)), + "type": Schema.Literal("mcp_toolset").annotate({ "title": "Type" }) +}).annotate({ + "title": "MCPToolset", + "description": + "Configuration for a group of tools from an MCP server.\n\nAllows configuring enabled status and defer_loading for all tools\nfrom an MCP server, with optional per-tool overrides." +}) +export type BetaListResponse_MessageBatch_ = { + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "has_more": boolean + readonly "last_id": string | null +} +export const BetaListResponse_MessageBatch_ = Schema.Struct({ + "data": Schema.Array(BetaMessageBatch).annotate({ "title": "Data" }), + "first_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "First Id", + "description": "First ID in the `data` list. Can be used as the `before_id` for the previous page." + }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": "Indicates if there are more results in the requested page direction." + }), + "last_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Last Id", + "description": "Last ID in the `data` list. Can be used as the `after_id` for the next page." + }) +}).annotate({ "title": "ListResponse[MessageBatch]" }) +export type BetaListResponse_ModelInfo_ = { + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "has_more": boolean + readonly "last_id": string | null +} +export const BetaListResponse_ModelInfo_ = Schema.Struct({ + "data": Schema.Array(BetaModelInfo).annotate({ "title": "Data" }), + "first_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "First Id", + "description": "First ID in the `data` list. Can be used as the `before_id` for the previous page." + }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": "Indicates if there are more results in the requested page direction." + }), + "last_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Last Id", + "description": "Last ID in the `data` list. Can be used as the `after_id` for the next page." + }) +}).annotate({ "title": "ListResponse[ModelInfo]" }) +export type BetaErrorResponse = { + readonly "error": + | BetaInvalidRequestError + | BetaAuthenticationError + | BetaBillingError + | BetaPermissionError + | BetaNotFoundError + | BetaRateLimitError + | BetaGatewayTimeoutError + | BetaAPIError + | BetaOverloadedError + readonly "request_id": string | null + readonly "type": "error" +} +export const BetaErrorResponse = Schema.Struct({ + "error": Schema.Union([ + BetaInvalidRequestError, + BetaAuthenticationError, + BetaBillingError, + BetaPermissionError, + BetaNotFoundError, + BetaRateLimitError, + BetaGatewayTimeoutError, + BetaAPIError, + BetaOverloadedError + ], { mode: "oneOf" }).annotate({ "title": "Error" }), + "request_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Request Id", "default": null }), + "type": Schema.Literal("error").annotate({ "title": "Type", "default": "error" }) +}).annotate({ "title": "ErrorResponse" }) +export type BetaRequestBashCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "bash_code_execution_result" +} +export const BetaRequestBashCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(BetaRequestBashCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("bash_code_execution_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionResultBlock" }) +export type BetaWebFetchTool_20250910 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: BetaRequestCitationsConfig | null + readonly "defer_loading"?: boolean + readonly "max_content_tokens"?: number | null + readonly "max_uses"?: number | null + readonly "name": "web_fetch" + readonly "strict"?: boolean + readonly "type": "web_fetch_20250910" +} +export const BetaWebFetchTool_20250910 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": "List of domains to allow fetching from" + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": "List of domains to block fetching from" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([BetaRequestCitationsConfig, Schema.Null]).annotate({ + "description": "Citations configuration for fetched documents. Citations are disabled by default." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_content_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Content Tokens", + "description": + "Maximum number of tokens used by including web page text content in the context. The limit is approximate and does not apply to binary content such as PDFs." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_fetch").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_fetch_20250910").annotate({ "title": "Type" }) +}).annotate({ "title": "WebFetchTool_20250910" }) +export type BetaWebFetchTool_20260209 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: BetaRequestCitationsConfig | null + readonly "defer_loading"?: boolean + readonly "max_content_tokens"?: number | null + readonly "max_uses"?: number | null + readonly "name": "web_fetch" + readonly "strict"?: boolean + readonly "type": "web_fetch_20260209" +} +export const BetaWebFetchTool_20260209 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": "List of domains to allow fetching from" + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": "List of domains to block fetching from" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([BetaRequestCitationsConfig, Schema.Null]).annotate({ + "description": "Citations configuration for fetched documents. Citations are disabled by default." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_content_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Content Tokens", + "description": + "Maximum number of tokens used by including web page text content in the context. The limit is approximate and does not apply to binary content such as PDFs." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_fetch").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_fetch_20260209").annotate({ "title": "Type" }) +}).annotate({ "title": "WebFetchTool_20260209" }) +export type BetaRequestCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "code_execution_result" +} +export const BetaRequestCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(BetaRequestCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("code_execution_result").annotate({ "title": "Type" }) +}).annotate({ "title": "Result Block" }) +export type BetaRequestEncryptedCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "encrypted_stdout": string + readonly "return_code": number + readonly "stderr": string + readonly "type": "encrypted_code_execution_result" +} +export const BetaRequestEncryptedCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(BetaRequestCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "encrypted_stdout": Schema.String.annotate({ "title": "Encrypted Stdout" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "type": Schema.Literal("encrypted_code_execution_result").annotate({ "title": "Type" }) +}).annotate({ + "title": "RequestEncryptedCodeExecutionResultBlock", + "description": "Code execution result with encrypted stdout for PFC + web_search results." +}) +export type BetaRequestMCPServerURLDefinition = { + readonly "authorization_token"?: string | null + readonly "name": string + readonly "tool_configuration"?: BetaRequestMCPServerToolConfiguration | null + readonly "type": "url" + readonly "url": string +} +export const BetaRequestMCPServerURLDefinition = Schema.Struct({ + "authorization_token": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Authorization Token" }) + ), + "name": Schema.String.annotate({ "title": "Name" }), + "tool_configuration": Schema.optionalKey(Schema.Union([BetaRequestMCPServerToolConfiguration, Schema.Null])), + "type": Schema.Literal("url").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url" }) +}).annotate({ "title": "RequestMCPServerURLDefinition" }) +export type BetaRequestMCPToolResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "content"?: + | string + | ReadonlyArray< + { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: + | ReadonlyArray< + | BetaRequestCharLocationCitation + | BetaRequestPageLocationCitation + | BetaRequestContentBlockLocationCitation + | BetaRequestWebSearchResultLocationCitation + | BetaRequestSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" + } + > + readonly "is_error"?: boolean + readonly "tool_use_id": string + readonly "type": "mcp_tool_result" +} +export const BetaRequestMCPToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Array( + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + BetaRequestCharLocationCitation, + BetaRequestPageLocationCitation, + BetaRequestContentBlockLocationCitation, + BetaRequestWebSearchResultLocationCitation, + BetaRequestSearchResultLocationCitation + ], { mode: "oneOf" }) + ), + Schema.Null + ]).annotate({ "title": "Citations" }) + ), + "text": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(1)), + "type": Schema.Literal("text").annotate({ "title": "Type" }) + }).annotate({ "title": "beta_mcp_tool_result_block_param_content_item" }) + ).annotate({ "title": "beta_mcp_tool_result_block_param_content" }) + ]).annotate({ "title": "Content" }) + ), + "is_error": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Is Error" })), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$")) + ), + "type": Schema.Literal("mcp_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestMCPToolResultBlock" }) +export type BetaRequestTextBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: + | ReadonlyArray< + | BetaRequestCharLocationCitation + | BetaRequestPageLocationCitation + | BetaRequestContentBlockLocationCitation + | BetaRequestWebSearchResultLocationCitation + | BetaRequestSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" +} +export const BetaRequestTextBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + BetaRequestCharLocationCitation, + BetaRequestPageLocationCitation, + BetaRequestContentBlockLocationCitation, + BetaRequestWebSearchResultLocationCitation, + BetaRequestSearchResultLocationCitation + ], { mode: "oneOf" }) + ), + Schema.Null + ]).annotate({ "title": "Citations" }) + ), + "text": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(1)), + "type": Schema.Literal("text").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextBlock" }) +export type BetaResponseBashCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "bash_code_execution_result" +} +export const BetaResponseBashCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(BetaResponseBashCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("bash_code_execution_result").annotate({ + "title": "Type", + "default": "bash_code_execution_result" + }) +}).annotate({ "title": "ResponseBashCodeExecutionResultBlock" }) +export type BetaResponseDocumentBlock = { + readonly "citations": BetaResponseCitationsConfig | null + readonly "source": BetaBase64PDFSource | BetaPlainTextSource + readonly "title": string | null + readonly "type": "document" +} +export const BetaResponseDocumentBlock = Schema.Struct({ + "citations": Schema.Union([BetaResponseCitationsConfig, Schema.Null]).annotate({ + "description": "Citation configuration for the document", + "default": null + }), + "source": Schema.Union([BetaBase64PDFSource, BetaPlainTextSource], { mode: "oneOf" }).annotate({ "title": "Source" }), + "title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Title", + "description": "The title of the document", + "default": null + }), + "type": Schema.Literal("document").annotate({ "title": "Type", "default": "document" }) +}).annotate({ "title": "ResponseDocumentBlock" }) +export type BetaResponseContextManagement = { + readonly "applied_edits": ReadonlyArray +} +export const BetaResponseContextManagement = Schema.Struct({ + "applied_edits": Schema.Array( + Schema.Union([BetaResponseClearToolUses20250919Edit, BetaResponseClearThinking20251015Edit], { mode: "oneOf" }) + ).annotate({ "title": "Applied Edits", "description": "List of context management edits that were applied." }) +}).annotate({ "title": "ResponseContextManagement" }) +export type BetaResponseCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "code_execution_result" +} +export const BetaResponseCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(BetaResponseCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("code_execution_result").annotate({ "title": "Type", "default": "code_execution_result" }) +}).annotate({ "title": "ResponseCodeExecutionResultBlock" }) +export type BetaResponseEncryptedCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "encrypted_stdout": string + readonly "return_code": number + readonly "stderr": string + readonly "type": "encrypted_code_execution_result" +} +export const BetaResponseEncryptedCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(BetaResponseCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "encrypted_stdout": Schema.String.annotate({ "title": "Encrypted Stdout" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "type": Schema.Literal("encrypted_code_execution_result").annotate({ + "title": "Type", + "default": "encrypted_code_execution_result" + }) +}).annotate({ + "title": "ResponseEncryptedCodeExecutionResultBlock", + "description": "Code execution result with encrypted stdout for PFC + web_search results." +}) +export type BetaResponseToolSearchToolSearchResultBlock = { + readonly "tool_references": ReadonlyArray + readonly "type": "tool_search_tool_search_result" +} +export const BetaResponseToolSearchToolSearchResultBlock = Schema.Struct({ + "tool_references": Schema.Array(BetaResponseToolReferenceBlock).annotate({ "title": "Tool References" }), + "type": Schema.Literal("tool_search_tool_search_result").annotate({ + "title": "Type", + "default": "tool_search_tool_search_result" + }) +}).annotate({ "title": "ResponseToolSearchToolSearchResultBlock" }) +export type BetaCitationsDelta = { + readonly "citation": + | BetaResponseCharLocationCitation + | BetaResponsePageLocationCitation + | BetaResponseContentBlockLocationCitation + | BetaResponseWebSearchResultLocationCitation + | BetaResponseSearchResultLocationCitation + readonly "type": "citations_delta" +} +export const BetaCitationsDelta = Schema.Struct({ + "citation": Schema.Union([ + BetaResponseCharLocationCitation, + BetaResponsePageLocationCitation, + BetaResponseContentBlockLocationCitation, + BetaResponseWebSearchResultLocationCitation, + BetaResponseSearchResultLocationCitation + ], { mode: "oneOf" }).annotate({ "title": "Citation" }), + "type": Schema.Literal("citations_delta").annotate({ "title": "Type", "default": "citations_delta" }) +}).annotate({ "title": "CitationsDelta" }) +export type BetaResponseMCPToolResultBlock = { + readonly "content": + | string + | ReadonlyArray< + { + readonly "citations": + | ReadonlyArray< + | BetaResponseCharLocationCitation + | BetaResponsePageLocationCitation + | BetaResponseContentBlockLocationCitation + | BetaResponseWebSearchResultLocationCitation + | BetaResponseSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" + } + > + readonly "is_error": boolean + readonly "tool_use_id": string + readonly "type": "mcp_tool_result" +} +export const BetaResponseMCPToolResultBlock = Schema.Struct({ + "content": Schema.Union([ + Schema.String, + Schema.Array( + 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": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(0)).check( + Schema.isMaxLength(5000000) + ), + "type": Schema.Literal("text").annotate({ "title": "Type", "default": "text" }) + }).annotate({ "title": "beta_mcp_tool_result_block_content_item" }) + ).annotate({ "title": "beta_mcp_tool_result_block_content" }) + ]).annotate({ "title": "Content" }), + "is_error": Schema.Boolean.annotate({ "title": "Is Error", "default": false }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$")) + ), + "type": Schema.Literal("mcp_tool_result").annotate({ "title": "Type", "default": "mcp_tool_result" }) +}).annotate({ "title": "ResponseMCPToolResultBlock" }) +export type BetaResponseTextBlock = { + readonly "citations"?: + | ReadonlyArray< + | BetaResponseCharLocationCitation + | BetaResponsePageLocationCitation + | BetaResponseContentBlockLocationCitation + | BetaResponseWebSearchResultLocationCitation + | BetaResponseSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" +} +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": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(0)).check(Schema.isMaxLength(5000000)), + "type": Schema.Literal("text").annotate({ "title": "Type", "default": "text" }) +}).annotate({ "title": "ResponseTextBlock" }) +export type BetaRequestServerToolUseBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": + | "web_search" + | "web_fetch" + | "code_execution" + | "bash_code_execution" + | "text_editor_code_execution" + | "tool_search_tool_regex" + | "tool_search_tool_bm25" + readonly "type": "server_tool_use" +} +export const BetaRequestServerToolUseBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.Literals([ + "web_search", + "web_fetch", + "code_execution", + "bash_code_execution", + "text_editor_code_execution", + "tool_search_tool_regex", + "tool_search_tool_bm25" + ]).annotate({ "title": "Name" }), + "type": Schema.Literal("server_tool_use").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestServerToolUseBlock" }) +export type BetaResponseServerToolUseBlock = { + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": + | "web_search" + | "web_fetch" + | "code_execution" + | "bash_code_execution" + | "text_editor_code_execution" + | "tool_search_tool_regex" + | "tool_search_tool_bm25" + readonly "type": "server_tool_use" +} +export const BetaResponseServerToolUseBlock = Schema.Struct({ + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.Literals([ + "web_search", + "web_fetch", + "code_execution", + "bash_code_execution", + "text_editor_code_execution", + "tool_search_tool_regex", + "tool_search_tool_bm25" + ]).annotate({ "title": "Name" }), + "type": Schema.Literal("server_tool_use").annotate({ "title": "Type", "default": "server_tool_use" }) +}).annotate({ "title": "ResponseServerToolUseBlock" }) +export type BetaResponseToolUseBlock = { + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": string + readonly "type": "tool_use" +} +export const BetaResponseToolUseBlock = Schema.Struct({ + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.String.annotate({ "title": "Name" }).check(Schema.isMinLength(1)), + "type": Schema.Literal("tool_use").annotate({ "title": "Type", "default": "tool_use" }) +}).annotate({ "title": "ResponseToolUseBlock" }) +export type BetaContainer = { + readonly "expires_at": string + readonly "id": string + readonly "skills": ReadonlyArray | null +} +export const BetaContainer = Schema.Struct({ + "expires_at": Schema.String.annotate({ + "title": "Expires At", + "description": "The time at which the container will expire.", + "format": "date-time" + }), + "id": Schema.String.annotate({ "title": "Id", "description": "Identifier for the container used in this request" }), + "skills": Schema.Union([Schema.Array(BetaSkill), Schema.Null]).annotate({ + "title": "Skills", + "description": "Skills loaded in the container", + "default": null + }) +}).annotate({ + "title": "Container", + "description": "Information about the container used in the request (for the code execution tool)" +}) +export type BetaContainerParams = { + readonly "id"?: string | null + readonly "skills"?: ReadonlyArray | null +} +export const BetaContainerParams = Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Id", "description": "Container id" }) + ), + "skills": Schema.optionalKey( + Schema.Union([Schema.Array(BetaSkillParams).check(Schema.isMaxLength(8)), Schema.Null]).annotate({ + "title": "Skills", + "description": "List of skills to load in the container" + }) + ) +}).annotate({ "title": "ContainerParams", "description": "Container parameters with skills to be loaded." }) +export type BetaListSkillVersionsResponse = { + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next_page": string | null +} +export const BetaListSkillVersionsResponse = Schema.Struct({ + "data": Schema.Array(BetaSkillVersion).annotate({ "title": "Data", "description": "List of skill versions." }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": "Indicates if there are more results in the requested page direction." + }), + "next_page": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Next Page", + "description": "Token to provide in as `page` in the subsequent request to retrieve the next page of data." + }) +}).annotate({ "title": "ListSkillVersionsResponse" }) +export type BetaRequestTextEditorCodeExecutionToolResultError = { + readonly "error_code": BetaTextEditorCodeExecutionToolResultErrorCode + readonly "error_message"?: string | null + readonly "type": "text_editor_code_execution_tool_result_error" +} +export const BetaRequestTextEditorCodeExecutionToolResultError = Schema.Struct({ + "error_code": BetaTextEditorCodeExecutionToolResultErrorCode, + "error_message": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Error Message" }) + ), + "type": Schema.Literal("text_editor_code_execution_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionToolResultError" }) +export type BetaResponseTextEditorCodeExecutionToolResultError = { + readonly "error_code": BetaTextEditorCodeExecutionToolResultErrorCode + readonly "error_message": string | null + readonly "type": "text_editor_code_execution_tool_result_error" +} +export const BetaResponseTextEditorCodeExecutionToolResultError = Schema.Struct({ + "error_code": BetaTextEditorCodeExecutionToolResultErrorCode, + "error_message": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Error Message", "default": null }), + "type": Schema.Literal("text_editor_code_execution_tool_result_error").annotate({ + "title": "Type", + "default": "text_editor_code_execution_tool_result_error" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionToolResultError" }) +export type BetaThinkingConfigParam = + | BetaThinkingConfigEnabled + | BetaThinkingConfigDisabled + | BetaThinkingConfigAdaptive +export const BetaThinkingConfigParam = Schema.Union([ + BetaThinkingConfigEnabled, + BetaThinkingConfigDisabled, + BetaThinkingConfigAdaptive +], { mode: "oneOf" }).annotate({ + "title": "Thinking", + "description": + "Configuration for enabling Claude's extended thinking.\n\nWhen enabled, responses include `thinking` content blocks showing Claude's thinking process before the final answer. Requires a minimum budget of 1,024 tokens and counts towards your `max_tokens` limit.\n\nSee [extended thinking](https://docs.claude.com/en/docs/build-with-claude/extended-thinking) for details." +}) +export type BetaClearThinking20251015 = { + readonly "keep"?: BetaThinkingTurns | BetaAllThinkingTurns | "all" + readonly "type": "clear_thinking_20251015" +} +export const BetaClearThinking20251015 = Schema.Struct({ + "keep": Schema.optionalKey( + Schema.Union([Schema.Union([BetaThinkingTurns, BetaAllThinkingTurns], { mode: "oneOf" }), Schema.Literal("all")]) + .annotate({ + "title": "Keep", + "description": + "Number of most recent assistant turns to keep thinking blocks for. Older turns will have their thinking blocks removed." + }) + ), + "type": Schema.Literal("clear_thinking_20251015").annotate({ "title": "Type" }) +}).annotate({ "title": "ClearThinking20251015" }) +export type BetaToolChoice = BetaToolChoiceAuto | BetaToolChoiceAny | BetaToolChoiceTool | BetaToolChoiceNone +export const BetaToolChoice = Schema.Union([ + BetaToolChoiceAuto, + BetaToolChoiceAny, + BetaToolChoiceTool, + BetaToolChoiceNone +], { mode: "oneOf" }).annotate({ + "title": "Tool Choice", + "description": + "How the model should use the provided tools. The model can use a specific tool, any available tool, decide by itself, or not use tools at all." +}) +export type BetaRequestToolSearchToolResultError = { + readonly "error_code": BetaToolSearchToolResultErrorCode + readonly "type": "tool_search_tool_result_error" +} +export const BetaRequestToolSearchToolResultError = Schema.Struct({ + "error_code": BetaToolSearchToolResultErrorCode, + "type": Schema.Literal("tool_search_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestToolSearchToolResultError" }) +export type BetaResponseToolSearchToolResultError = { + readonly "error_code": BetaToolSearchToolResultErrorCode + readonly "error_message": string | null + readonly "type": "tool_search_tool_result_error" +} +export const BetaResponseToolSearchToolResultError = Schema.Struct({ + "error_code": BetaToolSearchToolResultErrorCode, + "error_message": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Error Message", "default": null }), + "type": Schema.Literal("tool_search_tool_result_error").annotate({ + "title": "Type", + "default": "tool_search_tool_result_error" + }) +}).annotate({ "title": "ResponseToolSearchToolResultError" }) +export type BetaClearToolUses20250919 = { + readonly "clear_at_least"?: BetaInputTokensClearAtLeast | null + readonly "clear_tool_inputs"?: boolean | ReadonlyArray | null + readonly "exclude_tools"?: ReadonlyArray | null + readonly "keep"?: BetaToolUsesKeep + readonly "trigger"?: BetaInputTokensTrigger | BetaToolUsesTrigger + readonly "type": "clear_tool_uses_20250919" +} +export const BetaClearToolUses20250919 = Schema.Struct({ + "clear_at_least": Schema.optionalKey( + Schema.Union([BetaInputTokensClearAtLeast, Schema.Null]).annotate({ + "description": + "Minimum number of tokens that must be cleared when triggered. Context will only be modified if at least this many tokens can be removed." + }) + ), + "clear_tool_inputs": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Clear Tool Inputs", + "description": "Whether to clear all tool inputs (bool) or specific tool inputs to clear (list)" + }) + ), + "exclude_tools": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Exclude Tools", + "description": "Tool names whose uses are preserved from clearing" + }) + ), + "keep": Schema.optionalKey( + Schema.Union([BetaToolUsesKeep], { mode: "oneOf" }).annotate({ + "title": "Keep", + "description": "Number of tool uses to retain in the conversation" + }) + ), + "trigger": Schema.optionalKey( + Schema.Union([BetaInputTokensTrigger, BetaToolUsesTrigger], { mode: "oneOf" }).annotate({ + "title": "Trigger", + "description": "Condition that triggers the context management strategy" + }) + ), + "type": Schema.Literal("clear_tool_uses_20250919").annotate({ "title": "Type" }) +}).annotate({ "title": "ClearToolUses20250919" }) +export type BetaRequestImageBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "source": BetaBase64ImageSource | BetaURLImageSource | BetaFileImageSource + readonly "type": "image" +} +export const BetaRequestImageBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "source": Schema.Union([BetaBase64ImageSource, BetaURLImageSource, BetaFileImageSource], { mode: "oneOf" }).annotate({ + "title": "Source" + }), + "type": Schema.Literal("image").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestImageBlock" }) +export type BetaWebSearchTool_20250305 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "strict"?: boolean + readonly "type": "web_search_20250305" + readonly "user_location"?: BetaUserLocation | null +} +export const BetaWebSearchTool_20250305 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": + "If provided, only these domains will be included in results. Cannot be used alongside `blocked_domains`." + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": + "If provided, these domains will never appear in results. Cannot be used alongside `allowed_domains`." + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_search").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_search_20250305").annotate({ "title": "Type" }), + "user_location": Schema.optionalKey( + Schema.Union([BetaUserLocation, Schema.Null]).annotate({ + "description": "Parameters for the user's location. Used to provide more relevant search results." + }) + ) +}).annotate({ "title": "WebSearchTool_20250305" }) +export type BetaWebSearchTool_20260209 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "strict"?: boolean + readonly "type": "web_search_20260209" + readonly "user_location"?: BetaUserLocation | null +} +export const BetaWebSearchTool_20260209 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": + "If provided, only these domains will be included in results. Cannot be used alongside `blocked_domains`." + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": + "If provided, these domains will never appear in results. Cannot be used alongside `allowed_domains`." + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_search").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_search_20260209").annotate({ "title": "Type" }), + "user_location": Schema.optionalKey( + Schema.Union([BetaUserLocation, Schema.Null]).annotate({ + "description": "Parameters for the user's location. Used to provide more relevant search results." + }) + ) +}).annotate({ "title": "WebSearchTool_20260209" }) +export type BetaRequestWebFetchToolResultError = { + readonly "error_code": BetaWebFetchToolResultErrorCode + readonly "type": "web_fetch_tool_result_error" +} +export const BetaRequestWebFetchToolResultError = Schema.Struct({ + "error_code": BetaWebFetchToolResultErrorCode, + "type": Schema.Literal("web_fetch_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestWebFetchToolResultError" }) +export type BetaResponseWebFetchToolResultError = { + readonly "error_code": BetaWebFetchToolResultErrorCode + readonly "type": "web_fetch_tool_result_error" +} +export const BetaResponseWebFetchToolResultError = Schema.Struct({ + "error_code": BetaWebFetchToolResultErrorCode, + "type": Schema.Literal("web_fetch_tool_result_error").annotate({ + "title": "Type", + "default": "web_fetch_tool_result_error" + }) +}).annotate({ "title": "ResponseWebFetchToolResultError" }) +export type BetaRequestWebSearchToolResultError = { + readonly "error_code": BetaWebSearchToolResultErrorCode + readonly "type": "web_search_tool_result_error" +} +export const BetaRequestWebSearchToolResultError = Schema.Struct({ + "error_code": BetaWebSearchToolResultErrorCode, + "type": Schema.Literal("web_search_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "Error" }) +export type BetaResponseWebSearchToolResultError = { + readonly "error_code": BetaWebSearchToolResultErrorCode + readonly "type": "web_search_tool_result_error" +} +export const BetaResponseWebSearchToolResultError = Schema.Struct({ + "error_code": BetaWebSearchToolResultErrorCode, + "type": Schema.Literal("web_search_tool_result_error").annotate({ + "title": "Type", + "default": "web_search_tool_result_error" + }) +}).annotate({ "title": "ResponseWebSearchToolResultError" }) +export type BetaListSkillsResponse = { + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next_page": string | null +} +export const BetaListSkillsResponse = Schema.Struct({ + "data": Schema.Array(Betaapi__schemas__skills__Skill).annotate({ "title": "Data", "description": "List of skills." }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": + "Whether there are more results available.\n\nIf `true`, there are additional results that can be fetched using the `next_page` token." + }), + "next_page": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Next Page", + "description": + "Token for fetching the next page of results.\n\nIf `null`, there are no more results available. Pass this value to the `page_token` parameter in the next request to get the next page." + }) +}).annotate({ "title": "ListSkillsResponse" }) +export type CodeExecutionTool_20250522 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "name": "code_execution" + readonly "strict"?: boolean + readonly "type": "code_execution_20250522" +} +export const CodeExecutionTool_20250522 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("code_execution").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("code_execution_20250522").annotate({ "title": "Type" }) +}).annotate({ "title": "CodeExecutionTool_20250522" }) +export type CodeExecutionTool_20250825 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "name": "code_execution" + readonly "strict"?: boolean + readonly "type": "code_execution_20250825" +} +export const CodeExecutionTool_20250825 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("code_execution").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("code_execution_20250825").annotate({ "title": "Type" }) +}).annotate({ "title": "CodeExecutionTool_20250825" }) +export type CodeExecutionTool_20260120 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "name": "code_execution" + readonly "strict"?: boolean + readonly "type": "code_execution_20260120" +} +export const CodeExecutionTool_20260120 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("code_execution").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("code_execution_20260120").annotate({ "title": "Type" }) +}).annotate({ + "title": "CodeExecutionTool_20260120", + "description": "Code execution tool with REPL state persistence (daemon mode + gVisor checkpoint)." +}) +export type RequestContainerUploadBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "file_id": string + readonly "type": "container_upload" +} +export const RequestContainerUploadBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "file_id": Schema.String.annotate({ "title": "File Id" }), + "type": Schema.Literal("container_upload").annotate({ "title": "Type" }) +}).annotate({ + "title": "RequestContainerUploadBlock", + "description": + "A content block that represents a file to be uploaded to the container\nFiles uploaded via this block will be available in the container's input directory." +}) +export type RequestToolReferenceBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "tool_name": string + readonly "type": "tool_reference" +} +export const RequestToolReferenceBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "tool_name": Schema.String.annotate({ "title": "Tool Name" }).check(Schema.isMinLength(1)).check( + Schema.isMaxLength(256) + ).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,256}$"))), + "type": Schema.Literal("tool_reference").annotate({ "title": "Type" }) +}).annotate({ + "title": "RequestToolReferenceBlock", + "description": "Tool reference block that can be included in tool_result content." +}) +export type ToolSearchToolBM25_20251119 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + 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 const ToolSearchToolBM25_20251119 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("tool_search_tool_bm25").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literals(["tool_search_tool_bm25_20251119", "tool_search_tool_bm25"]).annotate({ "title": "Type" }) +}).annotate({ "title": "ToolSearchToolBM25_20251119" }) +export type ToolSearchToolRegex_20251119 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + 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 ToolSearchToolRegex_20251119 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "name": Schema.Literal("tool_search_tool_regex").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literals(["tool_search_tool_regex_20251119", "tool_search_tool_regex"]).annotate({ "title": "Type" }) +}).annotate({ "title": "ToolSearchToolRegex_20251119" }) +export type RequestCodeExecutionToolResultError = { + readonly "error_code": CodeExecutionToolResultErrorCode + readonly "type": "code_execution_tool_result_error" +} +export const RequestCodeExecutionToolResultError = Schema.Struct({ + "error_code": CodeExecutionToolResultErrorCode, + "type": Schema.Literal("code_execution_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCodeExecutionToolResultError" }) +export type ResponseCodeExecutionToolResultError = { + readonly "error_code": CodeExecutionToolResultErrorCode + readonly "type": "code_execution_tool_result_error" +} +export const ResponseCodeExecutionToolResultError = Schema.Struct({ + "error_code": CodeExecutionToolResultErrorCode, + "type": Schema.Literal("code_execution_tool_result_error").annotate({ + "title": "Type", + "default": "code_execution_tool_result_error" + }) +}).annotate({ "title": "ResponseCodeExecutionToolResultError" }) +export type FileListResponse = { + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "has_more"?: boolean + readonly "last_id"?: string | null +} +export const FileListResponse = Schema.Struct({ + "data": Schema.Array(FileMetadataSchema).annotate({ + "title": "Data", + "description": "List of file metadata objects." + }), + "first_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "First Id", + "description": "ID of the first file in this page of results." + }) + ), + "has_more": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Has More", + "description": "Whether there are more results available.", + "default": false + }) + ), + "last_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Last Id", + "description": "ID of the last file in this page of results." + }) + ) +}).annotate({ "title": "FileListResponse" }) +export type BashTool_20250124 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: JsonValue }> + readonly "name": "bash" + readonly "strict"?: boolean + readonly "type": "bash_20250124" +} +export const BashTool_20250124 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, JsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("bash").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("bash_20250124").annotate({ "title": "Type" }) +}).annotate({ "title": "BashTool_20250124" }) +export type MemoryTool_20250818 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: JsonValue }> + readonly "name": "memory" + readonly "strict"?: boolean + readonly "type": "memory_20250818" +} +export const MemoryTool_20250818 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, JsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("memory").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("memory_20250818").annotate({ "title": "Type" }) +}).annotate({ "title": "MemoryTool_20250818" }) +export type TextEditor_20250124 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: JsonValue }> + readonly "name": "str_replace_editor" + readonly "strict"?: boolean + readonly "type": "text_editor_20250124" +} +export const TextEditor_20250124 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, JsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("str_replace_editor").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("text_editor_20250124").annotate({ "title": "Type" }) +}).annotate({ "title": "TextEditor_20250124" }) +export type TextEditor_20250429 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: JsonValue }> + readonly "name": "str_replace_based_edit_tool" + readonly "strict"?: boolean + readonly "type": "text_editor_20250429" +} +export const TextEditor_20250429 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, JsonValue)).annotate({ "title": "Input Examples" }) + ), + "name": Schema.Literal("str_replace_based_edit_tool").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("text_editor_20250429").annotate({ "title": "Type" }) +}).annotate({ "title": "TextEditor_20250429" }) +export type TextEditor_20250728 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: JsonValue }> + readonly "max_characters"?: number | null + readonly "name": "str_replace_based_edit_tool" + readonly "strict"?: boolean + readonly "type": "text_editor_20250728" +} +export const TextEditor_20250728 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, JsonValue)).annotate({ "title": "Input Examples" }) + ), + "max_characters": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), Schema.Null]).annotate({ + "title": "Max Characters", + "description": + "Maximum number of characters to display when viewing a file. If not specified, defaults to displaying the full file." + }) + ), + "name": Schema.Literal("str_replace_based_edit_tool").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("text_editor_20250728").annotate({ "title": "Type" }) +}).annotate({ "title": "TextEditor_20250728" }) +export type Tool = { + readonly "type"?: null | "custom" + readonly "description"?: string + readonly "name": string + readonly "input_schema": { + readonly "properties"?: { readonly [x: string]: Schema.Json } | null + readonly "required"?: ReadonlyArray | null + readonly "type": "object" + readonly [x: string]: Schema.Json + } + readonly "cache_control"?: CacheControlEphemeral | null + readonly "strict"?: boolean + readonly "eager_input_streaming"?: boolean | null + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "defer_loading"?: boolean + readonly "input_examples"?: ReadonlyArray<{ readonly [x: string]: JsonValue }> +} +export const Tool = Schema.Struct({ + "type": Schema.optionalKey(Schema.Union([Schema.Null, Schema.Literal("custom")]).annotate({ "title": "Type" })), + "description": Schema.optionalKey(Schema.String.annotate({ + "title": "Description", + "description": + "Description of what this tool does.\n\nTool descriptions should be as detailed as possible. The more information that the model has about what the tool is and how to use it, the better it will perform. You can use natural language descriptions to reinforce important aspects of the tool input JSON schema." + })), + "name": Schema.String.annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(128)).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,128}$")) + ), + "input_schema": Schema.StructWithRest( + Schema.Struct({ + "properties": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json), Schema.Null]).annotate({ "title": "Properties" }) + ), + "required": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "title": "Required" }) + ), + "type": Schema.Literal("object").annotate({ "title": "Type" }) + }), + [Schema.Record(Schema.String, Schema.Json)] + ).annotate({ + "title": "InputSchema", + "description": + "[JSON schema](https://json-schema.org/draft/2020-12) for this tool's input.\n\nThis defines the shape of the `input` that your tool accepts and that the model will produce." + }), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "eager_input_streaming": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "title": "Eager Input Streaming", + "description": + "Enable eager input streaming for this tool. When true, tool input parameters will be streamed incrementally as they are generated, and types will be inferred on-the-fly rather than buffering the full JSON output. When false, streaming is disabled for this tool even if the fine-grained-tool-streaming beta is active. When null (default), uses the default behavior based on beta headers." + }) + ), + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "input_examples": Schema.optionalKey( + Schema.Array(Schema.Record(Schema.String, JsonValue)).annotate({ "title": "Input Examples" }) + ) +}).annotate({ "title": "Tool" }) +export type ListResponse_MessageBatch_ = { + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "has_more": boolean + readonly "last_id": string | null +} +export const ListResponse_MessageBatch_ = Schema.Struct({ + "data": Schema.Array(MessageBatch).annotate({ "title": "Data" }), + "first_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "First Id", + "description": "First ID in the `data` list. Can be used as the `before_id` for the previous page." + }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": "Indicates if there are more results in the requested page direction." + }), + "last_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Last Id", + "description": "Last ID in the `data` list. Can be used as the `after_id` for the next page." + }) +}).annotate({ "title": "ListResponse[MessageBatch]" }) +export type ListResponse_ModelInfo_ = { + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "has_more": boolean + readonly "last_id": string | null +} +export const ListResponse_ModelInfo_ = Schema.Struct({ + "data": Schema.Array(ModelInfo).annotate({ "title": "Data" }), + "first_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "First Id", + "description": "First ID in the `data` list. Can be used as the `before_id` for the previous page." + }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": "Indicates if there are more results in the requested page direction." + }), + "last_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Last Id", + "description": "Last ID in the `data` list. Can be used as the `after_id` for the next page." + }) +}).annotate({ "title": "ListResponse[ModelInfo]" }) +export type ErrorResponse = { + readonly "error": + | InvalidRequestError + | AuthenticationError + | BillingError + | PermissionError + | NotFoundError + | RateLimitError + | GatewayTimeoutError + | APIError + | OverloadedError + readonly "request_id": string | null + readonly "type": "error" +} +export const ErrorResponse = Schema.Struct({ + "error": Schema.Union([ + InvalidRequestError, + AuthenticationError, + BillingError, + PermissionError, + NotFoundError, + RateLimitError, + GatewayTimeoutError, + APIError, + OverloadedError + ], { mode: "oneOf" }).annotate({ "title": "Error" }), + "request_id": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Request Id", "default": null }), + "type": Schema.Literal("error").annotate({ "title": "Type", "default": "error" }) +}).annotate({ "title": "ErrorResponse" }) +export type RequestBashCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "bash_code_execution_result" +} +export const RequestBashCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(RequestBashCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("bash_code_execution_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionResultBlock" }) +export type WebFetchTool_20250910 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: RequestCitationsConfig | null + readonly "defer_loading"?: boolean + readonly "max_content_tokens"?: number | null + readonly "max_uses"?: number | null + readonly "name": "web_fetch" + readonly "strict"?: boolean + readonly "type": "web_fetch_20250910" +} +export const WebFetchTool_20250910 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": "List of domains to allow fetching from" + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": "List of domains to block fetching from" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([RequestCitationsConfig, Schema.Null]).annotate({ + "description": "Citations configuration for fetched documents. Citations are disabled by default." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_content_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Content Tokens", + "description": + "Maximum number of tokens used by including web page text content in the context. The limit is approximate and does not apply to binary content such as PDFs." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_fetch").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_fetch_20250910").annotate({ "title": "Type" }) +}).annotate({ "title": "WebFetchTool_20250910" }) +export type WebFetchTool_20260209 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: RequestCitationsConfig | null + readonly "defer_loading"?: boolean + readonly "max_content_tokens"?: number | null + readonly "max_uses"?: number | null + readonly "name": "web_fetch" + readonly "strict"?: boolean + readonly "type": "web_fetch_20260209" +} +export const WebFetchTool_20260209 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": "List of domains to allow fetching from" + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": "List of domains to block fetching from" + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([RequestCitationsConfig, Schema.Null]).annotate({ + "description": "Citations configuration for fetched documents. Citations are disabled by default." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_content_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Content Tokens", + "description": + "Maximum number of tokens used by including web page text content in the context. The limit is approximate and does not apply to binary content such as PDFs." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_fetch").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_fetch_20260209").annotate({ "title": "Type" }) +}).annotate({ "title": "WebFetchTool_20260209" }) +export type RequestCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "code_execution_result" +} +export const RequestCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(RequestCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("code_execution_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCodeExecutionResultBlock" }) +export type RequestEncryptedCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "encrypted_stdout": string + readonly "return_code": number + readonly "stderr": string + readonly "type": "encrypted_code_execution_result" +} +export const RequestEncryptedCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(RequestCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "encrypted_stdout": Schema.String.annotate({ "title": "Encrypted Stdout" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "type": Schema.Literal("encrypted_code_execution_result").annotate({ "title": "Type" }) +}).annotate({ + "title": "RequestEncryptedCodeExecutionResultBlock", + "description": "Code execution result with encrypted stdout for PFC + web_search results." +}) +export type RequestTextBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: + | ReadonlyArray< + | RequestCharLocationCitation + | RequestPageLocationCitation + | RequestContentBlockLocationCitation + | RequestWebSearchResultLocationCitation + | RequestSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" +} +export const RequestTextBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + RequestCharLocationCitation, + RequestPageLocationCitation, + RequestContentBlockLocationCitation, + RequestWebSearchResultLocationCitation, + RequestSearchResultLocationCitation + ], { mode: "oneOf" }) + ), + Schema.Null + ]).annotate({ "title": "Citations" }) + ), + "text": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(1)), + "type": Schema.Literal("text").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextBlock" }) +export type ResponseBashCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "bash_code_execution_result" +} +export const ResponseBashCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(ResponseBashCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("bash_code_execution_result").annotate({ + "title": "Type", + "default": "bash_code_execution_result" + }) +}).annotate({ "title": "ResponseBashCodeExecutionResultBlock" }) +export type ResponseDocumentBlock = { + readonly "citations": ResponseCitationsConfig | null + readonly "source": Base64PDFSource | PlainTextSource + readonly "title": string | null + readonly "type": "document" +} +export const ResponseDocumentBlock = Schema.Struct({ + "citations": Schema.Union([ResponseCitationsConfig, Schema.Null]).annotate({ + "description": "Citation configuration for the document", + "default": null + }), + "source": Schema.Union([Base64PDFSource, PlainTextSource], { mode: "oneOf" }).annotate({ "title": "Source" }), + "title": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Title", + "description": "The title of the document", + "default": null + }), + "type": Schema.Literal("document").annotate({ "title": "Type", "default": "document" }) +}).annotate({ "title": "ResponseDocumentBlock" }) +export type ResponseCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "code_execution_result" +} +export const ResponseCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(ResponseCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "stdout": Schema.String.annotate({ "title": "Stdout" }), + "type": Schema.Literal("code_execution_result").annotate({ "title": "Type", "default": "code_execution_result" }) +}).annotate({ "title": "ResponseCodeExecutionResultBlock" }) +export type ResponseEncryptedCodeExecutionResultBlock = { + readonly "content": ReadonlyArray + readonly "encrypted_stdout": string + readonly "return_code": number + readonly "stderr": string + readonly "type": "encrypted_code_execution_result" +} +export const ResponseEncryptedCodeExecutionResultBlock = Schema.Struct({ + "content": Schema.Array(ResponseCodeExecutionOutputBlock).annotate({ "title": "Content" }), + "encrypted_stdout": Schema.String.annotate({ "title": "Encrypted Stdout" }), + "return_code": Schema.Number.annotate({ "title": "Return Code" }).check(Schema.isInt()), + "stderr": Schema.String.annotate({ "title": "Stderr" }), + "type": Schema.Literal("encrypted_code_execution_result").annotate({ + "title": "Type", + "default": "encrypted_code_execution_result" + }) +}).annotate({ + "title": "ResponseEncryptedCodeExecutionResultBlock", + "description": "Code execution result with encrypted stdout for PFC + web_search results." +}) +export type ResponseToolSearchToolSearchResultBlock = { + readonly "tool_references": ReadonlyArray + readonly "type": "tool_search_tool_search_result" +} +export const ResponseToolSearchToolSearchResultBlock = Schema.Struct({ + "tool_references": Schema.Array(ResponseToolReferenceBlock).annotate({ "title": "Tool References" }), + "type": Schema.Literal("tool_search_tool_search_result").annotate({ + "title": "Type", + "default": "tool_search_tool_search_result" + }) +}).annotate({ "title": "ResponseToolSearchToolSearchResultBlock" }) +export type CitationsDelta = { + readonly "citation": + | ResponseCharLocationCitation + | ResponsePageLocationCitation + | ResponseContentBlockLocationCitation + | ResponseWebSearchResultLocationCitation + | ResponseSearchResultLocationCitation + readonly "type": "citations_delta" +} +export const CitationsDelta = Schema.Struct({ + "citation": Schema.Union([ + ResponseCharLocationCitation, + ResponsePageLocationCitation, + ResponseContentBlockLocationCitation, + ResponseWebSearchResultLocationCitation, + ResponseSearchResultLocationCitation + ], { mode: "oneOf" }).annotate({ "title": "Citation" }), + "type": Schema.Literal("citations_delta").annotate({ "title": "Type", "default": "citations_delta" }) +}).annotate({ "title": "CitationsDelta" }) +export type ResponseTextBlock = { + readonly "citations"?: + | ReadonlyArray< + | ResponseCharLocationCitation + | ResponsePageLocationCitation + | ResponseContentBlockLocationCitation + | ResponseWebSearchResultLocationCitation + | ResponseSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" +} +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": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(0)).check(Schema.isMaxLength(5000000)), + "type": Schema.Literal("text").annotate({ "title": "Type", "default": "text" }) +}).annotate({ "title": "ResponseTextBlock" }) +export type RequestServerToolUseBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "caller"?: DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": + | "web_search" + | "web_fetch" + | "code_execution" + | "bash_code_execution" + | "text_editor_code_execution" + | "tool_search_tool_regex" + | "tool_search_tool_bm25" + readonly "type": "server_tool_use" +} +export const RequestServerToolUseBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.Literals([ + "web_search", + "web_fetch", + "code_execution", + "bash_code_execution", + "text_editor_code_execution", + "tool_search_tool_regex", + "tool_search_tool_bm25" + ]).annotate({ "title": "Name" }), + "type": Schema.Literal("server_tool_use").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestServerToolUseBlock" }) +export type ResponseServerToolUseBlock = { + readonly "caller": DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": + | "web_search" + | "web_fetch" + | "code_execution" + | "bash_code_execution" + | "text_editor_code_execution" + | "tool_search_tool_regex" + | "tool_search_tool_bm25" + readonly "type": "server_tool_use" +} +export const ResponseServerToolUseBlock = Schema.Struct({ + "caller": Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller", + "default": { "type": "direct" } + }), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.Literals([ + "web_search", + "web_fetch", + "code_execution", + "bash_code_execution", + "text_editor_code_execution", + "tool_search_tool_regex", + "tool_search_tool_bm25" + ]).annotate({ "title": "Name" }), + "type": Schema.Literal("server_tool_use").annotate({ "title": "Type", "default": "server_tool_use" }) +}).annotate({ "title": "ResponseServerToolUseBlock" }) +export type ResponseToolUseBlock = { + readonly "caller": DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": string + readonly "type": "tool_use" +} +export const ResponseToolUseBlock = Schema.Struct({ + "caller": Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller", + "default": { "type": "direct" } + }), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.String.annotate({ "title": "Name" }).check(Schema.isMinLength(1)), + "type": Schema.Literal("tool_use").annotate({ "title": "Type", "default": "tool_use" }) +}).annotate({ "title": "ResponseToolUseBlock" }) +export type ListSkillsResponse = { + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next_page": string | null +} +export const ListSkillsResponse = Schema.Struct({ + "data": Schema.Array(Skill).annotate({ "title": "Data", "description": "List of skills." }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": + "Whether there are more results available.\n\nIf `true`, there are additional results that can be fetched using the `next_page` token." + }), + "next_page": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Next Page", + "description": + "Token for fetching the next page of results.\n\nIf `null`, there are no more results available. Pass this value to the `page_token` parameter in the next request to get the next page." + }) +}).annotate({ "title": "ListSkillsResponse" }) +export type ListSkillVersionsResponse = { + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next_page": string | null +} +export const ListSkillVersionsResponse = Schema.Struct({ + "data": Schema.Array(SkillVersion).annotate({ "title": "Data", "description": "List of skill versions." }), + "has_more": Schema.Boolean.annotate({ + "title": "Has More", + "description": "Indicates if there are more results in the requested page direction." + }), + "next_page": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Next Page", + "description": "Token to provide in as `page` in the subsequent request to retrieve the next page of data." + }) +}).annotate({ "title": "ListSkillVersionsResponse" }) +export type RequestTextEditorCodeExecutionToolResultError = { + readonly "error_code": TextEditorCodeExecutionToolResultErrorCode + readonly "error_message"?: string | null + readonly "type": "text_editor_code_execution_tool_result_error" +} +export const RequestTextEditorCodeExecutionToolResultError = Schema.Struct({ + "error_code": TextEditorCodeExecutionToolResultErrorCode, + "error_message": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Error Message" }) + ), + "type": Schema.Literal("text_editor_code_execution_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionToolResultError" }) +export type ResponseTextEditorCodeExecutionToolResultError = { + readonly "error_code": TextEditorCodeExecutionToolResultErrorCode + readonly "error_message": string | null + readonly "type": "text_editor_code_execution_tool_result_error" +} +export const ResponseTextEditorCodeExecutionToolResultError = Schema.Struct({ + "error_code": TextEditorCodeExecutionToolResultErrorCode, + "error_message": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Error Message", "default": null }), + "type": Schema.Literal("text_editor_code_execution_tool_result_error").annotate({ + "title": "Type", + "default": "text_editor_code_execution_tool_result_error" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionToolResultError" }) +export type ThinkingConfigParam = ThinkingConfigEnabled | ThinkingConfigDisabled | ThinkingConfigAdaptive +export const ThinkingConfigParam = Schema.Union( + [ThinkingConfigEnabled, ThinkingConfigDisabled, ThinkingConfigAdaptive], + { mode: "oneOf" } +).annotate({ + "title": "Thinking", + "description": + "Configuration for enabling Claude's extended thinking.\n\nWhen enabled, responses include `thinking` content blocks showing Claude's thinking process before the final answer. Requires a minimum budget of 1,024 tokens and counts towards your `max_tokens` limit.\n\nSee [extended thinking](https://docs.claude.com/en/docs/build-with-claude/extended-thinking) for details." +}) +export type ToolChoice = ToolChoiceAuto | ToolChoiceAny | ToolChoiceTool | ToolChoiceNone +export const ToolChoice = Schema.Union([ToolChoiceAuto, ToolChoiceAny, ToolChoiceTool, ToolChoiceNone], { + mode: "oneOf" +}).annotate({ + "title": "Tool Choice", + "description": + "How the model should use the provided tools. The model can use a specific tool, any available tool, decide by itself, or not use tools at all." +}) +export type RequestToolSearchToolResultError = { + readonly "error_code": ToolSearchToolResultErrorCode + readonly "type": "tool_search_tool_result_error" +} +export const RequestToolSearchToolResultError = Schema.Struct({ + "error_code": ToolSearchToolResultErrorCode, + "type": Schema.Literal("tool_search_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestToolSearchToolResultError" }) +export type ResponseToolSearchToolResultError = { + readonly "error_code": ToolSearchToolResultErrorCode + readonly "error_message": string | null + readonly "type": "tool_search_tool_result_error" +} +export const ResponseToolSearchToolResultError = Schema.Struct({ + "error_code": ToolSearchToolResultErrorCode, + "error_message": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Error Message", "default": null }), + "type": Schema.Literal("tool_search_tool_result_error").annotate({ + "title": "Type", + "default": "tool_search_tool_result_error" + }) +}).annotate({ "title": "ResponseToolSearchToolResultError" }) +export type RequestImageBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "source": Base64ImageSource | URLImageSource + readonly "type": "image" +} +export const RequestImageBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "source": Schema.Union([Base64ImageSource, URLImageSource], { mode: "oneOf" }).annotate({ "title": "Source" }), + "type": Schema.Literal("image").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestImageBlock" }) +export type WebSearchTool_20250305 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "strict"?: boolean + readonly "type": "web_search_20250305" + readonly "user_location"?: UserLocation | null +} +export const WebSearchTool_20250305 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": + "If provided, only these domains will be included in results. Cannot be used alongside `blocked_domains`." + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": + "If provided, these domains will never appear in results. Cannot be used alongside `allowed_domains`." + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_search").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_search_20250305").annotate({ "title": "Type" }), + "user_location": Schema.optionalKey( + Schema.Union([UserLocation, Schema.Null]).annotate({ + "description": "Parameters for the user's location. Used to provide more relevant search results." + }) + ) +}).annotate({ "title": "WebSearchTool_20250305" }) +export type WebSearchTool_20260209 = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: CacheControlEphemeral | null + readonly "defer_loading"?: boolean + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "strict"?: boolean + readonly "type": "web_search_20260209" + readonly "user_location"?: UserLocation | null +} +export const WebSearchTool_20260209 = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Array(Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"])).annotate({ + "title": "Allowed Callers" + }) + ), + "allowed_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Allowed Domains", + "description": + "If provided, only these domains will be included in results. Cannot be used alongside `blocked_domains`." + }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "title": "Blocked Domains", + "description": + "If provided, these domains will never appear in results. Cannot be used alongside `allowed_domains`." + }) + ), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Defer Loading", + "description": + "If true, tool will not be included in initial system prompt. Only loaded when returned via tool_reference from tool search." + }) + ), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)), Schema.Null]).annotate({ + "title": "Max Uses", + "description": "Maximum number of times the tool can be used in the API request." + }) + ), + "name": Schema.Literal("web_search").annotate({ + "title": "Name", + "description": "Name of the tool.\n\nThis is how the tool will be called by the model and in `tool_use` blocks." + }), + "strict": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Strict", + "description": "When true, guarantees schema validation on tool names and inputs" + }) + ), + "type": Schema.Literal("web_search_20260209").annotate({ "title": "Type" }), + "user_location": Schema.optionalKey( + Schema.Union([UserLocation, Schema.Null]).annotate({ + "description": "Parameters for the user's location. Used to provide more relevant search results." + }) + ) +}).annotate({ "title": "WebSearchTool_20260209" }) +export type RequestWebFetchToolResultError = { + readonly "error_code": WebFetchToolResultErrorCode + readonly "type": "web_fetch_tool_result_error" +} +export const RequestWebFetchToolResultError = Schema.Struct({ + "error_code": WebFetchToolResultErrorCode, + "type": Schema.Literal("web_fetch_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestWebFetchToolResultError" }) +export type ResponseWebFetchToolResultError = { + readonly "error_code": WebFetchToolResultErrorCode + readonly "type": "web_fetch_tool_result_error" +} +export const ResponseWebFetchToolResultError = Schema.Struct({ + "error_code": WebFetchToolResultErrorCode, + "type": Schema.Literal("web_fetch_tool_result_error").annotate({ + "title": "Type", + "default": "web_fetch_tool_result_error" + }) +}).annotate({ "title": "ResponseWebFetchToolResultError" }) +export type RequestWebSearchToolResultError = { + readonly "error_code": WebSearchToolResultErrorCode + readonly "type": "web_search_tool_result_error" +} +export const RequestWebSearchToolResultError = Schema.Struct({ + "error_code": WebSearchToolResultErrorCode, + "type": Schema.Literal("web_search_tool_result_error").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestWebSearchToolResultError" }) +export type ResponseWebSearchToolResultError = { + readonly "error_code": WebSearchToolResultErrorCode + readonly "type": "web_search_tool_result_error" +} +export const ResponseWebSearchToolResultError = Schema.Struct({ + "error_code": WebSearchToolResultErrorCode, + "type": Schema.Literal("web_search_tool_result_error").annotate({ + "title": "Type", + "default": "web_search_tool_result_error" + }) +}).annotate({ "title": "ResponseWebSearchToolResultError" }) +export type MessageDelta = { + readonly "container": Container | null + readonly "stop_reason": StopReason | null + readonly "stop_sequence": string | null +} +export const MessageDelta = Schema.Struct({ + "container": Schema.Union([Container, 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 + }), + "stop_reason": Schema.Union([StopReason, Schema.Null]).annotate({ "title": "Stop Reason", "default": null }), + "stop_sequence": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Stop Sequence", "default": null }) +}).annotate({ "title": "MessageDelta" }) +export type CompletionRequest = { + readonly "model": Model + readonly "prompt": string + readonly "max_tokens_to_sample": number + readonly "stop_sequences"?: ReadonlyArray + readonly "temperature"?: number + readonly "top_p"?: number + readonly "top_k"?: number + readonly "metadata"?: { readonly "user_id"?: string | null } + readonly "stream"?: boolean +} +export const CompletionRequest = Schema.Struct({ + "model": Model, + "prompt": Schema.String.annotate({ + "title": "Prompt", + "description": + "The prompt that you want Claude to complete.\n\nFor proper response generation you will need to format your prompt using alternating `\\n\\nHuman:` and `\\n\\nAssistant:` conversational turns. For example:\n\n```\n\"\\n\\nHuman: {userQuestion}\\n\\nAssistant:\"\n```\n\nSee [prompt validation](https://docs.claude.com/en/api/prompt-validation) and our guide to [prompt design](https://docs.claude.com/en/docs/intro-to-prompting) for more details." + }).check(Schema.isMinLength(1)), + "max_tokens_to_sample": Schema.Number.annotate({ + "title": "Max Tokens To Sample", + "description": + "The maximum number of tokens to generate before stopping.\n\nNote that our models may stop _before_ reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "stop_sequences": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "title": "Stop Sequences", + "description": + "Sequences that will cause the model to stop generating.\n\nOur models stop on `\"\\n\\nHuman:\"`, and may include additional built-in stop sequences in the future. By providing the stop_sequences parameter, you may include additional strings that will cause the model to stop generating." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Temperature", + "description": + "Amount of randomness injected into the response.\n\nDefaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0` for analytical / multiple choice, and closer to `1.0` for creative and generative tasks.\n\nNote that even with `temperature` of `0.0`, the results will not be fully deterministic." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top P", + "description": + "Use nucleus sampling.\n\nIn nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in decreasing probability order and cut it off once it reaches a particular probability specified by `top_p`. You should either alter `temperature` or `top_p`, but not both.\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ), + "top_k": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top K", + "description": + "Only sample from the top K options for each subsequent token.\n\nUsed to remove \"long tail\" low probability responses. [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) + ), + "metadata": Schema.optionalKey( + Schema.Struct({ + "user_id": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMaxLength(256)), Schema.Null]).annotate({ + "title": "User Id", + "description": + "An external identifier for the user who is associated with the request.\n\nThis should be a uuid, hash value, or other opaque identifier. Anthropic may use this id to help detect abuse. Do not include any identifying information such as name, email address, or phone number." + }) + ) + }).annotate({ "title": "Metadata", "description": "An object describing metadata about the request." }) + ), + "stream": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Stream", + "description": + "Whether to incrementally stream the response using server-sent events.\n\nSee [streaming](https://docs.claude.com/en/api/streaming) for details." + }) + ) +}).annotate({ "title": "CompletionRequest" }) +export type CompletionResponse = { + readonly "completion": string + readonly "id": string + readonly "model": Model + readonly "stop_reason": string | null + readonly "type": "completion" +} +export const CompletionResponse = Schema.Struct({ + "completion": Schema.String.annotate({ + "title": "Completion", + "description": "The resulting completion up to and excluding the stop sequences." + }), + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique object identifier.\n\nThe format and length of IDs may change over time." + }), + "model": Model, + "stop_reason": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Stop Reason", + "description": + "The reason that we stopped.\n\nThis may be one the following values:\n* `\"stop_sequence\"`: we reached a stop sequence — either provided by you via the `stop_sequences` parameter, or a stop sequence built into the model\n* `\"max_tokens\"`: we exceeded `max_tokens_to_sample` or the model's maximum" + }), + "type": Schema.Literal("completion").annotate({ + "title": "Type", + "description": "Object type.\n\nFor Text Completions, this is always `\"completion\"`.", + "default": "completion" + }) +}).annotate({ "title": "CompletionResponse" }) +export type BetaRequestToolSearchToolSearchResultBlock = { + readonly "tool_references": ReadonlyArray + readonly "type": "tool_search_tool_search_result" +} +export const BetaRequestToolSearchToolSearchResultBlock = Schema.Struct({ + "tool_references": Schema.Array(BetaRequestToolReferenceBlock).annotate({ "title": "Tool References" }), + "type": Schema.Literal("tool_search_tool_search_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestToolSearchToolSearchResultBlock" }) +export type BetaIterationsUsage = ReadonlyArray | null +export const BetaIterationsUsage = Schema.Union([ + Schema.Array(Schema.Union([BetaMessageIterationUsage, BetaCompactionIterationUsage])), + Schema.Null +]).annotate({ + "title": "Iterations", + "description": + "Per-iteration token usage breakdown.\n\nEach entry represents one sampling iteration, with its own input/output token counts and cache statistics. This allows you to:\n- Determine which iterations exceeded long context thresholds (>=200k tokens)\n- Calculate the true context window size from the last iteration\n- Understand token accumulation across server-side tool use loops", + "default": null +}) +export type BetaErroredResult = { readonly "error": BetaErrorResponse; readonly "type": "errored" } +export const BetaErroredResult = Schema.Struct({ + "error": BetaErrorResponse, + "type": Schema.Literal("errored").annotate({ "title": "Type", "default": "errored" }) +}).annotate({ "title": "ErroredResult" }) +export type BetaRequestBashCodeExecutionToolResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "content": BetaRequestBashCodeExecutionToolResultError | BetaRequestBashCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "bash_code_execution_tool_result" +} +export const BetaRequestBashCodeExecutionToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([BetaRequestBashCodeExecutionToolResultError, BetaRequestBashCodeExecutionResultBlock]) + .annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("bash_code_execution_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionToolResultBlock" }) +export type BetaRequestCodeExecutionToolResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "content": + | BetaRequestCodeExecutionToolResultError + | BetaRequestCodeExecutionResultBlock + | BetaRequestEncryptedCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "code_execution_tool_result" +} +export const BetaRequestCodeExecutionToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([ + BetaRequestCodeExecutionToolResultError, + BetaRequestCodeExecutionResultBlock, + BetaRequestEncryptedCodeExecutionResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCodeExecutionToolResultBlock" }) +export type BetaRequestSearchResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: BetaRequestCitationsConfig + readonly "content": ReadonlyArray + readonly "source": string + readonly "title": string + readonly "type": "search_result" +} +export const BetaRequestSearchResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(BetaRequestCitationsConfig), + "content": Schema.Array(BetaRequestTextBlock).annotate({ "title": "Content" }), + "source": Schema.String.annotate({ "title": "Source" }), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("search_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestSearchResultBlock" }) +export type BetaResponseBashCodeExecutionToolResultBlock = { + readonly "content": BetaResponseBashCodeExecutionToolResultError | BetaResponseBashCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "bash_code_execution_tool_result" +} +export const BetaResponseBashCodeExecutionToolResultBlock = Schema.Struct({ + "content": Schema.Union([BetaResponseBashCodeExecutionToolResultError, BetaResponseBashCodeExecutionResultBlock]) + .annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("bash_code_execution_tool_result").annotate({ + "title": "Type", + "default": "bash_code_execution_tool_result" + }) +}).annotate({ "title": "ResponseBashCodeExecutionToolResultBlock" }) +export type BetaResponseWebFetchResultBlock = { + readonly "content": BetaResponseDocumentBlock + readonly "retrieved_at": string | null + readonly "type": "web_fetch_result" + readonly "url": string +} +export const BetaResponseWebFetchResultBlock = Schema.Struct({ + "content": BetaResponseDocumentBlock, + "retrieved_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Retrieved At", + "description": "ISO 8601 timestamp when the content was retrieved", + "default": null + }), + "type": Schema.Literal("web_fetch_result").annotate({ "title": "Type", "default": "web_fetch_result" }), + "url": Schema.String.annotate({ "title": "Url", "description": "Fetched content URL" }) +}).annotate({ "title": "ResponseWebFetchResultBlock" }) +export type BetaResponseCodeExecutionToolResultBlock = { + readonly "content": + | BetaResponseCodeExecutionToolResultError + | BetaResponseCodeExecutionResultBlock + | BetaResponseEncryptedCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "code_execution_tool_result" +} +export const BetaResponseCodeExecutionToolResultBlock = Schema.Struct({ + "content": Schema.Union([ + BetaResponseCodeExecutionToolResultError, + BetaResponseCodeExecutionResultBlock, + BetaResponseEncryptedCodeExecutionResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_tool_result").annotate({ + "title": "Type", + "default": "code_execution_tool_result" + }) +}).annotate({ "title": "ResponseCodeExecutionToolResultBlock" }) +export type BetaContentBlockDeltaEvent = { + readonly "delta": + | BetaTextContentBlockDelta + | BetaInputJsonContentBlockDelta + | BetaCitationsDelta + | BetaThinkingContentBlockDelta + | BetaSignatureContentBlockDelta + | BetaCompactionContentBlockDelta + readonly "index": number + readonly "type": "content_block_delta" +} +export const BetaContentBlockDeltaEvent = Schema.Struct({ + "delta": Schema.Union([ + BetaTextContentBlockDelta, + BetaInputJsonContentBlockDelta, + BetaCitationsDelta, + BetaThinkingContentBlockDelta, + BetaSignatureContentBlockDelta, + BetaCompactionContentBlockDelta + ], { mode: "oneOf" }).annotate({ "title": "Delta" }), + "index": Schema.Number.annotate({ "title": "Index" }).check(Schema.isInt()), + "type": Schema.Literal("content_block_delta").annotate({ "title": "Type", "default": "content_block_delta" }) +}).annotate({ "title": "ContentBlockDeltaEvent" }) +export type BetaMessageDelta = { + readonly "container"?: BetaContainer | null + readonly "stop_reason": BetaStopReason | null + readonly "stop_sequence": string | null +} +export const BetaMessageDelta = Schema.Struct({ + "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 + }) + ), + "stop_reason": Schema.Union([BetaStopReason, Schema.Null]).annotate({ "title": "Stop Reason", "default": null }), + "stop_sequence": Schema.Union([Schema.String, Schema.Null]).annotate({ "title": "Stop Sequence", "default": null }) +}).annotate({ "title": "MessageDelta" }) +export type BetaRequestTextEditorCodeExecutionToolResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "content": + | BetaRequestTextEditorCodeExecutionToolResultError + | BetaRequestTextEditorCodeExecutionViewResultBlock + | BetaRequestTextEditorCodeExecutionCreateResultBlock + | BetaRequestTextEditorCodeExecutionStrReplaceResultBlock + readonly "tool_use_id": string + readonly "type": "text_editor_code_execution_tool_result" +} +export const BetaRequestTextEditorCodeExecutionToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([ + BetaRequestTextEditorCodeExecutionToolResultError, + BetaRequestTextEditorCodeExecutionViewResultBlock, + BetaRequestTextEditorCodeExecutionCreateResultBlock, + BetaRequestTextEditorCodeExecutionStrReplaceResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("text_editor_code_execution_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionToolResultBlock" }) +export type BetaResponseTextEditorCodeExecutionToolResultBlock = { + readonly "content": + | BetaResponseTextEditorCodeExecutionToolResultError + | BetaResponseTextEditorCodeExecutionViewResultBlock + | BetaResponseTextEditorCodeExecutionCreateResultBlock + | BetaResponseTextEditorCodeExecutionStrReplaceResultBlock + readonly "tool_use_id": string + readonly "type": "text_editor_code_execution_tool_result" +} +export const BetaResponseTextEditorCodeExecutionToolResultBlock = Schema.Struct({ + "content": Schema.Union([ + BetaResponseTextEditorCodeExecutionToolResultError, + BetaResponseTextEditorCodeExecutionViewResultBlock, + BetaResponseTextEditorCodeExecutionCreateResultBlock, + BetaResponseTextEditorCodeExecutionStrReplaceResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("text_editor_code_execution_tool_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_tool_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionToolResultBlock" }) +export type BetaResponseToolSearchToolResultBlock = { + readonly "content": BetaResponseToolSearchToolResultError | BetaResponseToolSearchToolSearchResultBlock + readonly "tool_use_id": string + readonly "type": "tool_search_tool_result" +} +export const BetaResponseToolSearchToolResultBlock = Schema.Struct({ + "content": Schema.Union([BetaResponseToolSearchToolResultError, BetaResponseToolSearchToolSearchResultBlock]) + .annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("tool_search_tool_result").annotate({ "title": "Type", "default": "tool_search_tool_result" }) +}).annotate({ "title": "ResponseToolSearchToolResultBlock" }) +export type BetaContextManagementConfig = { + readonly "edits"?: ReadonlyArray +} +export const BetaContextManagementConfig = Schema.Struct({ + "edits": Schema.optionalKey( + Schema.Array( + Schema.Union([BetaClearToolUses20250919, BetaClearThinking20251015, BetaCompact20260112], { mode: "oneOf" }) + ).annotate({ "title": "Edits", "description": "List of context management edits to apply" }).check( + Schema.isMinLength(0) + ) + ) +}).annotate({ "title": "ContextManagementConfig" }) +export type BetaContentBlockSource = { + readonly "content": string | ReadonlyArray + readonly "type": "content" +} +export const BetaContentBlockSource = Schema.Struct({ + "content": Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([BetaRequestTextBlock, BetaRequestImageBlock], { mode: "oneOf" }).annotate({ + "title": "beta_content_block_source_content_item" + }) + ).annotate({ "title": "beta_content_block_source_content" }) + ]).annotate({ "title": "Content" }), + "type": Schema.Literal("content").annotate({ "title": "Type" }) +}).annotate({ "title": "ContentBlockSource" }) +export type BetaRequestWebSearchToolResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "content": ReadonlyArray | BetaRequestWebSearchToolResultError + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" +} +export const BetaRequestWebSearchToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "content": Schema.Union([ + Schema.Array(BetaRequestWebSearchResultBlock).annotate({ "title": "Result Block" }), + BetaRequestWebSearchToolResultError + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_search_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestWebSearchToolResultBlock" }) +export type BetaResponseWebSearchToolResultBlock = { + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "content": BetaResponseWebSearchToolResultError | ReadonlyArray + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" +} +export const BetaResponseWebSearchToolResultBlock = Schema.Struct({ + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "content": Schema.Union([BetaResponseWebSearchToolResultError, Schema.Array(BetaResponseWebSearchResultBlock)]) + .annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_search_tool_result").annotate({ "title": "Type", "default": "web_search_tool_result" }) +}).annotate({ "title": "ResponseWebSearchToolResultBlock" }) +export type RequestToolSearchToolSearchResultBlock = { + readonly "tool_references": ReadonlyArray + readonly "type": "tool_search_tool_search_result" +} +export const RequestToolSearchToolSearchResultBlock = Schema.Struct({ + "tool_references": Schema.Array(RequestToolReferenceBlock).annotate({ "title": "Tool References" }), + "type": Schema.Literal("tool_search_tool_search_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestToolSearchToolSearchResultBlock" }) +export type ErroredResult = { readonly "error": ErrorResponse; readonly "type": "errored" } +export const ErroredResult = Schema.Struct({ + "error": ErrorResponse, + "type": Schema.Literal("errored").annotate({ "title": "Type", "default": "errored" }) +}).annotate({ "title": "ErroredResult" }) +export type RequestBashCodeExecutionToolResultBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "content": RequestBashCodeExecutionToolResultError | RequestBashCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "bash_code_execution_tool_result" +} +export const RequestBashCodeExecutionToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([RequestBashCodeExecutionToolResultError, RequestBashCodeExecutionResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("bash_code_execution_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestBashCodeExecutionToolResultBlock" }) +export type RequestCodeExecutionToolResultBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "content": + | RequestCodeExecutionToolResultError + | RequestCodeExecutionResultBlock + | RequestEncryptedCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "code_execution_tool_result" +} +export const RequestCodeExecutionToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([ + RequestCodeExecutionToolResultError, + RequestCodeExecutionResultBlock, + RequestEncryptedCodeExecutionResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestCodeExecutionToolResultBlock" }) +export type RequestSearchResultBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: RequestCitationsConfig + readonly "content": ReadonlyArray + readonly "source": string + readonly "title": string + readonly "type": "search_result" +} +export const RequestSearchResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(RequestCitationsConfig), + "content": Schema.Array(RequestTextBlock).annotate({ "title": "Content" }), + "source": Schema.String.annotate({ "title": "Source" }), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("search_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestSearchResultBlock" }) +export type ResponseBashCodeExecutionToolResultBlock = { + readonly "content": ResponseBashCodeExecutionToolResultError | ResponseBashCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "bash_code_execution_tool_result" +} +export const ResponseBashCodeExecutionToolResultBlock = Schema.Struct({ + "content": Schema.Union([ResponseBashCodeExecutionToolResultError, ResponseBashCodeExecutionResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("bash_code_execution_tool_result").annotate({ + "title": "Type", + "default": "bash_code_execution_tool_result" + }) +}).annotate({ "title": "ResponseBashCodeExecutionToolResultBlock" }) +export type ResponseWebFetchResultBlock = { + readonly "content": ResponseDocumentBlock + readonly "retrieved_at": string | null + readonly "type": "web_fetch_result" + readonly "url": string +} +export const ResponseWebFetchResultBlock = Schema.Struct({ + "content": ResponseDocumentBlock, + "retrieved_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Retrieved At", + "description": "ISO 8601 timestamp when the content was retrieved", + "default": null + }), + "type": Schema.Literal("web_fetch_result").annotate({ "title": "Type", "default": "web_fetch_result" }), + "url": Schema.String.annotate({ "title": "Url", "description": "Fetched content URL" }) +}).annotate({ "title": "ResponseWebFetchResultBlock" }) +export type ResponseCodeExecutionToolResultBlock = { + readonly "content": + | ResponseCodeExecutionToolResultError + | ResponseCodeExecutionResultBlock + | ResponseEncryptedCodeExecutionResultBlock + readonly "tool_use_id": string + readonly "type": "code_execution_tool_result" +} +export const ResponseCodeExecutionToolResultBlock = Schema.Struct({ + "content": Schema.Union([ + ResponseCodeExecutionToolResultError, + ResponseCodeExecutionResultBlock, + ResponseEncryptedCodeExecutionResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("code_execution_tool_result").annotate({ + "title": "Type", + "default": "code_execution_tool_result" + }) +}).annotate({ "title": "ResponseCodeExecutionToolResultBlock" }) +export type ContentBlockDeltaEvent = { + readonly "delta": + | TextContentBlockDelta + | InputJsonContentBlockDelta + | CitationsDelta + | ThinkingContentBlockDelta + | SignatureContentBlockDelta + readonly "index": number + readonly "type": "content_block_delta" +} +export const ContentBlockDeltaEvent = Schema.Struct({ + "delta": Schema.Union([ + TextContentBlockDelta, + InputJsonContentBlockDelta, + CitationsDelta, + ThinkingContentBlockDelta, + SignatureContentBlockDelta + ], { mode: "oneOf" }).annotate({ "title": "Delta" }), + "index": Schema.Number.annotate({ "title": "Index" }).check(Schema.isInt()), + "type": Schema.Literal("content_block_delta").annotate({ "title": "Type", "default": "content_block_delta" }) +}).annotate({ "title": "ContentBlockDeltaEvent" }) +export type RequestTextEditorCodeExecutionToolResultBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "content": + | RequestTextEditorCodeExecutionToolResultError + | RequestTextEditorCodeExecutionViewResultBlock + | RequestTextEditorCodeExecutionCreateResultBlock + | RequestTextEditorCodeExecutionStrReplaceResultBlock + readonly "tool_use_id": string + readonly "type": "text_editor_code_execution_tool_result" +} +export const RequestTextEditorCodeExecutionToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([ + RequestTextEditorCodeExecutionToolResultError, + RequestTextEditorCodeExecutionViewResultBlock, + RequestTextEditorCodeExecutionCreateResultBlock, + RequestTextEditorCodeExecutionStrReplaceResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("text_editor_code_execution_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestTextEditorCodeExecutionToolResultBlock" }) +export type ResponseTextEditorCodeExecutionToolResultBlock = { + readonly "content": + | ResponseTextEditorCodeExecutionToolResultError + | ResponseTextEditorCodeExecutionViewResultBlock + | ResponseTextEditorCodeExecutionCreateResultBlock + | ResponseTextEditorCodeExecutionStrReplaceResultBlock + readonly "tool_use_id": string + readonly "type": "text_editor_code_execution_tool_result" +} +export const ResponseTextEditorCodeExecutionToolResultBlock = Schema.Struct({ + "content": Schema.Union([ + ResponseTextEditorCodeExecutionToolResultError, + ResponseTextEditorCodeExecutionViewResultBlock, + ResponseTextEditorCodeExecutionCreateResultBlock, + ResponseTextEditorCodeExecutionStrReplaceResultBlock + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("text_editor_code_execution_tool_result").annotate({ + "title": "Type", + "default": "text_editor_code_execution_tool_result" + }) +}).annotate({ "title": "ResponseTextEditorCodeExecutionToolResultBlock" }) +export type ResponseToolSearchToolResultBlock = { + readonly "content": ResponseToolSearchToolResultError | ResponseToolSearchToolSearchResultBlock + readonly "tool_use_id": string + readonly "type": "tool_search_tool_result" +} +export const ResponseToolSearchToolResultBlock = Schema.Struct({ + "content": Schema.Union([ResponseToolSearchToolResultError, ResponseToolSearchToolSearchResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("tool_search_tool_result").annotate({ "title": "Type", "default": "tool_search_tool_result" }) +}).annotate({ "title": "ResponseToolSearchToolResultBlock" }) +export type ContentBlockSource = { + readonly "content": string | ReadonlyArray + readonly "type": "content" +} +export const ContentBlockSource = Schema.Struct({ + "content": Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([RequestTextBlock, RequestImageBlock], { mode: "oneOf" }).annotate({ + "title": "content_block_source_content_item" + }) + ).annotate({ "title": "content_block_source_content" }) + ]).annotate({ "title": "Content" }), + "type": Schema.Literal("content").annotate({ "title": "Type" }) +}).annotate({ "title": "ContentBlockSource" }) +export type RequestWebSearchToolResultBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "caller"?: DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "content": ReadonlyArray | RequestWebSearchToolResultError + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" +} +export const RequestWebSearchToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "content": Schema.Union([ + Schema.Array(RequestWebSearchResultBlock).annotate({ "title": "web_search_tool_result_block_item" }), + RequestWebSearchToolResultError + ]).annotate({ "title": "Content" }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_search_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestWebSearchToolResultBlock" }) +export type ResponseWebSearchToolResultBlock = { + readonly "caller": DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "content": ResponseWebSearchToolResultError | ReadonlyArray + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" +} +export const ResponseWebSearchToolResultBlock = Schema.Struct({ + "caller": Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller", + "default": { "type": "direct" } + }), + "content": Schema.Union([ResponseWebSearchToolResultError, Schema.Array(ResponseWebSearchResultBlock)]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_search_tool_result").annotate({ "title": "Type", "default": "web_search_tool_result" }) +}).annotate({ "title": "ResponseWebSearchToolResultBlock" }) +export type MessageDeltaEvent = { + readonly "delta": MessageDelta + readonly "type": "message_delta" + readonly "usage": { + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "input_tokens": number | null + readonly "output_tokens": number + readonly "server_tool_use"?: ServerToolUsage | null + } +} +export const MessageDeltaEvent = Schema.Struct({ + "delta": MessageDelta, + "type": Schema.Literal("message_delta").annotate({ "title": "Type", "default": "message_delta" }), + "usage": Schema.Struct({ + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Creation Input Tokens", + "description": "The cumulative number of input tokens used to create the cache entry.", + "default": null + }), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Read Input Tokens", + "description": "The cumulative number of input tokens read from the cache.", + "default": null + }), + "input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Input Tokens", + "description": "The cumulative number of input tokens which were used.", + "default": null + }), + "output_tokens": Schema.Number.annotate({ + "title": "Output Tokens", + "description": "The cumulative number of output tokens which were used." + }).check(Schema.isInt()), + "server_tool_use": Schema.optionalKey( + Schema.Union([ServerToolUsage, Schema.Null]).annotate({ + "description": "The number of server tool requests.", + "default": null + }) + ) + }).annotate({ + "title": "MessageDeltaUsage", + "description": + "Billing and rate-limit usage.\n\nAnthropic's API bills and rate-limits by token counts, as tokens represent the underlying cost to our systems.\n\nUnder the hood, the API transforms requests into a format suitable for the model. The model's output then goes through a parsing stage before becoming an API response. As a result, the token counts in `usage` will not match one-to-one with the exact visible content of an API request or response.\n\nFor example, `output_tokens` will be non-zero, even for an empty string response from Claude.\n\nTotal input tokens in a request is the summation of `input_tokens`, `cache_creation_input_tokens`, and `cache_read_input_tokens`." + }) +}).annotate({ "title": "MessageDeltaEvent" }) +export type BetaRequestToolSearchToolResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "content": BetaRequestToolSearchToolResultError | BetaRequestToolSearchToolSearchResultBlock + readonly "tool_use_id": string + readonly "type": "tool_search_tool_result" +} +export const BetaRequestToolSearchToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([BetaRequestToolSearchToolResultError, BetaRequestToolSearchToolSearchResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("tool_search_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestToolSearchToolResultBlock" }) +export type BetaResponseWebFetchToolResultBlock = { + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "content": BetaResponseWebFetchToolResultError | BetaResponseWebFetchResultBlock + readonly "tool_use_id": string + readonly "type": "web_fetch_tool_result" +} +export const BetaResponseWebFetchToolResultBlock = Schema.Struct({ + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "content": Schema.Union([BetaResponseWebFetchToolResultError, BetaResponseWebFetchResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_fetch_tool_result").annotate({ "title": "Type", "default": "web_fetch_tool_result" }) +}).annotate({ "title": "ResponseWebFetchToolResultBlock" }) +export type BetaMessageDeltaEvent = { + readonly "context_management"?: BetaResponseContextManagement | null + readonly "delta": BetaMessageDelta + readonly "type": "message_delta" + readonly "usage": { + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "input_tokens": number | null + readonly "iterations"?: BetaIterationsUsage + readonly "output_tokens": number + readonly "server_tool_use"?: BetaServerToolUsage | null + } +} +export const BetaMessageDeltaEvent = Schema.Struct({ + "context_management": Schema.optionalKey( + Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ + "description": "Information about context management strategies applied during the request", + "default": null + }) + ), + "delta": BetaMessageDelta, + "type": Schema.Literal("message_delta").annotate({ "title": "Type", "default": "message_delta" }), + "usage": Schema.Struct({ + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Creation Input Tokens", + "description": "The cumulative number of input tokens used to create the cache entry.", + "default": null + }), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Read Input Tokens", + "description": "The cumulative number of input tokens read from the cache.", + "default": null + }), + "input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Input Tokens", + "description": "The cumulative number of input tokens which were used.", + "default": null + }), + "iterations": Schema.optionalKey(BetaIterationsUsage), + "output_tokens": Schema.Number.annotate({ + "title": "Output Tokens", + "description": "The cumulative number of output tokens which were used." + }).check(Schema.isInt()), + "server_tool_use": Schema.optionalKey( + Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ + "description": "The number of server tool requests.", + "default": null + }) + ) + }).annotate({ + "title": "MessageDeltaUsage", + "description": + "Billing and rate-limit usage.\n\nAnthropic's API bills and rate-limits by token counts, as tokens represent the underlying cost to our systems.\n\nUnder the hood, the API transforms requests into a format suitable for the model. The model's output then goes through a parsing stage before becoming an API response. As a result, the token counts in `usage` will not match one-to-one with the exact visible content of an API request or response.\n\nFor example, `output_tokens` will be non-zero, even for an empty string response from Claude.\n\nTotal input tokens in a request is the summation of `input_tokens`, `cache_creation_input_tokens`, and `cache_read_input_tokens`." + }) +}).annotate({ "title": "MessageDeltaEvent" }) +export type BetaRequestDocumentBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: BetaRequestCitationsConfig | null + readonly "context"?: string | null + readonly "source": + | BetaBase64PDFSource + | BetaPlainTextSource + | BetaContentBlockSource + | BetaURLPDFSource + | BetaFileDocumentSource + readonly "title"?: string | null + readonly "type": "document" +} +export const BetaRequestDocumentBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(Schema.Union([BetaRequestCitationsConfig, Schema.Null])), + "context": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)), Schema.Null]).annotate({ "title": "Context" }) + ), + "source": Schema.Union([ + BetaBase64PDFSource, + BetaPlainTextSource, + BetaContentBlockSource, + BetaURLPDFSource, + BetaFileDocumentSource + ], { mode: "oneOf" }).annotate({ "title": "Source" }), + "title": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(500)), Schema.Null]).annotate({ + "title": "Title" + }) + ), + "type": Schema.Literal("document").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestDocumentBlock" }) +export type RequestToolSearchToolResultBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "content": RequestToolSearchToolResultError | RequestToolSearchToolSearchResultBlock + readonly "tool_use_id": string + readonly "type": "tool_search_tool_result" +} +export const RequestToolSearchToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.Union([RequestToolSearchToolResultError, RequestToolSearchToolSearchResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("tool_search_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestToolSearchToolResultBlock" }) +export type ResponseWebFetchToolResultBlock = { + readonly "caller": DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "content": ResponseWebFetchToolResultError | ResponseWebFetchResultBlock + readonly "tool_use_id": string + readonly "type": "web_fetch_tool_result" +} +export const ResponseWebFetchToolResultBlock = Schema.Struct({ + "caller": Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller", + "default": { "type": "direct" } + }), + "content": Schema.Union([ResponseWebFetchToolResultError, ResponseWebFetchResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_fetch_tool_result").annotate({ "title": "Type", "default": "web_fetch_tool_result" }) +}).annotate({ "title": "ResponseWebFetchToolResultBlock" }) +export type RequestDocumentBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: RequestCitationsConfig | null + readonly "context"?: string | null + readonly "source": Base64PDFSource | PlainTextSource | ContentBlockSource | URLPDFSource + readonly "title"?: string | null + readonly "type": "document" +} +export const RequestDocumentBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(Schema.Union([RequestCitationsConfig, Schema.Null])), + "context": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)), Schema.Null]).annotate({ "title": "Context" }) + ), + "source": Schema.Union([Base64PDFSource, PlainTextSource, ContentBlockSource, URLPDFSource], { mode: "oneOf" }) + .annotate({ "title": "Source" }), + "title": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(500)), Schema.Null]).annotate({ + "title": "Title" + }) + ), + "type": Schema.Literal("document").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestDocumentBlock" }) +export type BetaContentBlockStartEvent = { + readonly "content_block": + | BetaResponseTextBlock + | BetaResponseThinkingBlock + | BetaResponseRedactedThinkingBlock + | BetaResponseToolUseBlock + | BetaResponseServerToolUseBlock + | BetaResponseWebSearchToolResultBlock + | BetaResponseWebFetchToolResultBlock + | BetaResponseCodeExecutionToolResultBlock + | BetaResponseBashCodeExecutionToolResultBlock + | BetaResponseTextEditorCodeExecutionToolResultBlock + | BetaResponseToolSearchToolResultBlock + | BetaResponseMCPToolUseBlock + | BetaResponseMCPToolResultBlock + | BetaResponseContainerUploadBlock + | BetaResponseCompactionBlock + readonly "index": number + readonly "type": "content_block_start" +} +export const BetaContentBlockStartEvent = Schema.Struct({ + "content_block": Schema.Union([ + BetaResponseTextBlock, + BetaResponseThinkingBlock, + BetaResponseRedactedThinkingBlock, + BetaResponseToolUseBlock, + BetaResponseServerToolUseBlock, + BetaResponseWebSearchToolResultBlock, + BetaResponseWebFetchToolResultBlock, + BetaResponseCodeExecutionToolResultBlock, + BetaResponseBashCodeExecutionToolResultBlock, + BetaResponseTextEditorCodeExecutionToolResultBlock, + BetaResponseToolSearchToolResultBlock, + BetaResponseMCPToolUseBlock, + BetaResponseMCPToolResultBlock, + BetaResponseContainerUploadBlock, + BetaResponseCompactionBlock + ], { mode: "oneOf" }).annotate({ "title": "Content Block" }), + "index": Schema.Number.annotate({ "title": "Index" }).check(Schema.isInt()), + "type": Schema.Literal("content_block_start").annotate({ "title": "Type", "default": "content_block_start" }) +}).annotate({ "title": "ContentBlockStartEvent" }) +export type BetaContentBlock = + | BetaResponseTextBlock + | BetaResponseThinkingBlock + | BetaResponseRedactedThinkingBlock + | BetaResponseToolUseBlock + | BetaResponseServerToolUseBlock + | BetaResponseWebSearchToolResultBlock + | BetaResponseWebFetchToolResultBlock + | BetaResponseCodeExecutionToolResultBlock + | BetaResponseBashCodeExecutionToolResultBlock + | BetaResponseTextEditorCodeExecutionToolResultBlock + | BetaResponseToolSearchToolResultBlock + | BetaResponseMCPToolUseBlock + | BetaResponseMCPToolResultBlock + | BetaResponseContainerUploadBlock + | BetaResponseCompactionBlock +export const BetaContentBlock = Schema.Union([ + BetaResponseTextBlock, + BetaResponseThinkingBlock, + BetaResponseRedactedThinkingBlock, + BetaResponseToolUseBlock, + BetaResponseServerToolUseBlock, + BetaResponseWebSearchToolResultBlock, + BetaResponseWebFetchToolResultBlock, + BetaResponseCodeExecutionToolResultBlock, + BetaResponseBashCodeExecutionToolResultBlock, + BetaResponseTextEditorCodeExecutionToolResultBlock, + BetaResponseToolSearchToolResultBlock, + BetaResponseMCPToolUseBlock, + BetaResponseMCPToolResultBlock, + BetaResponseContainerUploadBlock, + BetaResponseCompactionBlock +], { mode: "oneOf" }) +export type BetaRequestWebFetchResultBlock = { + readonly "content": BetaRequestDocumentBlock + readonly "retrieved_at"?: string | null + readonly "type": "web_fetch_result" + readonly "url": string +} +export const BetaRequestWebFetchResultBlock = Schema.Struct({ + "content": BetaRequestDocumentBlock, + "retrieved_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Retrieved At", + "description": "ISO 8601 timestamp when the content was retrieved" + }) + ), + "type": Schema.Literal("web_fetch_result").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url", "description": "Fetched content URL" }) +}).annotate({ "title": "RequestWebFetchResultBlock" }) +export type ContentBlockStartEvent = { + readonly "content_block": + | ResponseTextBlock + | ResponseThinkingBlock + | ResponseRedactedThinkingBlock + | ResponseToolUseBlock + | ResponseServerToolUseBlock + | ResponseWebSearchToolResultBlock + | ResponseWebFetchToolResultBlock + | ResponseCodeExecutionToolResultBlock + | ResponseBashCodeExecutionToolResultBlock + | ResponseTextEditorCodeExecutionToolResultBlock + | ResponseToolSearchToolResultBlock + | ResponseContainerUploadBlock + readonly "index": number + readonly "type": "content_block_start" +} +export const ContentBlockStartEvent = Schema.Struct({ + "content_block": Schema.Union([ + ResponseTextBlock, + ResponseThinkingBlock, + ResponseRedactedThinkingBlock, + ResponseToolUseBlock, + ResponseServerToolUseBlock, + ResponseWebSearchToolResultBlock, + ResponseWebFetchToolResultBlock, + ResponseCodeExecutionToolResultBlock, + ResponseBashCodeExecutionToolResultBlock, + ResponseTextEditorCodeExecutionToolResultBlock, + ResponseToolSearchToolResultBlock, + ResponseContainerUploadBlock + ], { mode: "oneOf" }).annotate({ "title": "Content Block" }), + "index": Schema.Number.annotate({ "title": "Index" }).check(Schema.isInt()), + "type": Schema.Literal("content_block_start").annotate({ "title": "Type", "default": "content_block_start" }) +}).annotate({ "title": "ContentBlockStartEvent" }) +export type ContentBlock = + | ResponseTextBlock + | ResponseThinkingBlock + | ResponseRedactedThinkingBlock + | ResponseToolUseBlock + | ResponseServerToolUseBlock + | ResponseWebSearchToolResultBlock + | ResponseWebFetchToolResultBlock + | ResponseCodeExecutionToolResultBlock + | ResponseBashCodeExecutionToolResultBlock + | ResponseTextEditorCodeExecutionToolResultBlock + | ResponseToolSearchToolResultBlock + | ResponseContainerUploadBlock +export const ContentBlock = Schema.Union([ + ResponseTextBlock, + ResponseThinkingBlock, + ResponseRedactedThinkingBlock, + ResponseToolUseBlock, + ResponseServerToolUseBlock, + ResponseWebSearchToolResultBlock, + ResponseWebFetchToolResultBlock, + ResponseCodeExecutionToolResultBlock, + ResponseBashCodeExecutionToolResultBlock, + ResponseTextEditorCodeExecutionToolResultBlock, + ResponseToolSearchToolResultBlock, + ResponseContainerUploadBlock +], { mode: "oneOf" }) +export type RequestWebFetchResultBlock = { + readonly "content": RequestDocumentBlock + readonly "retrieved_at"?: string | null + readonly "type": "web_fetch_result" + readonly "url": string +} +export const RequestWebFetchResultBlock = Schema.Struct({ + "content": RequestDocumentBlock, + "retrieved_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Retrieved At", + "description": "ISO 8601 timestamp when the content was retrieved" + }) + ), + "type": Schema.Literal("web_fetch_result").annotate({ "title": "Type" }), + "url": Schema.String.annotate({ "title": "Url", "description": "Fetched content URL" }) +}).annotate({ "title": "RequestWebFetchResultBlock" }) +export type BetaMessage = { + readonly "id": string + readonly "type": "message" + readonly "role": "assistant" + readonly "content": ReadonlyArray + readonly "model": Model + readonly "stop_reason": BetaStopReason | null + readonly "stop_sequence": string | null + readonly "usage": { + readonly "cache_creation": BetaCacheCreation | 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 "iterations"?: BetaIterationsUsage + readonly "output_tokens": number + readonly "server_tool_use"?: BetaServerToolUsage | null + readonly "service_tier": "standard" | "priority" | "batch" | null + readonly "speed"?: BetaSpeed | null + } + readonly "context_management"?: BetaResponseContextManagement | null + readonly "container"?: BetaContainer | null +} +export const BetaMessage = Schema.Struct({ + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique object identifier.\n\nThe format and length of IDs may change over time." + }), + "type": Schema.Literal("message").annotate({ + "title": "Type", + "description": "Object type.\n\nFor Messages, this is always `\"message\"`.", + "default": "message" + }), + "role": Schema.Literal("assistant").annotate({ + "title": "Role", + "description": "Conversational role of the generated message.\n\nThis will always be `\"assistant\"`.", + "default": "assistant" + }), + "content": Schema.Array(BetaContentBlock).annotate({ + "title": "Content", + "description": + "Content generated by the model.\n\nThis is an array of content blocks, each of which has a `type` that determines its shape.\n\nExample:\n\n```json\n[{\"type\": \"text\", \"text\": \"Hi, I'm Claude.\"}]\n```\n\nIf the request input `messages` ended with an `assistant` turn, then the response `content` will continue directly from that last turn. You can use this to constrain the model's output.\n\nFor example, if the input `messages` were:\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"}\n]\n```\n\nThen the response `content` might be:\n\n```json\n[{\"type\": \"text\", \"text\": \"B)\"}]\n```" + }), + "model": Model, + "stop_reason": Schema.Union([BetaStopReason, Schema.Null]).annotate({ + "title": "Stop Reason", + "description": + "The reason that we stopped.\n\nThis may be one the following values:\n* `\"end_turn\"`: the model reached a natural stopping point\n* `\"max_tokens\"`: we exceeded the requested `max_tokens` or the model's maximum\n* `\"stop_sequence\"`: one of your provided custom `stop_sequences` was generated\n* `\"tool_use\"`: the model invoked one or more tools\n* `\"pause_turn\"`: we paused a long-running turn. You may provide the response back as-is in a subsequent request to let the model continue.\n* `\"refusal\"`: when streaming classifiers intervene to handle potential policy violations\n\nIn non-streaming mode this value is always non-null. In streaming mode, it is null in the `message_start` event and non-null otherwise." + }), + "stop_sequence": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Stop Sequence", + "description": + "Which custom stop sequence was generated, if any.\n\nThis value will be a non-null string if one of your custom stop sequences was generated.", + "default": null + }), + "usage": Schema.Struct({ + "cache_creation": Schema.Union([BetaCacheCreation, Schema.Null]).annotate({ + "description": "Breakdown of cached tokens by TTL", + "default": null + }), + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Creation Input Tokens", + "description": "The number of input tokens used to create the cache entry.", + "default": null + }), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Read Input Tokens", + "description": "The number of input tokens read from the cache.", + "default": null + }), + "inference_geo": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Inference Geo", + "description": "The geographic region where inference was performed for this request.", + "default": null + }), + "input_tokens": Schema.Number.annotate({ + "title": "Input Tokens", + "description": "The number of input tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "iterations": Schema.optionalKey(BetaIterationsUsage), + "output_tokens": Schema.Number.annotate({ + "title": "Output Tokens", + "description": "The number of output tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "server_tool_use": Schema.optionalKey( + Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ + "description": "The number of server tool requests.", + "default": null + }) + ), + "service_tier": Schema.Union([Schema.Literals(["standard", "priority", "batch"]), Schema.Null]).annotate({ + "title": "Service Tier", + "description": "If the request used the priority, standard, or batch tier.", + "default": null + }), + "speed": Schema.optionalKey( + Schema.Union([BetaSpeed, Schema.Null]).annotate({ + "description": "The inference speed mode used for this request.", + "default": null + }) + ) + }).annotate({ + "title": "Usage", + "description": + "Billing and rate-limit usage.\n\nAnthropic's API bills and rate-limits by token counts, as tokens represent the underlying cost to our systems.\n\nUnder the hood, the API transforms requests into a format suitable for the model. The model's output then goes through a parsing stage before becoming an API response. As a result, the token counts in `usage` will not match one-to-one with the exact visible content of an API request or response.\n\nFor example, `output_tokens` will be non-zero, even for an empty string response from Claude.\n\nTotal input tokens in a request is the summation of `input_tokens`, `cache_creation_input_tokens`, and `cache_read_input_tokens`." + }), + "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 + }) + ), + "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 + }) + ) +}).annotate({ "title": "Message" }) +export type BetaRequestWebFetchToolResultBlock = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "content": BetaRequestWebFetchToolResultError | BetaRequestWebFetchResultBlock + readonly "tool_use_id": string + readonly "type": "web_fetch_tool_result" +} +export const BetaRequestWebFetchToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "content": Schema.Union([BetaRequestWebFetchToolResultError, BetaRequestWebFetchResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_fetch_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestWebFetchToolResultBlock" }) +export type Message = { + readonly "id": string + readonly "type": "message" + readonly "role": "assistant" + readonly "content": ReadonlyArray + readonly "model": Model + readonly "stop_reason": StopReason | null + readonly "stop_sequence": string | null + readonly "usage": { + readonly "cache_creation": CacheCreation | 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 "server_tool_use"?: ServerToolUsage | null + readonly "service_tier": "standard" | "priority" | "batch" | null + } + readonly "container": Container | null +} +export const Message = Schema.Struct({ + "id": Schema.String.annotate({ + "title": "Id", + "description": "Unique object identifier.\n\nThe format and length of IDs may change over time." + }), + "type": Schema.Literal("message").annotate({ + "title": "Type", + "description": "Object type.\n\nFor Messages, this is always `\"message\"`.", + "default": "message" + }), + "role": Schema.Literal("assistant").annotate({ + "title": "Role", + "description": "Conversational role of the generated message.\n\nThis will always be `\"assistant\"`.", + "default": "assistant" + }), + "content": Schema.Array(ContentBlock).annotate({ + "title": "Content", + "description": + "Content generated by the model.\n\nThis is an array of content blocks, each of which has a `type` that determines its shape.\n\nExample:\n\n```json\n[{\"type\": \"text\", \"text\": \"Hi, I'm Claude.\"}]\n```\n\nIf the request input `messages` ended with an `assistant` turn, then the response `content` will continue directly from that last turn. You can use this to constrain the model's output.\n\nFor example, if the input `messages` were:\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"}\n]\n```\n\nThen the response `content` might be:\n\n```json\n[{\"type\": \"text\", \"text\": \"B)\"}]\n```" + }), + "model": Model, + "stop_reason": Schema.Union([StopReason, Schema.Null]).annotate({ + "title": "Stop Reason", + "description": + "The reason that we stopped.\n\nThis may be one the following values:\n* `\"end_turn\"`: the model reached a natural stopping point\n* `\"max_tokens\"`: we exceeded the requested `max_tokens` or the model's maximum\n* `\"stop_sequence\"`: one of your provided custom `stop_sequences` was generated\n* `\"tool_use\"`: the model invoked one or more tools\n* `\"pause_turn\"`: we paused a long-running turn. You may provide the response back as-is in a subsequent request to let the model continue.\n* `\"refusal\"`: when streaming classifiers intervene to handle potential policy violations\n\nIn non-streaming mode this value is always non-null. In streaming mode, it is null in the `message_start` event and non-null otherwise." + }), + "stop_sequence": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Stop Sequence", + "description": + "Which custom stop sequence was generated, if any.\n\nThis value will be a non-null string if one of your custom stop sequences was generated.", + "default": null + }), + "usage": Schema.Struct({ + "cache_creation": Schema.Union([CacheCreation, Schema.Null]).annotate({ + "description": "Breakdown of cached tokens by TTL", + "default": null + }), + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Creation Input Tokens", + "description": "The number of input tokens used to create the cache entry.", + "default": null + }), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]).annotate({ + "title": "Cache Read Input Tokens", + "description": "The number of input tokens read from the cache.", + "default": null + }), + "inference_geo": Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Inference Geo", + "description": "The geographic region where inference was performed for this request.", + "default": null + }), + "input_tokens": Schema.Number.annotate({ + "title": "Input Tokens", + "description": "The number of input tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "output_tokens": Schema.Number.annotate({ + "title": "Output Tokens", + "description": "The number of output tokens which were used." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "server_tool_use": Schema.optionalKey( + Schema.Union([ServerToolUsage, Schema.Null]).annotate({ + "description": "The number of server tool requests.", + "default": null + }) + ), + "service_tier": Schema.Union([Schema.Literals(["standard", "priority", "batch"]), Schema.Null]).annotate({ + "title": "Service Tier", + "description": "If the request used the priority, standard, or batch tier.", + "default": null + }) + }).annotate({ + "title": "Usage", + "description": + "Billing and rate-limit usage.\n\nAnthropic's API bills and rate-limits by token counts, as tokens represent the underlying cost to our systems.\n\nUnder the hood, the API transforms requests into a format suitable for the model. The model's output then goes through a parsing stage before becoming an API response. As a result, the token counts in `usage` will not match one-to-one with the exact visible content of an API request or response.\n\nFor example, `output_tokens` will be non-zero, even for an empty string response from Claude.\n\nTotal input tokens in a request is the summation of `input_tokens`, `cache_creation_input_tokens`, and `cache_read_input_tokens`." + }), + "container": Schema.Union([Container, 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 + }) +}).annotate({ "title": "Message" }) +export type RequestWebFetchToolResultBlock = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "caller"?: DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "content": RequestWebFetchToolResultError | RequestWebFetchResultBlock + readonly "tool_use_id": string + readonly "type": "web_fetch_tool_result" +} +export const RequestWebFetchToolResultBlock = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "content": Schema.Union([RequestWebFetchToolResultError, RequestWebFetchResultBlock]).annotate({ + "title": "Content" + }), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^srvtoolu_[a-zA-Z0-9_]+$")) + ), + "type": Schema.Literal("web_fetch_tool_result").annotate({ "title": "Type" }) +}).annotate({ "title": "RequestWebFetchToolResultBlock" }) +export type BetaMessageStartEvent = { readonly "message": BetaMessage; readonly "type": "message_start" } +export const BetaMessageStartEvent = Schema.Struct({ + "message": BetaMessage, + "type": Schema.Literal("message_start").annotate({ "title": "Type", "default": "message_start" }) +}).annotate({ "title": "MessageStartEvent" }) +export type BetaSucceededResult = { readonly "message": BetaMessage; readonly "type": "succeeded" } +export const BetaSucceededResult = Schema.Struct({ + "message": BetaMessage, + "type": Schema.Literal("succeeded").annotate({ "title": "Type", "default": "succeeded" }) +}).annotate({ "title": "SucceededResult" }) +export type BetaInputContentBlock = + | { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: + | ReadonlyArray< + | BetaRequestCharLocationCitation + | BetaRequestPageLocationCitation + | BetaRequestContentBlockLocationCitation + | BetaRequestWebSearchResultLocationCitation + | BetaRequestSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" + } + | { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "source": BetaBase64ImageSource | BetaURLImageSource | BetaFileImageSource + readonly "type": "image" + } + | { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: BetaRequestCitationsConfig | null + readonly "context"?: string | null + readonly "source": + | BetaBase64PDFSource + | BetaPlainTextSource + | BetaContentBlockSource + | BetaURLPDFSource + | BetaFileDocumentSource + readonly "title"?: string | null + readonly "type": "document" + } + | { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "citations"?: BetaRequestCitationsConfig + readonly "content": ReadonlyArray + readonly "source": string + readonly "title": string + readonly "type": "search_result" + } + | { readonly "signature": string; readonly "thinking": string; readonly "type": "thinking" } + | { readonly "data": string; readonly "type": "redacted_thinking" } + | { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "caller"?: BetaDirectCaller | BetaServerToolCaller | BetaServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": string + readonly "type": "tool_use" + } + | { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "content"?: + | string + | ReadonlyArray< + | BetaRequestTextBlock + | BetaRequestImageBlock + | BetaRequestSearchResultBlock + | BetaRequestDocumentBlock + | BetaRequestToolReferenceBlock + > + readonly "is_error"?: boolean + readonly "tool_use_id": string + readonly "type": "tool_result" + } + | BetaRequestServerToolUseBlock + | BetaRequestWebSearchToolResultBlock + | BetaRequestWebFetchToolResultBlock + | BetaRequestCodeExecutionToolResultBlock + | BetaRequestBashCodeExecutionToolResultBlock + | BetaRequestTextEditorCodeExecutionToolResultBlock + | BetaRequestToolSearchToolResultBlock + | BetaRequestMCPToolUseBlock + | BetaRequestMCPToolResultBlock + | BetaRequestContainerUploadBlock + | BetaRequestCompactionBlock +export const BetaInputContentBlock = Schema.Union([ + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + BetaRequestCharLocationCitation, + BetaRequestPageLocationCitation, + BetaRequestContentBlockLocationCitation, + BetaRequestWebSearchResultLocationCitation, + BetaRequestSearchResultLocationCitation + ], { mode: "oneOf" }) + ), + Schema.Null + ]).annotate({ "title": "Citations" }) + ), + "text": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(1)), + "type": Schema.Literal("text").annotate({ "title": "Type" }) + }).annotate({ "title": "RequestTextBlock", "description": "Regular text content." }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "source": Schema.Union([BetaBase64ImageSource, BetaURLImageSource, BetaFileImageSource], { mode: "oneOf" }) + .annotate({ "title": "Source" }), + "type": Schema.Literal("image").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestImageBlock", + "description": "Image content specified directly as base64 data or as a reference via a URL." + }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(Schema.Union([BetaRequestCitationsConfig, Schema.Null])), + "context": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)), Schema.Null]).annotate({ "title": "Context" }) + ), + "source": Schema.Union([ + BetaBase64PDFSource, + BetaPlainTextSource, + BetaContentBlockSource, + BetaURLPDFSource, + BetaFileDocumentSource + ], { mode: "oneOf" }).annotate({ "title": "Source" }), + "title": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(500)), Schema.Null]).annotate({ + "title": "Title" + }) + ), + "type": Schema.Literal("document").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestDocumentBlock", + "description": "Document content, either specified directly as base64 data, as text, or as a reference via a URL." + }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(BetaRequestCitationsConfig), + "content": Schema.Array(BetaRequestTextBlock).annotate({ "title": "Content" }), + "source": Schema.String.annotate({ "title": "Source" }), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("search_result").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestSearchResultBlock", + "description": "A search result block containing source, title, and content from search operations." + }), + Schema.Struct({ + "signature": Schema.String.annotate({ "title": "Signature" }), + "thinking": Schema.String.annotate({ "title": "Thinking" }), + "type": Schema.Literal("thinking").annotate({ "title": "Type" }) + }).annotate({ "title": "RequestThinkingBlock", "description": "A block specifying internal thinking by the model." }), + Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data" }), + "type": Schema.Literal("redacted_thinking").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestRedactedThinkingBlock", + "description": "A block specifying internal, redacted thinking by the model." + }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([BetaDirectCaller, BetaServerToolCaller, BetaServerToolCaller_20260120], { mode: "oneOf" }).annotate( + { "title": "Caller" } + ) + ), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.String.annotate({ "title": "Name" }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(200)), + "type": Schema.Literal("tool_use").annotate({ "title": "Type" }) + }).annotate({ "title": "RequestToolUseBlock", "description": "A block indicating a tool use by the model." }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([ + BetaRequestTextBlock, + BetaRequestImageBlock, + BetaRequestSearchResultBlock, + BetaRequestDocumentBlock, + BetaRequestToolReferenceBlock + ], { mode: "oneOf" }).annotate({ "title": "Block" }) + ) + ]).annotate({ "title": "Content" }) + ), + "is_error": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Is Error" })), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$")) + ), + "type": Schema.Literal("tool_result").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestToolResultBlock", + "description": "A block specifying the results of a tool use by the model." + }), + BetaRequestServerToolUseBlock, + BetaRequestWebSearchToolResultBlock, + BetaRequestWebFetchToolResultBlock, + BetaRequestCodeExecutionToolResultBlock, + BetaRequestBashCodeExecutionToolResultBlock, + BetaRequestTextEditorCodeExecutionToolResultBlock, + BetaRequestToolSearchToolResultBlock, + BetaRequestMCPToolUseBlock, + BetaRequestMCPToolResultBlock, + BetaRequestContainerUploadBlock, + BetaRequestCompactionBlock +], { mode: "oneOf" }) +export type MessageStartEvent = { readonly "message": Message; readonly "type": "message_start" } +export const MessageStartEvent = Schema.Struct({ + "message": Message, + "type": Schema.Literal("message_start").annotate({ "title": "Type", "default": "message_start" }) +}).annotate({ "title": "MessageStartEvent" }) +export type SucceededResult = { readonly "message": Message; readonly "type": "succeeded" } +export const SucceededResult = Schema.Struct({ + "message": Message, + "type": Schema.Literal("succeeded").annotate({ "title": "Type", "default": "succeeded" }) +}).annotate({ "title": "SucceededResult" }) +export type InputContentBlock = + | { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: + | ReadonlyArray< + | RequestCharLocationCitation + | RequestPageLocationCitation + | RequestContentBlockLocationCitation + | RequestWebSearchResultLocationCitation + | RequestSearchResultLocationCitation + > + | null + readonly "text": string + readonly "type": "text" + } + | { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "source": Base64ImageSource | URLImageSource + readonly "type": "image" + } + | { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: RequestCitationsConfig | null + readonly "context"?: string | null + readonly "source": Base64PDFSource | PlainTextSource | ContentBlockSource | URLPDFSource + readonly "title"?: string | null + readonly "type": "document" + } + | { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "citations"?: RequestCitationsConfig + readonly "content": ReadonlyArray + readonly "source": string + readonly "title": string + readonly "type": "search_result" + } + | { readonly "signature": string; readonly "thinking": string; readonly "type": "thinking" } + | { readonly "data": string; readonly "type": "redacted_thinking" } + | { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "caller"?: DirectCaller | ServerToolCaller | ServerToolCaller_20260120 + readonly "id": string + readonly "input": { readonly [x: string]: Schema.Json } + readonly "name": string + readonly "type": "tool_use" + } + | { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "content"?: + | string + | ReadonlyArray< + | RequestTextBlock + | RequestImageBlock + | RequestSearchResultBlock + | RequestDocumentBlock + | RequestToolReferenceBlock + > + readonly "is_error"?: boolean + readonly "tool_use_id": string + readonly "type": "tool_result" + } + | RequestServerToolUseBlock + | RequestWebSearchToolResultBlock + | RequestWebFetchToolResultBlock + | RequestCodeExecutionToolResultBlock + | RequestBashCodeExecutionToolResultBlock + | RequestTextEditorCodeExecutionToolResultBlock + | RequestToolSearchToolResultBlock + | RequestContainerUploadBlock +export const InputContentBlock = Schema.Union([ + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + RequestCharLocationCitation, + RequestPageLocationCitation, + RequestContentBlockLocationCitation, + RequestWebSearchResultLocationCitation, + RequestSearchResultLocationCitation + ], { mode: "oneOf" }) + ), + Schema.Null + ]).annotate({ "title": "Citations" }) + ), + "text": Schema.String.annotate({ "title": "Text" }).check(Schema.isMinLength(1)), + "type": Schema.Literal("text").annotate({ "title": "Type" }) + }).annotate({ "title": "RequestTextBlock", "description": "Regular text content." }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "source": Schema.Union([Base64ImageSource, URLImageSource], { mode: "oneOf" }).annotate({ "title": "Source" }), + "type": Schema.Literal("image").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestImageBlock", + "description": "Image content specified directly as base64 data or as a reference via a URL." + }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(Schema.Union([RequestCitationsConfig, Schema.Null])), + "context": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)), Schema.Null]).annotate({ "title": "Context" }) + ), + "source": Schema.Union([Base64PDFSource, PlainTextSource, ContentBlockSource, URLPDFSource], { mode: "oneOf" }) + .annotate({ "title": "Source" }), + "title": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(500)), Schema.Null]).annotate({ + "title": "Title" + }) + ), + "type": Schema.Literal("document").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestDocumentBlock", + "description": "Document content, either specified directly as base64 data, as text, or as a reference via a URL." + }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "citations": Schema.optionalKey(RequestCitationsConfig), + "content": Schema.Array(RequestTextBlock).annotate({ "title": "Content" }), + "source": Schema.String.annotate({ "title": "Source" }), + "title": Schema.String.annotate({ "title": "Title" }), + "type": Schema.Literal("search_result").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestSearchResultBlock", + "description": "A search result block containing source, title, and content from search operations." + }), + Schema.Struct({ + "signature": Schema.String.annotate({ "title": "Signature" }), + "thinking": Schema.String.annotate({ "title": "Thinking" }), + "type": Schema.Literal("thinking").annotate({ "title": "Type" }) + }).annotate({ "title": "RequestThinkingBlock", "description": "A block specifying internal thinking by the model." }), + Schema.Struct({ + "data": Schema.String.annotate({ "title": "Data" }), + "type": Schema.Literal("redacted_thinking").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestRedactedThinkingBlock", + "description": "A block specifying internal, redacted thinking by the model." + }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "caller": Schema.optionalKey( + Schema.Union([DirectCaller, ServerToolCaller, ServerToolCaller_20260120], { mode: "oneOf" }).annotate({ + "title": "Caller" + }) + ), + "id": Schema.String.annotate({ "title": "Id" }).check(Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$"))), + "input": Schema.Record(Schema.String, Schema.Json).annotate({ "title": "Input" }), + "name": Schema.String.annotate({ "title": "Name" }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(200)), + "type": Schema.Literal("tool_use").annotate({ "title": "Type" }) + }).annotate({ "title": "RequestToolUseBlock", "description": "A block indicating a tool use by the model." }), + Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": "Create a cache control breakpoint at this content block." + }) + ), + "content": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([ + RequestTextBlock, + RequestImageBlock, + RequestSearchResultBlock, + RequestDocumentBlock, + RequestToolReferenceBlock + ], { mode: "oneOf" }).annotate({ "title": "Block" }) + ) + ]).annotate({ "title": "Content" }) + ), + "is_error": Schema.optionalKey(Schema.Boolean.annotate({ "title": "Is Error" })), + "tool_use_id": Schema.String.annotate({ "title": "Tool Use Id" }).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$")) + ), + "type": Schema.Literal("tool_result").annotate({ "title": "Type" }) + }).annotate({ + "title": "RequestToolResultBlock", + "description": "A block specifying the results of a tool use by the model." + }), + RequestServerToolUseBlock, + RequestWebSearchToolResultBlock, + RequestWebFetchToolResultBlock, + RequestCodeExecutionToolResultBlock, + RequestBashCodeExecutionToolResultBlock, + RequestTextEditorCodeExecutionToolResultBlock, + RequestToolSearchToolResultBlock, + RequestContainerUploadBlock +], { mode: "oneOf" }) +export type BetaInputMessage = { + readonly "content": string | ReadonlyArray + readonly "role": "user" | "assistant" +} +export const BetaInputMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(BetaInputContentBlock)]).annotate({ "title": "Content" }), + "role": Schema.Literals(["user", "assistant"]).annotate({ "title": "Role" }) +}).annotate({ "title": "InputMessage" }) +export type InputMessage = { + readonly "content": string | ReadonlyArray + readonly "role": "user" | "assistant" +} +export const InputMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(InputContentBlock)]).annotate({ "title": "Content" }), + "role": Schema.Literals(["user", "assistant"]).annotate({ "title": "Role" }) +}).annotate({ "title": "InputMessage" }) +export type BetaCountMessageTokensParams = { + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "context_management"?: BetaContextManagementConfig | null + readonly "mcp_servers"?: ReadonlyArray + readonly "messages": ReadonlyArray + readonly "model": Model + readonly "output_config"?: { + readonly "effort"?: BetaEffortLevel | null + readonly "format"?: BetaJsonOutputFormat | null + } + readonly "output_format"?: BetaJsonOutputFormat | null + readonly "speed"?: BetaSpeed | null + readonly "system"?: string | ReadonlyArray + readonly "thinking"?: BetaThinkingConfigParam + readonly "tool_choice"?: BetaToolChoice + readonly "tools"?: ReadonlyArray< + | BetaTool + | BetaBashTool_20241022 + | BetaBashTool_20250124 + | BetaCodeExecutionTool_20250522 + | BetaCodeExecutionTool_20250825 + | BetaCodeExecutionTool_20260120 + | BetaComputerUseTool_20241022 + | BetaMemoryTool_20250818 + | BetaComputerUseTool_20250124 + | BetaTextEditor_20241022 + | BetaComputerUseTool_20251124 + | BetaTextEditor_20250124 + | BetaTextEditor_20250429 + | BetaTextEditor_20250728 + | BetaWebSearchTool_20250305 + | BetaWebFetchTool_20250910 + | BetaWebSearchTool_20260209 + | BetaWebFetchTool_20260209 + | BetaToolSearchToolBM25_20251119 + | BetaToolSearchToolRegex_20251119 + | BetaMCPToolset + > +} +export const BetaCountMessageTokensParams = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": + "Top-level cache control automatically applies a cache_control marker to the last cacheable block in the request." + }) + ), + "context_management": Schema.optionalKey( + Schema.Union([BetaContextManagementConfig, Schema.Null]).annotate({ + "description": + "Context management configuration.\n\nThis allows you to control how Claude manages context across multiple requests, such as whether to clear function results or not." + }) + ), + "mcp_servers": Schema.optionalKey( + Schema.Array(BetaRequestMCPServerURLDefinition).annotate({ + "title": "Mcp Servers", + "description": "MCP servers to be utilized in this request" + }).check(Schema.isMaxLength(20)) + ), + "messages": Schema.Array(BetaInputMessage).annotate({ + "title": "Messages", + "description": + "Input messages.\n\nOur models are trained to operate on alternating `user` and `assistant` conversational turns. When creating a new `Message`, you specify the prior conversational turns with the `messages` parameter, and the model then generates the next `Message` in the conversation. Consecutive `user` or `assistant` turns in your request will be combined into a single turn.\n\nEach input message must be an object with a `role` and `content`. You can specify a single `user`-role message, or you can include multiple `user` and `assistant` messages.\n\nIf the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response.\n\nExample with a single `user` message:\n\n```json\n[{\"role\": \"user\", \"content\": \"Hello, Claude\"}]\n```\n\nExample with multiple conversational turns:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"Hello there.\"},\n {\"role\": \"assistant\", \"content\": \"Hi, I'm Claude. How can I help you?\"},\n {\"role\": \"user\", \"content\": \"Can you explain LLMs in plain English?\"},\n]\n```\n\nExample with a partially-filled response from Claude:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"},\n]\n```\n\nEach input message `content` may be either a single `string` or an array of content blocks, where each block has a specific `type`. Using a `string` for `content` is shorthand for an array of one content block of type `\"text\"`. The following input messages are equivalent:\n\n```json\n{\"role\": \"user\", \"content\": \"Hello, Claude\"}\n```\n\n```json\n{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"Hello, Claude\"}]}\n```\n\nSee [input examples](https://docs.claude.com/en/api/messages-examples).\n\nNote that if you want to include a [system prompt](https://docs.claude.com/en/docs/system-prompts), you can use the top-level `system` parameter — there is no `\"system\"` role for input messages in the Messages API.\n\nThere is a limit of 100,000 messages in a single request." + }), + "model": Model, + "output_config": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([BetaEffortLevel, Schema.Null]).annotate({ + "description": + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer.\n\nValid values are `low`, `medium`, `high`, or `max`." + }) + ), + "format": Schema.optionalKey( + Schema.Union([BetaJsonOutputFormat, 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)" + }) + ) + }).annotate({ + "title": "OutputConfig", + "description": "Configuration options for the model's output, such as the output format." + }) + ), + "output_format": Schema.optionalKey( + Schema.Union([BetaJsonOutputFormat, Schema.Null]).annotate({ + "description": + "Deprecated: Use `output_config.format` instead. See [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)\n\nA schema to specify Claude's output format in responses. This parameter will be removed in a future release." + }) + ), + "speed": Schema.optionalKey( + Schema.Union([BetaSpeed, Schema.Null]).annotate({ + "description": + "The inference speed mode for this request. `\"fast\"` enables high output-tokens-per-second inference." + }) + ), + "system": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(BetaRequestTextBlock)]).annotate({ + "title": "System", + "description": + "System prompt.\n\nA system prompt is a way of providing context and instructions to Claude, such as specifying a particular goal or role. See our [guide to system prompts](https://docs.claude.com/en/docs/system-prompts)." + }) + ), + "thinking": Schema.optionalKey(BetaThinkingConfigParam), + "tool_choice": Schema.optionalKey(BetaToolChoice), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([ + BetaTool, + BetaBashTool_20241022, + BetaBashTool_20250124, + BetaCodeExecutionTool_20250522, + BetaCodeExecutionTool_20250825, + BetaCodeExecutionTool_20260120, + BetaComputerUseTool_20241022, + BetaMemoryTool_20250818, + BetaComputerUseTool_20250124, + BetaTextEditor_20241022, + BetaComputerUseTool_20251124, + BetaTextEditor_20250124, + BetaTextEditor_20250429, + BetaTextEditor_20250728, + BetaWebSearchTool_20250305, + BetaWebFetchTool_20250910, + BetaWebSearchTool_20260209, + BetaWebFetchTool_20260209, + BetaToolSearchToolBM25_20251119, + BetaToolSearchToolRegex_20251119, + BetaMCPToolset + ], { mode: "oneOf" }) + ).annotate({ + "title": "Tools", + "description": + "Definitions of tools that the model may use.\n\nIf you include `tools` in your API request, the model may return `tool_use` content blocks that represent the model's use of those tools. You can then run those tools using the tool input generated by the model and then optionally return results back to the model using `tool_result` content blocks.\n\nThere are two types of tools: **client tools** and **server tools**. The behavior described below applies to client tools. For [server tools](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview\\#server-tools), see their individual documentation as each has its own behavior (e.g., the [web search tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool)).\n\nEach tool definition includes:\n\n* `name`: Name of the tool.\n* `description`: Optional, but strongly-recommended description of the tool.\n* `input_schema`: [JSON schema](https://json-schema.org/draft/2020-12) for the tool `input` shape that the model will produce in `tool_use` output content blocks.\n\nFor example, if you defined `tools` as:\n\n```json\n[\n {\n \"name\": \"get_stock_price\",\n \"description\": \"Get the current stock price for a given ticker symbol.\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"ticker\": {\n \"type\": \"string\",\n \"description\": \"The stock ticker symbol, e.g. AAPL for Apple Inc.\"\n }\n },\n \"required\": [\"ticker\"]\n }\n }\n]\n```\n\nAnd then asked the model \"What's the S&P 500 at today?\", the model might produce `tool_use` content blocks in the response like this:\n\n```json\n[\n {\n \"type\": \"tool_use\",\n \"id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"name\": \"get_stock_price\",\n \"input\": { \"ticker\": \"^GSPC\" }\n }\n]\n```\n\nYou might then run your `get_stock_price` tool with `{\"ticker\": \"^GSPC\"}` as an input, and return the following back to the model in a subsequent `user` message:\n\n```json\n[\n {\n \"type\": \"tool_result\",\n \"tool_use_id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"content\": \"259.75 USD\"\n }\n]\n```\n\nTools can be used for workflows that include running client-side tools and functions, or more generally whenever you want the model to produce a particular JSON structure of output.\n\nSee our [guide](https://docs.claude.com/en/docs/tool-use) for more details." + }) + ) +}).annotate({ "title": "CountMessageTokensParams" }) +export type BetaCreateMessageParams = { + readonly "model": Model + readonly "messages": ReadonlyArray + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "container"?: BetaContainerParams | string | null + readonly "context_management"?: BetaContextManagementConfig | null + readonly "inference_geo"?: string | null + readonly "max_tokens": number + readonly "mcp_servers"?: ReadonlyArray + readonly "metadata"?: { readonly "user_id"?: string | null } + readonly "output_config"?: { + readonly "effort"?: BetaEffortLevel | null + readonly "format"?: BetaJsonOutputFormat | null + } + readonly "output_format"?: BetaJsonOutputFormat | null + readonly "service_tier"?: "auto" | "standard_only" + readonly "speed"?: BetaSpeed | null + readonly "stop_sequences"?: ReadonlyArray + readonly "stream"?: boolean + readonly "system"?: string | ReadonlyArray + readonly "temperature"?: number + readonly "thinking"?: BetaThinkingConfigParam + readonly "tool_choice"?: BetaToolChoice + readonly "tools"?: ReadonlyArray< + | BetaTool + | BetaBashTool_20241022 + | BetaBashTool_20250124 + | BetaCodeExecutionTool_20250522 + | BetaCodeExecutionTool_20250825 + | BetaCodeExecutionTool_20260120 + | BetaComputerUseTool_20241022 + | BetaMemoryTool_20250818 + | BetaComputerUseTool_20250124 + | BetaTextEditor_20241022 + | BetaComputerUseTool_20251124 + | BetaTextEditor_20250124 + | BetaTextEditor_20250429 + | BetaTextEditor_20250728 + | BetaWebSearchTool_20250305 + | BetaWebFetchTool_20250910 + | BetaWebSearchTool_20260209 + | BetaWebFetchTool_20260209 + | BetaToolSearchToolBM25_20251119 + | BetaToolSearchToolRegex_20251119 + | BetaMCPToolset + > + readonly "top_k"?: number + readonly "top_p"?: number +} +export const BetaCreateMessageParams = Schema.Struct({ + "model": Model, + "messages": Schema.Array(BetaInputMessage).annotate({ + "title": "Messages", + "description": + "Input messages.\n\nOur models are trained to operate on alternating `user` and `assistant` conversational turns. When creating a new `Message`, you specify the prior conversational turns with the `messages` parameter, and the model then generates the next `Message` in the conversation. Consecutive `user` or `assistant` turns in your request will be combined into a single turn.\n\nEach input message must be an object with a `role` and `content`. You can specify a single `user`-role message, or you can include multiple `user` and `assistant` messages.\n\nIf the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response.\n\nExample with a single `user` message:\n\n```json\n[{\"role\": \"user\", \"content\": \"Hello, Claude\"}]\n```\n\nExample with multiple conversational turns:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"Hello there.\"},\n {\"role\": \"assistant\", \"content\": \"Hi, I'm Claude. How can I help you?\"},\n {\"role\": \"user\", \"content\": \"Can you explain LLMs in plain English?\"},\n]\n```\n\nExample with a partially-filled response from Claude:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"},\n]\n```\n\nEach input message `content` may be either a single `string` or an array of content blocks, where each block has a specific `type`. Using a `string` for `content` is shorthand for an array of one content block of type `\"text\"`. The following input messages are equivalent:\n\n```json\n{\"role\": \"user\", \"content\": \"Hello, Claude\"}\n```\n\n```json\n{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"Hello, Claude\"}]}\n```\n\nSee [input examples](https://docs.claude.com/en/api/messages-examples).\n\nNote that if you want to include a [system prompt](https://docs.claude.com/en/docs/system-prompts), you can use the top-level `system` parameter — there is no `\"system\"` role for input messages in the Messages API.\n\nThere is a limit of 100,000 messages in a single request." + }), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": + "Top-level cache control automatically applies a cache_control marker to the last cacheable block in the request." + }) + ), + "container": Schema.optionalKey( + Schema.Union([BetaContainerParams, Schema.String, Schema.Null]).annotate({ + "title": "Container", + "description": "Container identifier for reuse across requests." + }) + ), + "context_management": Schema.optionalKey( + Schema.Union([BetaContextManagementConfig, Schema.Null]).annotate({ + "description": + "Context management configuration.\n\nThis allows you to control how Claude manages context across multiple requests, such as whether to clear function results or not." + }) + ), + "inference_geo": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Inference Geo", + "description": + "Specifies the geographic region for inference processing. If not specified, the workspace's `default_inference_geo` is used." + }) + ), + "max_tokens": Schema.Number.annotate({ + "title": "Max Tokens", + "description": + "The maximum number of tokens to generate before stopping.\n\nNote that our models may stop _before_ reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate.\n\nDifferent models have different maximum values for this parameter. See [models](https://docs.claude.com/en/docs/models-overview) for details." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "mcp_servers": Schema.optionalKey( + Schema.Array(BetaRequestMCPServerURLDefinition).annotate({ + "title": "Mcp Servers", + "description": "MCP servers to be utilized in this request" + }).check(Schema.isMaxLength(20)) + ), + "metadata": Schema.optionalKey( + Schema.Struct({ + "user_id": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMaxLength(256)), Schema.Null]).annotate({ + "title": "User Id", + "description": + "An external identifier for the user who is associated with the request.\n\nThis should be a uuid, hash value, or other opaque identifier. Anthropic may use this id to help detect abuse. Do not include any identifying information such as name, email address, or phone number." + }) + ) + }).annotate({ "title": "Metadata", "description": "An object describing metadata about the request." }) + ), + "output_config": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([BetaEffortLevel, Schema.Null]).annotate({ + "description": + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer.\n\nValid values are `low`, `medium`, `high`, or `max`." + }) + ), + "format": Schema.optionalKey( + Schema.Union([BetaJsonOutputFormat, 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)" + }) + ) + }).annotate({ + "title": "OutputConfig", + "description": "Configuration options for the model's output, such as the output format." + }) + ), + "output_format": Schema.optionalKey( + Schema.Union([BetaJsonOutputFormat, Schema.Null]).annotate({ + "description": + "Deprecated: Use `output_config.format` instead. See [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)\n\nA schema to specify Claude's output format in responses. This parameter will be removed in a future release." + }) + ), + "service_tier": Schema.optionalKey( + Schema.Literals(["auto", "standard_only"]).annotate({ + "title": "Service Tier", + "description": + "Determines whether to use priority capacity (if available) or standard capacity for this request.\n\nAnthropic offers different levels of service for your API requests. See [service-tiers](https://docs.claude.com/en/api/service-tiers) for details." + }) + ), + "speed": Schema.optionalKey( + Schema.Union([BetaSpeed, Schema.Null]).annotate({ + "description": + "The inference speed mode for this request. `\"fast\"` enables high output-tokens-per-second inference." + }) + ), + "stop_sequences": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "title": "Stop Sequences", + "description": + "Custom text sequences that will cause the model to stop generating.\n\nOur models will normally stop when they have naturally completed their turn, which will result in a response `stop_reason` of `\"end_turn\"`.\n\nIf you want the model to stop generating when it encounters custom strings of text, you can use the `stop_sequences` parameter. If the model encounters one of the custom sequences, the response `stop_reason` value will be `\"stop_sequence\"` and the response `stop_sequence` value will contain the matched stop sequence." + }) + ), + "stream": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Stream", + "description": + "Whether to incrementally stream the response using server-sent events.\n\nSee [streaming](https://docs.claude.com/en/api/messages-streaming) for details." + }) + ), + "system": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(BetaRequestTextBlock)]).annotate({ + "title": "System", + "description": + "System prompt.\n\nA system prompt is a way of providing context and instructions to Claude, such as specifying a particular goal or role. See our [guide to system prompts](https://docs.claude.com/en/docs/system-prompts)." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Temperature", + "description": + "Amount of randomness injected into the response.\n\nDefaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0` for analytical / multiple choice, and closer to `1.0` for creative and generative tasks.\n\nNote that even with `temperature` of `0.0`, the results will not be fully deterministic." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ), + "thinking": Schema.optionalKey(BetaThinkingConfigParam), + "tool_choice": Schema.optionalKey(BetaToolChoice), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([ + BetaTool, + BetaBashTool_20241022, + BetaBashTool_20250124, + BetaCodeExecutionTool_20250522, + BetaCodeExecutionTool_20250825, + BetaCodeExecutionTool_20260120, + BetaComputerUseTool_20241022, + BetaMemoryTool_20250818, + BetaComputerUseTool_20250124, + BetaTextEditor_20241022, + BetaComputerUseTool_20251124, + BetaTextEditor_20250124, + BetaTextEditor_20250429, + BetaTextEditor_20250728, + BetaWebSearchTool_20250305, + BetaWebFetchTool_20250910, + BetaWebSearchTool_20260209, + BetaWebFetchTool_20260209, + BetaToolSearchToolBM25_20251119, + BetaToolSearchToolRegex_20251119, + BetaMCPToolset + ], { mode: "oneOf" }) + ).annotate({ + "title": "Tools", + "description": + "Definitions of tools that the model may use.\n\nIf you include `tools` in your API request, the model may return `tool_use` content blocks that represent the model's use of those tools. You can then run those tools using the tool input generated by the model and then optionally return results back to the model using `tool_result` content blocks.\n\nThere are two types of tools: **client tools** and **server tools**. The behavior described below applies to client tools. For [server tools](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview\\#server-tools), see their individual documentation as each has its own behavior (e.g., the [web search tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool)).\n\nEach tool definition includes:\n\n* `name`: Name of the tool.\n* `description`: Optional, but strongly-recommended description of the tool.\n* `input_schema`: [JSON schema](https://json-schema.org/draft/2020-12) for the tool `input` shape that the model will produce in `tool_use` output content blocks.\n\nFor example, if you defined `tools` as:\n\n```json\n[\n {\n \"name\": \"get_stock_price\",\n \"description\": \"Get the current stock price for a given ticker symbol.\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"ticker\": {\n \"type\": \"string\",\n \"description\": \"The stock ticker symbol, e.g. AAPL for Apple Inc.\"\n }\n },\n \"required\": [\"ticker\"]\n }\n }\n]\n```\n\nAnd then asked the model \"What's the S&P 500 at today?\", the model might produce `tool_use` content blocks in the response like this:\n\n```json\n[\n {\n \"type\": \"tool_use\",\n \"id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"name\": \"get_stock_price\",\n \"input\": { \"ticker\": \"^GSPC\" }\n }\n]\n```\n\nYou might then run your `get_stock_price` tool with `{\"ticker\": \"^GSPC\"}` as an input, and return the following back to the model in a subsequent `user` message:\n\n```json\n[\n {\n \"type\": \"tool_result\",\n \"tool_use_id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"content\": \"259.75 USD\"\n }\n]\n```\n\nTools can be used for workflows that include running client-side tools and functions, or more generally whenever you want the model to produce a particular JSON structure of output.\n\nSee our [guide](https://docs.claude.com/en/docs/tool-use) for more details." + }) + ), + "top_k": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top K", + "description": + "Only sample from the top K options for each subsequent token.\n\nUsed to remove \"long tail\" low probability responses. [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top P", + "description": + "Use nucleus sampling.\n\nIn nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in decreasing probability order and cut it off once it reaches a particular probability specified by `top_p`. You should either alter `temperature` or `top_p`, but not both.\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ) +}).annotate({ "title": "CreateMessageParams" }) +export type BetaMessageBatchIndividualRequestParams = { + readonly "custom_id": string + readonly "params": { + readonly "model": Model + readonly "messages": ReadonlyArray + readonly "cache_control"?: BetaCacheControlEphemeral | null + readonly "container"?: BetaContainerParams | string | null + readonly "context_management"?: BetaContextManagementConfig | null + readonly "inference_geo"?: string | null + readonly "max_tokens": number + readonly "mcp_servers"?: ReadonlyArray + readonly "metadata"?: { readonly "user_id"?: string | null } + readonly "output_config"?: { + readonly "effort"?: BetaEffortLevel | null + readonly "format"?: BetaJsonOutputFormat | null + } + readonly "output_format"?: BetaJsonOutputFormat | null + readonly "service_tier"?: "auto" | "standard_only" + readonly "speed"?: BetaSpeed | null + readonly "stop_sequences"?: ReadonlyArray + readonly "stream"?: boolean + readonly "system"?: string | ReadonlyArray + readonly "temperature"?: number + readonly "thinking"?: BetaThinkingConfigParam + readonly "tool_choice"?: BetaToolChoice + readonly "tools"?: ReadonlyArray< + | BetaTool + | BetaBashTool_20241022 + | BetaBashTool_20250124 + | BetaCodeExecutionTool_20250522 + | BetaCodeExecutionTool_20250825 + | BetaCodeExecutionTool_20260120 + | BetaComputerUseTool_20241022 + | BetaMemoryTool_20250818 + | BetaComputerUseTool_20250124 + | BetaTextEditor_20241022 + | BetaComputerUseTool_20251124 + | BetaTextEditor_20250124 + | BetaTextEditor_20250429 + | BetaTextEditor_20250728 + | BetaWebSearchTool_20250305 + | BetaWebFetchTool_20250910 + | BetaWebSearchTool_20260209 + | BetaWebFetchTool_20260209 + | BetaToolSearchToolBM25_20251119 + | BetaToolSearchToolRegex_20251119 + | BetaMCPToolset + > + readonly "top_k"?: number + readonly "top_p"?: number + } +} +export const BetaMessageBatchIndividualRequestParams = Schema.Struct({ + "custom_id": Schema.String.annotate({ + "title": "Custom Id", + "description": + "Developer-provided ID created for each request in a Message Batch. Useful for matching results to requests, as results may be given out of request order.\n\nMust be unique for each request within the Message Batch." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,64}$")) + ), + "params": Schema.Struct({ + "model": Model, + "messages": Schema.Array(BetaInputMessage).annotate({ + "title": "Messages", + "description": + "Input messages.\n\nOur models are trained to operate on alternating `user` and `assistant` conversational turns. When creating a new `Message`, you specify the prior conversational turns with the `messages` parameter, and the model then generates the next `Message` in the conversation. Consecutive `user` or `assistant` turns in your request will be combined into a single turn.\n\nEach input message must be an object with a `role` and `content`. You can specify a single `user`-role message, or you can include multiple `user` and `assistant` messages.\n\nIf the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response.\n\nExample with a single `user` message:\n\n```json\n[{\"role\": \"user\", \"content\": \"Hello, Claude\"}]\n```\n\nExample with multiple conversational turns:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"Hello there.\"},\n {\"role\": \"assistant\", \"content\": \"Hi, I'm Claude. How can I help you?\"},\n {\"role\": \"user\", \"content\": \"Can you explain LLMs in plain English?\"},\n]\n```\n\nExample with a partially-filled response from Claude:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"},\n]\n```\n\nEach input message `content` may be either a single `string` or an array of content blocks, where each block has a specific `type`. Using a `string` for `content` is shorthand for an array of one content block of type `\"text\"`. The following input messages are equivalent:\n\n```json\n{\"role\": \"user\", \"content\": \"Hello, Claude\"}\n```\n\n```json\n{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"Hello, Claude\"}]}\n```\n\nSee [input examples](https://docs.claude.com/en/api/messages-examples).\n\nNote that if you want to include a [system prompt](https://docs.claude.com/en/docs/system-prompts), you can use the top-level `system` parameter — there is no `\"system\"` role for input messages in the Messages API.\n\nThere is a limit of 100,000 messages in a single request." + }), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([BetaCacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": + "Top-level cache control automatically applies a cache_control marker to the last cacheable block in the request." + }) + ), + "container": Schema.optionalKey( + Schema.Union([BetaContainerParams, Schema.String, Schema.Null]).annotate({ + "title": "Container", + "description": "Container identifier for reuse across requests." + }) + ), + "context_management": Schema.optionalKey( + Schema.Union([BetaContextManagementConfig, Schema.Null]).annotate({ + "description": + "Context management configuration.\n\nThis allows you to control how Claude manages context across multiple requests, such as whether to clear function results or not." + }) + ), + "inference_geo": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Inference Geo", + "description": + "Specifies the geographic region for inference processing. If not specified, the workspace's `default_inference_geo` is used." + }) + ), + "max_tokens": Schema.Number.annotate({ + "title": "Max Tokens", + "description": + "The maximum number of tokens to generate before stopping.\n\nNote that our models may stop _before_ reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate.\n\nDifferent models have different maximum values for this parameter. See [models](https://docs.claude.com/en/docs/models-overview) for details." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "mcp_servers": Schema.optionalKey( + Schema.Array(BetaRequestMCPServerURLDefinition).annotate({ + "title": "Mcp Servers", + "description": "MCP servers to be utilized in this request" + }).check(Schema.isMaxLength(20)) + ), + "metadata": Schema.optionalKey( + Schema.Struct({ + "user_id": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMaxLength(256)), Schema.Null]).annotate({ + "title": "User Id", + "description": + "An external identifier for the user who is associated with the request.\n\nThis should be a uuid, hash value, or other opaque identifier. Anthropic may use this id to help detect abuse. Do not include any identifying information such as name, email address, or phone number." + }) + ) + }).annotate({ "title": "Metadata", "description": "An object describing metadata about the request." }) + ), + "output_config": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([BetaEffortLevel, Schema.Null]).annotate({ + "description": + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer.\n\nValid values are `low`, `medium`, `high`, or `max`." + }) + ), + "format": Schema.optionalKey( + Schema.Union([BetaJsonOutputFormat, 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)" + }) + ) + }).annotate({ + "title": "OutputConfig", + "description": "Configuration options for the model's output, such as the output format." + }) + ), + "output_format": Schema.optionalKey( + Schema.Union([BetaJsonOutputFormat, Schema.Null]).annotate({ + "description": + "Deprecated: Use `output_config.format` instead. See [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)\n\nA schema to specify Claude's output format in responses. This parameter will be removed in a future release." + }) + ), + "service_tier": Schema.optionalKey( + Schema.Literals(["auto", "standard_only"]).annotate({ + "title": "Service Tier", + "description": + "Determines whether to use priority capacity (if available) or standard capacity for this request.\n\nAnthropic offers different levels of service for your API requests. See [service-tiers](https://docs.claude.com/en/api/service-tiers) for details." + }) + ), + "speed": Schema.optionalKey( + Schema.Union([BetaSpeed, Schema.Null]).annotate({ + "description": + "The inference speed mode for this request. `\"fast\"` enables high output-tokens-per-second inference." + }) + ), + "stop_sequences": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "title": "Stop Sequences", + "description": + "Custom text sequences that will cause the model to stop generating.\n\nOur models will normally stop when they have naturally completed their turn, which will result in a response `stop_reason` of `\"end_turn\"`.\n\nIf you want the model to stop generating when it encounters custom strings of text, you can use the `stop_sequences` parameter. If the model encounters one of the custom sequences, the response `stop_reason` value will be `\"stop_sequence\"` and the response `stop_sequence` value will contain the matched stop sequence." + }) + ), + "stream": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Stream", + "description": + "Whether to incrementally stream the response using server-sent events.\n\nSee [streaming](https://docs.claude.com/en/api/messages-streaming) for details." + }) + ), + "system": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(BetaRequestTextBlock)]).annotate({ + "title": "System", + "description": + "System prompt.\n\nA system prompt is a way of providing context and instructions to Claude, such as specifying a particular goal or role. See our [guide to system prompts](https://docs.claude.com/en/docs/system-prompts)." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Temperature", + "description": + "Amount of randomness injected into the response.\n\nDefaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0` for analytical / multiple choice, and closer to `1.0` for creative and generative tasks.\n\nNote that even with `temperature` of `0.0`, the results will not be fully deterministic." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ), + "thinking": Schema.optionalKey(BetaThinkingConfigParam), + "tool_choice": Schema.optionalKey(BetaToolChoice), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([ + BetaTool, + BetaBashTool_20241022, + BetaBashTool_20250124, + BetaCodeExecutionTool_20250522, + BetaCodeExecutionTool_20250825, + BetaCodeExecutionTool_20260120, + BetaComputerUseTool_20241022, + BetaMemoryTool_20250818, + BetaComputerUseTool_20250124, + BetaTextEditor_20241022, + BetaComputerUseTool_20251124, + BetaTextEditor_20250124, + BetaTextEditor_20250429, + BetaTextEditor_20250728, + BetaWebSearchTool_20250305, + BetaWebFetchTool_20250910, + BetaWebSearchTool_20260209, + BetaWebFetchTool_20260209, + BetaToolSearchToolBM25_20251119, + BetaToolSearchToolRegex_20251119, + BetaMCPToolset + ], { mode: "oneOf" }) + ).annotate({ + "title": "Tools", + "description": + "Definitions of tools that the model may use.\n\nIf you include `tools` in your API request, the model may return `tool_use` content blocks that represent the model's use of those tools. You can then run those tools using the tool input generated by the model and then optionally return results back to the model using `tool_result` content blocks.\n\nThere are two types of tools: **client tools** and **server tools**. The behavior described below applies to client tools. For [server tools](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview\\#server-tools), see their individual documentation as each has its own behavior (e.g., the [web search tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool)).\n\nEach tool definition includes:\n\n* `name`: Name of the tool.\n* `description`: Optional, but strongly-recommended description of the tool.\n* `input_schema`: [JSON schema](https://json-schema.org/draft/2020-12) for the tool `input` shape that the model will produce in `tool_use` output content blocks.\n\nFor example, if you defined `tools` as:\n\n```json\n[\n {\n \"name\": \"get_stock_price\",\n \"description\": \"Get the current stock price for a given ticker symbol.\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"ticker\": {\n \"type\": \"string\",\n \"description\": \"The stock ticker symbol, e.g. AAPL for Apple Inc.\"\n }\n },\n \"required\": [\"ticker\"]\n }\n }\n]\n```\n\nAnd then asked the model \"What's the S&P 500 at today?\", the model might produce `tool_use` content blocks in the response like this:\n\n```json\n[\n {\n \"type\": \"tool_use\",\n \"id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"name\": \"get_stock_price\",\n \"input\": { \"ticker\": \"^GSPC\" }\n }\n]\n```\n\nYou might then run your `get_stock_price` tool with `{\"ticker\": \"^GSPC\"}` as an input, and return the following back to the model in a subsequent `user` message:\n\n```json\n[\n {\n \"type\": \"tool_result\",\n \"tool_use_id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"content\": \"259.75 USD\"\n }\n]\n```\n\nTools can be used for workflows that include running client-side tools and functions, or more generally whenever you want the model to produce a particular JSON structure of output.\n\nSee our [guide](https://docs.claude.com/en/docs/tool-use) for more details." + }) + ), + "top_k": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top K", + "description": + "Only sample from the top K options for each subsequent token.\n\nUsed to remove \"long tail\" low probability responses. [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top P", + "description": + "Use nucleus sampling.\n\nIn nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in decreasing probability order and cut it off once it reaches a particular probability specified by `top_p`. You should either alter `temperature` or `top_p`, but not both.\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ) + }).annotate({ + "title": "CreateMessageParams", + "description": + "Messages API creation parameters for the individual request.\n\nSee the [Messages API reference](https://docs.claude.com/en/api/messages) for full documentation on available parameters." + }) +}).annotate({ "title": "MessageBatchIndividualRequestParams" }) +export type CountMessageTokensParams = { + readonly "cache_control"?: CacheControlEphemeral | null + readonly "messages": ReadonlyArray + readonly "model": Model + readonly "output_config"?: { readonly "effort"?: EffortLevel | null; readonly "format"?: JsonOutputFormat | null } + readonly "system"?: string | ReadonlyArray + readonly "thinking"?: ThinkingConfigParam + readonly "tool_choice"?: ToolChoice + readonly "tools"?: ReadonlyArray< + | Tool + | BashTool_20250124 + | CodeExecutionTool_20250522 + | CodeExecutionTool_20250825 + | CodeExecutionTool_20260120 + | MemoryTool_20250818 + | TextEditor_20250124 + | TextEditor_20250429 + | TextEditor_20250728 + | WebSearchTool_20250305 + | WebFetchTool_20250910 + | WebSearchTool_20260209 + | WebFetchTool_20260209 + | ToolSearchToolBM25_20251119 + | ToolSearchToolRegex_20251119 + > +} +export const CountMessageTokensParams = Schema.Struct({ + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": + "Top-level cache control automatically applies a cache_control marker to the last cacheable block in the request." + }) + ), + "messages": Schema.Array(InputMessage).annotate({ + "title": "Messages", + "description": + "Input messages.\n\nOur models are trained to operate on alternating `user` and `assistant` conversational turns. When creating a new `Message`, you specify the prior conversational turns with the `messages` parameter, and the model then generates the next `Message` in the conversation. Consecutive `user` or `assistant` turns in your request will be combined into a single turn.\n\nEach input message must be an object with a `role` and `content`. You can specify a single `user`-role message, or you can include multiple `user` and `assistant` messages.\n\nIf the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response.\n\nExample with a single `user` message:\n\n```json\n[{\"role\": \"user\", \"content\": \"Hello, Claude\"}]\n```\n\nExample with multiple conversational turns:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"Hello there.\"},\n {\"role\": \"assistant\", \"content\": \"Hi, I'm Claude. How can I help you?\"},\n {\"role\": \"user\", \"content\": \"Can you explain LLMs in plain English?\"},\n]\n```\n\nExample with a partially-filled response from Claude:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"},\n]\n```\n\nEach input message `content` may be either a single `string` or an array of content blocks, where each block has a specific `type`. Using a `string` for `content` is shorthand for an array of one content block of type `\"text\"`. The following input messages are equivalent:\n\n```json\n{\"role\": \"user\", \"content\": \"Hello, Claude\"}\n```\n\n```json\n{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"Hello, Claude\"}]}\n```\n\nSee [input examples](https://docs.claude.com/en/api/messages-examples).\n\nNote that if you want to include a [system prompt](https://docs.claude.com/en/docs/system-prompts), you can use the top-level `system` parameter — there is no `\"system\"` role for input messages in the Messages API.\n\nThere is a limit of 100,000 messages in a single request." + }), + "model": Model, + "output_config": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([EffortLevel, Schema.Null]).annotate({ + "description": + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer.\n\nValid values are `low`, `medium`, `high`, or `max`." + }) + ), + "format": Schema.optionalKey( + Schema.Union([JsonOutputFormat, 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)" + }) + ) + }).annotate({ + "title": "OutputConfig", + "description": "Configuration options for the model's output, such as the output format." + }) + ), + "system": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(RequestTextBlock)]).annotate({ + "title": "System", + "description": + "System prompt.\n\nA system prompt is a way of providing context and instructions to Claude, such as specifying a particular goal or role. See our [guide to system prompts](https://docs.claude.com/en/docs/system-prompts)." + }) + ), + "thinking": Schema.optionalKey(ThinkingConfigParam), + "tool_choice": Schema.optionalKey(ToolChoice), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([ + Tool, + BashTool_20250124, + CodeExecutionTool_20250522, + CodeExecutionTool_20250825, + CodeExecutionTool_20260120, + MemoryTool_20250818, + TextEditor_20250124, + TextEditor_20250429, + TextEditor_20250728, + WebSearchTool_20250305, + WebFetchTool_20250910, + WebSearchTool_20260209, + WebFetchTool_20260209, + ToolSearchToolBM25_20251119, + ToolSearchToolRegex_20251119 + ], { mode: "oneOf" }) + ).annotate({ + "title": "Tools", + "description": + "Definitions of tools that the model may use.\n\nIf you include `tools` in your API request, the model may return `tool_use` content blocks that represent the model's use of those tools. You can then run those tools using the tool input generated by the model and then optionally return results back to the model using `tool_result` content blocks.\n\nThere are two types of tools: **client tools** and **server tools**. The behavior described below applies to client tools. For [server tools](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview\\#server-tools), see their individual documentation as each has its own behavior (e.g., the [web search tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool)).\n\nEach tool definition includes:\n\n* `name`: Name of the tool.\n* `description`: Optional, but strongly-recommended description of the tool.\n* `input_schema`: [JSON schema](https://json-schema.org/draft/2020-12) for the tool `input` shape that the model will produce in `tool_use` output content blocks.\n\nFor example, if you defined `tools` as:\n\n```json\n[\n {\n \"name\": \"get_stock_price\",\n \"description\": \"Get the current stock price for a given ticker symbol.\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"ticker\": {\n \"type\": \"string\",\n \"description\": \"The stock ticker symbol, e.g. AAPL for Apple Inc.\"\n }\n },\n \"required\": [\"ticker\"]\n }\n }\n]\n```\n\nAnd then asked the model \"What's the S&P 500 at today?\", the model might produce `tool_use` content blocks in the response like this:\n\n```json\n[\n {\n \"type\": \"tool_use\",\n \"id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"name\": \"get_stock_price\",\n \"input\": { \"ticker\": \"^GSPC\" }\n }\n]\n```\n\nYou might then run your `get_stock_price` tool with `{\"ticker\": \"^GSPC\"}` as an input, and return the following back to the model in a subsequent `user` message:\n\n```json\n[\n {\n \"type\": \"tool_result\",\n \"tool_use_id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"content\": \"259.75 USD\"\n }\n]\n```\n\nTools can be used for workflows that include running client-side tools and functions, or more generally whenever you want the model to produce a particular JSON structure of output.\n\nSee our [guide](https://docs.claude.com/en/docs/tool-use) for more details." + }) + ) +}).annotate({ "title": "CountMessageTokensParams" }) +export type CreateMessageParams = { + readonly "model": Model + readonly "messages": ReadonlyArray + readonly "cache_control"?: CacheControlEphemeral | null + readonly "container"?: string | null + readonly "inference_geo"?: string | null + readonly "max_tokens": number + readonly "metadata"?: { readonly "user_id"?: string | null } + readonly "output_config"?: { readonly "effort"?: EffortLevel | null; readonly "format"?: JsonOutputFormat | null } + readonly "service_tier"?: "auto" | "standard_only" + readonly "stop_sequences"?: ReadonlyArray + readonly "stream"?: boolean + readonly "system"?: string | ReadonlyArray + readonly "temperature"?: number + readonly "thinking"?: ThinkingConfigParam + readonly "tool_choice"?: ToolChoice + readonly "tools"?: ReadonlyArray< + | Tool + | BashTool_20250124 + | CodeExecutionTool_20250522 + | CodeExecutionTool_20250825 + | CodeExecutionTool_20260120 + | MemoryTool_20250818 + | TextEditor_20250124 + | TextEditor_20250429 + | TextEditor_20250728 + | WebSearchTool_20250305 + | WebFetchTool_20250910 + | WebSearchTool_20260209 + | WebFetchTool_20260209 + | ToolSearchToolBM25_20251119 + | ToolSearchToolRegex_20251119 + > + readonly "top_k"?: number + readonly "top_p"?: number +} +export const CreateMessageParams = Schema.Struct({ + "model": Model, + "messages": Schema.Array(InputMessage).annotate({ + "title": "Messages", + "description": + "Input messages.\n\nOur models are trained to operate on alternating `user` and `assistant` conversational turns. When creating a new `Message`, you specify the prior conversational turns with the `messages` parameter, and the model then generates the next `Message` in the conversation. Consecutive `user` or `assistant` turns in your request will be combined into a single turn.\n\nEach input message must be an object with a `role` and `content`. You can specify a single `user`-role message, or you can include multiple `user` and `assistant` messages.\n\nIf the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response.\n\nExample with a single `user` message:\n\n```json\n[{\"role\": \"user\", \"content\": \"Hello, Claude\"}]\n```\n\nExample with multiple conversational turns:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"Hello there.\"},\n {\"role\": \"assistant\", \"content\": \"Hi, I'm Claude. How can I help you?\"},\n {\"role\": \"user\", \"content\": \"Can you explain LLMs in plain English?\"},\n]\n```\n\nExample with a partially-filled response from Claude:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"},\n]\n```\n\nEach input message `content` may be either a single `string` or an array of content blocks, where each block has a specific `type`. Using a `string` for `content` is shorthand for an array of one content block of type `\"text\"`. The following input messages are equivalent:\n\n```json\n{\"role\": \"user\", \"content\": \"Hello, Claude\"}\n```\n\n```json\n{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"Hello, Claude\"}]}\n```\n\nSee [input examples](https://docs.claude.com/en/api/messages-examples).\n\nNote that if you want to include a [system prompt](https://docs.claude.com/en/docs/system-prompts), you can use the top-level `system` parameter — there is no `\"system\"` role for input messages in the Messages API.\n\nThere is a limit of 100,000 messages in a single request." + }), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": + "Top-level cache control automatically applies a cache_control marker to the last cacheable block in the request." + }) + ), + "container": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Container", + "description": "Container identifier for reuse across requests." + }) + ), + "inference_geo": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Inference Geo", + "description": + "Specifies the geographic region for inference processing. If not specified, the workspace's `default_inference_geo` is used." + }) + ), + "max_tokens": Schema.Number.annotate({ + "title": "Max Tokens", + "description": + "The maximum number of tokens to generate before stopping.\n\nNote that our models may stop _before_ reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate.\n\nDifferent models have different maximum values for this parameter. See [models](https://docs.claude.com/en/docs/models-overview) for details." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "metadata": Schema.optionalKey( + Schema.Struct({ + "user_id": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMaxLength(256)), Schema.Null]).annotate({ + "title": "User Id", + "description": + "An external identifier for the user who is associated with the request.\n\nThis should be a uuid, hash value, or other opaque identifier. Anthropic may use this id to help detect abuse. Do not include any identifying information such as name, email address, or phone number." + }) + ) + }).annotate({ "title": "Metadata", "description": "An object describing metadata about the request." }) + ), + "output_config": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([EffortLevel, Schema.Null]).annotate({ + "description": + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer.\n\nValid values are `low`, `medium`, `high`, or `max`." + }) + ), + "format": Schema.optionalKey( + Schema.Union([JsonOutputFormat, 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)" + }) + ) + }).annotate({ + "title": "OutputConfig", + "description": "Configuration options for the model's output, such as the output format." + }) + ), + "service_tier": Schema.optionalKey( + Schema.Literals(["auto", "standard_only"]).annotate({ + "title": "Service Tier", + "description": + "Determines whether to use priority capacity (if available) or standard capacity for this request.\n\nAnthropic offers different levels of service for your API requests. See [service-tiers](https://docs.claude.com/en/api/service-tiers) for details." + }) + ), + "stop_sequences": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "title": "Stop Sequences", + "description": + "Custom text sequences that will cause the model to stop generating.\n\nOur models will normally stop when they have naturally completed their turn, which will result in a response `stop_reason` of `\"end_turn\"`.\n\nIf you want the model to stop generating when it encounters custom strings of text, you can use the `stop_sequences` parameter. If the model encounters one of the custom sequences, the response `stop_reason` value will be `\"stop_sequence\"` and the response `stop_sequence` value will contain the matched stop sequence." + }) + ), + "stream": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Stream", + "description": + "Whether to incrementally stream the response using server-sent events.\n\nSee [streaming](https://docs.claude.com/en/api/messages-streaming) for details." + }) + ), + "system": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(RequestTextBlock)]).annotate({ + "title": "System", + "description": + "System prompt.\n\nA system prompt is a way of providing context and instructions to Claude, such as specifying a particular goal or role. See our [guide to system prompts](https://docs.claude.com/en/docs/system-prompts)." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Temperature", + "description": + "Amount of randomness injected into the response.\n\nDefaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0` for analytical / multiple choice, and closer to `1.0` for creative and generative tasks.\n\nNote that even with `temperature` of `0.0`, the results will not be fully deterministic." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ), + "thinking": Schema.optionalKey(ThinkingConfigParam), + "tool_choice": Schema.optionalKey(ToolChoice), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([ + Tool, + BashTool_20250124, + CodeExecutionTool_20250522, + CodeExecutionTool_20250825, + CodeExecutionTool_20260120, + MemoryTool_20250818, + TextEditor_20250124, + TextEditor_20250429, + TextEditor_20250728, + WebSearchTool_20250305, + WebFetchTool_20250910, + WebSearchTool_20260209, + WebFetchTool_20260209, + ToolSearchToolBM25_20251119, + ToolSearchToolRegex_20251119 + ], { mode: "oneOf" }) + ).annotate({ + "title": "Tools", + "description": + "Definitions of tools that the model may use.\n\nIf you include `tools` in your API request, the model may return `tool_use` content blocks that represent the model's use of those tools. You can then run those tools using the tool input generated by the model and then optionally return results back to the model using `tool_result` content blocks.\n\nThere are two types of tools: **client tools** and **server tools**. The behavior described below applies to client tools. For [server tools](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview\\#server-tools), see their individual documentation as each has its own behavior (e.g., the [web search tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool)).\n\nEach tool definition includes:\n\n* `name`: Name of the tool.\n* `description`: Optional, but strongly-recommended description of the tool.\n* `input_schema`: [JSON schema](https://json-schema.org/draft/2020-12) for the tool `input` shape that the model will produce in `tool_use` output content blocks.\n\nFor example, if you defined `tools` as:\n\n```json\n[\n {\n \"name\": \"get_stock_price\",\n \"description\": \"Get the current stock price for a given ticker symbol.\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"ticker\": {\n \"type\": \"string\",\n \"description\": \"The stock ticker symbol, e.g. AAPL for Apple Inc.\"\n }\n },\n \"required\": [\"ticker\"]\n }\n }\n]\n```\n\nAnd then asked the model \"What's the S&P 500 at today?\", the model might produce `tool_use` content blocks in the response like this:\n\n```json\n[\n {\n \"type\": \"tool_use\",\n \"id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"name\": \"get_stock_price\",\n \"input\": { \"ticker\": \"^GSPC\" }\n }\n]\n```\n\nYou might then run your `get_stock_price` tool with `{\"ticker\": \"^GSPC\"}` as an input, and return the following back to the model in a subsequent `user` message:\n\n```json\n[\n {\n \"type\": \"tool_result\",\n \"tool_use_id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"content\": \"259.75 USD\"\n }\n]\n```\n\nTools can be used for workflows that include running client-side tools and functions, or more generally whenever you want the model to produce a particular JSON structure of output.\n\nSee our [guide](https://docs.claude.com/en/docs/tool-use) for more details." + }) + ), + "top_k": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top K", + "description": + "Only sample from the top K options for each subsequent token.\n\nUsed to remove \"long tail\" low probability responses. [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top P", + "description": + "Use nucleus sampling.\n\nIn nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in decreasing probability order and cut it off once it reaches a particular probability specified by `top_p`. You should either alter `temperature` or `top_p`, but not both.\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ) +}).annotate({ "title": "CreateMessageParams" }) +export type MessageBatchIndividualRequestParams = { + readonly "custom_id": string + readonly "params": { + readonly "model": Model + readonly "messages": ReadonlyArray + readonly "cache_control"?: CacheControlEphemeral | null + readonly "container"?: string | null + readonly "inference_geo"?: string | null + readonly "max_tokens": number + readonly "metadata"?: { readonly "user_id"?: string | null } + readonly "output_config"?: { readonly "effort"?: EffortLevel | null; readonly "format"?: JsonOutputFormat | null } + readonly "service_tier"?: "auto" | "standard_only" + readonly "stop_sequences"?: ReadonlyArray + readonly "stream"?: boolean + readonly "system"?: string | ReadonlyArray + readonly "temperature"?: number + readonly "thinking"?: ThinkingConfigParam + readonly "tool_choice"?: ToolChoice + readonly "tools"?: ReadonlyArray< + | Tool + | BashTool_20250124 + | CodeExecutionTool_20250522 + | CodeExecutionTool_20250825 + | CodeExecutionTool_20260120 + | MemoryTool_20250818 + | TextEditor_20250124 + | TextEditor_20250429 + | TextEditor_20250728 + | WebSearchTool_20250305 + | WebFetchTool_20250910 + | WebSearchTool_20260209 + | WebFetchTool_20260209 + | ToolSearchToolBM25_20251119 + | ToolSearchToolRegex_20251119 + > + readonly "top_k"?: number + readonly "top_p"?: number + } +} +export const MessageBatchIndividualRequestParams = Schema.Struct({ + "custom_id": Schema.String.annotate({ + "title": "Custom Id", + "description": + "Developer-provided ID created for each request in a Message Batch. Useful for matching results to requests, as results may be given out of request order.\n\nMust be unique for each request within the Message Batch." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]{1,64}$")) + ), + "params": Schema.Struct({ + "model": Model, + "messages": Schema.Array(InputMessage).annotate({ + "title": "Messages", + "description": + "Input messages.\n\nOur models are trained to operate on alternating `user` and `assistant` conversational turns. When creating a new `Message`, you specify the prior conversational turns with the `messages` parameter, and the model then generates the next `Message` in the conversation. Consecutive `user` or `assistant` turns in your request will be combined into a single turn.\n\nEach input message must be an object with a `role` and `content`. You can specify a single `user`-role message, or you can include multiple `user` and `assistant` messages.\n\nIf the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response.\n\nExample with a single `user` message:\n\n```json\n[{\"role\": \"user\", \"content\": \"Hello, Claude\"}]\n```\n\nExample with multiple conversational turns:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"Hello there.\"},\n {\"role\": \"assistant\", \"content\": \"Hi, I'm Claude. How can I help you?\"},\n {\"role\": \"user\", \"content\": \"Can you explain LLMs in plain English?\"},\n]\n```\n\nExample with a partially-filled response from Claude:\n\n```json\n[\n {\"role\": \"user\", \"content\": \"What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun\"},\n {\"role\": \"assistant\", \"content\": \"The best answer is (\"},\n]\n```\n\nEach input message `content` may be either a single `string` or an array of content blocks, where each block has a specific `type`. Using a `string` for `content` is shorthand for an array of one content block of type `\"text\"`. The following input messages are equivalent:\n\n```json\n{\"role\": \"user\", \"content\": \"Hello, Claude\"}\n```\n\n```json\n{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"Hello, Claude\"}]}\n```\n\nSee [input examples](https://docs.claude.com/en/api/messages-examples).\n\nNote that if you want to include a [system prompt](https://docs.claude.com/en/docs/system-prompts), you can use the top-level `system` parameter — there is no `\"system\"` role for input messages in the Messages API.\n\nThere is a limit of 100,000 messages in a single request." + }), + "cache_control": Schema.optionalKey( + Schema.Union([Schema.Union([CacheControlEphemeral], { mode: "oneOf" }), Schema.Null]).annotate({ + "title": "Cache Control", + "description": + "Top-level cache control automatically applies a cache_control marker to the last cacheable block in the request." + }) + ), + "container": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Container", + "description": "Container identifier for reuse across requests." + }) + ), + "inference_geo": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Inference Geo", + "description": + "Specifies the geographic region for inference processing. If not specified, the workspace's `default_inference_geo` is used." + }) + ), + "max_tokens": Schema.Number.annotate({ + "title": "Max Tokens", + "description": + "The maximum number of tokens to generate before stopping.\n\nNote that our models may stop _before_ reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate.\n\nDifferent models have different maximum values for this parameter. See [models](https://docs.claude.com/en/docs/models-overview) for details." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + "metadata": Schema.optionalKey( + Schema.Struct({ + "user_id": Schema.optionalKey( + Schema.Union([Schema.String.check(Schema.isMaxLength(256)), Schema.Null]).annotate({ + "title": "User Id", + "description": + "An external identifier for the user who is associated with the request.\n\nThis should be a uuid, hash value, or other opaque identifier. Anthropic may use this id to help detect abuse. Do not include any identifying information such as name, email address, or phone number." + }) + ) + }).annotate({ "title": "Metadata", "description": "An object describing metadata about the request." }) + ), + "output_config": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([EffortLevel, Schema.Null]).annotate({ + "description": + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer.\n\nValid values are `low`, `medium`, `high`, or `max`." + }) + ), + "format": Schema.optionalKey( + Schema.Union([JsonOutputFormat, 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)" + }) + ) + }).annotate({ + "title": "OutputConfig", + "description": "Configuration options for the model's output, such as the output format." + }) + ), + "service_tier": Schema.optionalKey( + Schema.Literals(["auto", "standard_only"]).annotate({ + "title": "Service Tier", + "description": + "Determines whether to use priority capacity (if available) or standard capacity for this request.\n\nAnthropic offers different levels of service for your API requests. See [service-tiers](https://docs.claude.com/en/api/service-tiers) for details." + }) + ), + "stop_sequences": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "title": "Stop Sequences", + "description": + "Custom text sequences that will cause the model to stop generating.\n\nOur models will normally stop when they have naturally completed their turn, which will result in a response `stop_reason` of `\"end_turn\"`.\n\nIf you want the model to stop generating when it encounters custom strings of text, you can use the `stop_sequences` parameter. If the model encounters one of the custom sequences, the response `stop_reason` value will be `\"stop_sequence\"` and the response `stop_sequence` value will contain the matched stop sequence." + }) + ), + "stream": Schema.optionalKey( + Schema.Boolean.annotate({ + "title": "Stream", + "description": + "Whether to incrementally stream the response using server-sent events.\n\nSee [streaming](https://docs.claude.com/en/api/messages-streaming) for details." + }) + ), + "system": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(RequestTextBlock)]).annotate({ + "title": "System", + "description": + "System prompt.\n\nA system prompt is a way of providing context and instructions to Claude, such as specifying a particular goal or role. See our [guide to system prompts](https://docs.claude.com/en/docs/system-prompts)." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Temperature", + "description": + "Amount of randomness injected into the response.\n\nDefaults to `1.0`. Ranges from `0.0` to `1.0`. Use `temperature` closer to `0.0` for analytical / multiple choice, and closer to `1.0` for creative and generative tasks.\n\nNote that even with `temperature` of `0.0`, the results will not be fully deterministic." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ), + "thinking": Schema.optionalKey(ThinkingConfigParam), + "tool_choice": Schema.optionalKey(ToolChoice), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([ + Tool, + BashTool_20250124, + CodeExecutionTool_20250522, + CodeExecutionTool_20250825, + CodeExecutionTool_20260120, + MemoryTool_20250818, + TextEditor_20250124, + TextEditor_20250429, + TextEditor_20250728, + WebSearchTool_20250305, + WebFetchTool_20250910, + WebSearchTool_20260209, + WebFetchTool_20260209, + ToolSearchToolBM25_20251119, + ToolSearchToolRegex_20251119 + ], { mode: "oneOf" }) + ).annotate({ + "title": "Tools", + "description": + "Definitions of tools that the model may use.\n\nIf you include `tools` in your API request, the model may return `tool_use` content blocks that represent the model's use of those tools. You can then run those tools using the tool input generated by the model and then optionally return results back to the model using `tool_result` content blocks.\n\nThere are two types of tools: **client tools** and **server tools**. The behavior described below applies to client tools. For [server tools](https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview\\#server-tools), see their individual documentation as each has its own behavior (e.g., the [web search tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool)).\n\nEach tool definition includes:\n\n* `name`: Name of the tool.\n* `description`: Optional, but strongly-recommended description of the tool.\n* `input_schema`: [JSON schema](https://json-schema.org/draft/2020-12) for the tool `input` shape that the model will produce in `tool_use` output content blocks.\n\nFor example, if you defined `tools` as:\n\n```json\n[\n {\n \"name\": \"get_stock_price\",\n \"description\": \"Get the current stock price for a given ticker symbol.\",\n \"input_schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"ticker\": {\n \"type\": \"string\",\n \"description\": \"The stock ticker symbol, e.g. AAPL for Apple Inc.\"\n }\n },\n \"required\": [\"ticker\"]\n }\n }\n]\n```\n\nAnd then asked the model \"What's the S&P 500 at today?\", the model might produce `tool_use` content blocks in the response like this:\n\n```json\n[\n {\n \"type\": \"tool_use\",\n \"id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"name\": \"get_stock_price\",\n \"input\": { \"ticker\": \"^GSPC\" }\n }\n]\n```\n\nYou might then run your `get_stock_price` tool with `{\"ticker\": \"^GSPC\"}` as an input, and return the following back to the model in a subsequent `user` message:\n\n```json\n[\n {\n \"type\": \"tool_result\",\n \"tool_use_id\": \"toolu_01D7FLrfh4GYq7yT1ULFeyMV\",\n \"content\": \"259.75 USD\"\n }\n]\n```\n\nTools can be used for workflows that include running client-side tools and functions, or more generally whenever you want the model to produce a particular JSON structure of output.\n\nSee our [guide](https://docs.claude.com/en/docs/tool-use) for more details." + }) + ), + "top_k": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top K", + "description": + "Only sample from the top K options for each subsequent token.\n\nUsed to remove \"long tail\" low probability responses. [Learn more technical details here](https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277).\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Top P", + "description": + "Use nucleus sampling.\n\nIn nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in decreasing probability order and cut it off once it reaches a particular probability specified by `top_p`. You should either alter `temperature` or `top_p`, but not both.\n\nRecommended for advanced use cases only. You usually only need to use `temperature`." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + ) + }).annotate({ + "title": "CreateMessageParams", + "description": + "Messages API creation parameters for the individual request.\n\nSee the [Messages API reference](https://docs.claude.com/en/api/messages) for full documentation on available parameters." + }) +}).annotate({ "title": "MessageBatchIndividualRequestParams" }) +export type BetaCreateMessageBatchParams = { + readonly "requests": ReadonlyArray +} +export const BetaCreateMessageBatchParams = Schema.Struct({ + "requests": Schema.Array(BetaMessageBatchIndividualRequestParams).annotate({ + "title": "Requests", + "description": "List of requests for prompt completion. Each is an individual request to create a Message." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(100000)) +}).annotate({ "title": "CreateMessageBatchParams" }) +export type CreateMessageBatchParams = { readonly "requests": ReadonlyArray } +export const CreateMessageBatchParams = Schema.Struct({ + "requests": Schema.Array(MessageBatchIndividualRequestParams).annotate({ + "title": "Requests", + "description": "List of requests for prompt completion. Each is an individual request to create a Message." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(100000)) +}).annotate({ "title": "CreateMessageBatchParams" }) +// schemas +export type MessagesPostParams = { readonly "anthropic-version"?: string } +export const MessagesPostParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type MessagesPostRequestJson = CreateMessageParams +export const MessagesPostRequestJson = CreateMessageParams +export type MessagesPost200 = Message +export const MessagesPost200 = Message +export type MessagesPost4XX = ErrorResponse +export const MessagesPost4XX = ErrorResponse +export type CompletePostParams = { readonly "anthropic-version"?: string; readonly "anthropic-beta"?: string } +export const CompletePostParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ) +}) +export type CompletePostRequestJson = CompletionRequest +export const CompletePostRequestJson = CompletionRequest +export type CompletePost200 = CompletionResponse +export const CompletePost200 = CompletionResponse +export type CompletePost4XX = ErrorResponse +export const CompletePost4XX = ErrorResponse +export type ModelsListParams = { + readonly "before_id"?: string + readonly "after_id"?: string + readonly "limit"?: number + readonly "anthropic-version"?: string + readonly "x-api-key"?: string + readonly "anthropic-beta"?: string +} +export const ModelsListParams = Schema.Struct({ + "before_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "Before Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object." + }) + ), + "after_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "After Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`.", + "default": 20 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ) +}) +export type ModelsList200 = ListResponse_ModelInfo_ +export const ModelsList200 = ListResponse_ModelInfo_ +export type ModelsList4XX = ErrorResponse +export const ModelsList4XX = ErrorResponse +export type ModelsGetParams = { + readonly "anthropic-version"?: string + readonly "x-api-key"?: string + readonly "anthropic-beta"?: string +} +export const ModelsGetParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ) +}) +export type ModelsGet200 = ModelInfo +export const ModelsGet200 = ModelInfo +export type ModelsGet4XX = ErrorResponse +export const ModelsGet4XX = ErrorResponse +export type MessageBatchesListParams = { + readonly "before_id"?: string + readonly "after_id"?: string + readonly "limit"?: number + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const MessageBatchesListParams = Schema.Struct({ + "before_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "Before Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object." + }) + ), + "after_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "After Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`.", + "default": 20 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type MessageBatchesList200 = ListResponse_MessageBatch_ +export const MessageBatchesList200 = ListResponse_MessageBatch_ +export type MessageBatchesList4XX = ErrorResponse +export const MessageBatchesList4XX = ErrorResponse +export type MessageBatchesPostParams = { readonly "anthropic-version"?: string } +export const MessageBatchesPostParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type MessageBatchesPostRequestJson = CreateMessageBatchParams +export const MessageBatchesPostRequestJson = CreateMessageBatchParams +export type MessageBatchesPost200 = MessageBatch +export const MessageBatchesPost200 = MessageBatch +export type MessageBatchesPost4XX = ErrorResponse +export const MessageBatchesPost4XX = ErrorResponse +export type MessageBatchesRetrieveParams = { readonly "anthropic-version"?: string; readonly "x-api-key"?: string } +export const MessageBatchesRetrieveParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type MessageBatchesRetrieve200 = MessageBatch +export const MessageBatchesRetrieve200 = MessageBatch +export type MessageBatchesRetrieve4XX = ErrorResponse +export const MessageBatchesRetrieve4XX = ErrorResponse +export type MessageBatchesDeleteParams = { readonly "anthropic-version"?: string; readonly "x-api-key"?: string } +export const MessageBatchesDeleteParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type MessageBatchesDelete200 = DeleteMessageBatchResponse +export const MessageBatchesDelete200 = DeleteMessageBatchResponse +export type MessageBatchesDelete4XX = ErrorResponse +export const MessageBatchesDelete4XX = ErrorResponse +export type MessageBatchesCancelParams = { readonly "anthropic-version"?: string } +export const MessageBatchesCancelParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type MessageBatchesCancel200 = MessageBatch +export const MessageBatchesCancel200 = MessageBatch +export type MessageBatchesCancel4XX = ErrorResponse +export const MessageBatchesCancel4XX = ErrorResponse +export type MessageBatchesResultsParams = { readonly "anthropic-version"?: string; readonly "x-api-key"?: string } +export const MessageBatchesResultsParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type MessageBatchesResults4XX = ErrorResponse +export const MessageBatchesResults4XX = ErrorResponse +export type MessagesCountTokensPostParams = { readonly "anthropic-version"?: string } +export const MessagesCountTokensPostParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type MessagesCountTokensPostRequestJson = CountMessageTokensParams +export const MessagesCountTokensPostRequestJson = CountMessageTokensParams +export type MessagesCountTokensPost200 = CountMessageTokensResponse +export const MessagesCountTokensPost200 = CountMessageTokensResponse +export type MessagesCountTokensPost4XX = ErrorResponse +export const MessagesCountTokensPost4XX = ErrorResponse +export type ListFilesV1FilesGetParams = { + readonly "before_id"?: string + readonly "after_id"?: string + readonly "limit"?: number + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const ListFilesV1FilesGetParams = Schema.Struct({ + "before_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "Before Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object." + }) + ), + "after_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "After Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`.", + "default": 20 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type ListFilesV1FilesGet200 = FileListResponse +export const ListFilesV1FilesGet200 = FileListResponse +export type ListFilesV1FilesGet4XX = ErrorResponse +export const ListFilesV1FilesGet4XX = ErrorResponse +export type UploadFileV1FilesPostParams = { readonly "anthropic-beta"?: string; readonly "anthropic-version"?: string } +export const UploadFileV1FilesPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type UploadFileV1FilesPostRequestFormData = { readonly "file": string } +export const UploadFileV1FilesPostRequestFormData = Schema.Struct({ + "file": Schema.String.annotate({ "description": "The file to upload", "format": "binary" }) +}) +export type UploadFileV1FilesPost200 = FileMetadataSchema +export const UploadFileV1FilesPost200 = FileMetadataSchema +export type UploadFileV1FilesPost4XX = ErrorResponse +export const UploadFileV1FilesPost4XX = ErrorResponse +export type GetFileMetadataV1FilesFileIdGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const GetFileMetadataV1FilesFileIdGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type GetFileMetadataV1FilesFileIdGet200 = FileMetadataSchema +export const GetFileMetadataV1FilesFileIdGet200 = FileMetadataSchema +export type GetFileMetadataV1FilesFileIdGet4XX = ErrorResponse +export const GetFileMetadataV1FilesFileIdGet4XX = ErrorResponse +export type DeleteFileV1FilesFileIdDeleteParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const DeleteFileV1FilesFileIdDeleteParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type DeleteFileV1FilesFileIdDelete200 = FileDeleteResponse +export const DeleteFileV1FilesFileIdDelete200 = FileDeleteResponse +export type DeleteFileV1FilesFileIdDelete4XX = ErrorResponse +export const DeleteFileV1FilesFileIdDelete4XX = ErrorResponse +export type DownloadFileV1FilesFileIdContentGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const DownloadFileV1FilesFileIdContentGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type ListSkillsV1SkillsGetParams = { + readonly "page"?: string | null + readonly "limit"?: number + readonly "source"?: string | null + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const ListSkillsV1SkillsGetParams = Schema.Struct({ + "page": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Page", + "description": + "Pagination token for fetching a specific page of results.\n\nPass the value from a previous response's `next_page` field to get the next page of results." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of results to return per page.\n\nMaximum value is 100. Defaults to 20.", + "default": 20 + }).check(Schema.isInt()) + ), + "source": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Source", + "description": + "Filter skills by source.\n\nIf provided, only skills from the specified source will be returned:\n* `\"custom\"`: only return user-created skills\n* `\"anthropic\"`: only return Anthropic-created skills" + }) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type ListSkillsV1SkillsGet200 = ListSkillsResponse +export const ListSkillsV1SkillsGet200 = ListSkillsResponse +export type ListSkillsV1SkillsGet4XX = ErrorResponse +export const ListSkillsV1SkillsGet4XX = ErrorResponse +export type CreateSkillV1SkillsPostParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string +} +export const CreateSkillV1SkillsPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type CreateSkillV1SkillsPostRequestFormData = Body_create_skill_v1_skills_post +export const CreateSkillV1SkillsPostRequestFormData = Body_create_skill_v1_skills_post +export type CreateSkillV1SkillsPost200 = CreateSkillResponse +export const CreateSkillV1SkillsPost200 = CreateSkillResponse +export type CreateSkillV1SkillsPost4XX = ErrorResponse +export const CreateSkillV1SkillsPost4XX = ErrorResponse +export type GetSkillV1SkillsSkillIdGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const GetSkillV1SkillsSkillIdGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type GetSkillV1SkillsSkillIdGet200 = GetSkillResponse +export const GetSkillV1SkillsSkillIdGet200 = GetSkillResponse +export type GetSkillV1SkillsSkillIdGet4XX = ErrorResponse +export const GetSkillV1SkillsSkillIdGet4XX = ErrorResponse +export type DeleteSkillV1SkillsSkillIdDeleteParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const DeleteSkillV1SkillsSkillIdDeleteParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type DeleteSkillV1SkillsSkillIdDelete200 = DeleteSkillResponse +export const DeleteSkillV1SkillsSkillIdDelete200 = DeleteSkillResponse +export type DeleteSkillV1SkillsSkillIdDelete4XX = ErrorResponse +export const DeleteSkillV1SkillsSkillIdDelete4XX = ErrorResponse +export type ListSkillVersionsV1SkillsSkillIdVersionsGetParams = { + readonly "page"?: string | null + readonly "limit"?: number | null + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const ListSkillVersionsV1SkillsSkillIdVersionsGetParams = Schema.Struct({ + "page": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Page", + "description": "Optionally set to the `next_page` token from the previous response." + }) + ), + "limit": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`." + }) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type ListSkillVersionsV1SkillsSkillIdVersionsGet200 = ListSkillVersionsResponse +export const ListSkillVersionsV1SkillsSkillIdVersionsGet200 = ListSkillVersionsResponse +export type ListSkillVersionsV1SkillsSkillIdVersionsGet4XX = ErrorResponse +export const ListSkillVersionsV1SkillsSkillIdVersionsGet4XX = ErrorResponse +export type CreateSkillVersionV1SkillsSkillIdVersionsPostParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string +} +export const CreateSkillVersionV1SkillsSkillIdVersionsPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type CreateSkillVersionV1SkillsSkillIdVersionsPostRequestFormData = + Body_create_skill_version_v1_skills__skill_id__versions_post +export const CreateSkillVersionV1SkillsSkillIdVersionsPostRequestFormData = + Body_create_skill_version_v1_skills__skill_id__versions_post +export type CreateSkillVersionV1SkillsSkillIdVersionsPost200 = CreateSkillVersionResponse +export const CreateSkillVersionV1SkillsSkillIdVersionsPost200 = CreateSkillVersionResponse +export type CreateSkillVersionV1SkillsSkillIdVersionsPost4XX = ErrorResponse +export const CreateSkillVersionV1SkillsSkillIdVersionsPost4XX = ErrorResponse +export type GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type GetSkillVersionV1SkillsSkillIdVersionsVersionGet200 = GetSkillVersionResponse +export const GetSkillVersionV1SkillsSkillIdVersionsVersionGet200 = GetSkillVersionResponse +export type GetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX = ErrorResponse +export const GetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX = ErrorResponse +export type DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete200 = DeleteSkillVersionResponse +export const DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete200 = DeleteSkillVersionResponse +export type DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX = ErrorResponse +export const DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX = ErrorResponse +export type BetaMessagesPostParams = { readonly "anthropic-beta"?: string; readonly "anthropic-version"?: string } +export const BetaMessagesPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type BetaMessagesPostRequestJson = BetaCreateMessageParams +export const BetaMessagesPostRequestJson = BetaCreateMessageParams +export type BetaMessagesPost200 = BetaMessage +export const BetaMessagesPost200 = BetaMessage +export type BetaMessagesPost4XX = BetaErrorResponse +export const BetaMessagesPost4XX = BetaErrorResponse +export type BetaModelsListParams = { + readonly "before_id"?: string + readonly "after_id"?: string + readonly "limit"?: number + readonly "anthropic-version"?: string + readonly "x-api-key"?: string + readonly "anthropic-beta"?: string +} +export const BetaModelsListParams = Schema.Struct({ + "before_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "Before Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object." + }) + ), + "after_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "After Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`.", + "default": 20 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ) +}) +export type BetaModelsList200 = BetaListResponse_ModelInfo_ +export const BetaModelsList200 = BetaListResponse_ModelInfo_ +export type BetaModelsList4XX = BetaErrorResponse +export const BetaModelsList4XX = BetaErrorResponse +export type BetaModelsGetParams = { + readonly "anthropic-version"?: string + readonly "x-api-key"?: string + readonly "anthropic-beta"?: string +} +export const BetaModelsGetParams = Schema.Struct({ + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ) +}) +export type BetaModelsGet200 = BetaModelInfo +export const BetaModelsGet200 = BetaModelInfo +export type BetaModelsGet4XX = BetaErrorResponse +export const BetaModelsGet4XX = BetaErrorResponse +export type BetaMessageBatchesListParams = { + readonly "before_id"?: string + readonly "after_id"?: string + readonly "limit"?: number + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaMessageBatchesListParams = Schema.Struct({ + "before_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "Before Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object." + }) + ), + "after_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "After Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`.", + "default": 20 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaMessageBatchesList200 = BetaListResponse_MessageBatch_ +export const BetaMessageBatchesList200 = BetaListResponse_MessageBatch_ +export type BetaMessageBatchesList4XX = BetaErrorResponse +export const BetaMessageBatchesList4XX = BetaErrorResponse +export type BetaMessageBatchesPostParams = { readonly "anthropic-beta"?: string; readonly "anthropic-version"?: string } +export const BetaMessageBatchesPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type BetaMessageBatchesPostRequestJson = BetaCreateMessageBatchParams +export const BetaMessageBatchesPostRequestJson = BetaCreateMessageBatchParams +export type BetaMessageBatchesPost200 = BetaMessageBatch +export const BetaMessageBatchesPost200 = BetaMessageBatch +export type BetaMessageBatchesPost4XX = BetaErrorResponse +export const BetaMessageBatchesPost4XX = BetaErrorResponse +export type BetaMessageBatchesRetrieveParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaMessageBatchesRetrieveParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaMessageBatchesRetrieve200 = BetaMessageBatch +export const BetaMessageBatchesRetrieve200 = BetaMessageBatch +export type BetaMessageBatchesRetrieve4XX = BetaErrorResponse +export const BetaMessageBatchesRetrieve4XX = BetaErrorResponse +export type BetaMessageBatchesDeleteParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaMessageBatchesDeleteParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaMessageBatchesDelete200 = BetaDeleteMessageBatchResponse +export const BetaMessageBatchesDelete200 = BetaDeleteMessageBatchResponse +export type BetaMessageBatchesDelete4XX = BetaErrorResponse +export const BetaMessageBatchesDelete4XX = BetaErrorResponse +export type BetaMessageBatchesCancelParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string +} +export const BetaMessageBatchesCancelParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type BetaMessageBatchesCancel200 = BetaMessageBatch +export const BetaMessageBatchesCancel200 = BetaMessageBatch +export type BetaMessageBatchesCancel4XX = BetaErrorResponse +export const BetaMessageBatchesCancel4XX = BetaErrorResponse +export type BetaMessageBatchesResultsParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaMessageBatchesResultsParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaMessageBatchesResults4XX = BetaErrorResponse +export const BetaMessageBatchesResults4XX = BetaErrorResponse +export type BetaMessagesCountTokensPostParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string +} +export const BetaMessagesCountTokensPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type BetaMessagesCountTokensPostRequestJson = BetaCountMessageTokensParams +export const BetaMessagesCountTokensPostRequestJson = BetaCountMessageTokensParams +export type BetaMessagesCountTokensPost200 = BetaCountMessageTokensResponse +export const BetaMessagesCountTokensPost200 = BetaCountMessageTokensResponse +export type BetaMessagesCountTokensPost4XX = BetaErrorResponse +export const BetaMessagesCountTokensPost4XX = BetaErrorResponse +export type BetaListFilesV1FilesGetParams = { + readonly "before_id"?: string + readonly "after_id"?: string + readonly "limit"?: number + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaListFilesV1FilesGetParams = Schema.Struct({ + "before_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "Before Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately before this object." + }) + ), + "after_id": Schema.optionalKey( + Schema.String.annotate({ + "title": "After Id", + "description": + "ID of the object to use as a cursor for pagination. When provided, returns the page of results immediately after this object." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`.", + "default": 20 + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaListFilesV1FilesGet200 = BetaFileListResponse +export const BetaListFilesV1FilesGet200 = BetaFileListResponse +export type BetaListFilesV1FilesGet4XX = BetaErrorResponse +export const BetaListFilesV1FilesGet4XX = BetaErrorResponse +export type BetaUploadFileV1FilesPostParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string +} +export const BetaUploadFileV1FilesPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type BetaUploadFileV1FilesPostRequestFormData = { readonly "file": string } +export const BetaUploadFileV1FilesPostRequestFormData = Schema.Struct({ + "file": Schema.String.annotate({ "description": "The file to upload", "format": "binary" }) +}) +export type BetaUploadFileV1FilesPost200 = BetaFileMetadataSchema +export const BetaUploadFileV1FilesPost200 = BetaFileMetadataSchema +export type BetaUploadFileV1FilesPost4XX = BetaErrorResponse +export const BetaUploadFileV1FilesPost4XX = BetaErrorResponse +export type BetaGetFileMetadataV1FilesFileIdGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaGetFileMetadataV1FilesFileIdGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaGetFileMetadataV1FilesFileIdGet200 = BetaFileMetadataSchema +export const BetaGetFileMetadataV1FilesFileIdGet200 = BetaFileMetadataSchema +export type BetaGetFileMetadataV1FilesFileIdGet4XX = BetaErrorResponse +export const BetaGetFileMetadataV1FilesFileIdGet4XX = BetaErrorResponse +export type BetaDeleteFileV1FilesFileIdDeleteParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaDeleteFileV1FilesFileIdDeleteParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaDeleteFileV1FilesFileIdDelete200 = BetaFileDeleteResponse +export const BetaDeleteFileV1FilesFileIdDelete200 = BetaFileDeleteResponse +export type BetaDeleteFileV1FilesFileIdDelete4XX = BetaErrorResponse +export const BetaDeleteFileV1FilesFileIdDelete4XX = BetaErrorResponse +export type BetaDownloadFileV1FilesFileIdContentGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaDownloadFileV1FilesFileIdContentGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaListSkillsV1SkillsGetParams = { + readonly "page"?: string | null + readonly "limit"?: number + readonly "source"?: string | null + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaListSkillsV1SkillsGetParams = Schema.Struct({ + "page": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Page", + "description": + "Pagination token for fetching a specific page of results.\n\nPass the value from a previous response's `next_page` field to get the next page of results." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "title": "Limit", + "description": "Number of results to return per page.\n\nMaximum value is 100. Defaults to 20.", + "default": 20 + }).check(Schema.isInt()) + ), + "source": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Source", + "description": + "Filter skills by source.\n\nIf provided, only skills from the specified source will be returned:\n* `\"custom\"`: only return user-created skills\n* `\"anthropic\"`: only return Anthropic-created skills" + }) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaListSkillsV1SkillsGet200 = BetaListSkillsResponse +export const BetaListSkillsV1SkillsGet200 = BetaListSkillsResponse +export type BetaListSkillsV1SkillsGet4XX = BetaErrorResponse +export const BetaListSkillsV1SkillsGet4XX = BetaErrorResponse +export type BetaCreateSkillV1SkillsPostParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string +} +export const BetaCreateSkillV1SkillsPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type BetaCreateSkillV1SkillsPostRequestFormData = BetaBody_create_skill_v1_skills_post +export const BetaCreateSkillV1SkillsPostRequestFormData = BetaBody_create_skill_v1_skills_post +export type BetaCreateSkillV1SkillsPost200 = BetaCreateSkillResponse +export const BetaCreateSkillV1SkillsPost200 = BetaCreateSkillResponse +export type BetaCreateSkillV1SkillsPost4XX = BetaErrorResponse +export const BetaCreateSkillV1SkillsPost4XX = BetaErrorResponse +export type BetaGetSkillV1SkillsSkillIdGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaGetSkillV1SkillsSkillIdGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaGetSkillV1SkillsSkillIdGet200 = BetaGetSkillResponse +export const BetaGetSkillV1SkillsSkillIdGet200 = BetaGetSkillResponse +export type BetaGetSkillV1SkillsSkillIdGet4XX = BetaErrorResponse +export const BetaGetSkillV1SkillsSkillIdGet4XX = BetaErrorResponse +export type BetaDeleteSkillV1SkillsSkillIdDeleteParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaDeleteSkillV1SkillsSkillIdDeleteParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaDeleteSkillV1SkillsSkillIdDelete200 = BetaDeleteSkillResponse +export const BetaDeleteSkillV1SkillsSkillIdDelete200 = BetaDeleteSkillResponse +export type BetaDeleteSkillV1SkillsSkillIdDelete4XX = BetaErrorResponse +export const BetaDeleteSkillV1SkillsSkillIdDelete4XX = BetaErrorResponse +export type BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams = { + readonly "page"?: string | null + readonly "limit"?: number | null + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams = Schema.Struct({ + "page": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "title": "Page", + "description": "Optionally set to the `next_page` token from the previous response." + }) + ), + "limit": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "title": "Limit", + "description": "Number of items to return per page.\n\nDefaults to `20`. Ranges from `1` to `1000`." + }) + ), + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaListSkillVersionsV1SkillsSkillIdVersionsGet200 = BetaListSkillVersionsResponse +export const BetaListSkillVersionsV1SkillsSkillIdVersionsGet200 = BetaListSkillVersionsResponse +export type BetaListSkillVersionsV1SkillsSkillIdVersionsGet4XX = BetaErrorResponse +export const BetaListSkillVersionsV1SkillsSkillIdVersionsGet4XX = BetaErrorResponse +export type BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string +} +export const BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ) +}) +export type BetaCreateSkillVersionV1SkillsSkillIdVersionsPostRequestFormData = + BetaBody_create_skill_version_v1_skills__skill_id__versions_post +export const BetaCreateSkillVersionV1SkillsSkillIdVersionsPostRequestFormData = + BetaBody_create_skill_version_v1_skills__skill_id__versions_post +export type BetaCreateSkillVersionV1SkillsSkillIdVersionsPost200 = BetaCreateSkillVersionResponse +export const BetaCreateSkillVersionV1SkillsSkillIdVersionsPost200 = BetaCreateSkillVersionResponse +export type BetaCreateSkillVersionV1SkillsSkillIdVersionsPost4XX = BetaErrorResponse +export const BetaCreateSkillVersionV1SkillsSkillIdVersionsPost4XX = BetaErrorResponse +export type BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet200 = BetaGetSkillVersionResponse +export const BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet200 = BetaGetSkillVersionResponse +export type BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX = BetaErrorResponse +export const BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX = BetaErrorResponse +export type BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams = { + readonly "anthropic-beta"?: string + readonly "anthropic-version"?: string + readonly "x-api-key"?: string +} +export const BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams = Schema.Struct({ + "anthropic-beta": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Beta", + "description": + "Optional header to specify the beta version(s) you want to use.\n\nTo use multiple betas, use a comma separated list like `beta1,beta2` or specify the header multiple times for each beta." + }) + ), + "anthropic-version": Schema.optionalKey( + Schema.String.annotate({ + "title": "Anthropic-Version", + "description": + "The version of the Claude API you want to use.\n\nRead more about versioning and our version history [here](https://docs.claude.com/en/api/versioning)." + }) + ), + "x-api-key": Schema.optionalKey(Schema.String.annotate({ + "title": "X-Api-Key", + "description": + "Your unique API key for authentication.\n\nThis key is required in the header of all API requests, to authenticate your account and access Anthropic's services. Get your API key through the [Console](https://console.anthropic.com/settings/keys). Each key is scoped to a Workspace." + })) +}) +export type BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete200 = BetaDeleteSkillVersionResponse +export const BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete200 = BetaDeleteSkillVersionResponse +export type BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX = BetaErrorResponse +export const BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX = BetaErrorResponse + +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 + } = {} +): AnthropicClient => { + 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 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(AnthropicClientError(tag, cause, response)) + ) + return { + httpClient, + "messagesPost": (options) => + HttpClientRequest.post(`/v1/messages`).pipe( + HttpClientRequest.setHeaders({ "anthropic-version": options.params?.["anthropic-version"] ?? undefined }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(MessagesPost200), + "4xx": decodeError("MessagesPost4XX", MessagesPost4XX), + orElse: unexpectedStatus + })) + ), + "completePost": (options) => + HttpClientRequest.post(`/v1/complete`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-version": options.params?.["anthropic-version"] ?? undefined, + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CompletePost200), + "4xx": decodeError("CompletePost4XX", CompletePost4XX), + orElse: unexpectedStatus + })) + ), + "modelsList": (options) => + HttpClientRequest.get(`/v1/models`).pipe( + HttpClientRequest.setUrlParams({ + "before_id": options?.params?.["before_id"] as any, + "after_id": options?.params?.["after_id"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined, + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModelsList200), + "4xx": decodeError("ModelsList4XX", ModelsList4XX), + orElse: unexpectedStatus + })) + ), + "modelsGet": (modelId, options) => + HttpClientRequest.get(`/v1/models/${modelId}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined, + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModelsGet200), + "4xx": decodeError("ModelsGet4XX", ModelsGet4XX), + orElse: unexpectedStatus + })) + ), + "messageBatchesList": (options) => + HttpClientRequest.get(`/v1/messages/batches`).pipe( + HttpClientRequest.setUrlParams({ + "before_id": options?.params?.["before_id"] as any, + "after_id": options?.params?.["after_id"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(MessageBatchesList200), + "4xx": decodeError("MessageBatchesList4XX", MessageBatchesList4XX), + orElse: unexpectedStatus + })) + ), + "messageBatchesPost": (options) => + HttpClientRequest.post(`/v1/messages/batches`).pipe( + HttpClientRequest.setHeaders({ "anthropic-version": options.params?.["anthropic-version"] ?? undefined }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(MessageBatchesPost200), + "4xx": decodeError("MessageBatchesPost4XX", MessageBatchesPost4XX), + orElse: unexpectedStatus + })) + ), + "messageBatchesRetrieve": (messageBatchId, options) => + HttpClientRequest.get(`/v1/messages/batches/${messageBatchId}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(MessageBatchesRetrieve200), + "4xx": decodeError("MessageBatchesRetrieve4XX", MessageBatchesRetrieve4XX), + orElse: unexpectedStatus + })) + ), + "messageBatchesDelete": (messageBatchId, options) => + HttpClientRequest.delete(`/v1/messages/batches/${messageBatchId}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(MessageBatchesDelete200), + "4xx": decodeError("MessageBatchesDelete4XX", MessageBatchesDelete4XX), + orElse: unexpectedStatus + })) + ), + "messageBatchesCancel": (messageBatchId, options) => + HttpClientRequest.post(`/v1/messages/batches/${messageBatchId}/cancel`).pipe( + HttpClientRequest.setHeaders({ "anthropic-version": options?.params?.["anthropic-version"] ?? undefined }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(MessageBatchesCancel200), + "4xx": decodeError("MessageBatchesCancel4XX", MessageBatchesCancel4XX), + orElse: unexpectedStatus + })) + ), + "messageBatchesResults": (messageBatchId, options) => + HttpClientRequest.get(`/v1/messages/batches/${messageBatchId}/results`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "4xx": decodeError("MessageBatchesResults4XX", MessageBatchesResults4XX), + orElse: unexpectedStatus + })) + ), + "messagesCountTokensPost": (options) => + HttpClientRequest.post(`/v1/messages/count_tokens`).pipe( + HttpClientRequest.setHeaders({ "anthropic-version": options.params?.["anthropic-version"] ?? undefined }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(MessagesCountTokensPost200), + "4xx": decodeError("MessagesCountTokensPost4XX", MessagesCountTokensPost4XX), + orElse: unexpectedStatus + })) + ), + "listFilesV1FilesGet": (options) => + HttpClientRequest.get(`/v1/files`).pipe( + HttpClientRequest.setUrlParams({ + "before_id": options?.params?.["before_id"] as any, + "after_id": options?.params?.["after_id"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFilesV1FilesGet200), + "4xx": decodeError("ListFilesV1FilesGet4XX", ListFilesV1FilesGet4XX), + orElse: unexpectedStatus + })) + ), + "uploadFileV1FilesPost": (options) => + HttpClientRequest.post(`/v1/files`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UploadFileV1FilesPost200), + "4xx": decodeError("UploadFileV1FilesPost4XX", UploadFileV1FilesPost4XX), + orElse: unexpectedStatus + })) + ), + "getFileMetadataV1FilesFileIdGet": (fileId, options) => + HttpClientRequest.get(`/v1/files/${fileId}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetFileMetadataV1FilesFileIdGet200), + "4xx": decodeError("GetFileMetadataV1FilesFileIdGet4XX", GetFileMetadataV1FilesFileIdGet4XX), + orElse: unexpectedStatus + })) + ), + "deleteFileV1FilesFileIdDelete": (fileId, options) => + HttpClientRequest.delete(`/v1/files/${fileId}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteFileV1FilesFileIdDelete200), + "4xx": decodeError("DeleteFileV1FilesFileIdDelete4XX", DeleteFileV1FilesFileIdDelete4XX), + orElse: unexpectedStatus + })) + ), + "downloadFileV1FilesFileIdContentGet": (fileId, options) => + HttpClientRequest.get(`/v1/files/${fileId}/content`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + orElse: unexpectedStatus + })) + ), + "downloadFileV1FilesFileIdContentGetStream": (fileId, options) => + HttpClientRequest.get(`/v1/files/${fileId}/content`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + binaryRequest + ), + "listSkillsV1SkillsGet": (options) => + HttpClientRequest.get(`/v1/skills`).pipe( + HttpClientRequest.setUrlParams({ + "page": options?.params?.["page"] as any, + "limit": options?.params?.["limit"] as any, + "source": options?.params?.["source"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListSkillsV1SkillsGet200), + "4xx": decodeError("ListSkillsV1SkillsGet4XX", ListSkillsV1SkillsGet4XX), + orElse: unexpectedStatus + })) + ), + "createSkillV1SkillsPost": (options) => + HttpClientRequest.post(`/v1/skills`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateSkillV1SkillsPost200), + "4xx": decodeError("CreateSkillV1SkillsPost4XX", CreateSkillV1SkillsPost4XX), + orElse: unexpectedStatus + })) + ), + "getSkillV1SkillsSkillIdGet": (skillId, options) => + HttpClientRequest.get(`/v1/skills/${skillId}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetSkillV1SkillsSkillIdGet200), + "4xx": decodeError("GetSkillV1SkillsSkillIdGet4XX", GetSkillV1SkillsSkillIdGet4XX), + orElse: unexpectedStatus + })) + ), + "deleteSkillV1SkillsSkillIdDelete": (skillId, options) => + HttpClientRequest.delete(`/v1/skills/${skillId}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteSkillV1SkillsSkillIdDelete200), + "4xx": decodeError("DeleteSkillV1SkillsSkillIdDelete4XX", DeleteSkillV1SkillsSkillIdDelete4XX), + orElse: unexpectedStatus + })) + ), + "listSkillVersionsV1SkillsSkillIdVersionsGet": (skillId, options) => + HttpClientRequest.get(`/v1/skills/${skillId}/versions`).pipe( + HttpClientRequest.setUrlParams({ + "page": options?.params?.["page"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListSkillVersionsV1SkillsSkillIdVersionsGet200), + "4xx": decodeError( + "ListSkillVersionsV1SkillsSkillIdVersionsGet4XX", + ListSkillVersionsV1SkillsSkillIdVersionsGet4XX + ), + orElse: unexpectedStatus + })) + ), + "createSkillVersionV1SkillsSkillIdVersionsPost": (skillId, options) => + HttpClientRequest.post(`/v1/skills/${skillId}/versions`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateSkillVersionV1SkillsSkillIdVersionsPost200), + "4xx": decodeError( + "CreateSkillVersionV1SkillsSkillIdVersionsPost4XX", + CreateSkillVersionV1SkillsSkillIdVersionsPost4XX + ), + orElse: unexpectedStatus + })) + ), + "getSkillVersionV1SkillsSkillIdVersionsVersionGet": (skillId, version, options) => + HttpClientRequest.get(`/v1/skills/${skillId}/versions/${version}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetSkillVersionV1SkillsSkillIdVersionsVersionGet200), + "4xx": decodeError( + "GetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX", + GetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX + ), + orElse: unexpectedStatus + })) + ), + "deleteSkillVersionV1SkillsSkillIdVersionsVersionDelete": (skillId, version, options) => + HttpClientRequest.delete(`/v1/skills/${skillId}/versions/${version}`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete200), + "4xx": decodeError( + "DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX", + DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX + ), + orElse: unexpectedStatus + })) + ), + "betaMessagesPost": (options) => + HttpClientRequest.post(`/v1/messages?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaMessagesPost200), + "4xx": decodeError("BetaMessagesPost4XX", BetaMessagesPost4XX), + orElse: unexpectedStatus + })) + ), + "betaModelsList": (options) => + HttpClientRequest.get(`/v1/models?beta=true`).pipe( + HttpClientRequest.setUrlParams({ + "before_id": options?.params?.["before_id"] as any, + "after_id": options?.params?.["after_id"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined, + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaModelsList200), + "4xx": decodeError("BetaModelsList4XX", BetaModelsList4XX), + orElse: unexpectedStatus + })) + ), + "betaModelsGet": (modelId, options) => + HttpClientRequest.get(`/v1/models/${modelId}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined, + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaModelsGet200), + "4xx": decodeError("BetaModelsGet4XX", BetaModelsGet4XX), + orElse: unexpectedStatus + })) + ), + "betaMessageBatchesList": (options) => + HttpClientRequest.get(`/v1/messages/batches?beta=true`).pipe( + HttpClientRequest.setUrlParams({ + "before_id": options?.params?.["before_id"] as any, + "after_id": options?.params?.["after_id"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaMessageBatchesList200), + "4xx": decodeError("BetaMessageBatchesList4XX", BetaMessageBatchesList4XX), + orElse: unexpectedStatus + })) + ), + "betaMessageBatchesPost": (options) => + HttpClientRequest.post(`/v1/messages/batches?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaMessageBatchesPost200), + "4xx": decodeError("BetaMessageBatchesPost4XX", BetaMessageBatchesPost4XX), + orElse: unexpectedStatus + })) + ), + "betaMessageBatchesRetrieve": (messageBatchId, options) => + HttpClientRequest.get(`/v1/messages/batches/${messageBatchId}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaMessageBatchesRetrieve200), + "4xx": decodeError("BetaMessageBatchesRetrieve4XX", BetaMessageBatchesRetrieve4XX), + orElse: unexpectedStatus + })) + ), + "betaMessageBatchesDelete": (messageBatchId, options) => + HttpClientRequest.delete(`/v1/messages/batches/${messageBatchId}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaMessageBatchesDelete200), + "4xx": decodeError("BetaMessageBatchesDelete4XX", BetaMessageBatchesDelete4XX), + orElse: unexpectedStatus + })) + ), + "betaMessageBatchesCancel": (messageBatchId, options) => + HttpClientRequest.post(`/v1/messages/batches/${messageBatchId}/cancel?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaMessageBatchesCancel200), + "4xx": decodeError("BetaMessageBatchesCancel4XX", BetaMessageBatchesCancel4XX), + orElse: unexpectedStatus + })) + ), + "betaMessageBatchesResults": (messageBatchId, options) => + HttpClientRequest.get(`/v1/messages/batches/${messageBatchId}/results?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "4xx": decodeError("BetaMessageBatchesResults4XX", BetaMessageBatchesResults4XX), + orElse: unexpectedStatus + })) + ), + "betaMessagesCountTokensPost": (options) => + HttpClientRequest.post(`/v1/messages/count_tokens?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaMessagesCountTokensPost200), + "4xx": decodeError("BetaMessagesCountTokensPost4XX", BetaMessagesCountTokensPost4XX), + orElse: unexpectedStatus + })) + ), + "betaListFilesV1FilesGet": (options) => + HttpClientRequest.get(`/v1/files?beta=true`).pipe( + HttpClientRequest.setUrlParams({ + "before_id": options?.params?.["before_id"] as any, + "after_id": options?.params?.["after_id"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaListFilesV1FilesGet200), + "4xx": decodeError("BetaListFilesV1FilesGet4XX", BetaListFilesV1FilesGet4XX), + orElse: unexpectedStatus + })) + ), + "betaUploadFileV1FilesPost": (options) => + HttpClientRequest.post(`/v1/files?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaUploadFileV1FilesPost200), + "4xx": decodeError("BetaUploadFileV1FilesPost4XX", BetaUploadFileV1FilesPost4XX), + orElse: unexpectedStatus + })) + ), + "betaGetFileMetadataV1FilesFileIdGet": (fileId, options) => + HttpClientRequest.get(`/v1/files/${fileId}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaGetFileMetadataV1FilesFileIdGet200), + "4xx": decodeError("BetaGetFileMetadataV1FilesFileIdGet4XX", BetaGetFileMetadataV1FilesFileIdGet4XX), + orElse: unexpectedStatus + })) + ), + "betaDeleteFileV1FilesFileIdDelete": (fileId, options) => + HttpClientRequest.delete(`/v1/files/${fileId}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaDeleteFileV1FilesFileIdDelete200), + "4xx": decodeError("BetaDeleteFileV1FilesFileIdDelete4XX", BetaDeleteFileV1FilesFileIdDelete4XX), + orElse: unexpectedStatus + })) + ), + "betaDownloadFileV1FilesFileIdContentGet": (fileId, options) => + HttpClientRequest.get(`/v1/files/${fileId}/content?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + orElse: unexpectedStatus + })) + ), + "betaDownloadFileV1FilesFileIdContentGetStream": (fileId, options) => + HttpClientRequest.get(`/v1/files/${fileId}/content?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + binaryRequest + ), + "betaListSkillsV1SkillsGet": (options) => + HttpClientRequest.get(`/v1/skills?beta=true`).pipe( + HttpClientRequest.setUrlParams({ + "page": options?.params?.["page"] as any, + "limit": options?.params?.["limit"] as any, + "source": options?.params?.["source"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaListSkillsV1SkillsGet200), + "4xx": decodeError("BetaListSkillsV1SkillsGet4XX", BetaListSkillsV1SkillsGet4XX), + orElse: unexpectedStatus + })) + ), + "betaCreateSkillV1SkillsPost": (options) => + HttpClientRequest.post(`/v1/skills?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaCreateSkillV1SkillsPost200), + "4xx": decodeError("BetaCreateSkillV1SkillsPost4XX", BetaCreateSkillV1SkillsPost4XX), + orElse: unexpectedStatus + })) + ), + "betaGetSkillV1SkillsSkillIdGet": (skillId, options) => + HttpClientRequest.get(`/v1/skills/${skillId}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaGetSkillV1SkillsSkillIdGet200), + "4xx": decodeError("BetaGetSkillV1SkillsSkillIdGet4XX", BetaGetSkillV1SkillsSkillIdGet4XX), + orElse: unexpectedStatus + })) + ), + "betaDeleteSkillV1SkillsSkillIdDelete": (skillId, options) => + HttpClientRequest.delete(`/v1/skills/${skillId}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaDeleteSkillV1SkillsSkillIdDelete200), + "4xx": decodeError("BetaDeleteSkillV1SkillsSkillIdDelete4XX", BetaDeleteSkillV1SkillsSkillIdDelete4XX), + orElse: unexpectedStatus + })) + ), + "betaListSkillVersionsV1SkillsSkillIdVersionsGet": (skillId, options) => + HttpClientRequest.get(`/v1/skills/${skillId}/versions?beta=true`).pipe( + HttpClientRequest.setUrlParams({ + "page": options?.params?.["page"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaListSkillVersionsV1SkillsSkillIdVersionsGet200), + "4xx": decodeError( + "BetaListSkillVersionsV1SkillsSkillIdVersionsGet4XX", + BetaListSkillVersionsV1SkillsSkillIdVersionsGet4XX + ), + orElse: unexpectedStatus + })) + ), + "betaCreateSkillVersionV1SkillsSkillIdVersionsPost": (skillId, options) => + HttpClientRequest.post(`/v1/skills/${skillId}/versions?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options.params?.["anthropic-version"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaCreateSkillVersionV1SkillsSkillIdVersionsPost200), + "4xx": decodeError( + "BetaCreateSkillVersionV1SkillsSkillIdVersionsPost4XX", + BetaCreateSkillVersionV1SkillsSkillIdVersionsPost4XX + ), + orElse: unexpectedStatus + })) + ), + "betaGetSkillVersionV1SkillsSkillIdVersionsVersionGet": (skillId, version, options) => + HttpClientRequest.get(`/v1/skills/${skillId}/versions/${version}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet200), + "4xx": decodeError( + "BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX", + BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX + ), + orElse: unexpectedStatus + })) + ), + "betaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete": (skillId, version, options) => + HttpClientRequest.delete(`/v1/skills/${skillId}/versions/${version}?beta=true`).pipe( + HttpClientRequest.setHeaders({ + "anthropic-beta": options?.params?.["anthropic-beta"] ?? undefined, + "anthropic-version": options?.params?.["anthropic-version"] ?? undefined, + "x-api-key": options?.params?.["x-api-key"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete200), + "4xx": decodeError( + "BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX", + BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX + ), + orElse: unexpectedStatus + })) + ) + } +} + +export interface AnthropicClient { + readonly httpClient: HttpClient.HttpClient + /** + * Send a structured list of input messages with text and/or image content, and the model will generate the next message in the conversation. + * + * The Messages API can be used for either single queries or stateless multi-turn conversations. + * + * Learn more about the Messages API in our [user guide](https://docs.claude.com/en/docs/initial-setup) + */ + readonly "messagesPost": ( + options: { + readonly params?: typeof MessagesPostParams.Encoded | undefined + readonly payload: typeof MessagesPostRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | AnthropicClientError<"MessagesPost4XX", typeof MessagesPost4XX.Type> + > + /** + * [Legacy] Create a Text Completion. + * + * The Text Completions API is a legacy API. We recommend using the [Messages API](https://docs.claude.com/en/api/messages) going forward. + * + * Future models and features will not be compatible with Text Completions. See our [migration guide](https://docs.claude.com/en/api/migrating-from-text-completions-to-messages) for guidance in migrating from Text Completions to Messages. + */ + readonly "completePost": ( + options: { + readonly params?: typeof CompletePostParams.Encoded | undefined + readonly payload: typeof CompletePostRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | AnthropicClientError<"CompletePost4XX", typeof CompletePost4XX.Type> + > + /** + * List available models. + * + * The Models API response can be used to determine which models are available for use in the API. More recently released models are listed first. + */ + readonly "modelsList": ( + options: + | { readonly params?: typeof ModelsListParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | AnthropicClientError<"ModelsList4XX", typeof ModelsList4XX.Type> + > + /** + * Get a specific model. + * + * The Models API response can be used to determine information about a specific model or resolve a model alias to a model ID. + */ + readonly "modelsGet": ( + modelId: string, + options: + | { readonly params?: typeof ModelsGetParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | AnthropicClientError<"ModelsGet4XX", typeof ModelsGet4XX.Type> + > + /** + * List all Message Batches within a Workspace. Most recently created batches are returned first. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "messageBatchesList": ( + options: { + readonly params?: typeof MessageBatchesListParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"MessageBatchesList4XX", typeof MessageBatchesList4XX.Type> + > + /** + * Send a batch of Message creation requests. + * + * The Message Batches API can be used to process multiple Messages API requests at once. Once a Message Batch is created, it begins processing immediately. Batches can take up to 24 hours to complete. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "messageBatchesPost": ( + options: { + readonly params?: typeof MessageBatchesPostParams.Encoded | undefined + readonly payload: typeof MessageBatchesPostRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"MessageBatchesPost4XX", typeof MessageBatchesPost4XX.Type> + > + /** + * This endpoint is idempotent and can be used to poll for Message Batch completion. To access the results of a Message Batch, make a request to the `results_url` field in the response. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "messageBatchesRetrieve": ( + messageBatchId: string, + options: { + readonly params?: typeof MessageBatchesRetrieveParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"MessageBatchesRetrieve4XX", typeof MessageBatchesRetrieve4XX.Type> + > + /** + * Delete a Message Batch. + * + * Message Batches can only be deleted once they've finished processing. If you'd like to delete an in-progress batch, you must first cancel it. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "messageBatchesDelete": ( + messageBatchId: string, + options: { + readonly params?: typeof MessageBatchesDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"MessageBatchesDelete4XX", typeof MessageBatchesDelete4XX.Type> + > + /** + * Batches may be canceled any time before processing ends. Once cancellation is initiated, the batch enters a `canceling` state, at which time the system may complete any in-progress, non-interruptible requests before finalizing cancellation. + * + * The number of canceled requests is specified in `request_counts`. To determine which requests were canceled, check the individual results within the batch. Note that cancellation may not result in any canceled requests if they were non-interruptible. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "messageBatchesCancel": ( + messageBatchId: string, + options: { + readonly params?: typeof MessageBatchesCancelParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"MessageBatchesCancel4XX", typeof MessageBatchesCancel4XX.Type> + > + /** + * Streams the results of a Message Batch as a `.jsonl` file. + * + * Each line in the file is a JSON object containing the result of a single request in the Message Batch. Results are not guaranteed to be in the same order as requests. Use the `custom_id` field to match results to requests. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "messageBatchesResults": ( + messageBatchId: string, + options: { + readonly params?: typeof MessageBatchesResultsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"MessageBatchesResults4XX", typeof MessageBatchesResults4XX.Type> + > + /** + * Count the number of tokens in a Message. + * + * The Token Count API can be used to count the number of tokens in a Message, including tools, images, and documents, without creating it. + * + * Learn more about token counting in our [user guide](https://docs.claude.com/en/docs/build-with-claude/token-counting) + */ + readonly "messagesCountTokensPost": ( + options: { + readonly params?: typeof MessagesCountTokensPostParams.Encoded | undefined + readonly payload: typeof MessagesCountTokensPostRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"MessagesCountTokensPost4XX", typeof MessagesCountTokensPost4XX.Type> + > + /** + * List Files + */ + readonly "listFilesV1FilesGet": ( + options: { + readonly params?: typeof ListFilesV1FilesGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"ListFilesV1FilesGet4XX", typeof ListFilesV1FilesGet4XX.Type> + > + /** + * Upload File + */ + readonly "uploadFileV1FilesPost": ( + options: { + readonly params?: typeof UploadFileV1FilesPostParams.Encoded | undefined + readonly payload: typeof UploadFileV1FilesPostRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"UploadFileV1FilesPost4XX", typeof UploadFileV1FilesPost4XX.Type> + > + /** + * Get File Metadata + */ + readonly "getFileMetadataV1FilesFileIdGet": ( + fileId: string, + options: { + readonly params?: typeof GetFileMetadataV1FilesFileIdGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"GetFileMetadataV1FilesFileIdGet4XX", typeof GetFileMetadataV1FilesFileIdGet4XX.Type> + > + /** + * Delete File + */ + readonly "deleteFileV1FilesFileIdDelete": ( + fileId: string, + options: { + readonly params?: typeof DeleteFileV1FilesFileIdDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"DeleteFileV1FilesFileIdDelete4XX", typeof DeleteFileV1FilesFileIdDelete4XX.Type> + > + /** + * Download File + */ + readonly "downloadFileV1FilesFileIdContentGet": ( + fileId: string, + options: { + readonly params?: typeof DownloadFileV1FilesFileIdContentGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Download File + */ + readonly "downloadFileV1FilesFileIdContentGetStream": ( + fileId: string, + options: { readonly params?: typeof DownloadFileV1FilesFileIdContentGetParams.Encoded | undefined } | undefined + ) => Stream.Stream + /** + * List Skills + */ + readonly "listSkillsV1SkillsGet": ( + options: { + readonly params?: typeof ListSkillsV1SkillsGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"ListSkillsV1SkillsGet4XX", typeof ListSkillsV1SkillsGet4XX.Type> + > + /** + * Create Skill + */ + readonly "createSkillV1SkillsPost": ( + options: { + readonly params?: typeof CreateSkillV1SkillsPostParams.Encoded | undefined + readonly payload: typeof CreateSkillV1SkillsPostRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"CreateSkillV1SkillsPost4XX", typeof CreateSkillV1SkillsPost4XX.Type> + > + /** + * Get Skill + */ + readonly "getSkillV1SkillsSkillIdGet": ( + skillId: string, + options: { + readonly params?: typeof GetSkillV1SkillsSkillIdGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"GetSkillV1SkillsSkillIdGet4XX", typeof GetSkillV1SkillsSkillIdGet4XX.Type> + > + /** + * Delete Skill + */ + readonly "deleteSkillV1SkillsSkillIdDelete": ( + skillId: string, + options: { + readonly params?: typeof DeleteSkillV1SkillsSkillIdDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"DeleteSkillV1SkillsSkillIdDelete4XX", typeof DeleteSkillV1SkillsSkillIdDelete4XX.Type> + > + /** + * List Skill Versions + */ + readonly "listSkillVersionsV1SkillsSkillIdVersionsGet": ( + skillId: string, + options: { + readonly params?: typeof ListSkillVersionsV1SkillsSkillIdVersionsGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "ListSkillVersionsV1SkillsSkillIdVersionsGet4XX", + typeof ListSkillVersionsV1SkillsSkillIdVersionsGet4XX.Type + > + > + /** + * Create Skill Version + */ + readonly "createSkillVersionV1SkillsSkillIdVersionsPost": ( + skillId: string, + options: { + readonly params?: typeof CreateSkillVersionV1SkillsSkillIdVersionsPostParams.Encoded | undefined + readonly payload: typeof CreateSkillVersionV1SkillsSkillIdVersionsPostRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "CreateSkillVersionV1SkillsSkillIdVersionsPost4XX", + typeof CreateSkillVersionV1SkillsSkillIdVersionsPost4XX.Type + > + > + /** + * Get Skill Version + */ + readonly "getSkillVersionV1SkillsSkillIdVersionsVersionGet": ( + skillId: string, + version: string, + options: { + readonly params?: typeof GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "GetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX", + typeof GetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX.Type + > + > + /** + * Delete Skill Version + */ + readonly "deleteSkillVersionV1SkillsSkillIdVersionsVersionDelete": ( + skillId: string, + version: string, + options: { + readonly params?: typeof DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX", + typeof DeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX.Type + > + > + /** + * Send a structured list of input messages with text and/or image content, and the model will generate the next message in the conversation. + * + * The Messages API can be used for either single queries or stateless multi-turn conversations. + * + * Learn more about the Messages API in our [user guide](https://docs.claude.com/en/docs/initial-setup) + */ + readonly "betaMessagesPost": ( + options: { + readonly params?: typeof BetaMessagesPostParams.Encoded | undefined + readonly payload: typeof BetaMessagesPostRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessagesPost4XX", typeof BetaMessagesPost4XX.Type> + > + /** + * List available models. + * + * The Models API response can be used to determine which models are available for use in the API. More recently released models are listed first. + */ + readonly "betaModelsList": ( + options: + | { readonly params?: typeof BetaModelsListParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaModelsList4XX", typeof BetaModelsList4XX.Type> + > + /** + * Get a specific model. + * + * The Models API response can be used to determine information about a specific model or resolve a model alias to a model ID. + */ + readonly "betaModelsGet": ( + modelId: string, + options: + | { readonly params?: typeof BetaModelsGetParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaModelsGet4XX", typeof BetaModelsGet4XX.Type> + > + /** + * List all Message Batches within a Workspace. Most recently created batches are returned first. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "betaMessageBatchesList": ( + options: { + readonly params?: typeof BetaMessageBatchesListParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessageBatchesList4XX", typeof BetaMessageBatchesList4XX.Type> + > + /** + * Send a batch of Message creation requests. + * + * The Message Batches API can be used to process multiple Messages API requests at once. Once a Message Batch is created, it begins processing immediately. Batches can take up to 24 hours to complete. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "betaMessageBatchesPost": ( + options: { + readonly params?: typeof BetaMessageBatchesPostParams.Encoded | undefined + readonly payload: typeof BetaMessageBatchesPostRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessageBatchesPost4XX", typeof BetaMessageBatchesPost4XX.Type> + > + /** + * This endpoint is idempotent and can be used to poll for Message Batch completion. To access the results of a Message Batch, make a request to the `results_url` field in the response. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "betaMessageBatchesRetrieve": ( + messageBatchId: string, + options: { + readonly params?: typeof BetaMessageBatchesRetrieveParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessageBatchesRetrieve4XX", typeof BetaMessageBatchesRetrieve4XX.Type> + > + /** + * Delete a Message Batch. + * + * Message Batches can only be deleted once they've finished processing. If you'd like to delete an in-progress batch, you must first cancel it. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "betaMessageBatchesDelete": ( + messageBatchId: string, + options: { + readonly params?: typeof BetaMessageBatchesDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessageBatchesDelete4XX", typeof BetaMessageBatchesDelete4XX.Type> + > + /** + * Batches may be canceled any time before processing ends. Once cancellation is initiated, the batch enters a `canceling` state, at which time the system may complete any in-progress, non-interruptible requests before finalizing cancellation. + * + * The number of canceled requests is specified in `request_counts`. To determine which requests were canceled, check the individual results within the batch. Note that cancellation may not result in any canceled requests if they were non-interruptible. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "betaMessageBatchesCancel": ( + messageBatchId: string, + options: { + readonly params?: typeof BetaMessageBatchesCancelParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessageBatchesCancel4XX", typeof BetaMessageBatchesCancel4XX.Type> + > + /** + * Streams the results of a Message Batch as a `.jsonl` file. + * + * Each line in the file is a JSON object containing the result of a single request in the Message Batch. Results are not guaranteed to be in the same order as requests. Use the `custom_id` field to match results to requests. + * + * Learn more about the Message Batches API in our [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing) + */ + readonly "betaMessageBatchesResults": ( + messageBatchId: string, + options: { + readonly params?: typeof BetaMessageBatchesResultsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessageBatchesResults4XX", typeof BetaMessageBatchesResults4XX.Type> + > + /** + * Count the number of tokens in a Message. + * + * The Token Count API can be used to count the number of tokens in a Message, including tools, images, and documents, without creating it. + * + * Learn more about token counting in our [user guide](https://docs.claude.com/en/docs/build-with-claude/token-counting) + */ + readonly "betaMessagesCountTokensPost": ( + options: { + readonly params?: typeof BetaMessagesCountTokensPostParams.Encoded | undefined + readonly payload: typeof BetaMessagesCountTokensPostRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaMessagesCountTokensPost4XX", typeof BetaMessagesCountTokensPost4XX.Type> + > + /** + * List Files + */ + readonly "betaListFilesV1FilesGet": ( + options: { + readonly params?: typeof BetaListFilesV1FilesGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaListFilesV1FilesGet4XX", typeof BetaListFilesV1FilesGet4XX.Type> + > + /** + * Upload File + */ + readonly "betaUploadFileV1FilesPost": ( + options: { + readonly params?: typeof BetaUploadFileV1FilesPostParams.Encoded | undefined + readonly payload: typeof BetaUploadFileV1FilesPostRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaUploadFileV1FilesPost4XX", typeof BetaUploadFileV1FilesPost4XX.Type> + > + /** + * Get File Metadata + */ + readonly "betaGetFileMetadataV1FilesFileIdGet": ( + fileId: string, + options: { + readonly params?: typeof BetaGetFileMetadataV1FilesFileIdGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaGetFileMetadataV1FilesFileIdGet4XX", typeof BetaGetFileMetadataV1FilesFileIdGet4XX.Type> + > + /** + * Delete File + */ + readonly "betaDeleteFileV1FilesFileIdDelete": ( + fileId: string, + options: { + readonly params?: typeof BetaDeleteFileV1FilesFileIdDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaDeleteFileV1FilesFileIdDelete4XX", typeof BetaDeleteFileV1FilesFileIdDelete4XX.Type> + > + /** + * Download File + */ + readonly "betaDownloadFileV1FilesFileIdContentGet": ( + fileId: string, + options: { + readonly params?: typeof BetaDownloadFileV1FilesFileIdContentGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Download File + */ + readonly "betaDownloadFileV1FilesFileIdContentGetStream": ( + fileId: string, + options: { readonly params?: typeof BetaDownloadFileV1FilesFileIdContentGetParams.Encoded | undefined } | undefined + ) => Stream.Stream + /** + * List Skills + */ + readonly "betaListSkillsV1SkillsGet": ( + options: { + readonly params?: typeof BetaListSkillsV1SkillsGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaListSkillsV1SkillsGet4XX", typeof BetaListSkillsV1SkillsGet4XX.Type> + > + /** + * Create Skill + */ + readonly "betaCreateSkillV1SkillsPost": ( + options: { + readonly params?: typeof BetaCreateSkillV1SkillsPostParams.Encoded | undefined + readonly payload: typeof BetaCreateSkillV1SkillsPostRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaCreateSkillV1SkillsPost4XX", typeof BetaCreateSkillV1SkillsPost4XX.Type> + > + /** + * Get Skill + */ + readonly "betaGetSkillV1SkillsSkillIdGet": ( + skillId: string, + options: { + readonly params?: typeof BetaGetSkillV1SkillsSkillIdGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError<"BetaGetSkillV1SkillsSkillIdGet4XX", typeof BetaGetSkillV1SkillsSkillIdGet4XX.Type> + > + /** + * Delete Skill + */ + readonly "betaDeleteSkillV1SkillsSkillIdDelete": ( + skillId: string, + options: { + readonly params?: typeof BetaDeleteSkillV1SkillsSkillIdDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "BetaDeleteSkillV1SkillsSkillIdDelete4XX", + typeof BetaDeleteSkillV1SkillsSkillIdDelete4XX.Type + > + > + /** + * List Skill Versions + */ + readonly "betaListSkillVersionsV1SkillsSkillIdVersionsGet": ( + skillId: string, + options: { + readonly params?: typeof BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "BetaListSkillVersionsV1SkillsSkillIdVersionsGet4XX", + typeof BetaListSkillVersionsV1SkillsSkillIdVersionsGet4XX.Type + > + > + /** + * Create Skill Version + */ + readonly "betaCreateSkillVersionV1SkillsSkillIdVersionsPost": ( + skillId: string, + options: { + readonly params?: typeof BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams.Encoded | undefined + readonly payload: typeof BetaCreateSkillVersionV1SkillsSkillIdVersionsPostRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "BetaCreateSkillVersionV1SkillsSkillIdVersionsPost4XX", + typeof BetaCreateSkillVersionV1SkillsSkillIdVersionsPost4XX.Type + > + > + /** + * Get Skill Version + */ + readonly "betaGetSkillVersionV1SkillsSkillIdVersionsVersionGet": ( + skillId: string, + version: string, + options: { + readonly params?: typeof BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX", + typeof BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGet4XX.Type + > + > + /** + * Delete Skill Version + */ + readonly "betaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete": ( + skillId: string, + version: string, + options: { + readonly params?: typeof BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | AnthropicClientError< + "BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX", + typeof BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDelete4XX.Type + > + > +} + +export interface AnthropicClientError { + readonly _tag: Tag + readonly request: HttpClientRequest.HttpClientRequest + readonly response: HttpClientResponse.HttpClientResponse + readonly cause: E +} + +class AnthropicClientErrorImpl extends Data.Error<{ + _tag: string + cause: any + request: HttpClientRequest.HttpClientRequest + response: HttpClientResponse.HttpClientResponse +}> {} + +export const AnthropicClientError = ( + tag: Tag, + cause: E, + response: HttpClientResponse.HttpClientResponse +): AnthropicClientError => + new AnthropicClientErrorImpl({ + _tag: tag, + cause, + response, + request: response.request + }) as any diff --git a/.repos/effect/packages/ai/anthropic/src/index.ts b/.repos/effect/packages/ai/anthropic/src/index.ts new file mode 100644 index 000000000..48af73062 --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/index.ts @@ -0,0 +1,40 @@ +/** + * @since 4.0.0 + */ + +// @barrel: Auto-generated exports. Do not edit manually. + +/** + * @since 4.0.0 + */ +export * as AnthropicClient from "./AnthropicClient.ts" + +/** + * @since 4.0.0 + */ +export * as AnthropicConfig from "./AnthropicConfig.ts" + +/** + * @since 4.0.0 + */ +export * as AnthropicError from "./AnthropicError.ts" + +/** + * @since 4.0.0 + */ +export * as AnthropicLanguageModel from "./AnthropicLanguageModel.ts" + +/** + * @since 4.0.0 + */ +export * as AnthropicTelemetry from "./AnthropicTelemetry.ts" + +/** + * @since 4.0.0 + */ +export * as AnthropicTool from "./AnthropicTool.ts" + +/** + * @since 4.0.0 + */ +export * as Generated from "./Generated.ts" diff --git a/.repos/effect/packages/ai/anthropic/src/internal/errors.ts b/.repos/effect/packages/ai/anthropic/src/internal/errors.ts new file mode 100644 index 000000000..e0b2e95a9 --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/internal/errors.ts @@ -0,0 +1,347 @@ +import * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import * as Number from "effect/Number" +import * as Option from "effect/Option" +import * as Predicate from "effect/Predicate" +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" +import type { AnthropicErrorMetadata } from "../AnthropicError.ts" +import type * as Generated from "../Generated.ts" + +// ============================================================================= +// Anthropic Error Body Schema +// ============================================================================= + +/** @internal */ +export const AnthropicErrorBody = Schema.Struct({ + type: Schema.Literal("error"), + error: Schema.Struct({ + type: Schema.String, + message: Schema.String + }) +}) + +/** @internal */ +export type AnthropicClientErrorBody = { + readonly type: "error" + readonly error: { + readonly type: string + readonly message: string + } + readonly request_id: string | null +} + +// ============================================================================= +// Error Mappers +// ============================================================================= + +/** @internal */ +export const mapSchemaError = dual< + (method: string) => (error: Schema.SchemaError) => AiError.AiError, + (error: Schema.SchemaError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "AnthropicClient", + method, + 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, + (error: Generated.AnthropicClientError, method: string) => AiError.AiError +>(2, (error, method) => { + const { request, response, cause } = error + const status = response.status + const headers = response.headers as Record + const metadata: AnthropicErrorMetadata = { + errorType: cause.error.type, + requestId: cause.request_id + } + const http = buildHttpContext({ request, response, body: JSON.stringify(cause) }) + const reason = mapStatusCodeToReason({ + status, + headers, + message: cause.error.message, + metadata, + http + }) + return AiError.make({ module: "AnthropicClient", method, reason }) +}) + +/** @internal */ +export const mapHttpClientError = dual< + (method: string) => (error: HttpClientError.HttpClientError) => Effect.Effect, + (error: HttpClientError.HttpClientError, method: string) => Effect.Effect +>(2, (error, method) => { + const reason = error.reason + switch (reason._tag) { + case "TransportError": { + return Effect.fail(AiError.make({ + module: "AnthropicClient", + method, + reason: new AiError.NetworkError({ + reason: "TransportError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "EncodeError": { + return Effect.fail(AiError.make({ + module: "AnthropicClient", + method, + reason: new AiError.NetworkError({ + reason: "EncodeError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "InvalidUrlError": { + return Effect.fail(AiError.make({ + module: "AnthropicClient", + method, + reason: new AiError.NetworkError({ + reason: "InvalidUrlError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "StatusCodeError": { + return mapStatusCodeError(reason, method) + } + case "DecodeError": { + return Effect.fail(AiError.make({ + module: "AnthropicClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Failed to decode response" + }) + })) + } + case "EmptyBodyError": { + return Effect.fail(AiError.make({ + module: "AnthropicClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Response body was empty" + }) + })) + } + } +}) + +/** @internal */ +const mapStatusCodeError = Effect.fnUntraced(function*( + error: HttpClientError.StatusCodeError, + method: string +) { + const { request, response, description } = error + const status = response.status + const headers = response.headers as Record + const requestId = headers["request-id"] + + let body: string | undefined = description + if (!description || !description.startsWith("{")) { + const responseBody = yield* Effect.option(response.text) + if (Option.isSome(responseBody) && responseBody.value) { + body = responseBody.value + } + } + + let json: unknown = undefined + // @effect-diagnostics effect/tryCatchInEffectGen:off + try { + json = Predicate.isNotUndefined(body) ? JSON.parse(body) : undefined + } catch { + json = undefined + } + const decoded = Schema.decodeUnknownOption(AnthropicErrorBody)(json) + + const reason = mapStatusCodeToReason({ + status, + headers, + message: Option.isSome(decoded) ? decoded.value.error.message : undefined, + http: buildHttpContext({ request, response, body }), + metadata: { + errorType: Option.isSome(decoded) ? decoded.value.error.type : null, + requestId: requestId ?? null + } + }) + + return yield* AiError.make({ module: "AnthropicClient", method, reason }) +}) + +// ============================================================================= +// Rate Limits +// ============================================================================= + +/** @internal */ +export const parseRateLimitHeaders = (headers: Record) => { + const retryAfterRaw = headers["retry-after"] + let retryAfter: Duration.Duration | undefined + if (Predicate.isNotUndefined(retryAfterRaw)) { + const parsed = Number.parse(retryAfterRaw) + if (Option.isSome(parsed)) { + retryAfter = Duration.seconds(parsed.value) + } + } + const requestsLimitRaw = headers["anthropic-ratelimit-requests-limit"] + const requestsRemainingRaw = headers["anthropic-ratelimit-requests-remaining"] + const tokensLimitRaw = headers["anthropic-ratelimit-tokens-limit"] + const tokensRemainingRaw = headers["anthropic-ratelimit-tokens-remaining"] + return { + retryAfter, + requestsLimit: Predicate.isNotUndefined(requestsLimitRaw) ? Option.getOrNull(Number.parse(requestsLimitRaw)) : null, + requestsRemaining: Predicate.isNotUndefined(requestsRemainingRaw) + ? Option.getOrNull(Number.parse(requestsRemainingRaw)) + : null, + requestsReset: headers["anthropic-ratelimit-requests-reset"] ?? null, + tokensLimit: Predicate.isNotUndefined(tokensLimitRaw) ? Option.getOrNull(Number.parse(tokensLimitRaw)) : null, + tokensRemaining: Predicate.isNotUndefined(tokensRemainingRaw) + ? Option.getOrNull(Number.parse(tokensRemainingRaw)) + : null, + tokensReset: headers["anthropic-ratelimit-tokens-reset"] ?? null + } +} + +// ============================================================================= +// HTTP Context +// ============================================================================= + +/** @internal */ +export const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +/** @internal */ +export const buildHttpContext = (params: { + readonly request: HttpClientRequest.HttpClientRequest + readonly response?: HttpClientResponse.HttpClientResponse + readonly body?: string | undefined +}): typeof AiError.HttpContext.Type => ({ + request: buildHttpRequestDetails(params.request), + response: Predicate.isNotUndefined(params.response) + ? { + status: params.response.status, + headers: Redactable.redact(params.response.headers) as Record + } + : undefined, + body: params.body +}) + +// ============================================================================= +// HTTP Status Code +// ============================================================================= + +/** @internal */ +export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { + readonly status: number + readonly headers: Record + readonly message: string | undefined + readonly metadata: AnthropicErrorMetadata + readonly http: typeof AiError.HttpContext.Type +}): AiError.AiErrorReason => { + const errorDescription = AiError.buildErrorDescription({ + status, + message, + method: http.request.method, + url: http.request.url, + errorType: metadata.errorType, + requestId: metadata.requestId, + body: http.body + }) + + switch (status) { + case 400: + return new AiError.InvalidRequestError({ + 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: errorDescription, + metadata: { anthropic: metadata }, + http + }) + case 422: + return new AiError.InvalidRequestError({ + description: errorDescription, + metadata: { anthropic: metadata }, + http + }) + case 429: { + const { retryAfter, ...rateLimitMetadata } = parseRateLimitHeaders(headers) + return new AiError.RateLimitError({ + retryAfter, + metadata: { + anthropic: { + ...metadata, + ...rateLimitMetadata + } + }, + http + }) + } + case 529: + return new AiError.InternalProviderError({ + description: message ?? "Anthropic API is overloaded", + metadata: { anthropic: metadata }, + http + }) + default: + if (status >= 500) { + return new AiError.InternalProviderError({ + description: message ?? "Server error", + metadata: { anthropic: metadata }, + http + }) + } + return new AiError.UnknownError({ + description: message, + metadata: { anthropic: metadata }, + http + }) + } +} diff --git a/.repos/effect/packages/ai/anthropic/src/internal/utilities.ts b/.repos/effect/packages/ai/anthropic/src/internal/utilities.ts new file mode 100644 index 000000000..322b035ac --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/src/internal/utilities.ts @@ -0,0 +1,26 @@ +import * as Predicate from "effect/Predicate" +import type * as Response from "effect/unstable/ai/Response" + +const finishReasonMap: Record = { + end_turn: "stop", + max_tokens: "length", + pause_turn: "pause", + refusal: "content-filter", + stop_sequence: "stop", + tool_use: "tool-calls" +} + +/** @internal */ +export const resolveFinishReason = ( + finishReason: string, + isJsonResponse: boolean = false +): Response.FinishReason => { + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined + if (Predicate.isUndefined(reason)) { + return "unknown" + } + if (isJsonResponse && reason === "tool-calls") { + return "stop" + } + return reason +} diff --git a/.repos/effect/packages/ai/anthropic/test/AnthropicClient.test.ts b/.repos/effect/packages/ai/anthropic/test/AnthropicClient.test.ts new file mode 100644 index 000000000..7eeb0ea36 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts b/.repos/effect/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts new file mode 100644 index 000000000..bd8b19023 --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts @@ -0,0 +1,835 @@ +import { AnthropicClient, AnthropicLanguageModel, AnthropicTool } from "@effect/ai-anthropic" +import { assert, describe, it } from "@effect/vitest" +import { Effect, Layer, Redacted, Schema, Stream } from "effect" +import { + 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", () => { + describe("streamText", () => { + it.effect("decodes tool call params in content_block_stop", () => + Effect.gen(function*() { + const toolParams = { pattern: "*.ts" } + + 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(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: 5 + } + }, + { + type: "message_stop" + } + ])) + ) + )) + ) + + const GlobTool = Tool.make("GlobTool", { + description: "Search for files", + parameters: Schema.Struct({ pattern: Schema.String }), + success: 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, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + const toolCall = parts.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + + assert.strictEqual(toolCall.name, "GlobTool") + assert.deepStrictEqual(toolCall.params, toolParams) + })) + + 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("encodes dynamic tools", () => + 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 + } + })) + }) + )) + ) + + 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(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const body = yield* getRequestBody(capturedRequest) + const dynamicTool = body.tools.find((tool: any) => tool.name === "DynamicTool") + + assert.isDefined(dynamicTool) + if (dynamicTool === undefined) { + return + } + + 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", () => { + 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( + 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: JSON.stringify({ name: "John", age: 30 }) }], + 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 + } + })) + }) + )) + ) + + // Assert the request shape; the response outcome is irrelevant here. + yield* LanguageModel.generateObject({ + prompt: "Give me a person", + schema: Schema.Struct({ name: Schema.String, age: Schema.Number }) + }).pipe( + Effect.provide(AnthropicLanguageModel.model(model, config)), + Effect.provide(layer), + Effect.ignore + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return yield* Effect.die(new Error("Expected a captured request")) + } + + 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") + })) + }) + + // 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) + }) + } + }) +}) + +const makeHttpClient = ( + handler: ( + request: HttpClientRequest.HttpClientRequest + ) => Effect.Effect +) => + HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + return yield* handler(request) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + +const sseResponse = ( + request: HttpClientRequest.HttpClientRequest, + events: ReadonlyArray +): HttpClientResponse.HttpClientResponse => + HttpClientResponse.fromWeb( + request, + new Response(toSseBody(events), { + status: 200, + headers: { + "content-type": "text/event-stream" + } + }) + ) + +const jsonResponse = ( + request: HttpClientRequest.HttpClientRequest, + body: unknown +): HttpClientResponse.HttpClientResponse => + HttpClientResponse.fromWeb( + request, + new Response(JSON.stringify(body), { + status: 200, + headers: { + "content-type": "application/json" + } + }) + ) + +const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => + Effect.gen(function*() { + const body = request.body + if (body._tag !== "Uint8Array") { + return yield* Effect.die(new Error("Expected Uint8Array body")) + } + return JSON.parse(new TextDecoder().decode(body.body)) + }) + +const toSseBody = (events: ReadonlyArray): string => + events.map((event) => `event: message_stream\ndata: ${JSON.stringify(event)}\n\n`).join("") diff --git a/.repos/effect/packages/ai/anthropic/tsconfig.json b/.repos/effect/packages/ai/anthropic/tsconfig.json new file mode 100644 index 000000000..e2a8ca19a --- /dev/null +++ b/.repos/effect/packages/ai/anthropic/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../../effect" } + ] +} diff --git a/.repos/effect/packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts b/.repos/effect/packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts new file mode 100644 index 000000000..79d86a5ed --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/openai-compat/CHANGELOG.md b/.repos/effect/packages/ai/openai-compat/CHANGELOG.md new file mode 100644 index 000000000..363f05ad6 --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/CHANGELOG.md @@ -0,0 +1,856 @@ +# @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 + +- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]: + - effect@4.0.0-beta.90 + +## 4.0.0-beta.89 + +### Patch Changes + +- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]: + - effect@4.0.0-beta.89 + +## 4.0.0-beta.88 + +### Patch Changes + +- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]: + - effect@4.0.0-beta.88 + +## 4.0.0-beta.87 + +### Patch Changes + +- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]: + - effect@4.0.0-beta.87 + +## 4.0.0-beta.86 + +### Patch Changes + +- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]: + - effect@4.0.0-beta.86 + +## 4.0.0-beta.85 + +### Patch Changes + +- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]: + - effect@4.0.0-beta.85 + +## 4.0.0-beta.84 + +### Patch Changes + +- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]: + - effect@4.0.0-beta.84 + +## 4.0.0-beta.83 + +### Patch Changes + +- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]: + - effect@4.0.0-beta.83 + +## 4.0.0-beta.82 + +### Patch Changes + +- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]: + - effect@4.0.0-beta.82 + +## 4.0.0-beta.81 + +### Patch Changes + +- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]: + - effect@4.0.0-beta.81 + +## 4.0.0-beta.80 + +### Patch Changes + +- [#2380](https://github.com/Effect-TS/effect-smol/pull/2380) [`b5f67a0`](https://github.com/Effect-TS/effect-smol/commit/b5f67a0ce9c110b98731ac485e5894df9da07e3a) Thanks @walln! - Surface reasoning tokens from OpenAI-compatible chat completion responses. + +- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]: + - effect@4.0.0-beta.80 + +## 4.0.0-beta.79 + +### Patch Changes + +- [#2338](https://github.com/Effect-TS/effect-smol/pull/2338) [`b12991f`](https://github.com/Effect-TS/effect-smol/commit/b12991f2346eda47fec72df5a89034394ad6c8fb) Thanks @boozedog! - Fix dropped streamed tool-call arguments when a provider sends `function.name: null` on continuation fragments. + + OpenAI-compatible providers such as Fireworks send the tool name only on the first streamed `tool_calls` fragment and `function.name: null` on the continuation fragments that carry the argument deltas. `ChatCompletionToolFunctionDelta.name` was `Schema.optionalKey(Schema.String)` (non-nullable), so chunk validation rejected every continuation and silently discarded its argument delta, leaving the assembled tool call with empty or partial params. `name` is now nullable (`Schema.NullOr(Schema.String)`). + +- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]: + - effect@4.0.0-beta.79 + +## 4.0.0-beta.78 + +### Patch Changes + +- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]: + - effect@4.0.0-beta.78 + +## 4.0.0-beta.77 + +### Patch Changes + +- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]: + - effect@4.0.0-beta.77 + +## 4.0.0-beta.76 + +### Patch Changes + +- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]: + - effect@4.0.0-beta.76 + +## 4.0.0-beta.75 + +### Patch Changes + +- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]: + - effect@4.0.0-beta.75 + +## 4.0.0-beta.74 + +### Patch Changes + +- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]: + - effect@4.0.0-beta.74 + +## 4.0.0-beta.73 + +### Patch Changes + +- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]: + - effect@4.0.0-beta.73 + +## 4.0.0-beta.72 + +### Patch Changes + +- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]: + - effect@4.0.0-beta.72 + +## 4.0.0-beta.71 + +### Patch Changes + +- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]: + - effect@4.0.0-beta.71 + +## 4.0.0-beta.70 + +### Patch Changes + +- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]: + - effect@4.0.0-beta.70 + +## 4.0.0-beta.69 + +### Patch Changes + +- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]: + - effect@4.0.0-beta.69 + +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + +## 4.0.0-beta.67 + +### Patch Changes + +- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]: + - effect@4.0.0-beta.67 + +## 4.0.0-beta.66 + +### Patch Changes + +- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]: + - effect@4.0.0-beta.66 + +## 4.0.0-beta.65 + +### Patch Changes + +- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]: + - effect@4.0.0-beta.65 + +## 4.0.0-beta.64 + +### Patch Changes + +- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]: + - effect@4.0.0-beta.64 + +## 4.0.0-beta.63 + +### Patch Changes + +- [#2133](https://github.com/Effect-TS/effect-smol/pull/2133) [`5be0aaa`](https://github.com/Effect-TS/effect-smol/commit/5be0aaad694c9eb943a710eb4f896bc4c3fcae99) Thanks @Zelys-DFKH! - Fix `OpenAiLanguageModel` leaking library-only config fields (`fileIdPrefixes`, `strictJsonSchema`) into request body, causing OpenAI 400 errors. + +- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]: + - effect@4.0.0-beta.63 + +## 4.0.0-beta.62 + +### Patch Changes + +- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]: + - effect@4.0.0-beta.62 + +## 4.0.0-beta.61 + +### Patch Changes + +- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]: + - effect@4.0.0-beta.61 + +## 4.0.0-beta.60 + +### Patch Changes + +- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]: + - effect@4.0.0-beta.60 + +## 4.0.0-beta.59 + +### Patch Changes + +- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]: + - effect@4.0.0-beta.59 + +## 4.0.0-beta.58 + +### Patch Changes + +- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]: + - effect@4.0.0-beta.58 + +## 4.0.0-beta.57 + +### Patch Changes + +- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]: + - effect@4.0.0-beta.57 + +## 4.0.0-beta.56 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]: + - effect@4.0.0-beta.55 + +## 4.0.0-beta.54 + +### Patch Changes + +- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]: + - effect@4.0.0-beta.54 + +## 4.0.0-beta.53 + +### Patch Changes + +- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]: + - effect@4.0.0-beta.53 + +## 4.0.0-beta.52 + +### Patch Changes + +- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]: + - effect@4.0.0-beta.52 + +## 4.0.0-beta.51 + +### Patch Changes + +- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]: + - effect@4.0.0-beta.51 + +## 4.0.0-beta.50 + +### Patch Changes + +- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]: + - effect@4.0.0-beta.50 + +## 4.0.0-beta.49 + +### Patch Changes + +- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]: + - effect@4.0.0-beta.49 + +## 4.0.0-beta.48 + +### Patch Changes + +- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]: + - effect@4.0.0-beta.48 + +## 4.0.0-beta.47 + +### Patch Changes + +- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]: + - effect@4.0.0-beta.47 + +## 4.0.0-beta.46 + +### Patch Changes + +- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]: + - effect@4.0.0-beta.46 + +## 4.0.0-beta.45 + +### Patch Changes + +- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]: + - effect@4.0.0-beta.45 + +## 4.0.0-beta.44 + +### Patch Changes + +- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename the `ServiceMap` module to `Context` across exports, docs, and tests. + +- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]: + - effect@4.0.0-beta.44 + +## 4.0.0-beta.43 + +### Patch Changes + +- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]: + - effect@4.0.0-beta.43 + +## 4.0.0-beta.42 + +### Patch Changes + +- [#1859](https://github.com/Effect-TS/effect-smol/pull/1859) [`a4809db`](https://github.com/Effect-TS/effect-smol/commit/a4809db80f65601d37ccafc13d773e55909c4e98) Thanks @lloydrichards! - add dynamic tooling for openai and openai-compact language models + +- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]: + - effect@4.0.0-beta.42 + +## 4.0.0-beta.41 + +### Patch Changes + +- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]: + - effect@4.0.0-beta.41 + +## 4.0.0-beta.40 + +### Patch Changes + +- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]: + - effect@4.0.0-beta.40 + +## 4.0.0-beta.39 + +### Patch Changes + +- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]: + - effect@4.0.0-beta.39 + +## 4.0.0-beta.38 + +### Patch Changes + +- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]: + - effect@4.0.0-beta.38 + +## 4.0.0-beta.37 + +### Patch Changes + +- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]: + - effect@4.0.0-beta.37 + +## 4.0.0-beta.36 + +### Patch Changes + +- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]: + - effect@4.0.0-beta.36 + +## 4.0.0-beta.35 + +### Patch Changes + +- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]: + - effect@4.0.0-beta.35 + +## 4.0.0-beta.34 + +### Patch Changes + +- [#1764](https://github.com/Effect-TS/effect-smol/pull/1764) [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f) Thanks @tim-smart! - Add unstable EmbeddingModel support across core and OpenAI providers. + - Add the unstable EmbeddingModel module API surface in `effect`, including service, request, response, and provider types. + - Implement the unstable EmbeddingModel runtime constructor in `effect`, with `RequestResolver` batching, `embed` / `embedMany` spans, provider error propagation, deterministic ordering, and empty-input `embedMany` fast-path behavior. + - Add and align EmbeddingModel behavior tests in `effect` for embedding usage, batching, ordering, and error handling. + - Add `OpenAiEmbeddingModel` in `@effect/ai-openai`, including model / make / layer constructors, config overrides, and provider output index validation with deterministic reordering. + - Add OpenAI-compatible EmbeddingModel provider support in `@effect/ai-openai-compat`, including config overrides, layer constructors, and output index validation. + +- [#1771](https://github.com/Effect-TS/effect-smol/pull/1771) [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e) Thanks @tim-smart! - Add `EmbeddingModel.ModelDimensions` and require dimensions in embedding provider `model` constructors. + +- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]: + - effect@4.0.0-beta.34 + +## 4.0.0-beta.33 + +### Patch Changes + +- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]: + - effect@4.0.0-beta.33 + +## 4.0.0-beta.32 + +### Patch Changes + +- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]: + - effect@4.0.0-beta.32 + +## 4.0.0-beta.31 + +### Patch Changes + +- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]: + - effect@4.0.0-beta.31 + +## 4.0.0-beta.30 + +### Patch Changes + +- [#1692](https://github.com/Effect-TS/effect-smol/pull/1692) [`00caf4f`](https://github.com/Effect-TS/effect-smol/commit/00caf4fbe209ff56426e9bcd51a205bdb84b978e) Thanks @tim-smart! - Preserve streamed OpenAI compat tool call ids and names across fragmented chat completion chunks. + +- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]: + - effect@4.0.0-beta.30 + +## 4.0.0-beta.29 + +### Patch Changes + +- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]: + - effect@4.0.0-beta.29 + +## 4.0.0-beta.28 + +### Patch Changes + +- [#1634](https://github.com/Effect-TS/effect-smol/pull/1634) [`621d3a1`](https://github.com/Effect-TS/effect-smol/commit/621d3a1248ef50e83c35d71333c69bf01bc33a11) Thanks @tim-smart! - Allow custom request properties in openai-compat model config and chat request types, and forward model-level custom properties to chat-completions payloads. + +- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]: + - effect@4.0.0-beta.28 + +## 4.0.0-beta.27 + +### Patch Changes + +- [#1623](https://github.com/Effect-TS/effect-smol/pull/1623) [`2cf00c1`](https://github.com/Effect-TS/effect-smol/commit/2cf00c184ccc994b7977f33996321923fa3534c4) Thanks @tim-smart! - Forward `OpenAiLanguageModel` `reasoning` config into chat-completions requests. + +- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]: + - effect@4.0.0-beta.27 + +## 4.0.0-beta.26 + +### Patch Changes + +- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]: + - effect@4.0.0-beta.26 + +## 4.0.0-beta.25 + +### Patch Changes + +- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]: + - effect@4.0.0-beta.25 + +## 4.0.0-beta.24 + +### Patch Changes + +- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]: + - effect@4.0.0-beta.24 + +## 4.0.0-beta.23 + +### Patch Changes + +- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]: + - effect@4.0.0-beta.23 + +## 4.0.0-beta.22 + +### Patch Changes + +- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]: + - effect@4.0.0-beta.22 + +## 4.0.0-beta.21 + +### Patch Changes + +- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]: + - effect@4.0.0-beta.21 + +## 4.0.0-beta.20 + +### Patch Changes + +- [#1529](https://github.com/Effect-TS/effect-smol/pull/1529) [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8) Thanks @tim-smart! - Add dedicated AiError metadata interfaces per reason so provider packages can safely augment metadata without conflicting module declarations. + +- [#1528](https://github.com/Effect-TS/effect-smol/pull/1528) [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34) Thanks @tim-smart! - Add `Model.ModelName` and provide it from AI model constructors. + +- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]: + - effect@4.0.0-beta.20 + +## 4.0.0-beta.19 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Patch Changes + +- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]: + - effect@4.0.0-beta.18 + +## 4.0.0-beta.17 + +### Patch Changes + +- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]: + - effect@4.0.0-beta.17 + +## 4.0.0-beta.16 + +### Patch Changes + +- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]: + - effect@4.0.0-beta.16 + +## 4.0.0-beta.15 + +### Patch Changes + +- [#1502](https://github.com/Effect-TS/effect-smol/pull/1502) [`285b7e6`](https://github.com/Effect-TS/effect-smol/commit/285b7e667167566d5788367d5155b19c79f1bf22) Thanks @tim-smart! - allow undefined for ai config + +- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]: + - effect@4.0.0-beta.15 + +## 4.0.0-beta.14 + +### Patch Changes + +- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]: + - effect@4.0.0-beta.14 + +## 4.0.0-beta.13 + +### Patch Changes + +- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]: + - effect@4.0.0-beta.13 + +## 4.0.0-beta.12 + +### Patch Changes + +- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]: + - effect@4.0.0-beta.12 + +## 4.0.0-beta.11 + +### Patch Changes + +- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]: + - effect@4.0.0-beta.11 + +## 4.0.0-beta.10 + +### Patch Changes + +- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]: + - effect@4.0.0-beta.10 + +## 4.0.0-beta.9 + +### Patch Changes + +- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]: + - effect@4.0.0-beta.9 + +## 4.0.0-beta.8 + +### Patch Changes + +- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]: + - effect@4.0.0-beta.8 + +## 4.0.0-beta.7 + +### Patch Changes + +- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]: + - effect@4.0.0-beta.7 + +## 4.0.0-beta.6 + +### Patch Changes + +- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]: + - effect@4.0.0-beta.6 + +## 4.0.0-beta.5 + +### Patch Changes + +- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]: + - effect@4.0.0-beta.5 + +## 4.0.0-beta.4 + +### Patch Changes + +- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]: + - effect@4.0.0-beta.4 + +## 4.0.0-beta.3 + +### Patch Changes + +- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]: + - effect@4.0.0-beta.3 + +## 4.0.0-beta.2 + +### Patch Changes + +- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]: + - effect@4.0.0-beta.2 + +## 4.0.0-beta.1 + +### Patch Changes + +- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]: + - effect@4.0.0-beta.1 + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta + +### Patch Changes + +- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]: + - effect@4.0.0-beta.0 diff --git a/.repos/effect/packages/ai/openai-compat/README.md b/.repos/effect/packages/ai/openai-compat/README.md new file mode 100644 index 000000000..68bcbb776 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/openai-compat/package.json b/.repos/effect/packages/ai/openai-compat/package.json new file mode 100644 index 000000000..e68e72f5c --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/package.json @@ -0,0 +1,71 @@ +{ + "name": "@effect/ai-openai-compat", + "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.git", + "directory": "packages/ai/openai-compat" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "ai", + "openai", + "compat" + ], + "keywords": [ + "typescript", + "ai", + "openai", + "compat" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./internal/*": null, + "./index": null, + "./*/index": null + } + }, + "scripts": { + "codegen": "effect-utils codegen", + "build": "tsc -b tsconfig.json && pnpm babel", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "devDependencies": { + "effect": "workspace:^" + }, + "peerDependencies": { + "effect": "workspace:^" + } +} diff --git a/.repos/effect/packages/ai/openai-compat/src/OpenAiClient.ts b/.repos/effect/packages/ai/openai-compat/src/OpenAiClient.ts new file mode 100644 index 000000000..c8ecad004 --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/OpenAiClient.ts @@ -0,0 +1,1250 @@ +/** + * The `OpenAiClient` module provides an Effect service for OpenAI-compatible + * chat completions and embeddings APIs. It builds on the Effect HTTP client, + * adds authentication and OpenAI organization or project headers, and exposes + * typed helpers for non-streaming chat completions, streaming chat completions, + * and embedding requests. + * + * @since 4.0.0 + */ +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 } from "effect/Function" +import * as Layer from "effect/Layer" +import * as Redacted from "effect/Redacted" +import * as Schema from "effect/Schema" +import * as Stream from "effect/Stream" +import type * as AiError from "effect/unstable/ai/AiError" +import * as Sse from "effect/unstable/encoding/Sse" +import * as Headers from "effect/unstable/http/Headers" +import * as HttpClient from "effect/unstable/http/HttpClient" +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import * as Errors from "./internal/errors.ts" +import { OpenAiConfig } from "./OpenAiConfig.ts" + +/** + * Effect service interface for OpenAI-compatible chat completions and embeddings. + * + * **Details** + * + * Exposes the configured HTTP client plus helpers for non-streaming chat + * completions, streaming chat completions, and embeddings. Transport and + * schema decoding failures are mapped to `AiError`. + * + * @category services + * @since 4.0.0 + */ +export interface Service { + readonly client: HttpClient.HttpClient + readonly createResponse: ( + options: CreateResponseRequestJson + ) => Effect.Effect< + [body: CreateResponse200, response: HttpClientResponse.HttpClientResponse], + AiError.AiError + > + readonly createResponseStream: ( + options: Omit + ) => Effect.Effect< + [ + response: HttpClientResponse.HttpClientResponse, + stream: Stream.Stream + ], + AiError.AiError + > + readonly createEmbedding: ( + options: CreateEmbeddingRequestJson + ) => Effect.Effect +} + +/** + * Service tag for the OpenAI-compatible chat completions and embeddings client. + * + * **When to use** + * + * Use when building effects that depend on the low-level OpenAI-compatible + * client through context rather than receiving the client as a value. + * + * **Details** + * + * The tagged service is the `Service` interface produced by `make` and provided + * by `layer` or `layerConfig`. + * + * @see {@link Service} for the operations provided by the service + * @see {@link make} for constructing the service from explicit options + * @see {@link layer} for providing the service from explicit options + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category services + * @since 4.0.0 + */ +export class OpenAiClient extends Context.Service()( + "@effect/ai-openai-compat/OpenAiClient" +) {} + +/** + * Configuration options used to construct an OpenAI-compatible client. + * + * @category options + * @since 4.0.0 + */ +export type Options = { + readonly apiKey?: Redacted.Redacted | undefined + readonly apiUrl?: string | undefined + readonly organizationId?: Redacted.Redacted | undefined + readonly projectId?: Redacted.Redacted | undefined + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +} + +const RedactedOpenAiHeaders = { + 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. + * + * **When to use** + * + * Use when you need the OpenAI-compatible client service value inside an effect. + * + * **Details** + * + * The returned service uses the current `HttpClient`, prepends `apiUrl` or + * `https://api.openai.com/v1`, adds authentication and OpenAI + * organization/project headers, accepts JSON responses, and applies + * `transformClient` when provided. + * + * **Gotchas** + * + * A scoped `OpenAiConfig.withClientTransform` is applied when request helpers + * run, after the `transformClient` option supplied to `make`. + * + * @see {@link layer} for providing this client from explicit options + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced( + function*(options: Options): Effect.fn.Return { + const baseClient = yield* HttpClient.HttpClient + + const httpClient = baseClient.pipe( + HttpClient.mapRequest((request) => + request.pipe( + HttpClientRequest.prependUrl(options.apiUrl ?? "https://api.openai.com/v1"), + options.apiKey !== undefined + ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey)) + : identity, + options.organizationId !== undefined + ? HttpClientRequest.setHeader( + RedactedOpenAiHeaders.OpenAiOrganization, + Redacted.value(options.organizationId) + ) + : identity, + options.projectId !== undefined + ? HttpClientRequest.setHeader( + RedactedOpenAiHeaders.OpenAiProject, + Redacted.value(options.projectId) + ) + : identity, + HttpClientRequest.acceptJson + ) + ), + options.transformClient !== undefined + ? options.transformClient + : identity + ) + + const resolveHttpClient = Effect.map( + OpenAiConfig.getOrUndefined, + (config) => + config?.transformClient !== undefined + ? config.transformClient(httpClient) + : httpClient + ) + + const decodeResponse = HttpClientResponse.schemaBodyJson(ChatCompletionResponse) + + const createResponse = ( + payload: CreateResponseRequestJson + ): Effect.Effect< + [body: CreateResponse200, response: HttpClientResponse.HttpClientResponse], + AiError.AiError + > => + 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 + ): [ + HttpClientResponse.HttpClientResponse, + Stream.Stream + ] => { + const stream = response.stream.pipe( + Stream.decodeText(), + Stream.pipeThroughChannel(Sse.decode()), + Stream.flatMap((event) => { + const data = decodeChatCompletionSseData(event.data) + return Stream.fromIterable(data !== undefined ? [data] : []) + }), + 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 + return [response, stream] + } + + const createResponseStream: Service["createResponseStream"] = (payload) => + 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 => + 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, + createResponse, + createResponseStream, + createEmbedding + }) + }, + withRedactedHeaders +) + +/** + * Creates a layer that provides an OpenAI-compatible client from explicit options. + * + * **When to use** + * + * Use to install `OpenAiClient` in an application layer when the client options + * are already available as values rather than loaded from `Config`. + * + * @see {@link make} for constructing the client service effectfully + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: Options): Layer.Layer => + Layer.effect(OpenAiClient, make(options)) + +/** + * Creates a layer that loads OpenAI-compatible client settings from `Config` + * values before constructing the service. + * + * **When to use** + * + * Use when you need client settings for OpenAI-compatible APIs to be read from + * Effect `Config` values while providing `OpenAiClient` as a layer. + * + * **Details** + * + * Only config values supplied in `options` are loaded. Omitted fields are + * passed to `make` as `undefined`, and `transformClient` is forwarded as a + * plain option. + * + * @see {@link make} for constructing the client service effectfully + * @see {@link layer} for providing the client from already-resolved options + * + * @category layers + * @since 4.0.0 + */ +export const layerConfig = (options?: { + readonly apiKey?: Config.Config | undefined> | undefined + readonly apiUrl?: Config.Config | undefined + readonly organizationId?: Config.Config | undefined> | undefined + readonly projectId?: Config.Config | undefined> | undefined + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +}): Layer.Layer => + Layer.effect( + OpenAiClient, + Effect.gen(function*() { + const apiKey = options?.apiKey !== undefined + ? yield* options.apiKey : + undefined + const apiUrl = options?.apiUrl !== undefined + ? yield* options.apiUrl : + undefined + const organizationId = options?.organizationId !== undefined + ? yield* options.organizationId + : undefined + const projectId = options?.projectId !== undefined + ? yield* options.projectId : + undefined + return yield* make({ + apiKey, + apiUrl, + organizationId, + projectId, + transformClient: options?.transformClient + }) + }) + ) + +type JsonObject = { readonly [x: string]: Schema.Json } + +/** + * Optional response fields that can be requested with the `include` parameter. + * + * @category models + * @since 4.0.0 + */ +export type IncludeEnum = + | "message.input_image.image_url" + | "reasoning.encrypted_content" + | "message.output_text.logprobs" + +/** + * Lifecycle status shared by message, reasoning, and tool-call items. + * + * @category models + * @since 4.0.0 + */ +export type MessageStatus = "in_progress" | "completed" | "incomplete" + +type InputTextContent = { + readonly type: "input_text" + readonly text: string +} + +type InputImageContent = { + readonly type: "input_image" + readonly image_url?: string | null | undefined + readonly file_id?: string | null | undefined + readonly detail?: "low" | "high" | "auto" | null | undefined +} + +type InputFileContent = { + readonly type: "input_file" + readonly file_id?: string | null | undefined + readonly filename?: string | undefined + readonly file_url?: string | undefined + readonly file_data?: string | undefined +} + +/** + * Content blocks accepted in input messages. + * + * @category models + * @since 4.0.0 + */ +export type InputContent = InputTextContent | InputImageContent | InputFileContent + +/** + * Text content block used for model-provided reasoning summaries. + * + * @category models + * @since 4.0.0 + */ +export type SummaryTextContent = { + readonly type: "summary_text" + readonly text: string +} + +type ReasoningTextContent = { + readonly type: "reasoning_text" + readonly text: string +} + +type RefusalContent = { + readonly type: "refusal" + readonly refusal: string +} + +type TextContent = { + readonly type: "text" + readonly text: string +} + +type ComputerScreenshotContent = { + readonly type: "computer_screenshot" + readonly image_url: string | null + readonly file_id: string | null +} + +type FileCitationAnnotation = { + readonly type: "file_citation" + readonly file_id: string + readonly index: number + readonly filename: string +} + +type UrlCitationAnnotation = { + readonly type: "url_citation" + readonly url: string + readonly start_index: number + readonly end_index: number + readonly title: string +} + +type ContainerFileCitationAnnotation = { + readonly type: "container_file_citation" + readonly container_id: string + readonly file_id: string + readonly start_index: number + readonly end_index: number + readonly filename: string +} + +type FilePathAnnotation = { + readonly type: "file_path" + readonly file_id: string + readonly index: number +} + +/** + * Citation and file-path annotations attached to output text content. + * + * @category models + * @since 4.0.0 + */ +export type Annotation = + | FileCitationAnnotation + | UrlCitationAnnotation + | ContainerFileCitationAnnotation + | FilePathAnnotation + +type OutputTextContent = { + readonly type: "output_text" + readonly text: string + readonly annotations?: ReadonlyArray | undefined + readonly logprobs?: ReadonlyArray | undefined +} + +type OutputMessageContent = + | InputTextContent + | OutputTextContent + | TextContent + | SummaryTextContent + | ReasoningTextContent + | RefusalContent + | InputImageContent + | ComputerScreenshotContent + | InputFileContent + +type OutputMessage = { + readonly id: string + readonly type: "message" + readonly role: "assistant" + readonly content: ReadonlyArray + readonly status: MessageStatus +} + +/** + * Reasoning output item containing encrypted reasoning content, summaries, and + * optional reasoning text. + * + * @category models + * @since 4.0.0 + */ +export type ReasoningItem = { + readonly type: "reasoning" + readonly id: string + readonly encrypted_content?: string | null | undefined + readonly summary: ReadonlyArray + readonly content?: ReadonlyArray | undefined + readonly status?: MessageStatus | undefined +} + +type FunctionCall = { + readonly id?: string | undefined + readonly type: "function_call" + readonly call_id: string + readonly name: string + readonly arguments: string + readonly status?: MessageStatus | undefined +} + +type FunctionCallOutput = { + readonly id?: string | null | undefined + readonly call_id: string + readonly type: "function_call_output" + readonly output: string | ReadonlyArray + readonly status?: MessageStatus | null | undefined +} + +type CustomToolCall = { + readonly type: "custom_tool_call" + readonly id?: string | undefined + readonly call_id: string + readonly name: string + readonly input: string +} + +type CustomToolCallOutput = { + readonly type: "custom_tool_call_output" + readonly id?: string | undefined + readonly call_id: string + readonly output: string | ReadonlyArray +} + +type ItemReference = { + readonly type?: "item_reference" | null | undefined + readonly id: string +} + +/** + * Item shapes accepted by a Responses-style `input` field. + * + * **Details** + * + * Supports input messages, output messages, tool calls, tool outputs, reasoning + * items, custom tool interactions, and item references. + * + * @category models + * @since 4.0.0 + */ +export type InputItem = + | { + readonly role: "user" | "assistant" | "system" | "developer" + readonly content: string | ReadonlyArray + readonly type?: "message" | undefined + } + | { + readonly type?: "message" | undefined + readonly role: "user" | "system" | "developer" + readonly status?: MessageStatus | undefined + readonly content: ReadonlyArray + } + | OutputMessage + | FunctionCall + | FunctionCallOutput + | ReasoningItem + | CustomToolCallOutput + | CustomToolCall + | ItemReference + +type FunctionTool = { + readonly type: "function" + readonly name: string + readonly description?: string | null | undefined + readonly parameters?: JsonObject | null | undefined + readonly strict?: boolean | null | undefined +} + +type CustomToolParam = { + readonly type: "custom" + readonly name: string + readonly description?: string | undefined + readonly format?: unknown +} + +/** + * Tool definitions that can be supplied to a Responses-style request. + * + * @category models + * @since 4.0.0 + */ +export type Tool = + | FunctionTool + | CustomToolParam + +type ToolChoice = + | "none" + | "auto" + | "required" + | { + readonly type: "allowed_tools" + readonly mode: "auto" | "required" + readonly tools: ReadonlyArray + } + | { + readonly type: "function" + readonly name: string + } + | { + readonly type: "custom" + readonly name: string + } + +/** + * Text output format configuration for plain text, JSON object, or JSON Schema + * responses. + * + * @category configuration + * @since 4.0.0 + */ +export type TextResponseFormatConfiguration = + | { + readonly type: "text" + } + | { + readonly type: "json_schema" + readonly description?: string | undefined + readonly name: string + readonly schema: JsonObject + readonly strict?: boolean | null | undefined + } + | { + readonly type: "json_object" + } + +/** + * Request options for creating a Responses-style response with an + * OpenAI-compatible provider. + * + * @category models + * @since 4.0.0 + */ +export type CreateResponse = { + readonly metadata?: Readonly> | null | undefined + readonly top_logprobs?: number | undefined + readonly temperature?: number | null | undefined + readonly top_p?: number | null | undefined + readonly user?: string | null | undefined + readonly safety_identifier?: string | null | undefined + readonly prompt_cache_key?: string | null | undefined + readonly service_tier?: string | undefined + readonly prompt_cache_retention?: "in-memory" | "24h" | null | undefined + readonly previous_response_id?: string | null | undefined + readonly model?: string | undefined + readonly reasoning?: unknown + readonly background?: boolean | null | undefined + readonly max_output_tokens?: number | null | undefined + readonly max_tool_calls?: number | null | undefined + readonly text?: { + readonly format?: TextResponseFormatConfiguration | undefined + readonly verbosity?: "low" | "medium" | "high" | null | undefined + } | undefined + readonly tools?: ReadonlyArray | undefined + readonly tool_choice?: ToolChoice | undefined + readonly truncation?: "auto" | "disabled" | null | undefined + readonly input?: string | ReadonlyArray | undefined + readonly include?: ReadonlyArray | null | undefined + readonly parallel_tool_calls?: boolean | null | undefined + readonly store?: boolean | null | undefined + readonly instructions?: string | null | undefined + readonly stream?: boolean | null | undefined + readonly conversation?: string | null | undefined + readonly modalities?: ReadonlyArray<"text" | "audio"> | undefined + readonly seed?: number | undefined +} + +/** + * Token accounting reported on Responses-style response objects. + * + * @category models + * @since 4.0.0 + */ +export type ResponseUsage = { + readonly input_tokens: number + readonly output_tokens: number + readonly total_tokens: number + readonly input_tokens_details?: unknown + readonly output_tokens_details?: unknown +} + +type OutputItem = + | OutputMessage + | FunctionCall + | ReasoningItem + | CustomToolCall + +/** + * Responses-style response object returned by compatible providers or embedded + * in response stream lifecycle events. + * + * @category models + * @since 4.0.0 + */ +export type Response = { + readonly id: string + readonly object?: "response" | undefined + readonly model: string + readonly status?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" | undefined + readonly created_at: number + readonly output: ReadonlyArray + readonly usage?: ResponseUsage | null | undefined + readonly incomplete_details?: + | { + readonly reason?: "max_output_tokens" | "content_filter" | undefined + } + | null + | undefined + readonly service_tier?: string | undefined +} + +type ResponseCreatedEvent = { + readonly type: "response.created" + readonly response: Response + readonly sequence_number: number +} + +type ResponseCompletedEvent = { + readonly type: "response.completed" + readonly response: Response + readonly sequence_number: number +} + +type ResponseIncompleteEvent = { + readonly type: "response.incomplete" + readonly response: Response + readonly sequence_number: number +} + +type ResponseFailedEvent = { + readonly type: "response.failed" + readonly response: Response + readonly sequence_number: number +} + +type ResponseOutputItemAddedEvent = { + readonly type: "response.output_item.added" + readonly output_index: number + readonly sequence_number: number + readonly item: OutputItem +} + +type ResponseOutputItemDoneEvent = { + readonly type: "response.output_item.done" + readonly output_index: number + readonly sequence_number: number + readonly item: OutputItem +} + +type ResponseTextDeltaEvent = { + readonly type: "response.output_text.delta" + readonly item_id: string + readonly output_index: number + readonly content_index: number + readonly delta: string + readonly sequence_number: number + readonly logprobs?: ReadonlyArray | undefined +} + +type ResponseOutputTextAnnotationAddedEvent = { + readonly type: "response.output_text.annotation.added" + readonly item_id: string + readonly output_index: number + readonly content_index: number + readonly annotation_index: number + readonly sequence_number: number + readonly annotation: Annotation +} + +type ResponseFunctionCallArgumentsDeltaEvent = { + readonly type: "response.function_call_arguments.delta" + readonly item_id: string + readonly output_index: number + readonly sequence_number: number + readonly delta: string +} + +type ResponseReasoningSummaryPartAddedEvent = { + readonly type: "response.reasoning_summary_part.added" + readonly item_id: string + readonly output_index: number + readonly summary_index: number + readonly sequence_number: number + readonly part: SummaryTextContent +} + +type ResponseReasoningSummaryPartDoneEvent = { + readonly type: "response.reasoning_summary_part.done" + readonly item_id: string + readonly output_index: number + readonly summary_index: number + readonly sequence_number: number + readonly part: SummaryTextContent +} + +type ResponseReasoningSummaryTextDeltaEvent = { + readonly type: "response.reasoning_summary_text.delta" + readonly item_id: string + readonly output_index: number + readonly summary_index: number + readonly delta: string + readonly sequence_number: number +} + +type ResponseErrorEvent = { + readonly type: "error" + readonly code: string | null + readonly message: string + readonly param: string | null + readonly sequence_number: number +} + +type UnknownResponseStreamEvent = { + readonly type: string + readonly [key: string]: unknown +} + +/** + * Server-sent event shapes emitted by Responses-style response streams. + * + * @category streaming + * @since 4.0.0 + */ +export type ResponseStreamEvent = + | ResponseCreatedEvent + | ResponseCompletedEvent + | ResponseIncompleteEvent + | ResponseFailedEvent + | ResponseOutputItemAddedEvent + | ResponseOutputItemDoneEvent + | ResponseTextDeltaEvent + | ResponseOutputTextAnnotationAddedEvent + | ResponseFunctionCallArgumentsDeltaEvent + | ResponseReasoningSummaryPartAddedEvent + | ResponseReasoningSummaryPartDoneEvent + | ResponseReasoningSummaryTextDeltaEvent + | ResponseErrorEvent + | UnknownResponseStreamEvent + +/** + * Represents one embedding item returned by an OpenAI-compatible embeddings API. + * + * **Details** + * + * The embedding can be returned either as a numeric vector or as a base64-encoded + * string. The `index` field identifies the input item that produced this + * embedding. + * + * @category models + * @since 4.0.0 + */ +export type Embedding = { + readonly embedding: ReadonlyArray | string + readonly index: number + readonly object?: string | undefined +} + +/** + * Request payload for the embeddings endpoint. + * + * @category models + * @since 4.0.0 + */ +export type CreateEmbeddingRequest = { + readonly input: string | ReadonlyArray | ReadonlyArray | ReadonlyArray> + readonly model: string + readonly encoding_format?: "float" | "base64" | undefined + readonly dimensions?: number | undefined + readonly user?: string | undefined +} + +/** + * Successful response payload returned by the embeddings endpoint. + * + * @category models + * @since 4.0.0 + */ +export type CreateEmbeddingResponse = { + readonly data: ReadonlyArray + readonly model: string + readonly object?: "list" | undefined + readonly usage?: { + readonly prompt_tokens: number + readonly total_tokens: number + } | undefined +} + +/** + * JSON request body accepted by the embeddings endpoint. + * + * @category models + * @since 4.0.0 + */ +export type CreateEmbeddingRequestJson = CreateEmbeddingRequest +/** + * Decoded successful embeddings response body. + * + * @category models + * @since 4.0.0 + */ +export type CreateEmbedding200 = CreateEmbeddingResponse +/** + * Structured content parts accepted in chat completion messages. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionContentPart = + | { + readonly type: "text" + readonly text: string + } + | { + readonly type: "image_url" + readonly image_url: { + readonly url: string + readonly detail?: "low" | "high" | "auto" | undefined + } + } +/** + * Tool call data attached to an assistant chat completion message. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionRequestToolCall = { + readonly id: string + readonly type: "function" + readonly function: { + readonly name: string + readonly arguments: string + } +} +/** + * Message shapes accepted by the chat completions endpoint. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionRequestMessage = + | { + readonly role: "system" | "developer" | "user" | "assistant" + readonly content: string | ReadonlyArray | null + readonly tool_calls?: ReadonlyArray | undefined + } + | { + readonly role: "tool" + readonly tool_call_id: string + readonly content: string + } +/** + * Function tool definition accepted by the chat completions endpoint. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionTool = { + readonly type: "function" + readonly function: { + readonly name: string + readonly description?: string | null | undefined + readonly parameters?: JsonObject | undefined + readonly strict?: boolean | undefined + } +} +/** + * Controls whether the model may call tools and can force a specific function. + * + * @category configuration + * @since 4.0.0 + */ +export type ChatCompletionToolChoice = + | "none" + | "auto" + | "required" + | { + readonly type: "function" + readonly function: { + readonly name: string + } + } +/** + * JSON response format configuration for chat completion requests. + * + * @category configuration + * @since 4.0.0 + */ +export type ChatCompletionResponseFormat = + | { + readonly type: "json_object" + } + | { + readonly type: "json_schema" + readonly json_schema: { + readonly name: string + readonly schema: JsonObject + readonly description?: string | undefined + readonly strict?: boolean | undefined + } + } +/** + * Request payload for the OpenAI-compatible chat completions endpoint. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionRequest = { + readonly model: string + readonly messages: ReadonlyArray + readonly temperature?: number | null | undefined + readonly top_p?: number | null | undefined + readonly max_tokens?: number | null | undefined + readonly user?: string | null | undefined + readonly seed?: number | undefined + readonly parallel_tool_calls?: boolean | null | undefined + readonly response_format?: ChatCompletionResponseFormat | undefined + readonly tools?: ReadonlyArray | undefined + readonly tool_choice?: ChatCompletionToolChoice | undefined + readonly service_tier?: string | undefined + readonly reasoning?: unknown + readonly stream?: boolean | undefined + readonly stream_options?: { + readonly include_usage?: boolean | undefined + } | undefined + readonly [x: string]: unknown +} +/** + * JSON request body used by this client when creating a chat completion response. + * + * @category models + * @since 4.0.0 + */ +export type CreateResponseRequestJson = ChatCompletionRequest +/** + * Decoded successful chat completion response body returned by `createResponse`. + * + * @category models + * @since 4.0.0 + */ +export type CreateResponse200 = ChatCompletionResponse +/** + * Decoded server-sent event payload emitted by `createResponseStream`. + * + * @category streaming + * @since 4.0.0 + */ +export type CreateResponse200Sse = ChatCompletionStreamEvent + +const EmbeddingSchema = Schema.Struct({ + embedding: Schema.Union([Schema.Array(Schema.Finite), Schema.String]), + index: Schema.Int, + object: Schema.optionalKey(Schema.String) +}) + +const CreateEmbeddingResponseSchema = Schema.Struct({ + data: Schema.Array(EmbeddingSchema), + model: Schema.String, + object: Schema.optionalKey(Schema.Literal("list")), + usage: Schema.optionalKey(Schema.Struct({ + prompt_tokens: Schema.Int, + total_tokens: Schema.Int + })) +}) + +const ChatCompletionToolFunction = Schema.Struct({ + name: Schema.String, + arguments: Schema.optionalKey(Schema.String) +}) + +const ChatCompletionToolFunctionDelta = Schema.Struct({ + // Some OpenAI-compatible providers (e.g. Fireworks) send `name: null` on + // streamed tool-call continuation fragments. `name` must be nullable, else + // the whole chunk fails validation and its argument delta is dropped. + name: Schema.optionalKey(Schema.NullOr(Schema.String)), + arguments: Schema.optionalKey(Schema.String) +}) + +const ChatCompletionToolCall = Schema.Struct({ + id: Schema.optionalKey(Schema.String), + 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.Int), + type: Schema.optionalKey(Schema.String), + function: Schema.optionalKey(ChatCompletionToolFunctionDelta) +}) + +const ChatCompletionMessage = Schema.Struct({ + role: Schema.optionalKey(Schema.String), + 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(ChatCompletionToolCall)) +}) + +const ChatCompletionDelta = Schema.Struct({ + role: Schema.optionalKey(Schema.String), + content: Schema.optionalKey(Schema.NullOr(Schema.String)), + reasoning: Schema.optionalKey(Schema.NullOr(Schema.String)), + reasoning_content: Schema.optionalKey(Schema.NullOr(Schema.String)), + // 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.Int, + finish_reason: Schema.optionalKey(Schema.NullOr(Schema.String)), + message: Schema.optionalKey(ChatCompletionMessage), + delta: Schema.optionalKey(ChatCompletionDelta) +}) + +const ChatCompletionUsage = Schema.Struct({ + 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) +}) + +const ChatCompletionResponse = Schema.Struct({ + id: Schema.String, + model: Schema.String, + created: Schema.Int, + choices: Schema.Array(ChatCompletionChoice), + usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), + service_tier: Schema.optionalKey(Schema.String) +}) + +const ChatCompletionChunk = Schema.Struct({ + id: Schema.String, + model: Schema.String, + created: Schema.Int, + choices: Schema.Array(ChatCompletionChoice), + usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), + service_tier: Schema.optionalKey(Schema.String) +}) + +/** + * Decoded tool-call object from a chat completion response or streaming chunk. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionToolCall = typeof ChatCompletionToolCall.Type +/** + * Decoded message object from a non-streaming chat completion choice. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionMessage = typeof ChatCompletionMessage.Type +/** + * Decoded choice object returned by chat completion responses and chunks. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionChoice = typeof ChatCompletionChoice.Type +/** + * Decoded token usage summary returned by chat completions. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionUsage = typeof ChatCompletionUsage.Type +/** + * Decoded successful response from the chat completions endpoint. + * + * @category models + * @since 4.0.0 + */ +export type ChatCompletionResponse = typeof ChatCompletionResponse.Type +/** + * Decoded streaming chunk emitted by the chat completions endpoint. + * + * @category streaming + * @since 4.0.0 + */ +export type ChatCompletionChunk = typeof ChatCompletionChunk.Type +/** + * A parsed chat completion event that does not match the expected chunk schema. + * + * @category streaming + * @since 4.0.0 + */ +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 { + return JSON.parse(value) + } catch { + return undefined + } +} + +const isChatCompletionChunk = Schema.is(ChatCompletionChunk) + +const decodeChatCompletionSseData = ( + data: string +): ChatCompletionStreamEvent | undefined => { + if (data === "[DONE]") { + return data + } + const parsed = parseJson(data) + if (parsed === undefined) { + return undefined + } + return isChatCompletionChunk(parsed) ? parsed : { + _tag: "UnknownChatCompletionEvent", + data: parsed + } +} diff --git a/.repos/effect/packages/ai/openai-compat/src/OpenAiConfig.ts b/.repos/effect/packages/ai/openai-compat/src/OpenAiConfig.ts new file mode 100644 index 000000000..004b155da --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/OpenAiConfig.ts @@ -0,0 +1,87 @@ +/** + * The `OpenAiConfig` module lets a workflow temporarily customize the HTTP + * client used by OpenAI-compatible request helpers. Model, embedding, and + * tool-calling code can use this scoped configuration to add middleware, + * instrumentation, or routing without rebuilding the client layer. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import type { HttpClient } from "effect/unstable/http/HttpClient" + +/** + * Context service for OpenAI-compatible client configuration in the current + * Effect scope. + * + * **When to use** + * + * Use as the context service for scoped OpenAI-compatible client configuration + * and HTTP client transforms. + * + * @see {@link withClientTransform} for scoping an HTTP client transformation + * + * @category services + * @since 4.0.0 + */ +export class OpenAiConfig extends Context.Service< + OpenAiConfig, + OpenAiConfig.Service +>()("@effect/ai-openai-compat/OpenAiConfig") { + /** + * Gets the configured OpenAI-compatible service from the current context when present. + * + * @since 4.0.0 + */ + static readonly getOrUndefined: Effect.Effect = Effect.map( + Effect.context(), + Context.getOrUndefined(OpenAiConfig) + ) +} + +/** + * Types associated with the `OpenAiConfig` context service. + * + * @since 4.0.0 + */ +export declare namespace OpenAiConfig { + /** + * Configuration consumed by OpenAI-compatible clients when they build or + * resolve the underlying HTTP client. + * + * @category services + * @since 4.0.0 + */ + export interface Service { + readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined + } +} + +/** + * Provides an HTTP client transform for the supplied effect. + * + * **When to use** + * + * Use to add provider-specific OpenAI-compatible HTTP behavior, such as + * headers, retries, instrumentation, or proxy routing. + * + * **Details** + * + * OpenAI-compatible provider services read the transform from the + * `OpenAiConfig` context. + * + * @category configuration + * @since 4.0.0 + */ +export const withClientTransform: { + (transform: (client: HttpClient) => HttpClient): (self: Effect.Effect) => Effect.Effect + (self: Effect.Effect, transform: (client: HttpClient) => HttpClient): Effect.Effect +} = dual(2, ( + self: Effect.Effect, + transformClient: (client: HttpClient) => HttpClient +) => + Effect.flatMap( + OpenAiConfig.getOrUndefined, + (config) => Effect.provideService(self, OpenAiConfig, { ...config, transformClient }) + )) diff --git a/.repos/effect/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts b/.repos/effect/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts new file mode 100644 index 000000000..0cd5393aa --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts @@ -0,0 +1,243 @@ +/** + * The `OpenAiEmbeddingModel` module adapts OpenAI-compatible embeddings + * endpoints to Effect's embedding model service. It sends embedding requests + * through {@link OpenAiClient}, exposes constructors for layers and `AiModel` + * values, supports scoped request configuration overrides, and checks that the + * provider returns one numeric vector for each requested input. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import * as Layer from "effect/Layer" +import type { Simplify } from "effect/Types" +import * as AiError from "effect/unstable/ai/AiError" +import * as EmbeddingModel from "effect/unstable/ai/EmbeddingModel" +import * as AiModel from "effect/unstable/ai/Model" +import type { CreateEmbedding200, CreateEmbeddingRequestJson } from "./OpenAiClient.ts" +import { OpenAiClient } from "./OpenAiClient.ts" + +/** + * A model identifier accepted by an OpenAI-compatible embeddings endpoint. + * + * @category models + * @since 4.0.0 + */ +export type Model = string + +type ConfigOptions = Simplify>> +type ModelConfig = Omit & { readonly [x: string]: unknown } + +/** + * Context service for OpenAI embedding model configuration. + * + * **When to use** + * + * Use when you need to provide shared default request options for + * OpenAI-compatible embedding operations through the Effect context, such as + * `dimensions`, `encoding_format`, or `user`. + * + * **Details** + * + * The service stores the embedding request payload without `input`. Requests + * combine the selected model, layer or constructor config, and scoped context + * config, with scoped context config taking precedence. + * + * @see {@link withConfigOverride} for scoping embedding request overrides + * + * @category services + * @since 4.0.0 + */ +export class Config extends Context.Service< + Config, + ConfigOptions & { readonly [x: string]: unknown } +>()("@effect/ai-openai-compat/OpenAiEmbeddingModel/Config") {} + +/** + * Creates an `AiModel` for an OpenAI-compatible embedding model with its configured vector dimensions. + * + * **When to use** + * + * Use to provide an OpenAI-compatible `EmbeddingModel` and its `Dimensions` + * service to an Effect program. + * + * @see {@link layer} for providing only the embedding model service + * @see {@link withConfigOverride} for scoped request configuration overrides + * + * @category constructors + * @since 4.0.0 + */ +export const model = ( + model: string, + options: Omit & { + readonly dimensions: number + readonly [x: string]: unknown + } +): AiModel.Model<"openai", EmbeddingModel.EmbeddingModel | EmbeddingModel.Dimensions, OpenAiClient> => + AiModel.make( + "openai", + model, + Layer.merge( + layer({ + model, + config: options + }), + Layer.succeed(EmbeddingModel.Dimensions, options.dimensions) + ) + ) + +/** + * Creates an OpenAI-compatible embedding model service backed by `OpenAiClient`. + * + * **When to use** + * + * Use when you need to build or provide an `EmbeddingModel` service directly + * from an existing `OpenAiClient`. + * + * **Details** + * + * The service sends embedding requests through `OpenAiClient.createEmbedding`. + * Request config is merged as the selected model, constructor config, then + * scoped `Config`, so scoped overrides take precedence. Provider usage + * `prompt_tokens` is exposed as `usage.inputTokens`. + * + * **Gotchas** + * + * Provider responses must contain one numeric vector for every requested input + * with unique, in-range `index` values; otherwise embedding operations fail with + * `AiError.InvalidOutputError`. + * + * @see {@link model} for the higher-level `AiModel` descriptor that also provides `EmbeddingModel.Dimensions` + * @see {@link layer} for providing the service as a `Layer` + * @see {@link withConfigOverride} for scoping embedding request overrides + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { + readonly model: string + readonly config?: ModelConfig | undefined +}): Effect.fn.Return { + const client = yield* OpenAiClient + + const makeConfig = Effect.contextWith((services: Context.Context) => + Effect.succeed({ model, ...providerConfig, ...Context.getOrUndefined(services, Config) }) + ) + + return yield* EmbeddingModel.make({ + embedMany: Effect.fnUntraced(function*({ inputs }) { + const config = yield* makeConfig + const response = yield* client.createEmbedding({ ...config, input: inputs }) + return yield* mapProviderResponse(inputs.length, response) + }) + }) +}) + +/** + * Creates a layer for an OpenAI-compatible embedding model service. + * + * **When to use** + * + * Use when composing application layers and you want an OpenAI-compatible + * embeddings endpoint to satisfy `EmbeddingModel.EmbeddingModel` while + * supplying `OpenAiClient` from another layer. + * + * @see {@link make} for constructing the embedding model service effectfully + * @see {@link model} for creating an `AiModel` with configured dimensions + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: { + readonly model: string + readonly config?: ModelConfig | undefined +}): Layer.Layer => + Layer.effect(EmbeddingModel.EmbeddingModel, make(options)) + +/** + * Provides scoped request config overrides for OpenAI-compatible embedding model operations. + * + * **When to use** + * + * Use to apply embedding request options to one effect without changing the + * model's default configuration. + * + * **Details** + * + * The overrides are merged with any existing `Config` service for the duration + * of the supplied effect. Fields in `overrides` take precedence over existing + * config, and the helper supports both `effect.pipe(withConfigOverride(overrides))` + * and `withConfigOverride(effect, overrides)`. + * + * @see {@link Config} for available OpenAI-compatible embedding request configuration fields + * + * @category configuration + * @since 4.0.0 + */ +export const withConfigOverride: { + (overrides: typeof Config.Service): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, overrides: typeof Config.Service): Effect.Effect> +} = dual< + ( + overrides: typeof Config.Service + ) => (self: Effect.Effect) => Effect.Effect>, + (self: Effect.Effect, overrides: typeof Config.Service) => Effect.Effect> +>(2, (self, overrides) => + Effect.flatMap( + Effect.serviceOption(Config), + (config) => + Effect.provideService(self, Config, { + ...(config._tag === "Some" ? config.value : {}), + ...overrides + }) + )) + +const mapProviderResponse = ( + inputLength: number, + response: CreateEmbedding200 +): Effect.Effect => { + if (response.data.length !== inputLength) { + return Effect.fail( + invalidOutput(`Provider returned ${response.data.length} embeddings but expected ${inputLength}`) + ) + } + + const results = new Array>(inputLength) + const seen = new Set() + + for (const entry of response.data) { + if (!Number.isInteger(entry.index) || entry.index < 0 || entry.index >= inputLength) { + return Effect.fail(invalidOutput(`Provider returned invalid embedding index: ${entry.index}`)) + } + if (seen.has(entry.index)) { + return Effect.fail(invalidOutput(`Provider returned duplicate embedding index: ${entry.index}`)) + } + if (!Array.isArray(entry.embedding)) { + return Effect.fail(invalidOutput(`Provider returned non-vector embedding at index ${entry.index}`)) + } + + seen.add(entry.index) + results[entry.index] = [...entry.embedding] + } + + if (seen.size !== inputLength) { + return Effect.fail( + invalidOutput(`Provider returned embeddings for ${seen.size} inputs but expected ${inputLength}`) + ) + } + + return Effect.succeed({ + results, + usage: { + inputTokens: response.usage?.prompt_tokens + } + }) +} + +const invalidOutput = (description: string): AiError.AiError => + AiError.make({ + module: "OpenAiEmbeddingModel", + method: "embedMany", + reason: new AiError.InvalidOutputError({ description }) + }) diff --git a/.repos/effect/packages/ai/openai-compat/src/OpenAiError.ts b/.repos/effect/packages/ai/openai-compat/src/OpenAiError.ts new file mode 100644 index 000000000..88ba2dd9f --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/OpenAiError.ts @@ -0,0 +1,178 @@ +/** + * The `OpenAiError` module defines OpenAI-specific metadata that can be + * attached to the shared `AiError` error types used by the AI packages. It is + * primarily used by OpenAI-compatible clients to preserve provider details + * such as error codes, error types, request IDs, and rate limit headers while + * still exposing errors through the provider-neutral Effect AI error model. + * + * Use this module when mapping OpenAI API failures into `AiError` values and + * when consumers need enough structured metadata to debug failed requests, + * inspect quota or rate limit responses, or correlate an error with OpenAI + * support. The exported types are metadata shapes only; the module augmentation + * makes those shapes available on the corresponding shared AI error metadata + * interfaces without defining new runtime error classes. + * + * @since 4.0.0 + */ + +/** + * OpenAI-specific error metadata fields. + * + * @category models + * @since 4.0.0 + */ +export type OpenAiErrorMetadata = { + /** + * The OpenAI error code returned by the API. + */ + readonly errorCode: string | null + /** + * The OpenAI error type returned by the API. + */ + readonly errorType: string | null + /** + * The unique request ID for debugging with OpenAI support. + */ + readonly requestId: string | null +} + +/** + * OpenAI-specific rate limit metadata fields. + * + * **Details** + * + * Extends base error metadata with rate limit specific information from + * OpenAI's rate limit headers. + * + * @category models + * @since 4.0.0 + */ +export type OpenAiRateLimitMetadata = OpenAiErrorMetadata & { + /** + * The rate limit type (e.g. "requests", "tokens"). + */ + readonly limit: string | null + /** + * Number of remaining requests in the current window. + */ + readonly remaining: number | null + /** + * Time until the request rate limit resets. + */ + readonly resetRequests: string | null + /** + * Time until the token rate limit resets. + */ + readonly resetTokens: string | null +} + +declare module "effect/unstable/ai/AiError" { + /** + * Metadata attached to rate limit errors returned by OpenAI-compatible APIs. + * + * @category models + * @since 4.0.0 + */ + export interface RateLimitErrorMetadata { + readonly openai?: OpenAiRateLimitMetadata | null + } + + /** + * Metadata attached when an OpenAI-compatible provider reports that quota or + * billing limits have been exhausted. + * + * @category models + * @since 4.0.0 + */ + export interface QuotaExhaustedErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached to authentication failures from OpenAI-compatible APIs, + * such as invalid, missing, or unauthorized API credentials. + * + * @category models + * @since 4.0.0 + */ + export interface AuthenticationErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached when an OpenAI-compatible provider rejects content because + * it violates a safety or usage policy. + * + * @category models + * @since 4.0.0 + */ + export interface ContentPolicyErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached to malformed or unsupported requests rejected by an + * OpenAI-compatible API before model execution. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidRequestErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached to unexpected server-side failures reported by an + * OpenAI-compatible provider. + * + * @category models + * @since 4.0.0 + */ + export interface InternalProviderErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached when an OpenAI-compatible response cannot be converted + * into the expected AI package output shape. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidOutputErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached when an OpenAI-compatible structured output response does + * not satisfy the requested schema or parsing constraints. + * + * @category models + * @since 4.0.0 + */ + export interface StructuredOutputErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached when an OpenAI-compatible provider cannot support the + * schema supplied for structured output or tool definitions. + * + * @category models + * @since 4.0.0 + */ + export interface UnsupportedSchemaErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * Metadata attached when an OpenAI-compatible error response cannot be mapped + * to a more specific shared AI error category. + * + * @category models + * @since 4.0.0 + */ + export interface UnknownErrorMetadata { + readonly openai?: OpenAiErrorMetadata | null + } +} diff --git a/.repos/effect/packages/ai/openai-compat/src/OpenAiLanguageModel.ts b/.repos/effect/packages/ai/openai-compat/src/OpenAiLanguageModel.ts new file mode 100644 index 000000000..71e1e586a --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/OpenAiLanguageModel.ts @@ -0,0 +1,2035 @@ +/** + * The `OpenAiLanguageModel` module adapts OpenAI-compatible chat completions + * providers to Effect AI's `LanguageModel` service. It builds a model service + * from a model id, translates prompts, files, tools, structured output schemas, + * and provider-specific options into `OpenAiClient` requests, and maps normal + * or streaming chat completion results back into Effect AI response content and + * metadata. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as DateTime from "effect/DateTime" +import * as Effect from "effect/Effect" +import * as Encoding from "effect/Encoding" +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 * as Schema from "effect/Schema" +import * as AST from "effect/SchemaAST" +import * as SchemaIssue from "effect/SchemaIssue" +import * as Stream from "effect/Stream" +import type { Span } from "effect/Tracer" +import type { DeepMutable, Simplify } from "effect/Types" +import * as AiError from "effect/unstable/ai/AiError" +import * as LanguageModel from "effect/unstable/ai/LanguageModel" +import * as AiModel from "effect/unstable/ai/Model" +import { toCodecOpenAI } from "effect/unstable/ai/OpenAiStructuredOutput" +import type * as Prompt from "effect/unstable/ai/Prompt" +import type * as Response from "effect/unstable/ai/Response" +import * as Tool from "effect/unstable/ai/Tool" +import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import * as InternalUtilities from "./internal/utilities.ts" +import { + type Annotation, + type ChatCompletionContentPart, + type ChatCompletionRequestToolCall, + type CreateResponse, + type CreateResponse200, + type CreateResponse200Sse, + type CreateResponseRequestJson, + type IncludeEnum, + type InputContent, + type InputItem, + type MessageStatus, + OpenAiClient, + type ReasoningItem, + type SummaryTextContent, + type TextResponseFormatConfiguration, + type Tool as OpenAiClientTool, + type UnknownChatCompletionEvent +} from "./OpenAiClient.ts" +import { addGenAIAnnotations } from "./OpenAiTelemetry.ts" + +const formatIssue = SchemaIssue.makeFormatterDefault() + +/** + * Image detail level for vision requests. + */ +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. + * + * **When to use** + * + * Use as the context service for OpenAI-compatible language model request + * configuration, especially when a scoped operation should override the defaults + * supplied to `model`, `make`, or `layer`. + * + * @see {@link withConfigOverride} for scoping language model request overrides + * + * @category services + * @since 4.0.0 + */ +export class Config extends Context.Service< + Config, + ConfigOptions & { readonly [x: string]: unknown } +>()("@effect/ai-openai-compat/OpenAiLanguageModel/Config") {} + +// ============================================================================= +// Provider Options / Metadata +// ============================================================================= + +declare module "effect/unstable/ai/Prompt" { + /** + * OpenAI-compatible options for file prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface FilePartOptions extends ProviderOptions { + /** + * Provider-specific file options for OpenAI-compatible APIs. + */ + readonly openai?: { + /** + * The detail level of the image to be sent to the model. One of `high`, `low`, or `auto`. Defaults to `auto`. + */ + readonly imageDetail?: ImageDetail | null + } | null + } + + /** + * OpenAI-compatible options for reasoning prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartOptions extends ProviderOptions { + /** + * Provider-specific reasoning options for OpenAI-compatible APIs. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The encrypted content of the reasoning item - populated when a response + * is generated with `reasoning.encrypted_content` in the `include` + * parameter. + */ + readonly encryptedContent?: string | null + } | null + } + + /** + * OpenAI-compatible options for assistant tool-call prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartOptions extends ProviderOptions { + /** + * Provider-specific tool-call options for OpenAI-compatible APIs. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The status to send for the tool-call item. + */ + readonly status?: MessageStatus | null + } | null + } + + /** + * OpenAI-compatible options for tool-result prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface ToolResultPartOptions extends ProviderOptions { + /** + * Provider-specific tool-result options for OpenAI-compatible APIs. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The status to send for the tool-result item. + */ + readonly status?: MessageStatus | null + } | null + } + + /** + * OpenAI-compatible options for text prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface TextPartOptions extends ProviderOptions { + /** + * Provider-specific text options for OpenAI-compatible APIs. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The status to send for the text item. + */ + readonly status?: MessageStatus | null + /** + * A list of annotations that apply to the output text. + */ + readonly annotations?: ReadonlyArray | null + } | null + } +} + +declare module "effect/unstable/ai/Response" { + /** + * OpenAI-compatible metadata attached to a complete text response part. + * + * @category models + * @since 4.0.0 + */ + export interface TextPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the text part. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the text part. + */ + readonly itemId?: string | null + /** + * If the model emits a refusal content part, the refusal explanation + * from the model will be contained in the metadata of an empty text + * part. + */ + readonly refusal?: string | null + /** + * The status returned for the text item. + */ + readonly status?: MessageStatus | null + /** + * The text content part annotations. + */ + readonly annotations?: ReadonlyArray | null + } + } + + /** + * OpenAI-compatible metadata emitted when a streamed text part starts. + * + * @category models + * @since 4.0.0 + */ + export interface TextStartPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed text start. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the streamed text part. + */ + readonly itemId?: string | null + } | null + } + + /** + * OpenAI-compatible metadata emitted when a streamed text part ends. + * + * @category models + * @since 4.0.0 + */ + export interface TextEndPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed text end. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the streamed text part. + */ + readonly itemId?: string | null + /** + * The annotations collected for the completed streamed text part. + */ + readonly annotations?: ReadonlyArray | null + } | null + } + + /** + * OpenAI-compatible metadata attached to a complete reasoning response part. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the reasoning part. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + /** + * Encrypted reasoning content that can be sent back in later requests. + */ + readonly encryptedContent?: string | null + } | null + } + + /** + * OpenAI-compatible metadata emitted when a streamed reasoning part starts. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningStartPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning start. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + /** + * Encrypted reasoning content that can be sent back in later requests. + */ + readonly encryptedContent?: string | null + } | null + } + + /** + * OpenAI-compatible metadata emitted for a streamed reasoning delta. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningDeltaPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning delta. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + } | null + } + + /** + * OpenAI-compatible metadata emitted when a streamed reasoning part ends. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningEndPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning end. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + /** + * Encrypted reasoning content that can be sent back in later requests. + */ + readonly encryptedContent?: string + } | null + } + + /** + * OpenAI-compatible metadata attached to tool-call response parts. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the tool call. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the tool call. + */ + readonly itemId?: string | null + } | null + } + + /** + * OpenAI-compatible metadata attached to document source citations. + * + * @category models + * @since 4.0.0 + */ + export interface DocumentSourcePartMetadata extends ProviderMetadata { + /** + * Provider-specific citation metadata for OpenAI-compatible APIs. + */ + readonly openai?: + | { + /** + * Identifies a citation to an uploaded file. + */ + readonly type: "file_citation" + /** + * The index of the file in the list of files. + */ + readonly index: number + /** + * The ID of the file. + */ + readonly fileId: string + } + | { + /** + * Identifies a citation to a generated file path. + */ + readonly type: "file_path" + /** + * The index of the file in the list of files. + */ + readonly index: number + /** + * The ID of the file. + */ + readonly fileId: string + } + | { + /** + * Identifies a citation to a file inside a container. + */ + readonly type: "container_file_citation" + /** + * The ID of the file. + */ + readonly fileId: string + /** + * The ID of the container file. + */ + readonly containerId: string + } + | null + } + + /** + * OpenAI-compatible metadata attached to URL source citations. + * + * @category models + * @since 4.0.0 + */ + export interface UrlSourcePartMetadata extends ProviderMetadata { + /** + * Provider-specific URL citation metadata for OpenAI-compatible APIs. + */ + readonly openai?: { + /** + * Identifies a citation to a URL. + */ + readonly type: "url_citation" + /** + * The index of the first character of the URL citation in the message. + */ + readonly startIndex: number + /** + * The index of the last character of the URL citation in the message. + */ + readonly endIndex: number + } | null + } + + /** + * OpenAI-compatible metadata attached to finish response parts. + * + * @category models + * @since 4.0.0 + */ + export interface FinishPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned when generation finishes. + */ + readonly openai?: { + /** + * The service tier reported by the OpenAI-compatible provider. + */ + readonly serviceTier?: "default" | "auto" | "flex" | "scale" | "priority" | null + } | null + } +} + +// ============================================================================= +// Language Model +// ============================================================================= + +/** + * Creates an OpenAI-compatible model descriptor that can be provided with `Effect.provide`. + * + * **When to use** + * + * Use when you want an OpenAI-compatible language model value that carries + * provider and model metadata and can be supplied directly to an Effect program. + * + * @see {@link layer} for creating a `LanguageModel.LanguageModel` layer directly + * @see {@link make} for constructing the language model service effectfully + * + * @category constructors + * @since 4.0.0 + */ +export const model = ( + model: string, + config?: ModelConfig +): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> => + AiModel.make("openai", model, layer({ model, config })) + +// TODO +// /** +// * @since 4.0.0 +// * @category constructors +// */ +// export const modelWithTokenizer = ( +// model: string, +// config?: Omit +// ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> => +// AiModel.make("openai", model, layerWithTokenizer({ model, config })) + +/** + * Creates an OpenAI-compatible `LanguageModel` service from a model identifier and optional request defaults. + * + * **When to use** + * + * Use to construct an OpenAI-compatible chat-completions language model service + * backed by `OpenAiClient`. + * + * **Details** + * + * The returned effect requires `OpenAiClient`. Request defaults from the + * `config` option are merged with any `Config` service in the context, with + * context values taking precedence. The service supports both `generateText` and + * `streamText`. + * + * @see {@link layer} for providing the service as a `Layer` + * @see {@link model} for creating a model descriptor for `AiModel.provide` + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { + readonly model: string + readonly config?: ModelConfig | undefined +}): Effect.fn.Return { + const client = yield* OpenAiClient + + const makeConfig = Effect.contextWith((services: Context.Context) => + Effect.succeed({ model, ...providerConfig, ...Context.getOrUndefined(services, Config) }) + ) + + const makeRequest = Effect.fnUntraced( + function*>({ config, options, toolNameMapper }: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return { + const include = new Set() + const capabilities = getModelCapabilities(config.model!) + const messages = yield* prepareMessages({ + config, + options, + capabilities, + include, + toolNameMapper + }) + const { toolChoice, tools } = yield* prepareTools({ + config, + options, + toolNameMapper + }) + const responseFormat = yield* prepareResponseFormat({ + config, + options + }) + const { fileIdPrefixes: _fip, strictJsonSchema: _sjs, ...apiConfig } = config + const request: CreateResponse = { + ...apiConfig, + input: messages, + include: include.size > 0 ? Array.from(include) : null, + text: { + verbosity: config.text?.verbosity ?? null, + format: responseFormat + }, + ...(tools !== undefined ? { tools } : undefined), + ...(toolChoice !== undefined ? { tool_choice: toolChoice } : undefined) + } + return toChatCompletionsRequest(request) + } + ) + + return yield* LanguageModel.make({ + codecTransformer: toCodecOpenAI, + generateText: Effect.fnUntraced( + function*(options) { + const config = yield* makeConfig + const toolNameMapper = new Tool.NameMapper(options.tools) + const request = yield* makeRequest({ config, options, toolNameMapper }) + annotateRequest(options.span, request) + const [rawResponse, response] = yield* client.createResponse(request) + annotateResponse(options.span, rawResponse) + return yield* makeResponse({ + options, + rawResponse, + response, + toolNameMapper + }) + } + ), + streamText: Effect.fnUntraced( + function*(options) { + const config = yield* makeConfig + const toolNameMapper = new Tool.NameMapper(options.tools) + const request = yield* makeRequest({ config, options, toolNameMapper }) + annotateRequest(options.span, request) + const [response, stream] = yield* client.createResponseStream(request) + return yield* makeStreamResponse({ + options, + stream, + response, + toolNameMapper + }) + }, + (effect, options) => + effect.pipe( + Stream.unwrap, + Stream.map((response) => { + annotateStreamResponse(options.span, response) + return response + }) + ) + ) + }) +}) + +/** + * Creates a layer for the OpenAI-compatible language model. + * + * **When to use** + * + * Use when composing application layers and you want OpenAI-compatible APIs to + * satisfy `LanguageModel.LanguageModel` while supplying `OpenAiClient` from + * another layer. + * + * @see {@link make} for constructing the language model service effectfully + * @see {@link model} for creating an AI model descriptor + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: { + readonly model: string + readonly config?: ModelConfig | undefined +}): Layer.Layer => + Layer.effect(LanguageModel.LanguageModel, make(options)) + +/** + * Provides scoped config overrides for OpenAI-compatible language model operations. + * + * **When to use** + * + * Use to override request configuration for a single language model effect + * without changing the defaults supplied to `model`, `make`, or `layer`. + * + * **Details** + * + * Existing `Config` values from the Effect context are merged with `overrides`, + * and the override values take precedence. + * + * @see {@link Config} for the configuration shape + * + * @category configuration + * @since 4.0.0 + */ +export const withConfigOverride: { + (overrides: typeof Config.Service): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, overrides: typeof Config.Service): Effect.Effect> +} = dual< + ( + overrides: typeof Config.Service + ) => (self: Effect.Effect) => Effect.Effect>, + (self: Effect.Effect, overrides: typeof Config.Service) => Effect.Effect> +>(2, (self, overrides) => + Effect.flatMap( + Effect.serviceOption(Config), + (config) => + Effect.provideService(self, Config, { + ...(config._tag === "Some" ? config.value : {}), + ...overrides + }) + )) + +// ============================================================================= +// Prompt Conversion +// ============================================================================= + +const getSystemMessageMode = (model: string): "system" | "developer" => + model.startsWith("o") || + model.startsWith("gpt-5") || + model.startsWith("codex-") || + model.startsWith("computer-use") + ? "developer" + : "system" + +const prepareMessages = Effect.fnUntraced( + function*>({ + config, + options, + capabilities, + include, + toolNameMapper + }: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions + readonly include: Set + readonly capabilities: ModelCapabilities + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return, AiError.AiError> { + const hasConversation = Predicate.isNotNullish(config.conversation) + + // Handle Included Features + if (config.top_logprobs !== undefined) { + include.add("message.output_text.logprobs") + } + if (config.store === false && capabilities.isReasoningModel) { + include.add("reasoning.encrypted_content") + } + + const messages: Array = [] + + for (const message of options.prompt.content) { + switch (message.role) { + case "system": { + messages.push({ + role: getSystemMessageMode(config.model!), + content: message.content + }) + break + } + + case "user": { + const content: Array = [] + + for (let index = 0; index < message.content.length; index++) { + const part = message.content[index] + + switch (part.type) { + case "text": { + content.push({ type: "input_text", text: part.text }) + break + } + + case "file": { + if (part.mediaType.startsWith("image/")) { + const detail = getImageDetail(part) + const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType + + 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}` + content.push({ type: "input_image", image_url: imageUrl, detail }) + } + } else if (part.mediaType === "application/pdf") { + if (typeof part.data === "string" && isFileId(part.data, config)) { + content.push({ type: "input_file", file_id: part.data }) + } + + if (part.data instanceof URL) { + content.push({ type: "input_file", file_url: part.data.toString() }) + } + + if (part.data instanceof Uint8Array) { + const base64 = Encoding.encodeBase64(part.data) + const fileName = part.fileName ?? `part-${index}.pdf` + const fileData = `data:application/pdf;base64,${base64}` + content.push({ type: "input_file", filename: fileName, file_data: fileData }) + } + } else { + return yield* AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareMessages", + reason: new AiError.InvalidRequestError({ + description: `Detected unsupported media type for file: '${part.mediaType}'` + }) + }) + } + } + } + } + + messages.push({ role: "user", content }) + + break + } + + case "assistant": { + const reasoningMessages: Record> = Object.create(null) + + for (const part of message.content) { + switch (part.type) { + case "text": { + const id = getItemId(part) + + // When in conversation mode, skip items that already exist in the + // conversation context to avoid "Duplicate item found" errors + if (hasConversation && Predicate.isNotNull(id)) { + break + } + + if (config.store === true && Predicate.isNotNull(id)) { + messages.push({ type: "item_reference", id }) + break + } + + messages.push({ + id: id!, + type: "message", + role: "assistant", + status: part.options.openai?.status ?? "completed", + content: [{ + type: "output_text", + text: part.text, + annotations: part.options.openai?.annotations ?? [], + logprobs: [] + }] + }) + + break + } + + case "reasoning": { + const id = getItemId(part) + const encryptedContent = getEncryptedContent(part) + + if (hasConversation && Predicate.isNotNull(id)) { + break + } + + if (Predicate.isNotNull(id)) { + const message = reasoningMessages[id] + + if (config.store === true) { + // Use item references to refer to reasoning (single reference) + // when the first part is encountered + if (Predicate.isUndefined(message)) { + messages.push({ type: "item_reference", id }) + + // Store unused reasoning message to mark its id as used + reasoningMessages[id] = { + type: "reasoning", + id, + summary: [] + } + } + } else { + const summaryParts: Array = [] + + if (part.text.length > 0) { + summaryParts.push({ type: "summary_text", text: part.text }) + } + + if (Predicate.isUndefined(message)) { + reasoningMessages[id] = { + type: "reasoning", + id, + summary: summaryParts, + encrypted_content: encryptedContent ?? null + } + + messages.push(reasoningMessages[id]) + } else { + message.summary.push(...summaryParts) + + // Update encrypted content to enable setting it in the + // last summary part + if (Predicate.isNotNull(encryptedContent)) { + message.encrypted_content = encryptedContent + } + } + } + } + + break + } + + case "tool-call": { + const id = getItemId(part) + const status = getStatus(part) + + if (hasConversation && Predicate.isNotNull(id)) { + break + } + + if (config.store && Predicate.isNotNull(id)) { + messages.push({ type: "item_reference", id }) + break + } + + if (part.providerExecuted) { + break + } + + const toolName = toolNameMapper.getProviderName(part.name) + + messages.push({ + type: "function_call", + name: toolName, + call_id: part.id, + arguments: JSON.stringify(part.params), + ...(Predicate.isNotNull(id) ? { id } : {}), + ...(Predicate.isNotNull(status) ? { status } : {}) + }) + + break + } + + // Assistant tool-result parts are always provider executed + case "tool-result": { + // Skip execution denied results - these have no corresponding + // item in OpenAI's store + if ( + Predicate.hasProperty(part.result, "type") && + part.result.type === "execution-denied" + ) { + break + } + + if (hasConversation) { + break + } + + if (config.store === true) { + const id = getItemId(part) ?? part.id + messages.push({ type: "item_reference", id }) + } + } + } + } + + break + } + + case "tool": { + for (const part of message.content) { + if (part.type === "tool-approval-response") { + continue + } + + const status = getStatus(part) + + messages.push({ + type: "function_call_output", + call_id: part.id, + output: typeof part.result === "string" ? part.result : JSON.stringify(part.result), + ...(Predicate.isNotNull(status) ? { status } : {}) + }) + } + + break + } + } + } + + return messages + } +) + +// ============================================================================= +// HTTP Details +// ============================================================================= + +const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +const buildHttpResponseDetails = ( + response: HttpClientResponse.HttpClientResponse +): typeof Response.HttpResponseDetails.Type => ({ + status: response.status, + headers: Redactable.redact(response.headers) as Record +}) + +// ============================================================================= +// Response Conversion +// ============================================================================= + +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 + arguments: string +} + +const makeResponse = Effect.fnUntraced( + function*>({ + options, + rawResponse, + response, + toolNameMapper + }: { + readonly options: LanguageModel.ProviderOptions + readonly rawResponse: CreateResponse200 + readonly response: HttpClientResponse.HttpClientResponse + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return< + Array, + AiError.AiError + > { + let hasToolCalls = false + const parts: Array = [] + + const createdAt = new Date(rawResponse.created * 1000) + parts.push({ + type: "response-metadata", + id: rawResponse.id, + modelId: rawResponse.model as string, + timestamp: DateTime.formatIso(DateTime.fromDateUnsafe(createdAt)), + request: buildHttpRequestDetails(response.request) + }) + + const choice = rawResponse.choices[0] + const message = choice?.message + + if (message !== undefined) { + const reasoning = message.reasoning ?? message.reasoning_content + if (Predicate.isNotNullish(reasoning) && reasoning.length > 0) { + parts.push({ type: "reasoning", text: reasoning }) + } + + if ( + message.content !== undefined && Predicate.isNotNull(message.content) && message.content.length > 0 + ) { + parts.push({ type: "text", text: message.content }) + } + + if (message.tool_calls !== undefined) { + 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 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", + id: toolId, + name: toolName, + params, + metadata: { openai: { ...makeItemIdMetadata(toolCall.id) } } + }) + } + } + } + + const finishReason = InternalUtilities.resolveFinishReason( + choice?.finish_reason, + hasToolCalls + ) + const serviceTier = normalizeServiceTier(rawResponse.service_tier) + + parts.push({ + type: "finish", + reason: finishReason, + usage: getUsage(rawResponse.usage), + response: buildHttpResponseDetails(response), + ...(serviceTier !== undefined && { metadata: { openai: { serviceTier } } }) + }) + + return parts + } +) + +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 + }): Effect.fn.Return< + Stream.Stream, + AiError.AiError + > { + let serviceTier: string | undefined = undefined + let usage: CreateResponse200["usage"] = undefined + let finishReason: string | null | undefined = undefined + let metadataEmitted = false + let textStarted = false + let textId = "" + let reasoningStarted = false + let reasoningId = "" + let hasToolCalls = false + const activeToolCalls: Record = {} + + return stream.pipe( + Stream.mapEffect(Effect.fnUntraced(function*(event) { + const parts: Array = [] + + if (event === "[DONE]") { + if (reasoningStarted) { + parts.push({ + type: "reasoning-end", + id: reasoningId, + metadata: { openai: { ...makeItemIdMetadata(reasoningId) } } + }) + } + + if (textStarted) { + parts.push({ + type: "text-end", + id: textId, + metadata: { openai: { ...makeItemIdMetadata(textId) } } + }) + } + + for (const toolCall of Object.values(activeToolCalls)) { + const toolParams = toolCall.arguments.length > 0 ? toolCall.arguments : "{}" + const parsedParams = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(toolParams), + catch: (cause) => + AiError.make({ + module: "OpenAiLanguageModel", + 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", + id: toolCall.id, + name: toolCall.name, + params, + metadata: { openai: { ...makeItemIdMetadata(toolCall.id) } } + }) + hasToolCalls = true + } + + const normalizedServiceTier = normalizeServiceTier(serviceTier) + parts.push({ + type: "finish", + reason: InternalUtilities.resolveFinishReason(finishReason, hasToolCalls), + usage: getUsage(usage), + response: buildHttpResponseDetails(response), + ...(normalizedServiceTier !== undefined + ? { metadata: { openai: { serviceTier: normalizedServiceTier } } } + : undefined) + }) + 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 + } + if (event.usage !== undefined && Predicate.isNotNull(event.usage)) { + usage = event.usage + } + + if (!metadataEmitted) { + metadataEmitted = true + textId = `${event.id}_message` + reasoningId = `${event.id}_reasoning` + parts.push({ + type: "response-metadata", + id: event.id, + modelId: event.model, + timestamp: DateTime.formatIso(DateTime.fromDateUnsafe(new Date(event.created * 1000))), + request: buildHttpRequestDetails(response.request) + }) + } + + const choice = event.choices[0] + if (Predicate.isUndefined(choice)) { + return parts + } + + const reasoningDelta = choice.delta?.reasoning ?? choice.delta?.reasoning_content + if (Predicate.isNotNullish(reasoningDelta) && reasoningDelta.length > 0) { + if (!reasoningStarted) { + reasoningStarted = true + parts.push({ + type: "reasoning-start", + id: reasoningId, + metadata: { openai: { ...makeItemIdMetadata(reasoningId) } } + }) + } + parts.push({ type: "reasoning-delta", id: reasoningId, delta: reasoningDelta }) + } + + if (choice.delta?.content !== undefined && Predicate.isNotNull(choice.delta.content)) { + if (reasoningStarted) { + reasoningStarted = false + parts.push({ + type: "reasoning-end", + id: reasoningId, + metadata: { openai: { ...makeItemIdMetadata(reasoningId) } } + }) + } + + if (!textStarted) { + textStarted = true + parts.push({ + type: "text-start", + id: textId, + metadata: { openai: { ...makeItemIdMetadata(textId) } } + }) + } + parts.push({ type: "text-delta", id: textId, delta: choice.delta.content }) + } + + 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 + const activeToolCall = activeToolCalls[toolIndex] + const toolId = activeToolCall?.id ?? deltaTool.id ?? `${event.id}_tool_${toolIndex}` + const providerToolName = deltaTool.function?.name + const toolName = Predicate.isNotNullish(providerToolName) + ? toolNameMapper.getCustomName(providerToolName) + : activeToolCall?.name ?? toolNameMapper.getCustomName("unknown_tool") + const argumentsDelta = deltaTool.function?.arguments ?? "" + + if (Predicate.isUndefined(activeToolCall)) { + activeToolCalls[toolIndex] = { + id: toolId, + name: toolName, + arguments: argumentsDelta + } + parts.push({ type: "tool-params-start", id: toolId, name: toolName }) + } else { + activeToolCall.name = toolName + activeToolCall.arguments = `${activeToolCall.arguments}${argumentsDelta}` + } + + if (argumentsDelta.length > 0) { + parts.push({ type: "tool-params-delta", id: toolId, delta: argumentsDelta }) + } + }) + } + + if (choice.finish_reason !== undefined && Predicate.isNotNull(choice.finish_reason)) { + finishReason = choice.finish_reason + } + + return parts + })), + Stream.flattenIterable + ) + } +) + +// ============================================================================= +// Telemetry +// ============================================================================= + +const annotateRequest = ( + span: Span, + request: CreateResponseRequestJson +): void => { + addGenAIAnnotations(span, { + system: "openai", + operation: { name: "chat" }, + request: { + model: request.model as string, + temperature: request.temperature as number | undefined, + topP: request.top_p as number | undefined, + maxTokens: request.max_tokens as number | undefined + }, + openai: { + request: { + responseFormat: request.response_format?.type, + serviceTier: request.service_tier as string | undefined + } + } + }) +} + +const annotateResponse = (span: Span, response: CreateResponse200): void => { + const finishReason = response.choices[0]?.finish_reason ?? undefined + addGenAIAnnotations(span, { + response: { + id: response.id, + model: response.model as string, + finishReasons: finishReason !== undefined ? [finishReason] : undefined + }, + usage: { + inputTokens: response.usage?.prompt_tokens, + outputTokens: response.usage?.completion_tokens + }, + openai: { + response: { + serviceTier: response.service_tier as string | undefined + } + } + }) +} + +const annotateStreamResponse = (span: Span, part: Response.StreamPartEncoded) => { + if (part.type === "response-metadata") { + addGenAIAnnotations(span, { + response: { + id: part.id, + model: part.modelId + } + }) + } + if (part.type === "finish") { + const serviceTier = (part.metadata as any)?.openai?.serviceTier as string | undefined + addGenAIAnnotations(span, { + response: { + finishReasons: [part.reason] + }, + usage: { + inputTokens: part.usage.inputTokens.total, + outputTokens: part.usage.outputTokens.total + }, + openai: { + response: { serviceTier } + } + }) + } +} + +// ============================================================================= +// Tool Conversion +// ============================================================================= + +type OpenAiToolChoice = CreateResponse["tool_choice"] + +const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError => + AiError.make({ + module: "OpenAiLanguageModel", + method, + reason: new AiError.UnsupportedSchemaError({ + description: error instanceof Error ? error.message : String(error) + }) + }) + +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 }), + catch: (error) => unsupportedSchemaError(error, method) + }) + +const tryToolJsonSchema = (tool: T, method: string) => + Effect.try({ + try: () => Tool.getJsonSchema(tool, { transformer: toCodecOpenAI }), + 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") + const transform = Schema.decodeEffect(codec) + + return yield* ( + transform(toolParams) as Effect.Effect + ).pipe(Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "makeResponse", + reason: new AiError.ToolParameterValidationError({ + toolName, + toolParams, + description: formatIssue(error.issue) + }) + }) + )) +}) + +const prepareTools = Effect.fnUntraced(function*>({ + config, + options, + toolNameMapper +}: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper +}): Effect.fn.Return<{ + readonly tools: ReadonlyArray | undefined + readonly toolChoice: OpenAiToolChoice | undefined +}, AiError.AiError> { + // Return immediately if no tools are in the toolkit + if (options.tools.length === 0) { + return { tools: undefined, toolChoice: undefined } + } + + const tools: Array = [] + let toolChoice: OpenAiToolChoice | undefined = undefined + + // Filter the incoming tools down to the set of allowed tools as indicated by + // the tool choice. This must be done here given that there is no tool name + // in OpenAI's provider-defined tools, so there would be no way to perform + // this filter otherwise + let allowedTools = options.tools + if (typeof options.toolChoice === "object" && "oneOf" in options.toolChoice) { + const allowedToolNames = new Set(options.toolChoice.oneOf) + allowedTools = options.tools.filter((tool) => allowedToolNames.has(tool.name)) + toolChoice = options.toolChoice.mode === "required" ? "required" : "auto" + } + + // Convert the tools in the toolkit to the provider-defined format + for (const tool of allowedTools) { + if (Tool.isUserDefined(tool) || Tool.isDynamic(tool)) { + const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true + const parameters = yield* tryToolJsonSchema(tool, "prepareTools") + tools.push({ + type: "function", + name: tool.name, + description: Tool.getDescription(tool) ?? null, + parameters: parameters as { readonly [x: string]: Schema.Json }, + strict + }) + } + + if (Tool.isProviderDefined(tool)) { + tools.push({ + type: "function", + name: tool.providerName, + description: Tool.getDescription(tool) ?? null, + parameters: Tool.getJsonSchema(tool) as { readonly [x: string]: Schema.Json }, + strict: config.strictJsonSchema ?? true + }) + } + } + + if (options.toolChoice === "auto" || options.toolChoice === "none" || options.toolChoice === "required") { + toolChoice = options.toolChoice + } + + if (typeof options.toolChoice === "object" && "tool" in options.toolChoice) { + const toolName = toolNameMapper.getProviderName(options.toolChoice.tool) + const providerNames = toolNameMapper.providerNames + if (providerNames.includes(toolName)) { + toolChoice = { type: "function", name: toolName } + } else { + toolChoice = { type: "function", name: options.toolChoice.tool } + } + } + + return { tools, toolChoice } +}) + +const toChatCompletionsRequest = (payload: CreateResponse): CreateResponseRequestJson => { + const messages = toChatMessages(payload.input) + const responseFormat = toChatResponseFormat(payload.text?.format) + const tools = payload.tools !== undefined + ? payload.tools.map(toChatTool).filter((tool): tool is NonNullable> => + tool !== undefined + ) + : [] + const toolChoice = toChatToolChoice(payload.tool_choice) + + return { + ...extractCustomRequestProperties(payload), + model: payload.model ?? "", + messages: messages.length > 0 ? messages : [{ role: "user", content: "" }], + ...(payload.temperature !== undefined ? { temperature: payload.temperature } : undefined), + ...(payload.top_p !== undefined ? { top_p: payload.top_p } : undefined), + ...(payload.max_output_tokens !== undefined ? { max_tokens: payload.max_output_tokens } : undefined), + ...(payload.user !== undefined ? { user: payload.user } : undefined), + ...(payload.seed !== undefined ? { seed: payload.seed } : undefined), + ...(payload.parallel_tool_calls !== undefined + ? { parallel_tool_calls: payload.parallel_tool_calls } + : undefined), + ...(payload.service_tier !== undefined ? { service_tier: payload.service_tier } : undefined), + ...(payload.reasoning !== undefined ? { reasoning: payload.reasoning } : undefined), + ...(responseFormat !== undefined ? { response_format: responseFormat } : undefined), + ...(tools.length > 0 ? { tools } : undefined), + ...(toolChoice !== undefined ? { tool_choice: toolChoice } : undefined) + } +} + +const createResponseKnownProperties = new Set([ + "metadata", + "top_logprobs", + "temperature", + "top_p", + "user", + "safety_identifier", + "prompt_cache_key", + "service_tier", + "prompt_cache_retention", + "previous_response_id", + "model", + "reasoning", + "background", + "max_output_tokens", + "max_tool_calls", + "text", + "tools", + "tool_choice", + "truncation", + "input", + "include", + "parallel_tool_calls", + "store", + "instructions", + "stream", + "conversation", + "modalities", + "seed" +]) + +const extractCustomRequestProperties = (payload: CreateResponse): Record => { + const customProperties: Record = {} + for (const [key, value] of Object.entries(payload)) { + if (!createResponseKnownProperties.has(key)) { + Rec.assignProperty(customProperties, key, value) + } + } + return customProperties +} + +const toChatResponseFormat = ( + format: TextResponseFormatConfiguration | undefined +): CreateResponseRequestJson["response_format"] | undefined => { + if (Predicate.isUndefined(format) || Predicate.isNull(format)) { + return undefined + } + + switch (format.type) { + case "json_object": { + return { type: "json_object" } + } + case "json_schema": { + return { + type: "json_schema", + json_schema: { + name: format.name, + schema: format.schema, + ...(format.description !== undefined ? { description: format.description } : undefined), + ...(Predicate.isNotNullish(format.strict) ? { strict: format.strict } : undefined) + } + } + } + default: { + return undefined + } + } +} + +const toChatToolChoice = ( + toolChoice: OpenAiToolChoice +): CreateResponseRequestJson["tool_choice"] | undefined => { + if (Predicate.isUndefined(toolChoice)) { + return undefined + } + + if (typeof toolChoice === "string") { + return toolChoice + } + + if (toolChoice.type === "allowed_tools") { + return toolChoice.mode + } + + if (toolChoice.type === "function") { + return { + type: "function", + function: { + name: toolChoice.name + } + } + } + + const functionName = Predicate.hasProperty(toolChoice, "name") && typeof toolChoice.name === "string" + ? toolChoice.name + : toolChoice.type + + return { + type: "function", + function: { + name: functionName + } + } +} + +const toChatTool = ( + tool: OpenAiClientTool +): NonNullable[number] | undefined => { + if (tool.type === "function") { + return { + type: "function", + function: { + name: tool.name, + ...(tool.description !== undefined ? { description: tool.description } : undefined), + ...(Predicate.isNotNullish(tool.parameters) ? { parameters: tool.parameters } : undefined), + ...(Predicate.isNotNullish(tool.strict) ? { strict: tool.strict } : undefined) + } + } + } + + if (tool.type === "custom") { + return { + type: "function", + function: { + name: tool.name, + parameters: { type: "object", additionalProperties: true } + } + } + } + + return undefined +} + +const toChatMessages = ( + input: CreateResponse["input"] +): Array => { + if (Predicate.isUndefined(input)) { + return [] + } + + if (typeof input === "string") { + return [{ role: "user", content: input }] + } + + const messages: Array = [] + + for (const item of input) { + 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 +} + +const toChatMessagesFromItem = ( + item: InputItem +): Array => { + if (Predicate.hasProperty(item, "type") && item.type === "message") { + return [{ + role: item.role, + content: toAssistantChatMessageContent(item.content) + }] + } + + if (Predicate.hasProperty(item, "role")) { + return [{ + role: item.role, + content: toChatMessageContent(item.content) + }] + } + + switch (item.type) { + case "function_call": { + return [{ + role: "assistant", + content: null, + tool_calls: [toChatToolCall(item)] + }] + } + + case "function_call_output": { + return [{ + role: "tool", + tool_call_id: item.call_id, + content: stringifyJson(item.output) + }] + } + + default: { + return [] + } + } +} + +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 => { + let text = "" + for (const part of content) { + if (part.type === "output_text" && typeof part.text === "string") { + text += part.text + } + if (part.type === "refusal" && typeof part.refusal === "string") { + text += part.refusal + } + } + return text +} + +const toChatMessageContent = ( + content: string | ReadonlyArray +): string | ReadonlyArray => { + if (typeof content === "string") { + return content + } + + const parts: Array = [] + + for (const part of content) { + switch (part.type) { + case "input_text": { + parts.push({ type: "text", text: part.text }) + break + } + case "input_image": { + const imageUrl = part.image_url !== undefined + ? part.image_url + : part.file_id !== undefined + ? `openai://file/${part.file_id}` + : undefined + + if (imageUrl !== undefined && Predicate.isNotNull(imageUrl)) { + parts.push({ + type: "image_url", + image_url: { + url: imageUrl, + ...(Predicate.isNotNullish(part.detail) ? { detail: part.detail } : undefined) + } + }) + } + break + } + case "input_file": { + if (part.file_url !== undefined) { + parts.push({ type: "text", text: part.file_url }) + } else if (part.file_data !== undefined) { + parts.push({ type: "text", text: part.file_data }) + } else if (part.file_id !== undefined) { + parts.push({ type: "text", text: `openai://file/${part.file_id}` }) + } + break + } + } + } + + if (parts.length === 0) { + return "" + } + + if (parts.every((part) => part.type === "text")) { + return parts.map((part) => part.text).join("\n") + } + + return parts +} + +const stringifyJson = (value: unknown): string => + typeof value === "string" + ? value + : JSON.stringify(value) + +// ============================================================================= +// Utilities +// ============================================================================= + +const isFileId = (data: string, config: typeof Config.Service): boolean => + config.fileIdPrefixes != null && config.fileIdPrefixes.some((prefix) => data.startsWith(prefix)) + +const getItemId = ( + part: + | Prompt.TextPart + | Prompt.ReasoningPart + | Prompt.ToolCallPart + | Prompt.ToolResultPart +): string | null => part.options.openai?.itemId ?? null +const getStatus = ( + part: + | Prompt.TextPart + | Prompt.ToolCallPart + | Prompt.ToolResultPart +): MessageStatus | null => part.options.openai?.status ?? null +const getEncryptedContent = ( + part: Prompt.ReasoningPart +): string | null => part.options.openai?.encryptedContent ?? null + +const getImageDetail = (part: Prompt.FilePart): ImageDetail => part.options.openai?.imageDetail ?? "auto" + +const makeItemIdMetadata = (itemId: string | undefined) => itemId !== undefined ? { itemId } : undefined + +const normalizeServiceTier = ( + serviceTier: string | undefined +): "default" | "auto" | "flex" | "scale" | "priority" | null | undefined => { + switch (serviceTier) { + case undefined: + return undefined + case "default": + case "auto": + case "flex": + case "scale": + case "priority": + return serviceTier + default: + return null + } +} + +const prepareResponseFormat = Effect.fnUntraced(function*({ config, options }: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions +}): Effect.fn.Return { + if (options.responseFormat.type === "json") { + const name = options.responseFormat.objectName + const schema = options.responseFormat.schema + const jsonSchema = yield* tryJsonSchema(schema, "prepareResponseFormat") + return { + type: "json_schema", + name, + description: AST.resolveDescription(schema.ast) ?? "Response with a JSON object", + schema: jsonSchema as any, + strict: config.strictJsonSchema ?? true + } + } + return { type: "text" } +}) + +interface ModelCapabilities { + readonly isReasoningModel: boolean + readonly systemMessageMode: "remove" | "system" | "developer" + readonly supportsFlexProcessing: boolean + readonly supportsPriorityProcessing: boolean + /** + * Allow temperature, topP, logProbs when reasoningEffort is none. + */ + readonly supportsNonReasoningParameters: boolean +} + +const getModelCapabilities = (modelId: string): ModelCapabilities => { + const supportsFlexProcessing = modelId.startsWith("o3") || + modelId.startsWith("o4-mini") || + (modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat")) + + const supportsPriorityProcessing = modelId.startsWith("gpt-4") || + modelId.startsWith("gpt-5-mini") || + (modelId.startsWith("gpt-5") && + !modelId.startsWith("gpt-5-nano") && + !modelId.startsWith("gpt-5-chat")) || + modelId.startsWith("o3") || + modelId.startsWith("o4-mini") + + // Use allowlist approach: only known reasoning models should use 'developer' role + // This prevents issues with fine-tuned models, third-party models, and custom models + const isReasoningModel = modelId.startsWith("o1") || + modelId.startsWith("o3") || + modelId.startsWith("o4-mini") || + modelId.startsWith("codex-mini") || + modelId.startsWith("computer-use-preview") || + (modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat")) + + // https://platform.openai.com/docs/guides/latest-model#gpt-5-1-parameter-compatibility + // GPT-5.1 and GPT-5.2 support temperature, topP, logProbs when reasoningEffort is none + const supportsNonReasoningParameters = modelId.startsWith("gpt-5.1") || modelId.startsWith("gpt-5.2") + + const systemMessageMode = isReasoningModel ? "developer" : "system" + + return { + supportsFlexProcessing, + supportsPriorityProcessing, + isReasoningModel, + systemMessageMode, + supportsNonReasoningParameters + } +} + +const getUsage = (usage: CreateResponse200["usage"]): Response.Usage => { + if (Predicate.isNullish(usage)) { + return { + inputTokens: { + uncached: undefined, + total: undefined, + cacheRead: undefined, + cacheWrite: undefined + }, + outputTokens: { + total: undefined, + text: undefined, + reasoning: undefined + } + } + } + + const inputTokens = usage.prompt_tokens + const outputTokens = usage.completion_tokens + const cachedTokens = getUsageDetailNumber(usage.prompt_tokens_details, "cached_tokens") ?? 0 + const reasoningTokens = getUsageDetailNumber(usage.completion_tokens_details, "reasoning_tokens") ?? 0 + + return { + inputTokens: { + uncached: inputTokens - cachedTokens, + total: inputTokens, + cacheRead: cachedTokens, + cacheWrite: undefined + }, + outputTokens: { + total: outputTokens, + text: outputTokens - reasoningTokens, + reasoning: reasoningTokens + } + } +} + +const getUsageDetailNumber = ( + details: unknown, + field: string +): number | undefined => { + if (typeof details !== "object" || details === null) { + return undefined + } + + const value = (details as Record)[field] + return typeof value === "number" ? value : undefined +} diff --git a/.repos/effect/packages/ai/openai-compat/src/OpenAiTelemetry.ts b/.repos/effect/packages/ai/openai-compat/src/OpenAiTelemetry.ts new file mode 100644 index 000000000..ab4d10147 --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/OpenAiTelemetry.ts @@ -0,0 +1,164 @@ +/** + * The `OpenAiTelemetry` module defines OpenAI-compatible telemetry attributes + * and a helper for adding them to a tracing span. It keeps the standard GenAI + * telemetry attributes and adds request and response metadata under the + * `gen_ai.openai.*` OpenTelemetry namespaces. + * + * @since 4.0.0 + */ +import { dual } from "effect/Function" +import * as String from "effect/String" +import type { Span } from "effect/Tracer" +import type { Simplify } from "effect/Types" +import * as Telemetry from "effect/unstable/ai/Telemetry" + +/** + * The attributes used to describe telemetry in the context of Generative + * Artificial Intelligence (GenAI) Models requests and responses. + * + * **Details** + * + * These attributes follow the OpenTelemetry generative AI semantic + * conventions: + * https://opentelemetry.io/docs/specs/semconv/attributes-registry/gen-ai/ + * + * @category models + * @since 4.0.0 + */ +export type OpenAiTelemetryAttributes = Simplify< + & Telemetry.GenAITelemetryAttributes + & Telemetry.AttributesWithPrefix + & Telemetry.AttributesWithPrefix +> + +/** + * All telemetry attributes which are part of the GenAI specification, + * including the OpenAI-specific attributes. + * + * @category models + * @since 4.0.0 + */ +export type AllAttributes = Telemetry.AllAttributes & RequestAttributes & ResponseAttributes + +/** + * Telemetry attributes which are part of the GenAI specification and are + * namespaced by `gen_ai.openai.request`. + * + * @category models + * @since 4.0.0 + */ +export interface RequestAttributes { + /** + * The response format that is requested. + */ + readonly responseFormat?: (string & {}) | WellKnownResponseFormat | null | undefined + /** + * The service tier requested. May be a specific tier, `default`, or `auto`. + */ + readonly serviceTier?: (string & {}) | WellKnownServiceTier | null | undefined +} + +/** + * Telemetry attributes which are part of the GenAI specification and are + * namespaced by `gen_ai.openai.response`. + * + * @category models + * @since 4.0.0 + */ +export interface ResponseAttributes { + /** + * The service tier used for the response. + */ + readonly serviceTier?: string | null | undefined + /** + * A fingerprint to track any eventual change in the Generative AI + * environment. + */ + readonly systemFingerprint?: string | null | undefined +} + +/** + * The `gen_ai.openai.request.response_format` attribute has a list of + * well-known values. + * + * **Details** + * + * If one of them applies, then the respective value **MUST** be used; + * otherwise, a custom value **MAY** be used. + * + * @category models + * @since 4.0.0 + */ +export type WellKnownResponseFormat = "json_object" | "json_schema" | "text" + +/** + * The `gen_ai.openai.request.service_tier` attribute has a list of + * well-known values. + * + * **Details** + * + * If one of them applies, then the respective value **MUST** be used; + * otherwise, a custom value **MAY** be used. + * + * @category models + * @since 4.0.0 + */ +export type WellKnownServiceTier = "auto" | "default" + +/** + * Options accepted by `addGenAIAnnotations`, combining standard GenAI telemetry + * attributes with optional OpenAI-compatible request and response attributes. + * + * @category options + * @since 4.0.0 + */ +export type OpenAiTelemetryAttributeOptions = Telemetry.GenAITelemetryAttributeOptions & { + openai?: { + request?: RequestAttributes | undefined + response?: ResponseAttributes | undefined + } | undefined +} + +const addOpenAiRequestAttributes = Telemetry.addSpanAttributes("gen_ai.openai.request", String.camelToSnake)< + RequestAttributes +> +const addOpenAiResponseAttributes = Telemetry.addSpanAttributes("gen_ai.openai.response", String.camelToSnake)< + ResponseAttributes +> + +/** + * Applies the specified OpenAI GenAI telemetry attributes to the provided + * `Span`. + * + * **When to use** + * + * Use to annotate an OpenAI-compatible model span with standard GenAI telemetry + * attributes and OpenAI-specific request or response metadata. + * + * **Details** + * + * Standard GenAI attributes are applied first. When OpenAI request or response + * metadata is present, it is written under `gen_ai.openai.request.*` and + * `gen_ai.openai.response.*` attributes. + * + * **Gotchas** + * + * Mutates the supplied `Span` in place. + * + * @category tracing + * @since 4.0.0 + */ +export const addGenAIAnnotations: { + (options: OpenAiTelemetryAttributeOptions): (span: Span) => void + (span: Span, options: OpenAiTelemetryAttributeOptions): void +} = dual(2, (span: Span, options: OpenAiTelemetryAttributeOptions) => { + Telemetry.addGenAIAnnotations(span, options) + if (options.openai != null) { + if (options.openai.request != null) { + addOpenAiRequestAttributes(span, options.openai.request) + } + if (options.openai.response != null) { + addOpenAiResponseAttributes(span, options.openai.response) + } + } +}) diff --git a/.repos/effect/packages/ai/openai-compat/src/index.ts b/.repos/effect/packages/ai/openai-compat/src/index.ts new file mode 100644 index 000000000..7a5cd7f19 --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/index.ts @@ -0,0 +1,35 @@ +/** + * @since 4.0.0 + */ + +// @barrel: Auto-generated exports. Do not edit manually. + +/** + * @since 4.0.0 + */ +export * as OpenAiClient from "./OpenAiClient.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiConfig from "./OpenAiConfig.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiEmbeddingModel from "./OpenAiEmbeddingModel.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiError from "./OpenAiError.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiLanguageModel from "./OpenAiLanguageModel.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiTelemetry from "./OpenAiTelemetry.ts" diff --git a/.repos/effect/packages/ai/openai-compat/src/internal/errors.ts b/.repos/effect/packages/ai/openai-compat/src/internal/errors.ts new file mode 100644 index 000000000..500289c5c --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/internal/errors.ts @@ -0,0 +1,301 @@ +import * as Arr from "effect/Array" +import * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import * as Number from "effect/Number" +import * as Option from "effect/Option" +import * as Redactable from "effect/Redactable" +import * as Schema from "effect/Schema" +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" +import type { OpenAiErrorMetadata } from "../OpenAiError.ts" + +/** @internal */ +export const OpenAiErrorBody = Schema.Struct({ + error: Schema.Struct({ + message: Schema.String, + 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.Finite]))) + }) +}) +const OpenAiErrorBodyJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Union([ + OpenAiErrorBody, + Schema.NonEmptyArray(OpenAiErrorBody).pipe( + Schema.decodeTo( + Schema.toType(OpenAiErrorBody), + SchemaTransformation.transform({ + decode: Arr.headNonEmpty, + encode: (item) => [item] + }) + ) + ) +]))) + +/** @internal */ +export const mapSchemaError = dual< + (method: string) => (error: Schema.SchemaError) => AiError.AiError, + (error: Schema.SchemaError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenAiClient", + method, + 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, + (error: HttpClientError.HttpClientError, method: string) => Effect.Effect +>(2, (error, method) => { + const reason = error.reason + switch (reason._tag) { + case "TransportError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.NetworkError({ + reason: "TransportError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "EncodeError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.NetworkError({ + reason: "EncodeError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "InvalidUrlError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.NetworkError({ + reason: "InvalidUrlError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "StatusCodeError": { + return mapStatusCodeError(reason, method) + } + case "DecodeError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Failed to decode response" + }) + })) + } + case "EmptyBodyError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Response body was empty" + }) + })) + } + } +}) + +/** @internal */ +const mapStatusCodeError = Effect.fnUntraced(function*( + error: HttpClientError.StatusCodeError, + method: string +) { + const { request, response, description } = error + const status = response.status + const headers = response.headers as Record + const requestId = headers["x-request-id"] + + let body = yield* response.text.pipe( + Effect.catchCause(() => Effect.succeed(description?.startsWith("{") ? description : undefined)) + ) + const decoded = OpenAiErrorBodyJson(body) + + const reason = mapStatusCodeToReason({ + status, + headers, + message: Option.isSome(decoded) ? decoded.value.error.message : undefined, + http: buildHttpContext({ request, response, body }), + metadata: { + errorCode: Option.isSome(decoded) ? decoded.value.error.code?.toString() ?? null : null, + errorType: decoded.pipe( + Option.flatMapNullishOr((d) => d.error.type ?? d.error.status), + Option.map(String.toLowerCase), + Option.getOrNull + ), + requestId: requestId ?? null + } + }) + + return yield* AiError.make({ module: "OpenAiClient", method, reason }) +}) + +/** @internal */ +export const parseRateLimitHeaders = (headers: Record) => { + const retryAfterRaw = headers["retry-after"] + let retryAfter: Duration.Duration | undefined + if (retryAfterRaw !== undefined) { + const parsed = Number.parse(retryAfterRaw) + if (Option.isSome(parsed)) { + retryAfter = Duration.seconds(parsed.value) + } + } + const remainingRaw = headers["x-ratelimit-remaining-requests"] + const remaining = remainingRaw !== undefined ? Option.getOrNull(Number.parse(remainingRaw)) : null + return { + retryAfter, + limit: headers["x-ratelimit-limit-requests"] ?? null, + remaining, + resetRequests: headers["x-ratelimit-reset-requests"] ?? null, + resetTokens: headers["x-ratelimit-reset-tokens"] ?? null + } +} + +/** @internal */ +export const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +/** @internal */ +export const buildHttpContext = (params: { + readonly request: HttpClientRequest.HttpClientRequest + readonly response?: HttpClientResponse.HttpClientResponse + readonly body?: string | undefined +}): typeof AiError.HttpContext.Type => ({ + request: buildHttpRequestDetails(params.request), + response: params.response !== undefined + ? { + status: params.response.status, + headers: Redactable.redact(params.response.headers) as Record + } + : undefined, + body: params.body +}) + +/** @internal */ +export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { + readonly status: number + readonly headers: Record + readonly message: string | undefined + readonly metadata: OpenAiErrorMetadata + readonly http: typeof AiError.HttpContext.Type +}): AiError.AiErrorReason => { + const errorDescription = AiError.buildErrorDescription({ + status, + message, + method: http.request.method, + url: http.request.url, + errorCode: metadata.errorCode, + errorType: metadata.errorType, + requestId: metadata.requestId, + body: http.body + }) + + switch (status) { + case 400: + return new AiError.InvalidRequestError({ + 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: errorDescription, + metadata: { openai: metadata }, + http + }) + case 409: + case 422: + return new AiError.InvalidRequestError({ + description: errorDescription, + metadata: { openai: metadata }, + http + }) + case 429: { + if ( + metadata.errorCode === "insufficient_quota" || + metadata.errorType === "insufficient_quota" || + metadata.errorType?.includes("quota") || + metadata.errorType?.includes("exhausted") + ) { + return new AiError.QuotaExhaustedError({ + metadata: { openai: metadata }, + http + }) + } + const { retryAfter, ...rateLimitMetadata } = parseRateLimitHeaders(headers) + return new AiError.RateLimitError({ + retryAfter, + metadata: { + openai: { + ...metadata, + ...rateLimitMetadata + } + }, + http + }) + } + default: + if (status >= 500) { + return new AiError.InternalProviderError({ + description: message ?? "Server error", + metadata, + http + }) + } + return new AiError.UnknownError({ + description: message, + metadata, + http + }) + } +} diff --git a/.repos/effect/packages/ai/openai-compat/src/internal/utilities.ts b/.repos/effect/packages/ai/openai-compat/src/internal/utilities.ts new file mode 100644 index 000000000..75daf6b7d --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/src/internal/utilities.ts @@ -0,0 +1,24 @@ +import type * as Response from "effect/unstable/ai/Response" + +const finishReasonMap: Record = { + content_filter: "content-filter", + function_call: "tool-calls", + length: "length", + stop: "stop", + tool_calls: "tool-calls" +} + +/** @internal */ +export const resolveFinishReason = ( + finishReason: string | null | undefined, + hasToolCalls: boolean +): Response.FinishReason => { + if (finishReason == null) { + return hasToolCalls ? "tool-calls" : "stop" + } + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined + if (reason == null) { + return hasToolCalls ? "tool-calls" : "unknown" + } + return reason +} diff --git a/.repos/effect/packages/ai/openai-compat/test/OpenAiClient.test.ts b/.repos/effect/packages/ai/openai-compat/test/OpenAiClient.test.ts new file mode 100644 index 000000000..7cae883a9 --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/test/OpenAiClient.test.ts @@ -0,0 +1,629 @@ +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 { 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*() { + const client = yield* OpenAiClient.OpenAiClient + + yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }) + + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) + + 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") + + 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*() { + const client = yield* OpenAiClient.OpenAiClient + + yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }], + provider_feature: { + enabled: true + } + }) + + 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*() { + const client = yield* OpenAiClient.OpenAiClient + + const embedding = yield* client.createEmbedding({ + model: "my-custom-embedding-model", + input: "embed this" + }) + + const requests = yield* MockHttpClient.requests + const request = requests[0] + + 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*() { + 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)) + ) + + 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.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("drops invalid JSON 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.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" }], + tool_choice: { + type: "function", + function: { + name: "TestTool" + } + }, + tools: [{ + type: "function", + function: { + name: "TestTool", + parameters: { + type: "object", + additionalProperties: false, + properties: { + input: { type: "string" } + }, + required: ["input"] + } + } + }] + }) + + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) + + 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*() { + const client = yield* OpenAiClient.OpenAiClient + + yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [ + { + role: "assistant", + content: null, + tool_calls: [{ + id: "patch_call_1", + type: "function", + function: { + name: "apply_patch", + arguments: JSON.stringify({ + call_id: "patch_call_1", + operation: { + type: "delete_file", + path: "src/obsolete.ts" + } + }) + } + }] + }, + { + role: "tool", + tool_call_id: "patch_call_1", + content: "deleted" + } + ] + }) + + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) + + const assistantMessages = body.messages.filter((message: any) => message.role === "assistant") + const patchMessage = assistantMessages.find((message: any) => + message.tool_calls?.[0]?.function?.name === "apply_patch" + ) + + assert.isDefined(patchMessage) + + assert.strictEqual(patchMessage.tool_calls[0].id, "patch_call_1") + assert.deepStrictEqual(JSON.parse(patchMessage.tool_calls[0].function.arguments), { + call_id: "patch_call_1", + operation: { + type: "delete_file", + path: "src/obsolete.ts" + } + }) + + const toolMessages = body.messages.filter((message: any) => message.role === "tool") + 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.OpenAiClient + + const error = yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + 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("surfaces the provider message on 401 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, "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", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + 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" + } + } + })))) + }) +}) + +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 + 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 = { + _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", + created: 1, + choices: [{ + index: 0, + finish_reason: "stop", + message: { + role: "assistant", + content: "Hello" + } + }], + usage: { + prompt_tokens: 1, + completion_tokens: 1, + total_tokens: 2 + }, + ...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._tag === "Sse" + ? toSseBody(response.events) + : response.body + + return HttpClientResponse.fromWeb( + request, + new Response(body, { + status: response.status ?? 200, + headers: { + "content-type": contentType, + ...response.headers + } + }) + ) +} + +const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => + Effect.gen(function*() { + const body = request.body + if (body._tag === "Uint8Array") { + const text = new TextDecoder().decode(body.body) + return JSON.parse(text) + } + return yield* Effect.die(new Error("Expected Uint8Array body")) + }) + +const toSseBody = (events: ReadonlyArray): string => + events.map((event) => { + 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/.repos/effect/packages/ai/openai-compat/test/OpenAiEmbeddingModel.test.ts b/.repos/effect/packages/ai/openai-compat/test/OpenAiEmbeddingModel.test.ts new file mode 100644 index 000000000..b80e664f4 --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/test/OpenAiEmbeddingModel.test.ts @@ -0,0 +1,216 @@ +import { OpenAiClient, OpenAiEmbeddingModel } from "@effect/ai-openai-compat" +import { assert, describe, it } from "@effect/vitest" +import { Effect, Layer, Redacted } from "effect" +import { EmbeddingModel } from "effect/unstable/ai" +import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" + +describe("OpenAiEmbeddingModel", () => { + it.effect("model provides dimensions service", () => + Effect.gen(function*() { + const dimensions = yield* EmbeddingModel.Dimensions + assert.strictEqual(dimensions, 1536) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.model("text-embedding-3-small", { dimensions: 1536 })), + Effect.provideService(OpenAiClient.OpenAiClient, noopOpenAiClient) + )) + + it.effect("reorders embeddings by provider index", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + data: [ + { index: 1, embedding: [20, 21] }, + { index: 0, embedding: [10, 11] } + ], + model: "text-embedding-3-small", + usage: { + prompt_tokens: 7, + total_tokens: 7 + } + })) + }) + )) + ) + + const response = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embedMany(["first", "second"]) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.deepStrictEqual(response.embeddings.map((embedding) => embedding.vector), [[10, 11], [20, 21]]) + assert.strictEqual(response.usage.inputTokens, 7) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.model, "text-embedding-3-small") + assert.deepStrictEqual(requestBody.input, ["first", "second"]) + })) + + it.effect("merges config and applies withConfigOverride precedence", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + data: [{ index: 0, embedding: [1, 2, 3] }], + model: "override-model" + })) + }) + )) + ) + + yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + yield* model.embed("hello") + }).pipe( + OpenAiEmbeddingModel.withConfigOverride({ + model: "override-model", + dimensions: 1024, + user: "request-user" + }), + Effect.provide(OpenAiEmbeddingModel.layer({ + model: "base-model", + config: { + dimensions: 256, + user: "provider-user" + } + })), + Effect.provide(clientLayer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.model, "override-model") + assert.strictEqual(requestBody.dimensions, 1024) + assert.strictEqual(requestBody.user, "request-user") + assert.deepStrictEqual(requestBody.input, ["hello"]) + })) + + it.effect("fails with InvalidOutputError when provider returns duplicate indices", () => + Effect.gen(function*() { + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse(request, { + data: [ + { index: 0, embedding: [1] }, + { index: 0, embedding: [2] } + ], + model: "text-embedding-3-small", + usage: { + prompt_tokens: 2, + total_tokens: 2 + } + })) + ) + )) + ) + + const error = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embedMany(["a", "b"]).pipe(Effect.flip) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.strictEqual(error._tag, "AiError") + assert.strictEqual(error.reason._tag, "InvalidOutputError") + })) + + it.effect("fails with InvalidOutputError when provider returns non-vector embedding payload", () => + Effect.gen(function*() { + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse(request, { + data: [{ index: 0, embedding: "AQID" }], + model: "text-embedding-3-small", + usage: { + prompt_tokens: 1, + total_tokens: 1 + } + })) + ) + )) + ) + + const error = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embed("a").pipe(Effect.flip) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.strictEqual(error._tag, "AiError") + assert.strictEqual(error.reason._tag, "InvalidOutputError") + })) +}) + +const makeHttpClient = ( + handler: ( + request: HttpClientRequest.HttpClientRequest + ) => Effect.Effect +) => + HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + return yield* handler(request) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + +const jsonResponse = ( + request: HttpClientRequest.HttpClientRequest, + body: unknown +): HttpClientResponse.HttpClientResponse => + HttpClientResponse.fromWeb( + request, + new Response(JSON.stringify(body), { + status: 200, + headers: { + "content-type": "application/json" + } + }) + ) + +const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => + Effect.gen(function*() { + const body = request.body + if (body._tag === "Uint8Array") { + const text = new TextDecoder().decode(body.body) + return JSON.parse(text) + } + return yield* Effect.die(new Error("Expected Uint8Array body")) + }) + +const noopOpenAiClient: OpenAiClient.Service = { + client: undefined as unknown as OpenAiClient.Service["client"], + createResponse: () => Effect.die(new Error("noop")), + createResponseStream: () => Effect.die(new Error("noop")), + createEmbedding: () => Effect.die(new Error("noop")) +} diff --git a/.repos/effect/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts b/.repos/effect/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts new file mode 100644 index 000000000..df6edf923 --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts @@ -0,0 +1,1811 @@ +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 { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" + +describe("OpenAiLanguageModel", () => { + describe("generateText", () => { + it.effect("sends model in request and decodes text output", () => + 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: "Hello, compat!" + } + }] + }) + )) + }) + )) + ) + + const result = yield* LanguageModel.generateText({ prompt: "hello" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + assert.strictEqual(result.text, "Hello, compat!") + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.model, "gpt-4o-mini") + assert.strictEqual(requestBody.messages[0]?.content, "hello") + })) + + it.effect("forwards reasoning config to chat completions request", () => + 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: "hello" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-5", { + reasoning: { + effort: "medium", + summary: "auto" + } + })), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.deepStrictEqual(requestBody.reasoning, { + effort: "medium", + summary: "auto" + }) + })) + + it.effect("forwards custom model config properties to chat completions request", () => + 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: "hello" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini", { + vendor_setting: { + mode: "strict" + } + })), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.deepStrictEqual(requestBody.vendor_setting, { + mode: "strict" + }) + })) + + it.effect("preserves multimodal user content order in chat payload", () => + 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: [ + Prompt.textPart({ text: "first text" }), + Prompt.filePart({ + mediaType: "image/png", + data: new URL("https://example.com/image.png") + }), + Prompt.textPart({ text: "second text" }) + ] + }]) + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + const content = requestBody.messages[0]?.content + assert.isTrue(Array.isArray(content)) + assert.deepStrictEqual(content, [ + { + type: "text", + text: "first text" + }, + { + type: "image_url", + image_url: { + url: "https://example.com/image.png", + detail: "auto" + } + }, + { + type: "text", + text: "second text" + } + ]) + })) + + 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 + + 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: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [{ + id: "call_1", + type: "function", + function: { + name: "TestTool", + arguments: JSON.stringify({ input: "hello" }) + } + }] + } + }] + }) + )) + }) + )) + ) + + const result = yield* LanguageModel.generateText({ + prompt: "use the tool", + toolkit: TestToolkit, + disableToolCallResolution: true + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(TestToolkitLayer), + Effect.provide(layer) + ) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + + assert.strictEqual(toolCall.name, "TestTool") + assert.deepStrictEqual(toolCall.params, { input: "hello" }) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + const functionTool = requestBody.tools.find((tool: any) => tool.type === "function") + assert.isDefined(functionTool) + assert.strictEqual(functionTool.function.name, "TestTool") + 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 + + 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" + } + }] + }) + )) + }) + )) + ) + + 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 dynamic tool", + toolkit: Toolkit.make(DynamicTool), + disableToolCallResolution: true + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + const functionTool = requestBody.tools?.find((tool: any) => + tool.type === "function" && tool.function?.name === "DynamicTool" + ) + assert.isDefined(functionTool) + assert.strictEqual(functionTool.function.description, "A dynamic tool") + assert.deepStrictEqual(functionTool.function.parameters, inputSchema) + })) + + it.effect("maps provider apply_patch function call back to custom provider-defined tool", () => + 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: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [{ + id: "call_1", + type: "function", + function: { + name: "apply_patch", + arguments: JSON.stringify({ + call_id: "call_1", + operation: { + type: "delete_file", + path: "src/obsolete.ts" + } + }) + } + }] + } + }] + }) + )) + }) + )) + ) + + const toolkit = Toolkit.make(CompatApplyPatchTool({})) + const toolkitLayer = toolkit.toLayer({ + CompatApplyPatch: () => + Effect.succeed({ + status: "completed", + output: "deleted" + }) + }) + + const result = yield* LanguageModel.generateText({ + prompt: "delete src/obsolete.ts", + toolkit, + toolChoice: { tool: "CompatApplyPatch" }, + disableToolCallResolution: true + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + + assert.strictEqual(toolCall.name, "CompatApplyPatch") + assert.deepStrictEqual(toolCall.params, { + call_id: "call_1", + operation: { + type: "delete_file", + path: "src/obsolete.ts" + } + }) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + + const functionTool = requestBody.tools.find((tool: any) => tool.type === "function") + assert.isDefined(functionTool) + assert.strictEqual(functionTool.function.name, "apply_patch") + assert.deepStrictEqual(requestBody.tool_choice, { + type: "function", + function: { + name: "apply_patch" + } + }) + })) + + it.effect("decodes usage when token detail fields are absent", () => + 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: "stop", + message: { + role: "assistant", + content: "Hello" + } + }], + usage: { + prompt_tokens: 4, + completion_tokens: 5, + total_tokens: 9, + provider_future_field: true + } + }) + )) + ) + )) + ) + + const result = yield* LanguageModel.generateText({ prompt: "hello" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const finish = result.content.find((part) => part.type === "finish") + assert.isDefined(finish) + if (finish?.type !== "finish") { + return + } + + assert.deepStrictEqual(finish.usage.inputTokens, { + uncached: 4, + total: 4, + cacheRead: 0, + cacheWrite: undefined + }) + assert.deepStrictEqual(finish.usage.outputTokens, { + total: 5, + text: 5, + reasoning: 0 + }) + })) + + it.effect("surfaces reasoning from non-streaming responses", () => + 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: "stop", + message: { + role: "assistant", + reasoning: "I should greet the user.", + content: "Hello!" + } + }] + }) + )) + ) + )) + ) + + const result = yield* LanguageModel.generateText({ prompt: "hello" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const reasoning = result.content.find((part) => part.type === "reasoning") + assert.isDefined(reasoning) + if (reasoning?.type !== "reasoning") { + return + } + assert.strictEqual(reasoning.text, "I should greet the user.") + assert.strictEqual(result.text, "Hello!") + })) + + it.effect("surfaces reasoning_content from non-streaming responses", () => + 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: "stop", + message: { + role: "assistant", + reasoning_content: "I should greet the user.", + content: "Hello!" + } + }] + }) + )) + ) + )) + ) + + const result = yield* LanguageModel.generateText({ prompt: "hello" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const reasoning = result.content.find((part) => part.type === "reasoning") + assert.isDefined(reasoning) + if (reasoning?.type !== "reasoning") { + return + } + assert.strictEqual(reasoning.text, "I should greet the user.") + assert.strictEqual(result.text, "Hello!") + })) + }) + + describe("generateObject", () => { + it.effect("uses json_schema format for structured output", () => + 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: JSON.stringify({ name: "Ada", age: 37 }) + } + }] + }) + )) + }) + )) + ) + + const person = yield* LanguageModel.generateObject({ + prompt: "Return a person", + schema: Schema.Struct({ + name: Schema.String, + age: Schema.Number + }) + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + assert.strictEqual(person.value.name, "Ada") + assert.strictEqual(person.value.age, 37) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.response_format.type, "json_schema") + assert.strictEqual(requestBody.response_format.json_schema.strict, true) + })) + + it.effect("uses OpenAI codec transformer for optional structured fields", () => + 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: JSON.stringify({ name: "Ada", nickname: null }) + } + }] + }) + )) + }) + )) + ) + + const person = yield* LanguageModel.generateObject({ + prompt: "Return a person", + schema: Schema.Struct({ + name: Schema.String, + nickname: Schema.optionalKey(Schema.String) + }) + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + assert.strictEqual(person.value.name, "Ada") + assert.isUndefined(person.value.nickname) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.deepStrictEqual(requestBody.response_format.json_schema.schema.required, ["name", "nickname"]) + assert.deepStrictEqual(requestBody.response_format.json_schema.schema.properties.nickname, { + anyOf: [{ type: "string" }, { type: "null" }] + }) + })) + }) + + describe("streamText", () => { + it.effect("handles chat completion stream chunks", () => + 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_test123", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: {}, + finish_reason: "stop" + }] + }, + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ prompt: "test" }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const parts = Array.from(partsChunk) + + assert.isTrue(parts.some((part) => part.type === "response-metadata")) + + const finish = parts.find((part) => part.type === "finish") + assert.isDefined(finish) + if (finish?.type === "finish") { + assert.strictEqual(finish.reason, "stop") + } + })) + + 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>([]) + const requestCount = yield* Ref.make(0) + + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + yield* Ref.update(capturedRequests, (requests) => [...requests, request]) + const index = yield* Ref.getAndUpdate(requestCount, (value) => value + 1) + + if (index === 0) { + return sseResponse(request, [makeLocalShellChunk(), "[DONE]"]) + } + + return jsonResponse(request, makeChatCompletion()) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed(HttpClient.HttpClient, httpClient)) + ) + + const toolkit = Toolkit.make(CompatLocalShellTool({})) + const toolkitLayer = toolkit.toLayer({ + CompatLocalShell: () => Effect.succeed("done") + }) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Run pwd", + toolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(toolkitLayer), + 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.strictEqual(toolCall.name, "CompatLocalShell") + assert.deepStrictEqual(toolCall.params, { action: localShellAction }) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Run pwd" }, + { + role: "assistant", + content: [Prompt.toolCallPart({ + id: toolCall.id, + name: toolCall.name, + params: { action: localShellAction }, + providerExecuted: false, + options: { + openai: { + itemId: "ls_call_1" + } + } + })] + }, + { + role: "tool", + content: [Prompt.toolResultPart({ + id: toolCall.id, + name: toolCall.name, + isFailure: false, + result: "done", + providerExecuted: false + })] + } + ]), + toolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + const requests = yield* Ref.get(capturedRequests) + const followUpRequest = requests[1] + assert.isDefined(followUpRequest) + if (followUpRequest === undefined) { + return + } + + const followUpBody = yield* getRequestBody(followUpRequest) + + const localShellCall = followUpBody.messages.find((item: any) => + 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") + assert.isDefined(localShellOutput) + assert.strictEqual(localShellOutput.tool_call_id, toolCall.id) + assert.strictEqual(localShellOutput.content, "done") + })) + + it.effect("maps apply_patch stream tool calls to custom provider-defined tool", () => + 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_apply_patch_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + id: "patch_call_1", + type: "function", + function: { + name: "apply_patch", + arguments: JSON.stringify({ + call_id: "patch_call_1", + operation: { + type: "delete_file", + path: "src/legacy.ts" + } + }) + } + }] + }, + finish_reason: "tool_calls" + }] + }, + "[DONE]" + ])) + ) + )) + ) + + const toolkit = Toolkit.make(CompatApplyPatchTool({})) + const toolkitLayer = toolkit.toLayer({ + CompatApplyPatch: () => + Effect.succeed({ + status: "completed", + output: "deleted" + }) + }) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Delete src/legacy.ts", + toolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(toolkitLayer), + 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.strictEqual(toolCall.name, "CompatApplyPatch") + assert.deepStrictEqual(toolCall.params, { + call_id: "patch_call_1", + operation: { + type: "delete_file", + path: "src/legacy.ts" + } + }) + })) + + it.effect("preserves fragmented stream tool call ids and names", () => + Effect.gen(function*() { + const expectedParams = { + call_id: "patch_call_2", + operation: { + type: "delete_file", + path: "src/fragmented.ts" + } + } + const toolArguments = JSON.stringify(expectedParams) + + 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_apply_patch_fragmented_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + id: "patch_call_2", + type: "function", + function: { + name: "apply_patch", + arguments: toolArguments.slice(0, 24) + } + }] + }, + finish_reason: null + }] + }, + { + id: "chatcmpl_apply_patch_fragmented_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + function: { + arguments: toolArguments.slice(24, 48) + } + }] + }, + finish_reason: null + }] + }, + { + id: "chatcmpl_apply_patch_fragmented_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + function: { + arguments: toolArguments.slice(48) + } + }] + }, + finish_reason: "tool_calls" + }] + }, + "[DONE]" + ])) + ) + )) + ) + + const toolkit = Toolkit.make(CompatApplyPatchTool({})) + const toolkitLayer = toolkit.toLayer({ + CompatApplyPatch: () => + Effect.succeed({ + status: "completed", + output: "deleted" + }) + }) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Delete src/fragmented.ts", + toolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + + const start = parts.find((part) => part.type === "tool-params-start" && part.id === "patch_call_2") + assert.isDefined(start) + if (start?.type !== "tool-params-start") { + return + } + assert.strictEqual(start.name, "CompatApplyPatch") + + assert.deepStrictEqual(decodeToolParamsFromStream(parts, "patch_call_2"), expectedParams) + + const toolCall = parts.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + + assert.strictEqual(toolCall.id, "patch_call_2") + assert.strictEqual(toolCall.name, "CompatApplyPatch") + assert.deepStrictEqual(toolCall.params, expectedParams) + })) + + 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", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { content: "Hello" }, + finish_reason: null + }] + }, + { + type: "provider.chat.completion.delta", + provider_payload: { content: "provider-specific" } + }, + { + id: "chatcmpl_stream_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: {}, + finish_reason: "stop" + }], + usage: { + prompt_tokens: 10, + completion_tokens: 7, + total_tokens: 17, + prompt_tokens_details: { + cached_tokens: 3 + }, + completion_tokens_details: { + reasoning_tokens: 2 + } + }, + provider_future_field: { accepted: true } + }, + "[DONE]" + ] + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(sseResponse(request, events)) + }) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ prompt: "hello" }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + const metadata = parts.find((part) => part.type === "response-metadata") + const finish = parts.find((part) => part.type === "finish") + const deltas = parts.filter((part) => part.type === "text-delta") + + assert.isDefined(metadata) + assert.isDefined(finish) + assert.strictEqual(deltas.length, 1) + assert.strictEqual(deltas[0]?.delta, "Hello") + if (finish?.type === "finish") { + assert.strictEqual(finish.reason, "stop") + assert.deepStrictEqual(finish.usage.inputTokens, { + uncached: 7, + total: 10, + cacheRead: 3, + cacheWrite: undefined + }) + assert.deepStrictEqual(finish.usage.outputTokens, { + total: 7, + text: 5, + reasoning: 2 + }) + } + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.stream, true) + assert.isTrue(capturedRequest.url.endsWith("/chat/completions")) + })) + + it.effect("assembles streamed tool args when continuation fragments have function.name: null", () => + Effect.gen(function*() { + // Some OpenAI-compatible providers (e.g. Fireworks) only send the tool + // name on the first fragment and `function.name: null` on every + // continuation. The argument fragments live on those continuations, so + // they must not be dropped during chunk validation. + const chunk = (fnDelta: Record) => ({ + id: "chatcmpl_null_name_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { tool_calls: [{ index: 0, id: "call_1", type: "function", function: fnDelta }] } + }] + }) + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + chunk({ name: "TestTool", arguments: "" }), + chunk({ name: null, arguments: "{\"in" }), + chunk({ name: null, arguments: "put\":\"hel" }), + chunk({ name: null, arguments: "lo\"}" }), + { + id: "chatcmpl_null_name_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }] + }, + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "use the tool", + toolkit: TestToolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(TestToolkitLayer), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + const paramsDeltas = parts.filter((part) => part.type === "tool-params-delta") + assert.isAbove(paramsDeltas.length, 0) + + const toolCall = parts.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + assert.strictEqual(toolCall.name, "TestTool") + assert.deepStrictEqual(toolCall.params, { input: "hello" }) + })) + + it.effect("emits reasoning lifecycle parts for delta.reasoning", () => + Effect.gen(function*() { + const chunk = (delta: Record, finishReason: string | null = null) => ({ + id: "chatcmpl_reasoning_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ index: 0, delta, finish_reason: finishReason }] + }) + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + chunk({ reasoning: "Let me think" }), + chunk({ reasoning: " about this." }), + chunk({ content: "Hello" }), + chunk({ content: " there" }, "stop"), + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ prompt: "test" }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + assert.deepStrictEqual(parts.map((part) => part.type), [ + "response-metadata", + "reasoning-start", + "reasoning-delta", + "reasoning-delta", + "reasoning-end", + "text-start", + "text-delta", + "text-delta", + "text-end", + "finish" + ]) + + const reasoningText = parts + .flatMap((part) => part.type === "reasoning-delta" ? [part.delta] : []) + .join("") + assert.strictEqual(reasoningText, "Let me think about this.") + + const reasoningIds = parts.flatMap((part) => + part.type === "reasoning-start" || part.type === "reasoning-delta" || part.type === "reasoning-end" + ? [part.id] + : [] + ) + assert.strictEqual(new Set(reasoningIds).size, 1) + + const textIds = parts.flatMap((part) => part.type === "text-start" ? [part.id] : []) + assert.notStrictEqual(reasoningIds[0], textIds[0]) + })) + + it.effect("emits reasoning lifecycle parts for delta.reasoning_content", () => + Effect.gen(function*() { + const chunk = (delta: Record, finishReason: string | null = null) => ({ + id: "chatcmpl_reasoning_2", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ index: 0, delta, finish_reason: finishReason }] + }) + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + chunk({ reasoning_content: "Thinking..." }), + chunk({ content: "Answer." }, "stop"), + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ prompt: "test" }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + assert.deepStrictEqual(parts.map((part) => part.type), [ + "response-metadata", + "reasoning-start", + "reasoning-delta", + "reasoning-end", + "text-start", + "text-delta", + "text-end", + "finish" + ]) + + const reasoningText = parts + .flatMap((part) => part.type === "reasoning-delta" ? [part.delta] : []) + .join("") + assert.strictEqual(reasoningText, "Thinking...") + })) + + it.effect("closes an open reasoning part at stream end", () => + 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_reasoning_3", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ index: 0, delta: { reasoning: "Only thoughts." }, finish_reason: "stop" }] + }, + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ prompt: "test" }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + assert.deepStrictEqual(parts.map((part) => part.type), [ + "response-metadata", + "reasoning-start", + "reasoning-delta", + "reasoning-end", + "finish" + ]) + })) + }) + + describe("config", () => { + it.effect("does not leak library-only fields into request body", () => + 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: "test" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini", { + fileIdPrefixes: ["file-"], + strictJsonSchema: false, + temperature: 0.5 + })), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) return + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.fileIdPrefixes, undefined) + assert.strictEqual(requestBody.strictJsonSchema, undefined) + assert.strictEqual(requestBody.temperature, 0.5) + })) + }) +}) + +const TestTool = Tool.make("TestTool", { + description: "A test tool", + parameters: Schema.Struct({ input: Schema.String }), + success: Schema.Struct({ output: Schema.String }) +}) + +const TestToolkit = Toolkit.make(TestTool) + +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", + providerName: "apply_patch", + requiresHandler: true, + parameters: Schema.Struct({ + call_id: Schema.String, + operation: Schema.Any + }), + success: Schema.Struct({ + status: Schema.Literals(["completed", "failed"]), + output: Schema.optionalKey(Schema.NullOr(Schema.String)) + }) +}) + +const localShellAction = { + type: "exec", + command: ["pwd"], + env: {} +} + +const CompatLocalShellTool = Tool.providerDefined({ + id: "compat.local_shell", + customName: "CompatLocalShell", + providerName: "local_shell", + requiresHandler: true, + parameters: Schema.Struct({ + action: Schema.Any + }), + success: Schema.String +}) + +const makeHttpClient = ( + handler: ( + request: HttpClientRequest.HttpClientRequest + ) => Effect.Effect +) => + HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + return yield* handler(request) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + +const makeChatCompletion = (overrides: Record = {}) => ({ + id: "chatcmpl_test_1", + object: "chat.completion", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + finish_reason: "stop", + message: { + role: "assistant", + content: "" + } + }], + ...overrides +}) + +const makeLocalShellChunk = () => ({ + id: "chatcmpl_local_shell_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + id: "local_shell_call_1", + type: "function", + function: { + name: "local_shell", + arguments: JSON.stringify({ action: localShellAction }) + } + }] + }, + finish_reason: "tool_calls" + }] +}) + +const jsonResponse = ( + request: HttpClientRequest.HttpClientRequest, + body: unknown +): HttpClientResponse.HttpClientResponse => + HttpClientResponse.fromWeb( + request, + new Response(JSON.stringify(body), { + status: 200, + headers: { + "content-type": "application/json" + } + }) + ) + +const sseResponse = ( + request: HttpClientRequest.HttpClientRequest, + events: ReadonlyArray +): HttpClientResponse.HttpClientResponse => + HttpClientResponse.fromWeb( + request, + new Response(toSseBody(events), { + status: 200, + headers: { + "content-type": "text/event-stream" + } + }) + ) + +const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => + Effect.gen(function*() { + const body = request.body + if (body._tag === "Uint8Array") { + const text = new TextDecoder().decode(body.body) + return JSON.parse(text) + } + return yield* Effect.die(new Error("Expected Uint8Array body")) + }) + +const decodeToolParamsFromStream = ( + parts: ReadonlyArray, + toolCallId: string +): Record => { + const start = parts.find((part) => part.type === "tool-params-start" && part.id === toolCallId) + const end = parts.find((part) => part.type === "tool-params-end" && part.id === toolCallId) + assert.isDefined(start) + assert.isDefined(end) + + const deltas = parts + .filter((part) => part.type === "tool-params-delta" && part.id === toolCallId) + .map((part) => part.delta) + .join("") + + return JSON.parse(deltas) as Record +} + +const toSseBody = (events: ReadonlyArray): string => + events.map((event) => { + if (typeof event === "string") { + return `data: ${event}\n\n` + } + return `data: ${JSON.stringify(event)}\n\n` + }).join("") diff --git a/.repos/effect/packages/ai/openai-compat/tsconfig.json b/.repos/effect/packages/ai/openai-compat/tsconfig.json new file mode 100644 index 000000000..e2a8ca19a --- /dev/null +++ b/.repos/effect/packages/ai/openai-compat/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../../effect" } + ] +} diff --git a/.repos/effect/packages/ai/openai-compat/typetest/OpenAiConfig.tst.ts b/.repos/effect/packages/ai/openai-compat/typetest/OpenAiConfig.tst.ts new file mode 100644 index 000000000..b87ba69e8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/openai/CHANGELOG.md b/.repos/effect/packages/ai/openai/CHANGELOG.md new file mode 100644 index 000000000..cb5fb0a47 --- /dev/null +++ b/.repos/effect/packages/ai/openai/CHANGELOG.md @@ -0,0 +1,902 @@ +# @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 + +- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]: + - effect@4.0.0-beta.90 + +## 4.0.0-beta.89 + +### Patch Changes + +- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]: + - effect@4.0.0-beta.89 + +## 4.0.0-beta.88 + +### Patch Changes + +- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]: + - effect@4.0.0-beta.88 + +## 4.0.0-beta.87 + +### Patch Changes + +- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]: + - effect@4.0.0-beta.87 + +## 4.0.0-beta.86 + +### Patch Changes + +- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]: + - effect@4.0.0-beta.86 + +## 4.0.0-beta.85 + +### Patch Changes + +- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]: + - effect@4.0.0-beta.85 + +## 4.0.0-beta.84 + +### Patch Changes + +- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]: + - effect@4.0.0-beta.84 + +## 4.0.0-beta.83 + +### Patch Changes + +- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]: + - effect@4.0.0-beta.83 + +## 4.0.0-beta.82 + +### Patch Changes + +- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]: + - effect@4.0.0-beta.82 + +## 4.0.0-beta.81 + +### Patch Changes + +- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]: + - effect@4.0.0-beta.81 + +## 4.0.0-beta.80 + +### Patch Changes + +- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]: + - effect@4.0.0-beta.80 + +## 4.0.0-beta.79 + +### Patch Changes + +- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]: + - effect@4.0.0-beta.79 + +## 4.0.0-beta.78 + +### Patch Changes + +- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]: + - effect@4.0.0-beta.78 + +## 4.0.0-beta.77 + +### Patch Changes + +- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]: + - effect@4.0.0-beta.77 + +## 4.0.0-beta.76 + +### Patch Changes + +- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]: + - effect@4.0.0-beta.76 + +## 4.0.0-beta.75 + +### Patch Changes + +- [#2312](https://github.com/Effect-TS/effect-smol/pull/2312) [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e) Thanks @gcanti! - Validate `Schema.StructWithRest` fixed fields against rest index signatures at the type level so schemas cannot be constructed with incompatible decoded, encoded, or make shapes. This keeps `StructWithRest` types sound and updates the generated OpenAI conversation-items request schema to keep accepting arbitrary additional fields under the stricter validation. + +- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]: + - effect@4.0.0-beta.75 + +## 4.0.0-beta.74 + +### Patch Changes + +- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]: + - effect@4.0.0-beta.74 + +## 4.0.0-beta.73 + +### Patch Changes + +- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]: + - effect@4.0.0-beta.73 + +## 4.0.0-beta.72 + +### Patch Changes + +- [#2280](https://github.com/Effect-TS/effect-smol/pull/2280) [`fa07f9e`](https://github.com/Effect-TS/effect-smol/commit/fa07f9e7138bde3e9404aa91c22b41e273aba091) Thanks @tim-smart! - improve openai websocket error status + +- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]: + - effect@4.0.0-beta.72 + +## 4.0.0-beta.71 + +### Patch Changes + +- [#2279](https://github.com/Effect-TS/effect-smol/pull/2279) [`1fab4ac`](https://github.com/Effect-TS/effect-smol/commit/1fab4acc63818dece3a0e732b1a71843f0759f21) Thanks @tim-smart! - handle missing output array in openai responses + +- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]: + - effect@4.0.0-beta.71 + +## 4.0.0-beta.70 + +### Patch Changes + +- [#2224](https://github.com/Effect-TS/effect-smol/pull/2224) [`74d8f37`](https://github.com/Effect-TS/effect-smol/commit/74d8f37a05ce755ff09cff48f992cb80ce9e4769) Thanks @aniravi24! - Fix `OpenAIFile` schema decode failure on responses where `expires_at` and `status_details` are returned as literal `null`. The OpenAI files endpoint returns `null` (not omitted) for these fields when no expiration / status detail applies (e.g. uploads with `purpose: "user_data"`), but the upstream OpenAPI spec marks them only as optional. Codegen patches widen both fields to allow `null`, which now decodes cleanly via `OpenAiClient.createFile`, `retrieveFile`, `listFiles`, and any other endpoint returning the `OpenAIFile` shape. + +- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]: + - effect@4.0.0-beta.70 + +## 4.0.0-beta.69 + +### Patch Changes + +- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]: + - effect@4.0.0-beta.69 + +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + +## 4.0.0-beta.67 + +### Patch Changes + +- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]: + - effect@4.0.0-beta.67 + +## 4.0.0-beta.66 + +### Patch Changes + +- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]: + - effect@4.0.0-beta.66 + +## 4.0.0-beta.65 + +### Patch Changes + +- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]: + - effect@4.0.0-beta.65 + +## 4.0.0-beta.64 + +### Patch Changes + +- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]: + - effect@4.0.0-beta.64 + +## 4.0.0-beta.63 + +### Patch Changes + +- [#2133](https://github.com/Effect-TS/effect-smol/pull/2133) [`5be0aaa`](https://github.com/Effect-TS/effect-smol/commit/5be0aaad694c9eb943a710eb4f896bc4c3fcae99) Thanks @Zelys-DFKH! - Fix `OpenAiLanguageModel` leaking library-only config fields (`fileIdPrefixes`, `strictJsonSchema`) into request body, causing OpenAI 400 errors. + +- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]: + - effect@4.0.0-beta.63 + +## 4.0.0-beta.62 + +### Patch Changes + +- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]: + - effect@4.0.0-beta.62 + +## 4.0.0-beta.61 + +### Patch Changes + +- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]: + - effect@4.0.0-beta.61 + +## 4.0.0-beta.60 + +### Patch Changes + +- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]: + - effect@4.0.0-beta.60 + +## 4.0.0-beta.59 + +### Patch Changes + +- [#2101](https://github.com/Effect-TS/effect-smol/pull/2101) [`953edef`](https://github.com/Effect-TS/effect-smol/commit/953edef2e1ade369e530017d64391281ef22f28f) Thanks @tim-smart! - don't omit reasoning from openai config + +- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]: + - effect@4.0.0-beta.59 + +## 4.0.0-beta.58 + +### Patch Changes + +- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]: + - effect@4.0.0-beta.58 + +## 4.0.0-beta.57 + +### Patch Changes + +- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]: + - effect@4.0.0-beta.57 + +## 4.0.0-beta.56 + +### Patch Changes + +- [#1933](https://github.com/Effect-TS/effect-smol/pull/1933) [`c5a7327`](https://github.com/Effect-TS/effect-smol/commit/c5a732746690185b8bb7935d0cf26a5ef21658e4) Thanks @IMax153! - Accept both `in-memory` and `in_memory` for OpenAI `prompt_cache_retention` schema fields. + +- [#1933](https://github.com/Effect-TS/effect-smol/pull/1933) [`c5a7327`](https://github.com/Effect-TS/effect-smol/commit/c5a732746690185b8bb7935d0cf26a5ef21658e4) Thanks @IMax153! - Fix OpenAI MCP tool call handling to keep the canonical `OpenAiMcp` tool name across response and stream paths, including approval flows. + +- Updated dependencies []: + - effect@4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]: + - effect@4.0.0-beta.55 + +## 4.0.0-beta.54 + +### Patch Changes + +- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]: + - effect@4.0.0-beta.54 + +## 4.0.0-beta.53 + +### Patch Changes + +- [#2060](https://github.com/Effect-TS/effect-smol/pull/2060) [`aef2b1c`](https://github.com/Effect-TS/effect-smol/commit/aef2b1c0130a61a430f116465e4c200c51bcd9e2) Thanks @tim-smart! - add back openai reasoning types + +- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]: + - effect@4.0.0-beta.53 + +## 4.0.0-beta.52 + +### Minor Changes + +- [#2059](https://github.com/Effect-TS/effect-smol/pull/2059) [`6f73f92`](https://github.com/Effect-TS/effect-smol/commit/6f73f92291733dc1e970e222e63aba865183a072) Thanks @tim-smart! - Add a new public `OpenAiSchema` module with minimal local schemas for responses, streaming SSE events (including unknown-event fallback), and embeddings. + +### Patch Changes + +- [#2059](https://github.com/Effect-TS/effect-smol/pull/2059) [`6f73f92`](https://github.com/Effect-TS/effect-smol/commit/6f73f92291733dc1e970e222e63aba865183a072) Thanks @tim-smart! - Add a new `OpenAiClientGenerated` module that exposes the generated OpenAI client as a dedicated context service with `make`, `layer`, and `layerConfig` constructors. This provides a compatibility path for direct generated-client access while preserving existing auth, base URL, header, and `OpenAiConfig.transformClient` wiring. + +- [#2059](https://github.com/Effect-TS/effect-smol/pull/2059) [`6f73f92`](https://github.com/Effect-TS/effect-smol/commit/6f73f92291733dc1e970e222e63aba865183a072) Thanks @tim-smart! - Refactor `OpenAiClient` to the handwritten minimal-schema path so `client` now exposes the configured `HttpClient`, `createResponse` / `createResponseStream` / `createEmbedding` use `OpenAiSchema` request-response types, and websocket mode no longer depends on generated-client internals. + + Also migrate OpenAI language and embedding model request-response typing to `OpenAiSchema` and make embedding decoding explicitly reject non-vector (string/base64) payloads with `InvalidOutputError`. + +- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]: + - effect@4.0.0-beta.52 + +## 4.0.0-beta.51 + +### Patch Changes + +- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]: + - effect@4.0.0-beta.51 + +## 4.0.0-beta.50 + +### Patch Changes + +- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]: + - effect@4.0.0-beta.50 + +## 4.0.0-beta.49 + +### Patch Changes + +- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]: + - effect@4.0.0-beta.49 + +## 4.0.0-beta.48 + +### Patch Changes + +- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]: + - effect@4.0.0-beta.48 + +## 4.0.0-beta.47 + +### Patch Changes + +- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]: + - effect@4.0.0-beta.47 + +## 4.0.0-beta.46 + +### Patch Changes + +- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]: + - effect@4.0.0-beta.46 + +## 4.0.0-beta.45 + +### Patch Changes + +- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]: + - effect@4.0.0-beta.45 + +## 4.0.0-beta.44 + +### Patch Changes + +- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename the `ServiceMap` module to `Context` across exports, docs, and tests. + +- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]: + - effect@4.0.0-beta.44 + +## 4.0.0-beta.43 + +### Patch Changes + +- [#1905](https://github.com/Effect-TS/effect-smol/pull/1905) [`fc1444d`](https://github.com/Effect-TS/effect-smol/commit/fc1444df6fb7223ed228650c5c927b1df54a8757) Thanks @tim-smart! - openai ws tweaks + +- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]: + - effect@4.0.0-beta.43 + +## 4.0.0-beta.42 + +### Patch Changes + +- [#1859](https://github.com/Effect-TS/effect-smol/pull/1859) [`a4809db`](https://github.com/Effect-TS/effect-smol/commit/a4809db80f65601d37ccafc13d773e55909c4e98) Thanks @lloydrichards! - add dynamic tooling for openai and openai-compact language models + +- [#1890](https://github.com/Effect-TS/effect-smol/pull/1890) [`4549175`](https://github.com/Effect-TS/effect-smol/commit/454917547e8c305707f0777f4efac7adaa924f00) Thanks @tim-smart! - reset openai websocket on error + +- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]: + - effect@4.0.0-beta.42 + +## 4.0.0-beta.41 + +### Patch Changes + +- [#1887](https://github.com/Effect-TS/effect-smol/pull/1887) [`f291344`](https://github.com/Effect-TS/effect-smol/commit/f2913443d99146a6b36d8437e50b94f7c03c1284) Thanks @tim-smart! - make defects a retryable network error in websocket mode + +- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]: + - effect@4.0.0-beta.41 + +## 4.0.0-beta.40 + +### Patch Changes + +- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]: + - effect@4.0.0-beta.40 + +## 4.0.0-beta.39 + +### Patch Changes + +- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]: + - effect@4.0.0-beta.39 + +## 4.0.0-beta.38 + +### Patch Changes + +- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]: + - effect@4.0.0-beta.38 + +## 4.0.0-beta.37 + +### Patch Changes + +- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]: + - effect@4.0.0-beta.37 + +## 4.0.0-beta.36 + +### Patch Changes + +- [#1807](https://github.com/Effect-TS/effect-smol/pull/1807) [`1bbd4d9`](https://github.com/Effect-TS/effect-smol/commit/1bbd4d9d617e683a7f338ddd40d11f5838c722bd) Thanks @tim-smart! - Handle streamed OpenAI function calls from `response.function_call_arguments.done` so tool calls are emitted even when `response.output_item.done` is missing. + +- [#1809](https://github.com/Effect-TS/effect-smol/pull/1809) [`126db2e`](https://github.com/Effect-TS/effect-smol/commit/126db2e38774c8ef4934321a70d39428ed4491ed) Thanks @tim-smart! - Fix OpenAI reasoning stream state handling so out-of-order reasoning summary events do not crash when prior reasoning item state is missing. + +- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]: + - effect@4.0.0-beta.36 + +## 4.0.0-beta.35 + +### Patch Changes + +- [#1776](https://github.com/Effect-TS/effect-smol/pull/1776) [`f5e553d`](https://github.com/Effect-TS/effect-smol/commit/f5e553d133b8e0a303f6c82624d2fec4ab8043da) Thanks @tim-smart! - improve openai socket errors + +- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]: + - effect@4.0.0-beta.35 + +## 4.0.0-beta.34 + +### Patch Changes + +- [#1759](https://github.com/Effect-TS/effect-smol/pull/1759) [`8feecd2`](https://github.com/Effect-TS/effect-smol/commit/8feecd24158f254ca0571a1ddb554b560ed3177d) Thanks @tim-smart! - Ensure OpenAiSocket sends a `{"type":"response.cancel"}` websocket event when a response stream is interrupted. + +- [#1764](https://github.com/Effect-TS/effect-smol/pull/1764) [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f) Thanks @tim-smart! - Add unstable EmbeddingModel support across core and OpenAI providers. + - Add the unstable EmbeddingModel module API surface in `effect`, including service, request, response, and provider types. + - Implement the unstable EmbeddingModel runtime constructor in `effect`, with `RequestResolver` batching, `embed` / `embedMany` spans, provider error propagation, deterministic ordering, and empty-input `embedMany` fast-path behavior. + - Add and align EmbeddingModel behavior tests in `effect` for embedding usage, batching, ordering, and error handling. + - Add `OpenAiEmbeddingModel` in `@effect/ai-openai`, including model / make / layer constructors, config overrides, and provider output index validation with deterministic reordering. + - Add OpenAI-compatible EmbeddingModel provider support in `@effect/ai-openai-compat`, including config overrides, layer constructors, and output index validation. + +- [#1771](https://github.com/Effect-TS/effect-smol/pull/1771) [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e) Thanks @tim-smart! - Add `EmbeddingModel.ModelDimensions` and require dimensions in embedding provider `model` constructors. + +- [#1765](https://github.com/Effect-TS/effect-smol/pull/1765) [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2) Thanks @tim-smart! - retry incremental prompt on invalid request + +- [#1760](https://github.com/Effect-TS/effect-smol/pull/1760) [`273f4c6`](https://github.com/Effect-TS/effect-smol/commit/273f4c6517f5adc4994734a46698c52015890d1a) Thanks @tim-smart! - handle openai ws error events + +- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]: + - effect@4.0.0-beta.34 + +## 4.0.0-beta.33 + +### Patch Changes + +- [#1638](https://github.com/Effect-TS/effect-smol/pull/1638) [`e198fea`](https://github.com/Effect-TS/effect-smol/commit/e198fea76c89a36c6f05ea7b7d11d222cf5a240c) Thanks @tim-smart! - add OpenAiClient.withWebSocketMode + +- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]: + - effect@4.0.0-beta.33 + +## 4.0.0-beta.32 + +### Patch Changes + +- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]: + - effect@4.0.0-beta.32 + +## 4.0.0-beta.31 + +### Patch Changes + +- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]: + - effect@4.0.0-beta.31 + +## 4.0.0-beta.30 + +### Patch Changes + +- [#1679](https://github.com/Effect-TS/effect-smol/pull/1679) [`d440fd4`](https://github.com/Effect-TS/effect-smol/commit/d440fd4e79f5943998249f5e67fb42505bd20228) Thanks @tim-smart! - Add support for OpenAI `keepalive` response stream events. + +- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]: + - effect@4.0.0-beta.30 + +## 4.0.0-beta.29 + +### Patch Changes + +- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]: + - effect@4.0.0-beta.29 + +## 4.0.0-beta.28 + +### Patch Changes + +- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]: + - effect@4.0.0-beta.28 + +## 4.0.0-beta.27 + +### Patch Changes + +- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]: + - effect@4.0.0-beta.27 + +## 4.0.0-beta.26 + +### Patch Changes + +- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]: + - effect@4.0.0-beta.26 + +## 4.0.0-beta.25 + +### Patch Changes + +- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]: + - effect@4.0.0-beta.25 + +## 4.0.0-beta.24 + +### Patch Changes + +- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]: + - effect@4.0.0-beta.24 + +## 4.0.0-beta.23 + +### Patch Changes + +- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]: + - effect@4.0.0-beta.23 + +## 4.0.0-beta.22 + +### Patch Changes + +- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]: + - effect@4.0.0-beta.22 + +## 4.0.0-beta.21 + +### Patch Changes + +- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]: + - effect@4.0.0-beta.21 + +## 4.0.0-beta.20 + +### Patch Changes + +- [#1529](https://github.com/Effect-TS/effect-smol/pull/1529) [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8) Thanks @tim-smart! - Add dedicated AiError metadata interfaces per reason so provider packages can safely augment metadata without conflicting module declarations. + +- [#1528](https://github.com/Effect-TS/effect-smol/pull/1528) [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34) Thanks @tim-smart! - Add `Model.ModelName` and provide it from AI model constructors. + +- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]: + - effect@4.0.0-beta.20 + +## 4.0.0-beta.19 + +### Patch Changes + +- [#1522](https://github.com/Effect-TS/effect-smol/pull/1522) [`19984c2`](https://github.com/Effect-TS/effect-smol/commit/19984c2c1b23ef06d69f9bbd4c4ad992a03c6860) Thanks @IMax153! - Fix missing `yield*` in `OpenAiLanguageModel.prepareResponseFormat` + +- Updated dependencies []: + - effect@4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Patch Changes + +- [#1520](https://github.com/Effect-TS/effect-smol/pull/1520) [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d) Thanks @IMax153! - Ensure that OpenAI JSON schemas for tool calls and structured outputs are properly transformed + +- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]: + - effect@4.0.0-beta.18 + +## 4.0.0-beta.17 + +### Patch Changes + +- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]: + - effect@4.0.0-beta.17 + +## 4.0.0-beta.16 + +### Patch Changes + +- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]: + - effect@4.0.0-beta.16 + +## 4.0.0-beta.15 + +### Patch Changes + +- [#1502](https://github.com/Effect-TS/effect-smol/pull/1502) [`285b7e6`](https://github.com/Effect-TS/effect-smol/commit/285b7e667167566d5788367d5155b19c79f1bf22) Thanks @tim-smart! - allow undefined for ai config + +- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]: + - effect@4.0.0-beta.15 + +## 4.0.0-beta.14 + +### Patch Changes + +- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]: + - effect@4.0.0-beta.14 + +## 4.0.0-beta.13 + +### Patch Changes + +- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]: + - effect@4.0.0-beta.13 + +## 4.0.0-beta.12 + +### Patch Changes + +- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]: + - effect@4.0.0-beta.12 + +## 4.0.0-beta.11 + +### Patch Changes + +- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]: + - effect@4.0.0-beta.11 + +## 4.0.0-beta.10 + +### Patch Changes + +- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]: + - effect@4.0.0-beta.10 + +## 4.0.0-beta.9 + +### Patch Changes + +- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]: + - effect@4.0.0-beta.9 + +## 4.0.0-beta.8 + +### Patch Changes + +- [#1354](https://github.com/Effect-TS/effect-smol/pull/1354) [`b94962c`](https://github.com/Effect-TS/effect-smol/commit/b94962c249d46cf96cdf2e41188dc9feda41536a) Thanks @IMax153! - Fix the generated schemas for ai providers + +- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]: + - effect@4.0.0-beta.8 + +## 4.0.0-beta.7 + +### Patch Changes + +- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]: + - effect@4.0.0-beta.7 + +## 4.0.0-beta.6 + +### Patch Changes + +- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]: + - effect@4.0.0-beta.6 + +## 4.0.0-beta.5 + +### Patch Changes + +- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]: + - effect@4.0.0-beta.5 + +## 4.0.0-beta.4 + +### Patch Changes + +- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]: + - effect@4.0.0-beta.4 + +## 4.0.0-beta.3 + +### Patch Changes + +- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]: + - effect@4.0.0-beta.3 + +## 4.0.0-beta.2 + +### Patch Changes + +- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]: + - effect@4.0.0-beta.2 + +## 4.0.0-beta.1 + +### Patch Changes + +- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]: + - effect@4.0.0-beta.1 + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta + +### Patch Changes + +- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]: + - effect@4.0.0-beta.0 diff --git a/.repos/effect/packages/ai/openai/README.md b/.repos/effect/packages/ai/openai/README.md new file mode 100644 index 000000000..2ef377e34 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/openai/codegen.yaml b/.repos/effect/packages/ai/openai/codegen.yaml new file mode 100644 index 000000000..05724087a --- /dev/null +++ b/.repos/effect/packages/ai/openai/codegen.yaml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=../../tools/ai-codegen/codegen.schema.json +spec: https://app.stainless.com/api/spec/documented/openai/openapi.documented.yml +output: src/Generated.ts +name: OpenAiClient +header: | + /** + * @since 1.0.0 + */ +patches: + - '[{"op":"replace","path":"/openapi","value":"3.0.0"}]' + - '[{"op":"add","path":"/components/schemas/ModelResponseProperties/properties/user/nullable","value":true}]' + - '[{"op":"add","path":"/components/schemas/ModelResponseProperties/properties/safety_identifier/nullable","value":true}]' + - '[{"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":"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"}]}}]' + - '[{"op":"add","path":"/components/schemas/OpenAIFile/properties/status_details/nullable","value":true}]' + # Add missing keepalive stream event support + - '[{"op":"add","path":"/components/schemas/ResponseKeepAliveEvent","value":{"type":"object","title":"Keep alive","description":"A keepalive event emitted during long-running response streams.","properties":{"type":{"type":"string","enum":["keepalive"],"description":"The type of the keepalive event. Always `keepalive`.","default":"keepalive","x-stainless-const":true},"sequence_number":{"type":"integer","description":"The sequence number of this keepalive event."}},"required":["type","sequence_number"]}}]' + - '[{"op":"add","path":"/components/schemas/ResponseStreamEvent/anyOf/-","value":{"$ref":"#/components/schemas/ResponseKeepAliveEvent"}}]' + # Add missing ResponseApplyPatchCallOperationDiffDeltaEvent schema + - '[{"op":"add","path":"/components/schemas/ResponseApplyPatchCallOperationDiffDeltaEvent","value":{"title":"ResponseApplyPatchCallOperationDiffDelta","type":"object","description":"Event representing a delta for an apply_patch tool call operation diff.","properties":{"type":{"type":"string","enum":["response.apply_patch_call_operation_diff.delta"],"description":"The event type identifier.","x-stainless-const":true},"sequence_number":{"type":"integer","description":"The sequence number of this event."},"output_index":{"type":"integer","description":"The index of the output this delta applies to."},"item_id":{"type":"string","description":"Unique identifier for the API item associated with this event."},"delta":{"type":"string","description":"The incremental diff data for the apply_patch tool call."}},"required":["type","output_index","item_id","delta","sequence_number"]}}]' + # Add missing ResponseApplyPatchCallOperationDiffDoneEvent schema (delta is optional - not always sent by OpenAI) + - '[{"op":"add","path":"/components/schemas/ResponseApplyPatchCallOperationDiffDoneEvent","value":{"title":"ResponseApplyPatchCallOperationDiffDone","type":"object","description":"Event indicating that the operation diff for an apply_patch tool call is complete.","properties":{"type":{"type":"string","enum":["response.apply_patch_call_operation_diff.done"],"description":"The event type identifier.","x-stainless-const":true},"sequence_number":{"type":"integer","description":"The sequence number of this event."},"output_index":{"type":"integer","description":"The index of the output this event applies to."},"item_id":{"type":"string","description":"Unique identifier for the API item associated with this event."},"delta":{"type":"string","description":"The final diff data for the apply_patch tool call."}},"required":["type","output_index","item_id","sequence_number"]}}]' + # Add both events to ResponseStreamEvent anyOf array + - '[{"op":"add","path":"/components/schemas/ResponseStreamEvent/anyOf/-","value":{"$ref":"#/components/schemas/ResponseApplyPatchCallOperationDiffDeltaEvent"}}]' + - '[{"op":"add","path":"/components/schemas/ResponseStreamEvent/anyOf/-","value":{"$ref":"#/components/schemas/ResponseApplyPatchCallOperationDiffDoneEvent"}}]' +excludeAnnotations: + - examples + - default +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 + - 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 }" diff --git a/.repos/effect/packages/ai/openai/package.json b/.repos/effect/packages/ai/openai/package.json new file mode 100644 index 000000000..52e041041 --- /dev/null +++ b/.repos/effect/packages/ai/openai/package.json @@ -0,0 +1,69 @@ +{ + "name": "@effect/ai-openai", + "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.git", + "directory": "packages/ai/openai" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "ai", + "openai" + ], + "keywords": [ + "typescript", + "ai", + "openai" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./internal/*": null, + "./index": null, + "./*/index": null + } + }, + "scripts": { + "codegen": "effect-utils codegen", + "build": "tsc -b tsconfig.json && pnpm babel", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "devDependencies": { + "effect": "workspace:^" + }, + "peerDependencies": { + "effect": "workspace:^" + } +} diff --git a/.repos/effect/packages/ai/openai/src/Generated.ts b/.repos/effect/packages/ai/openai/src/Generated.ts new file mode 100644 index 000000000..d51b31244 --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/Generated.ts @@ -0,0 +1,35774 @@ +/** + * @since 1.0.0 + */ + +import * as Data from "effect/Data" +import * as Effect from "effect/Effect" +import type { SchemaError } from "effect/Schema" +import * as Schema from "effect/Schema" +import * as Stream from "effect/Stream" +import * as Sse from "effect/unstable/encoding/Sse" +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" +// non-recursive definitions +export type AddUploadPartRequest = { readonly "data": string } +export const AddUploadPartRequest = Schema.Struct({ + "data": Schema.String.annotate({ "description": "The chunk of bytes for this Part.\n", "format": "binary" }) +}) +export type AdminApiKey = { + readonly "object": "organization.admin_api_key" + readonly "id": string + readonly "name"?: string | null + readonly "redacted_value": string + readonly "created_at": number + readonly "last_used_at"?: number | null + readonly "owner": { + readonly "type"?: string + readonly "object"?: string + readonly "id"?: string + readonly "name"?: string + readonly "created_at"?: number + readonly "role"?: string + } +} +export const AdminApiKey = Schema.Struct({ + "object": Schema.Literal("organization.admin_api_key").annotate({ + "description": "The object type, which is always `organization.admin_api_key`" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the API key" }) + ), + "redacted_value": Schema.String.annotate({ "description": "The redacted value of the API key" }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the API key was created", + "format": "unixtime" + }).check(Schema.isInt()), + "last_used_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the API key was last used", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "owner": Schema.Struct({ + "type": Schema.optionalKey(Schema.String.annotate({ "description": "Always `user`" })), + "object": Schema.optionalKey( + Schema.String.annotate({ "description": "The object type, which is always organization.user" }) + ), + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the user" })), + "created_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the user was created", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "role": Schema.optionalKey(Schema.String.annotate({ "description": "Always `owner`" })) + }) +}).annotate({ "description": "Represents an individual Admin API key in an org." }) +export type AdminApiKeyCreateResponse = { + readonly "object": "organization.admin_api_key" + readonly "id": string + readonly "name"?: string | null + readonly "redacted_value": string + readonly "created_at": number + readonly "last_used_at"?: number | null + readonly "owner": { + readonly "type"?: string + readonly "object"?: string + readonly "id"?: string + readonly "name"?: string + readonly "created_at"?: number + readonly "role"?: string + } + readonly "value": string +} +export const AdminApiKeyCreateResponse = Schema.Struct({ + "object": Schema.Literal("organization.admin_api_key").annotate({ + "description": "The object type, which is always `organization.admin_api_key`" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the API key" }) + ), + "redacted_value": Schema.String.annotate({ "description": "The redacted value of the API key" }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the API key was created", + "format": "unixtime" + }).check(Schema.isInt()), + "last_used_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the API key was last used", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "owner": Schema.Struct({ + "type": Schema.optionalKey(Schema.String.annotate({ "description": "Always `user`" })), + "object": Schema.optionalKey( + Schema.String.annotate({ "description": "The object type, which is always organization.user" }) + ), + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the user" })), + "created_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the user was created", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "role": Schema.optionalKey(Schema.String.annotate({ "description": "Always `owner`" })) + }), + "value": Schema.String.annotate({ "description": "The value of the API key. Only shown on create." }) +}).annotate({ + "description": "The newly created admin API key. The `value` field is only returned once, when the key is created." +}) +export type AssignedRoleDetails = { + readonly "id": string + readonly "name": string + readonly "permissions": ReadonlyArray + readonly "resource_type": string + readonly "predefined_role": boolean + readonly "description": string | null + readonly "created_at": number | null + readonly "updated_at": number | null + readonly "created_by": string | null + readonly "created_by_user_obj": {} | null + readonly "metadata": {} | null +} +export const AssignedRoleDetails = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier for the role." }), + "name": Schema.String.annotate({ "description": "Name of the role." }), + "permissions": Schema.Array(Schema.String).annotate({ "description": "Permissions associated with the role." }), + "resource_type": Schema.String.annotate({ "description": "Resource type the role applies to." }), + "predefined_role": Schema.Boolean.annotate({ "description": "Whether the role is predefined by OpenAI." }), + "description": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Description of the role." }), + "created_at": Schema.Union([Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), Schema.Null]) + .annotate({ "description": "When the role was created." }), + "updated_at": Schema.Union([Schema.Number.annotate({ "format": "int64" }).check(Schema.isInt()), Schema.Null]) + .annotate({ "description": "When the role was last updated." }), + "created_by": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Identifier of the actor who created the role." + }), + "created_by_user_obj": Schema.Union([Schema.Struct({}), Schema.Null]).annotate({ + "description": "User details for the actor that created the role, when available." + }), + "metadata": Schema.Union([Schema.Struct({}), Schema.Null]).annotate({ + "description": "Arbitrary metadata stored on the role." + }) +}).annotate({ "description": "Detailed information about a role assignment entry returned when listing assignments." }) +export type AssistantSupportedModels = + | "gpt-5" + | "gpt-5-mini" + | "gpt-5-nano" + | "gpt-5-2025-08-07" + | "gpt-5-mini-2025-08-07" + | "gpt-5-nano-2025-08-07" + | "gpt-4.1" + | "gpt-4.1-mini" + | "gpt-4.1-nano" + | "gpt-4.1-2025-04-14" + | "gpt-4.1-mini-2025-04-14" + | "gpt-4.1-nano-2025-04-14" + | "o3-mini" + | "o3-mini-2025-01-31" + | "o1" + | "o1-2024-12-17" + | "gpt-4o" + | "gpt-4o-2024-11-20" + | "gpt-4o-2024-08-06" + | "gpt-4o-2024-05-13" + | "gpt-4o-mini" + | "gpt-4o-mini-2024-07-18" + | "gpt-4.5-preview" + | "gpt-4.5-preview-2025-02-27" + | "gpt-4-turbo" + | "gpt-4-turbo-2024-04-09" + | "gpt-4-0125-preview" + | "gpt-4-turbo-preview" + | "gpt-4-1106-preview" + | "gpt-4-vision-preview" + | "gpt-4" + | "gpt-4-0314" + | "gpt-4-0613" + | "gpt-4-32k" + | "gpt-4-32k-0314" + | "gpt-4-32k-0613" + | "gpt-3.5-turbo" + | "gpt-3.5-turbo-16k" + | "gpt-3.5-turbo-0613" + | "gpt-3.5-turbo-1106" + | "gpt-3.5-turbo-0125" + | "gpt-3.5-turbo-16k-0613" +export const AssistantSupportedModels = Schema.Literals([ + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4.5-preview", + "gpt-4.5-preview-2025-02-27", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613" +]) +export type AssistantToolsCode = { readonly "type": "code_interpreter" } +export const AssistantToolsCode = Schema.Struct({ + "type": Schema.Literal("code_interpreter").annotate({ + "description": "The type of tool being defined: `code_interpreter`" + }) +}).annotate({ "title": "Code interpreter tool" }) +export type AssistantToolsFileSearchTypeOnly = { readonly "type": "file_search" } +export const AssistantToolsFileSearchTypeOnly = Schema.Struct({ + "type": Schema.Literal("file_search").annotate({ "description": "The type of tool being defined: `file_search`" }) +}).annotate({ "title": "FileSearch tool" }) +export type AssistantsNamedToolChoice = { + readonly "type": "function" | "code_interpreter" | "file_search" + readonly "function"?: { readonly "name": string } +} +export const AssistantsNamedToolChoice = Schema.Struct({ + "type": Schema.Literals(["function", "code_interpreter", "file_search"]).annotate({ + "description": "The type of the tool. If type is `function`, the function name must be set" + }), + "function": Schema.optionalKey( + Schema.Struct({ "name": Schema.String.annotate({ "description": "The name of the function to call." }) }) + ) +}).annotate({ "description": "Specifies a tool the model should use. Use to force the model to call a specific tool." }) +export type AudioResponseFormat = "json" | "text" | "srt" | "verbose_json" | "vtt" | "diarized_json" +export const AudioResponseFormat = Schema.Literals(["json", "text", "srt", "verbose_json", "vtt", "diarized_json"]) + .annotate({ + "description": + "The format of the output, in one of these options: `json`, `text`, `srt`, `verbose_json`, `vtt`, or `diarized_json`. For `gpt-4o-transcribe` and `gpt-4o-mini-transcribe`, the only supported format is `json`. For `gpt-4o-transcribe-diarize`, the supported formats are `json`, `text`, and `diarized_json`, with `diarized_json` required to receive speaker annotations.\n" + }) +export type AuditLogActorServiceAccount = { readonly "id"?: string } +export const AuditLogActorServiceAccount = Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The service account id." })) +}).annotate({ "description": "The service account that performed the audit logged action." }) +export type AuditLogActorUser = { readonly "id"?: string; readonly "email"?: string } +export const AuditLogActorUser = Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The user id." })), + "email": Schema.optionalKey(Schema.String.annotate({ "description": "The user email." })) +}).annotate({ "description": "The user who performed the audit logged action." }) +export type AuditLogEventType = + | "api_key.created" + | "api_key.updated" + | "api_key.deleted" + | "certificate.created" + | "certificate.updated" + | "certificate.deleted" + | "certificates.activated" + | "certificates.deactivated" + | "checkpoint.permission.created" + | "checkpoint.permission.deleted" + | "external_key.registered" + | "external_key.removed" + | "group.created" + | "group.updated" + | "group.deleted" + | "invite.sent" + | "invite.accepted" + | "invite.deleted" + | "ip_allowlist.created" + | "ip_allowlist.updated" + | "ip_allowlist.deleted" + | "ip_allowlist.config.activated" + | "ip_allowlist.config.deactivated" + | "login.succeeded" + | "login.failed" + | "logout.succeeded" + | "logout.failed" + | "organization.updated" + | "project.created" + | "project.updated" + | "project.archived" + | "project.deleted" + | "rate_limit.updated" + | "rate_limit.deleted" + | "resource.deleted" + | "tunnel.created" + | "tunnel.updated" + | "tunnel.deleted" + | "role.created" + | "role.updated" + | "role.deleted" + | "role.assignment.created" + | "role.assignment.deleted" + | "scim.enabled" + | "scim.disabled" + | "service_account.created" + | "service_account.updated" + | "service_account.deleted" + | "user.added" + | "user.updated" + | "user.deleted" +export const AuditLogEventType = Schema.Literals([ + "api_key.created", + "api_key.updated", + "api_key.deleted", + "certificate.created", + "certificate.updated", + "certificate.deleted", + "certificates.activated", + "certificates.deactivated", + "checkpoint.permission.created", + "checkpoint.permission.deleted", + "external_key.registered", + "external_key.removed", + "group.created", + "group.updated", + "group.deleted", + "invite.sent", + "invite.accepted", + "invite.deleted", + "ip_allowlist.created", + "ip_allowlist.updated", + "ip_allowlist.deleted", + "ip_allowlist.config.activated", + "ip_allowlist.config.deactivated", + "login.succeeded", + "login.failed", + "logout.succeeded", + "logout.failed", + "organization.updated", + "project.created", + "project.updated", + "project.archived", + "project.deleted", + "rate_limit.updated", + "rate_limit.deleted", + "resource.deleted", + "tunnel.created", + "tunnel.updated", + "tunnel.deleted", + "role.created", + "role.updated", + "role.deleted", + "role.assignment.created", + "role.assignment.deleted", + "scim.enabled", + "scim.disabled", + "service_account.created", + "service_account.updated", + "service_account.deleted", + "user.added", + "user.updated", + "user.deleted" +]).annotate({ "description": "The event type." }) +export type BatchFileExpirationAfter = { readonly "anchor": "created_at"; readonly "seconds": number } +export const BatchFileExpirationAfter = Schema.Struct({ + "anchor": Schema.Literal("created_at").annotate({ + "description": + "Anchor timestamp after which the expiration policy applies. Supported anchors: `created_at`. Note that the anchor is the file creation time, not the time the batch is created." + }), + "seconds": Schema.Number.annotate({ + "description": + "The number of seconds after the anchor time that the file will expire. Must be between 3600 (1 hour) and 2592000 (30 days).", + "format": "int64" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(3600)).check(Schema.isLessThanOrEqualTo(2592000)) +}).annotate({ + "title": "File expiration policy", + "description": "The expiration policy for the output and/or error file that are generated for a batch." +}) +export type Certificate = { + readonly "object": "certificate" | "organization.certificate" | "organization.project.certificate" + readonly "id": string + readonly "name": string | null + readonly "created_at": number + readonly "certificate_details": { + readonly "valid_at"?: number + readonly "expires_at"?: number + readonly "content"?: string + } + readonly "active"?: boolean +} +export const Certificate = Schema.Struct({ + "object": Schema.Literals(["certificate", "organization.certificate", "organization.project.certificate"]).annotate({ + "description": + "The object type.\n\n- If creating, updating, or getting a specific certificate, the object type is `certificate`.\n- If listing, activating, or deactivating certificates for the organization, the object type is `organization.certificate`.\n- If listing, activating, or deactivating certificates for a project, the object type is `organization.project.certificate`.\n" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the certificate." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate was uploaded.", + "format": "unixtime" + }).check(Schema.isInt()), + "certificate_details": Schema.Struct({ + "valid_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate becomes valid.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate expires.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "content": Schema.optionalKey( + Schema.String.annotate({ "description": "The content of the certificate in PEM format." }) + ) + }), + "active": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether the certificate is currently active at the specified scope. Not returned when getting details for a specific certificate." + }) + ) +}).annotate({ "description": "Represents an individual `certificate` uploaded to the organization." }) +export type ChatCompletionAllowedTools = { readonly "mode": "auto" | "required"; readonly "tools": ReadonlyArray<{}> } +export const ChatCompletionAllowedTools = Schema.Struct({ + "mode": Schema.Literals(["auto", "required"]).annotate({ + "description": + "Constrains the tools available to the model to a pre-defined set.\n\n`auto` allows the model to pick from among the allowed tools and generate a\nmessage.\n\n`required` requires the model to call one or more of the allowed tools.\n" + }), + "tools": Schema.Array( + Schema.Struct({}).annotate({ "description": "A tool definition that the model should be allowed to call.\n" }) + ).annotate({ + "description": + "A list of tool definitions that the model should be allowed to call.\n\nFor the Chat Completions API, the list of tool definitions might look like:\n```json\n[\n { \"type\": \"function\", \"function\": { \"name\": \"get_weather\" } },\n { \"type\": \"function\", \"function\": { \"name\": \"get_time\" } }\n]\n```\n" + }) +}).annotate({ + "title": "Allowed tools", + "description": "Constrains the tools available to the model to a pre-defined set.\n" +}) +export type ChatCompletionDeleted = { + readonly "object": "chat.completion.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const ChatCompletionDeleted = Schema.Struct({ + "object": Schema.Literal("chat.completion.deleted").annotate({ "description": "The type of object being deleted." }), + "id": Schema.String.annotate({ "description": "The ID of the chat completion that was deleted." }), + "deleted": Schema.Boolean.annotate({ "description": "Whether the chat completion was deleted." }) +}) +export type ChatCompletionFunctionCallOption = { readonly "name": string } +export const ChatCompletionFunctionCallOption = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the function to call." }) +}).annotate({ + "description": + "Specifying a particular function via `{\"name\": \"my_function\"}` forces the model to call that function.\n" +}) +export type ChatCompletionMessageCustomToolCall = { + readonly "id": string + readonly "type": "custom" + readonly "custom": { readonly "name": string; readonly "input": string } +} +export const ChatCompletionMessageCustomToolCall = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The ID of the tool call." }), + "type": Schema.Literal("custom").annotate({ "description": "The type of the tool. Always `custom`." }), + "custom": Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the custom tool to call." }), + "input": Schema.String.annotate({ "description": "The input for the custom tool call generated by the model." }) + }).annotate({ "description": "The custom tool that the model called." }) +}).annotate({ "title": "Custom tool call", "description": "A call to a custom tool created by the model.\n" }) +export type ChatCompletionMessageToolCall = { + readonly "id": string + readonly "type": "function" + readonly "function": { readonly "name": string; readonly "arguments": string } +} +export const ChatCompletionMessageToolCall = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The ID of the tool call." }), + "type": Schema.Literal("function").annotate({ + "description": "The type of the tool. Currently, only `function` is supported." + }), + "function": Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the function to call." }), + "arguments": Schema.String.annotate({ + "description": + "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function." + }) + }).annotate({ "description": "The function that the model called." }) +}).annotate({ "title": "Function tool call", "description": "A call to a function tool created by the model.\n" }) +export type ChatCompletionMessageToolCallChunk = { + readonly "index": number + readonly "id"?: string + readonly "type"?: "function" + readonly "function"?: { readonly "name"?: string; readonly "arguments"?: string } +} +export const ChatCompletionMessageToolCallChunk = Schema.Struct({ + "index": Schema.Number.check(Schema.isInt()), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the tool call." })), + "type": Schema.optionalKey( + Schema.Literal("function").annotate({ + "description": "The type of the tool. Currently, only `function` is supported." + }) + ), + "function": Schema.optionalKey(Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the function to call." })), + "arguments": Schema.optionalKey(Schema.String.annotate({ + "description": + "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function." + })) + })) +}) +export type ChatCompletionNamedToolChoice = { + readonly "type": "function" + readonly "function": { readonly "name": string } +} +export const ChatCompletionNamedToolChoice = Schema.Struct({ + "type": Schema.Literal("function").annotate({ + "description": "For function calling, the type is always `function`." + }), + "function": Schema.Struct({ "name": Schema.String.annotate({ "description": "The name of the function to call." }) }) +}).annotate({ + "title": "Function tool choice", + "description": "Specifies a tool the model should use. Use to force the model to call a specific function." +}) +export type ChatCompletionNamedToolChoiceCustom = { + readonly "type": "custom" + readonly "custom": { readonly "name": string } +} +export const ChatCompletionNamedToolChoiceCustom = Schema.Struct({ + "type": Schema.Literal("custom").annotate({ "description": "For custom tool calling, the type is always `custom`." }), + "custom": Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the custom tool to call." }) + }) +}).annotate({ + "title": "Custom tool choice", + "description": "Specifies a tool the model should use. Use to force the model to call a specific custom tool." +}) +export type ChatCompletionRequestFunctionMessage = { + readonly "role": "function" + readonly "content": string | null + readonly "name": string +} +export const ChatCompletionRequestFunctionMessage = Schema.Struct({ + "role": Schema.Literal("function").annotate({ + "description": "The role of the messages author, in this case `function`." + }), + "content": Schema.Union([ + Schema.String.annotate({ "description": "The contents of the function message." }), + Schema.Null + ]), + "name": Schema.String.annotate({ "description": "The name of the function to call." }) +}).annotate({ "title": "Function message" }) +export type ChatCompletionRequestMessageContentPartAudio = { + readonly "type": "input_audio" + readonly "input_audio": { readonly "data": string; readonly "format": "wav" | "mp3" } +} +export const ChatCompletionRequestMessageContentPartAudio = Schema.Struct({ + "type": Schema.Literal("input_audio").annotate({ + "description": "The type of the content part. Always `input_audio`." + }), + "input_audio": Schema.Struct({ + "data": Schema.String.annotate({ "description": "Base64 encoded audio data." }), + "format": Schema.Literals(["wav", "mp3"]).annotate({ + "description": "The format of the encoded audio data. Currently supports \"wav\" and \"mp3\".\n" + }) + }) +}).annotate({ "title": "Audio content part", "description": "Learn about [audio inputs](/docs/guides/audio).\n" }) +export type ChatCompletionRequestMessageContentPartFile = { + readonly "type": "file" + readonly "file": { readonly "filename"?: string; readonly "file_data"?: string; readonly "file_id"?: string } +} +export const ChatCompletionRequestMessageContentPartFile = Schema.Struct({ + "type": Schema.Literal("file").annotate({ "description": "The type of the content part. Always `file`." }), + "file": Schema.Struct({ + "filename": Schema.optionalKey( + Schema.String.annotate({ + "description": "The name of the file, used when passing the file to the model as a \nstring.\n" + }) + ), + "file_data": Schema.optionalKey( + Schema.String.annotate({ + "description": "The base64 encoded file data, used when passing the file to the model \nas a string.\n" + }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of an uploaded file to use as input.\n" }) + ) + }) +}).annotate({ + "title": "File content part", + "description": "Learn about [file inputs](/docs/guides/text) for text generation.\n" +}) +export type ChatCompletionRequestMessageContentPartImage = { + readonly "type": "image_url" + readonly "image_url": { readonly "url": string; readonly "detail"?: "auto" | "low" | "high" } +} +export const ChatCompletionRequestMessageContentPartImage = Schema.Struct({ + "type": Schema.Literal("image_url").annotate({ "description": "The type of the content part." }), + "image_url": Schema.Struct({ + "url": Schema.String.annotate({ + "description": "Either a URL of the image or the base64 encoded image data.", + "format": "uri" + }), + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high"]).annotate({ + "description": + "Specifies the detail level of the image. Learn more in the [Vision guide](/docs/guides/vision#low-or-high-fidelity-image-understanding)." + }) + ) + }) +}).annotate({ "title": "Image content part", "description": "Learn about [image inputs](/docs/guides/vision).\n" }) +export type ChatCompletionRequestMessageContentPartRefusal = { readonly "type": "refusal"; readonly "refusal": string } +export const ChatCompletionRequestMessageContentPartRefusal = Schema.Struct({ + "type": Schema.Literal("refusal").annotate({ "description": "The type of the content part." }), + "refusal": Schema.String.annotate({ "description": "The refusal message generated by the model." }) +}).annotate({ "title": "Refusal content part" }) +export type ChatCompletionRequestMessageContentPartText = { readonly "type": "text"; readonly "text": string } +export const ChatCompletionRequestMessageContentPartText = Schema.Struct({ + "type": Schema.Literal("text").annotate({ "description": "The type of the content part." }), + "text": Schema.String.annotate({ "description": "The text content." }) +}).annotate({ + "title": "Text content part", + "description": "Learn about [text inputs](/docs/guides/text-generation).\n" +}) +export type ChatCompletionStreamOptions = { + readonly "include_usage"?: boolean + readonly "include_obfuscation"?: boolean +} | null +export const ChatCompletionStreamOptions = Schema.Union([ + Schema.Struct({ + "include_usage": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "If set, an additional chunk will be streamed before the `data: [DONE]`\nmessage. The `usage` field on this chunk shows the token usage statistics\nfor the entire request, and the `choices` field will always be an empty\narray.\n\nAll other chunks will also include a `usage` field, but with a null\nvalue. **NOTE:** If the stream is interrupted, you may not receive the\nfinal usage chunk which contains the total token usage for the request.\n" + })), + "include_obfuscation": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "When true, stream obfuscation will be enabled. Stream obfuscation adds\nrandom characters to an `obfuscation` field on streaming delta events to\nnormalize payload sizes as a mitigation to certain side-channel attacks.\nThese obfuscation fields are included by default, but add a small amount\nof overhead to the data stream. You can set `include_obfuscation` to\nfalse to optimize for bandwidth if you trust the network links between\nyour application and the OpenAI API.\n" + })) + }).annotate({ "description": "Options for streaming response. Only set this when you set `stream: true`.\n" }), + Schema.Null +]) +export type ChatCompletionTokenLogprob = { + 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 const ChatCompletionTokenLogprob = Schema.Struct({ + "token": Schema.String.annotate({ "description": "The token." }), + "logprob": Schema.Number.annotate({ + "description": + "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely." + }).check(Schema.isFinite()), + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": + "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token." + }), + Schema.Null + ]), + "top_logprobs": Schema.Array(Schema.Struct({ + "token": Schema.String.annotate({ "description": "The token." }), + "logprob": Schema.Number.annotate({ + "description": + "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely." + }).check(Schema.isFinite()), + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": + "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token." + }), + Schema.Null + ]) + })).annotate({ + "description": + "List of the most likely tokens and their log probability, at this token position. The number of entries may be fewer than the requested `top_logprobs`." + }) +}) +export type ComparisonFilter = { + readonly "type": "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" + readonly "key": string + readonly "value": string | number | boolean | ReadonlyArray +} +export const ComparisonFilter = Schema.Struct({ + "type": Schema.Literals(["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin"]).annotate({ + "description": + "Specifies the comparison operator: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`.\n- `eq`: equals\n- `ne`: not equal\n- `gt`: greater than\n- `gte`: greater than or equal\n- `lt`: less than\n- `lte`: less than or equal\n- `in`: in\n- `nin`: not in\n" + }), + "key": Schema.String.annotate({ "description": "The key to compare against the value." }), + "value": Schema.Union([ + Schema.String, + Schema.Number.check(Schema.isFinite()), + Schema.Boolean, + Schema.Array(Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite())], { mode: "oneOf" })) + ], { mode: "oneOf" }).annotate({ + "description": "The value to compare against the attribute key; supports string, number, or boolean types." + }) +}).annotate({ + "title": "Comparison Filter", + "description": + "A filter used to compare a specified attribute key to a given value using a defined comparison operation.\n" +}) +export type CompleteUploadRequest = { readonly "part_ids": ReadonlyArray; readonly "md5"?: string } +export const CompleteUploadRequest = Schema.Struct({ + "part_ids": Schema.Array(Schema.String).annotate({ "description": "The ordered list of Part IDs.\n" }), + "md5": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The optional md5 checksum for the file contents to verify if the bytes uploaded matches what you expect.\n" + }) + ) +}) +export type CompletionUsage = { + readonly "completion_tokens": number + readonly "prompt_tokens": number + readonly "total_tokens": number + readonly "completion_tokens_details"?: { + readonly "accepted_prediction_tokens"?: number + readonly "audio_tokens"?: number + readonly "reasoning_tokens"?: number + readonly "rejected_prediction_tokens"?: number + } + readonly "prompt_tokens_details"?: { readonly "audio_tokens"?: number; readonly "cached_tokens"?: number } +} +export const CompletionUsage = Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ "description": "Number of tokens in the generated completion." }).check( + Schema.isInt() + ), + "prompt_tokens": Schema.Number.annotate({ "description": "Number of tokens in the prompt." }).check(Schema.isInt()), + "total_tokens": Schema.Number.annotate({ + "description": "Total number of tokens used in the request (prompt + completion)." + }).check(Schema.isInt()), + "completion_tokens_details": Schema.optionalKey( + Schema.Struct({ + "accepted_prediction_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "When using Predicted Outputs, the number of tokens in the\nprediction that appeared in the completion.\n" + }).check(Schema.isInt()) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio input tokens generated by the model." }).check(Schema.isInt()) + ), + "reasoning_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Tokens generated by the model for reasoning." }).check(Schema.isInt()) + ), + "rejected_prediction_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "When using Predicted Outputs, the number of tokens in the\nprediction that did not appear in the completion. However, like\nreasoning tokens, these tokens are still counted in the total\ncompletion tokens for purposes of billing, output, and context window\nlimits.\n" + }).check(Schema.isInt()) + ) + }).annotate({ "description": "Breakdown of tokens used in a completion." }) + ), + "prompt_tokens_details": Schema.optionalKey( + Schema.Struct({ + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio input tokens present in the prompt." }).check(Schema.isInt()) + ), + "cached_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cached tokens present in the prompt." }).check(Schema.isInt()) + ) + }).annotate({ "description": "Breakdown of tokens used in the prompt." }) + ) +}).annotate({ "description": "Usage statistics for the completion request." }) +export type ComputerScreenshotImage = { + readonly "type": "computer_screenshot" + readonly "image_url"?: string + readonly "file_id"?: string +} +export const ComputerScreenshotImage = Schema.Struct({ + "type": Schema.Literal("computer_screenshot").annotate({ + "description": + "Specifies the event type. For a computer screenshot, this property is \nalways set to `computer_screenshot`.\n" + }), + "image_url": Schema.optionalKey( + Schema.String.annotate({ "description": "The URL of the screenshot image.", "format": "uri" }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of an uploaded file that contains the screenshot." }) + ) +}).annotate({ "description": "A computer screenshot image used with the computer use tool.\n" }) +export type ContainerFileResource = { + readonly "id": string + readonly "object": string + readonly "container_id": string + readonly "created_at": number + readonly "bytes": number + readonly "path": string + readonly "source": string +} +export const ContainerFileResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Unique identifier for the file." }), + "object": Schema.String.annotate({ "description": "The type of this object (`container.file`)." }), + "container_id": Schema.String.annotate({ "description": "The container this file belongs to." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the file was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "bytes": Schema.Number.annotate({ "description": "Size of the file in bytes." }).check(Schema.isInt()), + "path": Schema.String.annotate({ "description": "Path of the file in the container." }), + "source": Schema.String.annotate({ "description": "Source of the file (e.g., `user`, `assistant`)." }) +}).annotate({ "title": "The container file object" }) +export type ContainerResource = { + readonly "id": string + readonly "object": string + readonly "name": string + readonly "created_at": number + readonly "status": string + readonly "last_active_at"?: number + readonly "expires_after"?: { readonly "anchor"?: "last_active_at"; readonly "minutes"?: number } + readonly "memory_limit"?: "1g" | "4g" | "16g" | "64g" + readonly "network_policy"?: { + readonly "type": "allowlist" | "disabled" + readonly "allowed_domains"?: ReadonlyArray + } +} +export const ContainerResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Unique identifier for the container." }), + "object": Schema.String.annotate({ "description": "The type of this object." }), + "name": Schema.String.annotate({ "description": "Name of the container." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the container was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "status": Schema.String.annotate({ "description": "Status of the container (e.g., active, deleted)." }), + "last_active_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the container was last active.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "expires_after": Schema.optionalKey( + Schema.Struct({ + "anchor": Schema.optionalKey( + Schema.Literal("last_active_at").annotate({ "description": "The reference point for the expiration." }) + ), + "minutes": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of minutes after the anchor before the container expires." + }).check(Schema.isInt()) + ) + }).annotate({ + "description": + "The container will expire after this time period.\nThe anchor is the reference point for the expiration.\nThe minutes is the number of minutes after the anchor before the container expires.\n" + }) + ), + "memory_limit": Schema.optionalKey( + Schema.Literals(["1g", "4g", "16g", "64g"]).annotate({ + "description": "The memory limit configured for the container." + }) + ), + "network_policy": Schema.optionalKey( + Schema.Struct({ + "type": Schema.Literals(["allowlist", "disabled"]).annotate({ "description": "The network policy mode." }), + "allowed_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "Allowed outbound domains when `type` is `allowlist`." }) + ) + }).annotate({ "description": "Network access policy for the container." }) + ) +}).annotate({ "title": "The container object" }) +export type CostsResult = { + readonly "object": "organization.costs.result" + readonly "amount"?: { readonly "value"?: number; readonly "currency"?: string } + readonly "line_item"?: string | null + readonly "project_id"?: string | null + readonly "api_key_id"?: string | null + readonly "quantity"?: number | null +} +export const CostsResult = Schema.Struct({ + "object": Schema.Literal("organization.costs.result"), + "amount": Schema.optionalKey( + Schema.Struct({ + "value": Schema.optionalKey( + Schema.Number.annotate({ "description": "The numeric value of the cost." }).check(Schema.isFinite()) + ), + "currency": Schema.optionalKey( + Schema.String.annotate({ "description": "Lowercase ISO-4217 currency e.g. \"usd\"" }) + ) + }).annotate({ "description": "The monetary value in its associated currency." }) + ), + "line_item": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=line_item`, this field provides the line item of the grouped costs result." + }), + Schema.Null + ]) + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped costs result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API Key ID of the grouped costs result." + }), + Schema.Null + ]) + ), + "quantity": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "When `group_by=line_item`, this field provides the quantity of the grouped costs result." + }).check(Schema.isFinite()), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated costs details of the specific time bucket." }) +export type CreateContainerFileBody = { readonly "file_id"?: string; readonly "file"?: string } +export const CreateContainerFileBody = Schema.Struct({ + "file_id": Schema.optionalKey(Schema.String.annotate({ "description": "Name of the file to create." })), + "file": Schema.optionalKey( + Schema.String.annotate({ "description": "The File object (not file name) to be uploaded.\n", "format": "binary" }) + ) +}) +export type CreateEmbeddingRequest = { + readonly "input": string | ReadonlyArray | ReadonlyArray | ReadonlyArray> + readonly "model": string | "text-embedding-ada-002" | "text-embedding-3-small" | "text-embedding-3-large" + readonly "encoding_format"?: "float" | "base64" + readonly "dimensions"?: number + readonly "user"?: string +} +export const CreateEmbeddingRequest = Schema.Struct({ + "input": Schema.Union([ + Schema.String.annotate({ "title": "string", "description": "The string that will be turned into an embedding." }), + Schema.Array(Schema.String).annotate({ + "title": "array", + "description": "The array of strings that will be turned into an embedding." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2048)), + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "title": "array", + "description": "The array of integers that will be turned into an embedding." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2048)), + Schema.Array(Schema.Array(Schema.Number.check(Schema.isInt())).check(Schema.isMinLength(1))).annotate({ + "title": "array", + "description": "The array of arrays containing integers that will be turned into an embedding." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2048)) + ], { mode: "oneOf" }).annotate({ + "description": + "Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for all embedding models), cannot be an empty string, and any array must be 2048 dimensions or less. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. In addition to the per-input token limit, all embedding models enforce a maximum of 300,000 tokens summed across all inputs in a single request.\n" + }), + "model": Schema.Union([ + Schema.String, + Schema.Literals(["text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large"]) + ]).annotate({ + "description": + "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n" + }), + "encoding_format": Schema.optionalKey( + Schema.Literals(["float", "base64"]).annotate({ + "description": + "The format to return the embeddings in. Can be either `float` or [`base64`](https://pypi.org/project/pybase64/)." + }) + ), + "dimensions": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The number of dimensions the resulting output embeddings should have. Only supported in `text-embedding-3` and later models.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)) + ), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n" + }) + ) +}) +export type CreateFineTuningCheckpointPermissionRequest = { readonly "project_ids": ReadonlyArray } +export const CreateFineTuningCheckpointPermissionRequest = Schema.Struct({ + "project_ids": Schema.Array(Schema.String).annotate({ "description": "The project identifiers to grant access to." }) +}) +export type CreateGroupBody = { readonly "name": string } +export const CreateGroupBody = Schema.Struct({ + "name": Schema.String.annotate({ "description": "Human readable name for the group." }).check(Schema.isMinLength(1)) + .check(Schema.isMaxLength(255)) +}).annotate({ "description": "Request payload for creating a new group in the organization." }) +export type CreateGroupUserBody = { readonly "user_id": string } +export const CreateGroupUserBody = Schema.Struct({ + "user_id": Schema.String.annotate({ "description": "Identifier of the user to add to the group." }) +}).annotate({ "description": "Request payload for adding a user to a group." }) +export type CreateImageVariationRequest = { + readonly "image": string + readonly "model"?: string | "dall-e-2" | null + readonly "n"?: number + readonly "response_format"?: "url" | "b64_json" | null + readonly "size"?: "256x256" | "512x512" | "1024x1024" | null + readonly "user"?: string +} +export const CreateImageVariationRequest = Schema.Struct({ + "image": Schema.String.annotate({ + "description": + "The image to use as the basis for the variation(s). Must be a valid PNG file, less than 4MB, and square.", + "format": "binary" + }), + "model": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.String, Schema.Literal("dall-e-2")]).annotate({ + "description": "The model to use for image generation. Only `dall-e-2` is supported at this time." + }), + Schema.Null + ]) + ), + "n": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(10)], { + "description": "The number of images to generate. Must be between 1 and 10." + }) + ) + ]) + ), + "response_format": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["url", "b64_json"]).annotate({ + "description": + "The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated." + }) + ]) + ), + "size": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["256x256", "512x512", "1024x1024"]).annotate({ + "description": "The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024`." + }), + Schema.Union([Schema.Null]).annotate({ + "description": "The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024`." + }) + ]) + ), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n" + }) + ) +}) +export type CreateModerationRequest = { + readonly "input": + | string + | ReadonlyArray + | ReadonlyArray< + { readonly "type": "image_url"; readonly "image_url": { readonly "url": string } } | { + readonly "type": "text" + readonly "text": string + } + > + readonly "model"?: + | string + | "omni-moderation-latest" + | "omni-moderation-2024-09-26" + | "text-moderation-latest" + | "text-moderation-stable" +} +export const CreateModerationRequest = Schema.Struct({ + "input": Schema.Union([ + Schema.String.annotate({ "description": "A string of text to classify for moderation." }), + Schema.Array(Schema.String).annotate({ "description": "An array of strings to classify for moderation." }), + Schema.Array(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("image_url").annotate({ "description": "Always `image_url`." }), + "image_url": Schema.Struct({ + "url": Schema.String.annotate({ + "description": "Either a URL of the image or the base64 encoded image data.", + "format": "uri" + }) + }).annotate({ "description": "Contains either an image URL or a data URL for a base64 encoded image." }) + }).annotate({ "description": "An object describing an image to classify." }), + Schema.Struct({ + "type": Schema.Literal("text").annotate({ "description": "Always `text`." }), + "text": Schema.String.annotate({ "description": "A string of text to classify." }) + }).annotate({ "description": "An object describing text to classify." }) + ], { mode: "oneOf" })).annotate({ "description": "An array of multi-modal inputs to the moderation model." }) + ], { mode: "oneOf" }).annotate({ + "description": + "Input (or inputs) to classify. Can be a single string, an array of strings, or\nan array of multi-modal input objects similar to other models.\n" + }), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "omni-moderation-latest", + "omni-moderation-2024-09-26", + "text-moderation-latest", + "text-moderation-stable" + ]) + ]).annotate({ + "description": + "The content moderation model you would like to use. Learn more in\n[the moderation guide](/docs/guides/moderation), and learn about\navailable models [here](/docs/models#moderation).\n" + }) + ) +}) +export type CreateModerationResponse = { + readonly "id": string + readonly "model": string + readonly "results": ReadonlyArray< + { + readonly "flagged": boolean + readonly "categories": { + readonly "hate": boolean + readonly "hate/threatening": boolean + readonly "harassment": boolean + readonly "harassment/threatening": boolean + readonly "illicit": boolean | null + readonly "illicit/violent": boolean | null + readonly "self-harm": boolean + readonly "self-harm/intent": boolean + readonly "self-harm/instructions": boolean + readonly "sexual": boolean + readonly "sexual/minors": boolean + readonly "violence": boolean + readonly "violence/graphic": boolean + } + readonly "category_scores": { + readonly "hate": number + readonly "hate/threatening": number + readonly "harassment": number + readonly "harassment/threatening": number + readonly "illicit": number + readonly "illicit/violent": number + readonly "self-harm": number + readonly "self-harm/intent": number + readonly "self-harm/instructions": number + readonly "sexual": number + readonly "sexual/minors": number + readonly "violence": number + readonly "violence/graphic": number + } + readonly "category_applied_input_types": { + readonly "hate": ReadonlyArray<"text"> + readonly "hate/threatening": ReadonlyArray<"text"> + readonly "harassment": ReadonlyArray<"text"> + readonly "harassment/threatening": ReadonlyArray<"text"> + readonly "illicit": ReadonlyArray<"text"> + readonly "illicit/violent": ReadonlyArray<"text"> + readonly "self-harm": ReadonlyArray<"text" | "image"> + readonly "self-harm/intent": ReadonlyArray<"text" | "image"> + readonly "self-harm/instructions": ReadonlyArray<"text" | "image"> + readonly "sexual": ReadonlyArray<"text" | "image"> + readonly "sexual/minors": ReadonlyArray<"text"> + readonly "violence": ReadonlyArray<"text" | "image"> + readonly "violence/graphic": ReadonlyArray<"text" | "image"> + } + } + > +} +export const CreateModerationResponse = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique identifier for the moderation request." }), + "model": Schema.String.annotate({ "description": "The model used to generate the moderation results." }), + "results": Schema.Array(Schema.Struct({ + "flagged": Schema.Boolean.annotate({ "description": "Whether any of the below categories are flagged." }), + "categories": Schema.Struct({ + "hate": Schema.Boolean.annotate({ + "description": + "Content that expresses, incites, or promotes hate based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. Hateful content aimed at non-protected groups (e.g., chess players) is harassment." + }), + "hate/threatening": Schema.Boolean.annotate({ + "description": + "Hateful content that also includes violence or serious harm towards the targeted group based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste." + }), + "harassment": Schema.Boolean.annotate({ + "description": "Content that expresses, incites, or promotes harassing language towards any target." + }), + "harassment/threatening": Schema.Boolean.annotate({ + "description": "Harassment content that also includes violence or serious harm towards any target." + }), + "illicit": Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Content that includes instructions or advice that facilitate the planning or execution of wrongdoing, or that gives advice or instruction on how to commit illicit acts. For example, \"how to shoplift\" would fit this category." + }), + Schema.Null + ]), + "illicit/violent": Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Content that includes instructions or advice that facilitate the planning or execution of wrongdoing that also includes violence, or that gives advice or instruction on the procurement of any weapon." + }), + Schema.Null + ]), + "self-harm": Schema.Boolean.annotate({ + "description": + "Content that promotes, encourages, or depicts acts of self-harm, such as suicide, cutting, and eating disorders." + }), + "self-harm/intent": Schema.Boolean.annotate({ + "description": + "Content where the speaker expresses that they are engaging or intend to engage in acts of self-harm, such as suicide, cutting, and eating disorders." + }), + "self-harm/instructions": Schema.Boolean.annotate({ + "description": + "Content that encourages performing acts of self-harm, such as suicide, cutting, and eating disorders, or that gives instructions or advice on how to commit such acts." + }), + "sexual": Schema.Boolean.annotate({ + "description": + "Content meant to arouse sexual excitement, such as the description of sexual activity, or that promotes sexual services (excluding sex education and wellness)." + }), + "sexual/minors": Schema.Boolean.annotate({ + "description": "Sexual content that includes an individual who is under 18 years old." + }), + "violence": Schema.Boolean.annotate({ + "description": "Content that depicts death, violence, or physical injury." + }), + "violence/graphic": Schema.Boolean.annotate({ + "description": "Content that depicts death, violence, or physical injury in graphic detail." + }) + }).annotate({ "description": "A list of the categories, and whether they are flagged or not." }), + "category_scores": Schema.Struct({ + "hate": Schema.Number.annotate({ "description": "The score for the category 'hate'." }).check(Schema.isFinite()), + "hate/threatening": Schema.Number.annotate({ "description": "The score for the category 'hate/threatening'." }) + .check(Schema.isFinite()), + "harassment": Schema.Number.annotate({ "description": "The score for the category 'harassment'." }).check( + Schema.isFinite() + ), + "harassment/threatening": Schema.Number.annotate({ + "description": "The score for the category 'harassment/threatening'." + }).check(Schema.isFinite()), + "illicit": Schema.Number.annotate({ "description": "The score for the category 'illicit'." }).check( + Schema.isFinite() + ), + "illicit/violent": Schema.Number.annotate({ "description": "The score for the category 'illicit/violent'." }) + .check(Schema.isFinite()), + "self-harm": Schema.Number.annotate({ "description": "The score for the category 'self-harm'." }).check( + Schema.isFinite() + ), + "self-harm/intent": Schema.Number.annotate({ "description": "The score for the category 'self-harm/intent'." }) + .check(Schema.isFinite()), + "self-harm/instructions": Schema.Number.annotate({ + "description": "The score for the category 'self-harm/instructions'." + }).check(Schema.isFinite()), + "sexual": Schema.Number.annotate({ "description": "The score for the category 'sexual'." }).check( + Schema.isFinite() + ), + "sexual/minors": Schema.Number.annotate({ "description": "The score for the category 'sexual/minors'." }).check( + Schema.isFinite() + ), + "violence": Schema.Number.annotate({ "description": "The score for the category 'violence'." }).check( + Schema.isFinite() + ), + "violence/graphic": Schema.Number.annotate({ "description": "The score for the category 'violence/graphic'." }) + .check(Schema.isFinite()) + }).annotate({ "description": "A list of the categories along with their scores as predicted by model." }), + "category_applied_input_types": Schema.Struct({ + "hate": Schema.Array(Schema.Literal("text")).annotate({ + "description": "The applied input type(s) for the category 'hate'." + }), + "hate/threatening": Schema.Array(Schema.Literal("text")).annotate({ + "description": "The applied input type(s) for the category 'hate/threatening'." + }), + "harassment": Schema.Array(Schema.Literal("text")).annotate({ + "description": "The applied input type(s) for the category 'harassment'." + }), + "harassment/threatening": Schema.Array(Schema.Literal("text")).annotate({ + "description": "The applied input type(s) for the category 'harassment/threatening'." + }), + "illicit": Schema.Array(Schema.Literal("text")).annotate({ + "description": "The applied input type(s) for the category 'illicit'." + }), + "illicit/violent": Schema.Array(Schema.Literal("text")).annotate({ + "description": "The applied input type(s) for the category 'illicit/violent'." + }), + "self-harm": Schema.Array(Schema.Literals(["text", "image"])).annotate({ + "description": "The applied input type(s) for the category 'self-harm'." + }), + "self-harm/intent": Schema.Array(Schema.Literals(["text", "image"])).annotate({ + "description": "The applied input type(s) for the category 'self-harm/intent'." + }), + "self-harm/instructions": Schema.Array(Schema.Literals(["text", "image"])).annotate({ + "description": "The applied input type(s) for the category 'self-harm/instructions'." + }), + "sexual": Schema.Array(Schema.Literals(["text", "image"])).annotate({ + "description": "The applied input type(s) for the category 'sexual'." + }), + "sexual/minors": Schema.Array(Schema.Literal("text")).annotate({ + "description": "The applied input type(s) for the category 'sexual/minors'." + }), + "violence": Schema.Array(Schema.Literals(["text", "image"])).annotate({ + "description": "The applied input type(s) for the category 'violence'." + }), + "violence/graphic": Schema.Array(Schema.Literals(["text", "image"])).annotate({ + "description": "The applied input type(s) for the category 'violence/graphic'." + }) + }).annotate({ "description": "A list of the categories along with the input type(s) that the score applies to." }) + })).annotate({ "description": "A list of moderation objects." }) +}).annotate({ "description": "Represents if a given text input is potentially harmful." }) +export type CreateTranscriptionResponseJson = { + readonly "text": string + readonly "logprobs"?: ReadonlyArray< + { readonly "token"?: string; readonly "logprob"?: number; readonly "bytes"?: ReadonlyArray } + > + readonly "usage"?: { + readonly "type": "tokens" + readonly "input_tokens": number + readonly "input_token_details"?: { readonly "text_tokens"?: number; readonly "audio_tokens"?: number } + readonly "output_tokens": number + readonly "total_tokens": number + } | { readonly "type": "duration"; readonly "seconds": number } +} +export const CreateTranscriptionResponseJson = Schema.Struct({ + "text": Schema.String.annotate({ "description": "The transcribed text." }), + "logprobs": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "token": Schema.optionalKey(Schema.String.annotate({ "description": "The token in the transcription." })), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "description": "The log probability of the token." }).check(Schema.isFinite()) + ), + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isFinite())).annotate({ "description": "The bytes of the token." }) + ) + })).annotate({ + "description": + "The log probabilities of the tokens in the transcription. Only returned with the models `gpt-4o-transcribe` and `gpt-4o-mini-transcribe` if `logprobs` is added to the `include` array.\n" + }) + ), + "usage": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("tokens").annotate({ + "description": "The type of the usage object. Always `tokens` for this variant." + }), + "input_tokens": Schema.Number.annotate({ "description": "Number of input tokens billed for this request." }) + .check(Schema.isInt()), + "input_token_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of text tokens billed for this request." }).check( + Schema.isInt() + ) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of audio tokens billed for this request." }).check( + Schema.isInt() + ) + ) + }).annotate({ "description": "Details about the input tokens billed for this request." }) + ), + "output_tokens": Schema.Number.annotate({ "description": "Number of output tokens generated." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used (input + output)." }).check( + Schema.isInt() + ) + }).annotate({ "title": "Token Usage", "description": "Token usage statistics for the request." }), + Schema.Struct({ + "type": Schema.Literal("duration").annotate({ + "description": "The type of the usage object. Always `duration` for this variant." + }), + "seconds": Schema.Number.annotate({ + "description": "Duration of the input audio in seconds.", + "format": "double" + }).check(Schema.isFinite()) + }).annotate({ "title": "Duration Usage", "description": "Token usage statistics for the request." }) + ], { mode: "oneOf" })) +}).annotate({ "description": "Represents a transcription response returned by model, based on the provided input." }) +export type CreateTranslationRequest = { + readonly "file": string + readonly "model": string | "whisper-1" + readonly "prompt"?: string + readonly "response_format"?: "json" | "text" | "srt" | "verbose_json" | "vtt" + readonly "temperature"?: number +} +export const CreateTranslationRequest = Schema.Struct({ + "file": Schema.String.annotate({ + "description": + "The audio file object (not file name) translate, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.\n", + "format": "binary" + }), + "model": Schema.Union([Schema.String, Schema.Literal("whisper-1")]).annotate({ + "description": + "ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) is currently available.\n" + }), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text#prompting) should be in English.\n" + }) + ), + "response_format": Schema.optionalKey( + Schema.Literals(["json", "text", "srt", "verbose_json", "vtt"]).annotate({ + "description": + "The format of the output, in one of these options: `json`, `text`, `srt`, `verbose_json`, or `vtt`.\n" + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit.\n" + }).check(Schema.isFinite()) + ) +}) +export type CreateTranslationResponseJson = { readonly "text": string } +export const CreateTranslationResponseJson = Schema.Struct({ "text": Schema.String }) +export type CreateVoiceConsentRequest = { + readonly "name": string + readonly "recording": string + readonly "language": string +} +export const CreateVoiceConsentRequest = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The label to use for this consent recording." }), + "recording": Schema.String.annotate({ + "description": + "The consent audio recording file. Maximum size is 10 MiB.\n\nSupported MIME types:\n`audio/mpeg`, `audio/wav`, `audio/x-wav`, `audio/ogg`, `audio/aac`, `audio/flac`, `audio/webm`, `audio/mp4`.\n", + "format": "binary" + }), + "language": Schema.String.annotate({ + "description": "The BCP 47 language tag for the consent phrase (for example, `en-US`)." + }) +}) +export type CreateVoiceRequest = { + readonly "name": string + readonly "audio_sample": string + readonly "consent": string +} +export const CreateVoiceRequest = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the new voice." }), + "audio_sample": Schema.String.annotate({ + "description": + "The sample audio recording file. Maximum size is 10 MiB.\n\nSupported MIME types:\n`audio/mpeg`, `audio/wav`, `audio/x-wav`, `audio/ogg`, `audio/aac`, `audio/flac`, `audio/webm`, `audio/mp4`.\n", + "format": "binary" + }), + "consent": Schema.String.annotate({ "description": "The consent recording ID (for example, `cons_1234`)." }) +}) +export type CustomToolCall = { + readonly "type": "custom_tool_call" + readonly "id"?: string + readonly "call_id": string + readonly "namespace"?: string + readonly "name": string + readonly "input": string +} +export const CustomToolCall = Schema.Struct({ + "type": Schema.Literal("custom_tool_call").annotate({ + "description": "The type of the custom tool call. Always `custom_tool_call`.\n" + }), + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The unique ID of the custom tool call in the OpenAI platform.\n" }) + ), + "call_id": Schema.String.annotate({ + "description": "An identifier used to map this custom tool call to a tool call output.\n" + }), + "namespace": Schema.optionalKey( + Schema.String.annotate({ "description": "The namespace of the custom tool being called.\n" }) + ), + "name": Schema.String.annotate({ "description": "The name of the custom tool being called.\n" }), + "input": Schema.String.annotate({ "description": "The input for the custom tool call generated by the model.\n" }) +}).annotate({ "title": "Custom tool call", "description": "A call to a custom tool created by the model.\n" }) +export type CustomToolCallResource = { + readonly "type": "custom_tool_call" + readonly "id": string + readonly "call_id": string + readonly "namespace"?: string + readonly "name": string + readonly "input": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "created_by"?: string +} +export const CustomToolCallResource = Schema.Struct({ + "type": Schema.Literal("custom_tool_call").annotate({ + "description": "The type of the custom tool call. Always `custom_tool_call`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the custom tool call item.\n" }), + "call_id": Schema.String.annotate({ + "description": "An identifier used to map this custom tool call to a tool call output.\n" + }), + "namespace": Schema.optionalKey( + Schema.String.annotate({ "description": "The namespace of the custom tool being called.\n" }) + ), + "name": Schema.String.annotate({ "description": "The name of the custom tool being called.\n" }), + "input": Schema.String.annotate({ "description": "The input for the custom tool call generated by the model.\n" }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item.\n" }) + ) +}).annotate({ "title": "Custom tool call", "description": "A call to a custom tool created by the model.\n" }) +export type CustomToolChatCompletions = { + readonly "type": "custom" + readonly "custom": { + readonly "name": string + readonly "description"?: string + readonly "format"?: { readonly "type": "text" } | { + readonly "type": "grammar" + readonly "grammar": { readonly "definition": string; readonly "syntax": "lark" | "regex" } + } + } +} +export const CustomToolChatCompletions = Schema.Struct({ + "type": Schema.Literal("custom").annotate({ "description": "The type of the custom tool. Always `custom`." }), + "custom": Schema.Struct({ + "name": Schema.String.annotate({ + "description": "The name of the custom tool, used to identify it in tool calls." + }), + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional description of the custom tool, used to provide more context.\n" + }) + ), + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("text").annotate({ "description": "Unconstrained text format. Always `text`." }) + }).annotate({ "title": "Text format", "description": "Unconstrained free-form text." }), + Schema.Struct({ + "type": Schema.Literal("grammar").annotate({ "description": "Grammar format. Always `grammar`." }), + "grammar": Schema.Struct({ + "definition": Schema.String.annotate({ "description": "The grammar definition." }), + "syntax": Schema.Literals(["lark", "regex"]).annotate({ + "description": "The syntax of the grammar definition. One of `lark` or `regex`." + }) + }).annotate({ "title": "Grammar format", "description": "Your chosen grammar." }) + }).annotate({ "title": "Grammar format", "description": "A grammar defined by the user." }) + ], { mode: "oneOf" }).annotate({ + "description": "The input format for the custom tool. Default is unconstrained text.\n" + }) + ) + }).annotate({ "title": "Custom tool properties", "description": "Properties of the custom tool.\n" }) +}).annotate({ "title": "Custom tool", "description": "A custom tool that processes input using a specified format.\n" }) +export type DeleteAssistantResponse = { + readonly "id": string + readonly "deleted": boolean + readonly "object": "assistant.deleted" +} +export const DeleteAssistantResponse = Schema.Struct({ + "id": Schema.String, + "deleted": Schema.Boolean, + "object": Schema.Literal("assistant.deleted") +}) +export type DeleteCertificateResponse = { readonly "object": "certificate.deleted"; readonly "id": string } +export const DeleteCertificateResponse = Schema.Struct({ + "object": Schema.Literal("certificate.deleted").annotate({ + "description": "The object type, must be `certificate.deleted`." + }), + "id": Schema.String.annotate({ "description": "The ID of the certificate that was deleted." }) +}) +export type DeleteFileResponse = { readonly "id": string; readonly "object": "file"; readonly "deleted": boolean } +export const DeleteFileResponse = Schema.Struct({ + "id": Schema.String, + "object": Schema.Literal("file"), + "deleted": Schema.Boolean +}) +export type DeleteFineTuningCheckpointPermissionResponse = { + readonly "id": string + readonly "object": "checkpoint.permission" + readonly "deleted": boolean +} +export const DeleteFineTuningCheckpointPermissionResponse = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The ID of the fine-tuned model checkpoint permission that was deleted." + }), + "object": Schema.Literal("checkpoint.permission").annotate({ + "description": "The object type, which is always \"checkpoint.permission\"." + }), + "deleted": Schema.Boolean.annotate({ + "description": "Whether the fine-tuned model checkpoint permission was successfully deleted." + }) +}) +export type DeleteMessageResponse = { + readonly "id": string + readonly "deleted": boolean + readonly "object": "thread.message.deleted" +} +export const DeleteMessageResponse = Schema.Struct({ + "id": Schema.String, + "deleted": Schema.Boolean, + "object": Schema.Literal("thread.message.deleted") +}) +export type DeleteModelResponse = { readonly "id": string; readonly "deleted": boolean; readonly "object": string } +export const DeleteModelResponse = Schema.Struct({ + "id": Schema.String, + "deleted": Schema.Boolean, + "object": Schema.String +}) +export type DeleteThreadResponse = { + readonly "id": string + readonly "deleted": boolean + readonly "object": "thread.deleted" +} +export const DeleteThreadResponse = Schema.Struct({ + "id": Schema.String, + "deleted": Schema.Boolean, + "object": Schema.Literal("thread.deleted") +}) +export type DeleteVectorStoreFileResponse = { + readonly "id": string + readonly "deleted": boolean + readonly "object": "vector_store.file.deleted" +} +export const DeleteVectorStoreFileResponse = Schema.Struct({ + "id": Schema.String, + "deleted": Schema.Boolean, + "object": Schema.Literal("vector_store.file.deleted") +}) +export type DeleteVectorStoreResponse = { + readonly "id": string + readonly "deleted": boolean + readonly "object": "vector_store.deleted" +} +export const DeleteVectorStoreResponse = Schema.Struct({ + "id": Schema.String, + "deleted": Schema.Boolean, + "object": Schema.Literal("vector_store.deleted") +}) +export type DeletedRoleAssignmentResource = { readonly "object": string; readonly "deleted": boolean } +export const DeletedRoleAssignmentResource = Schema.Struct({ + "object": Schema.String.annotate({ + "description": "Identifier for the deleted assignment, such as `group.role.deleted` or `user.role.deleted`." + }), + "deleted": Schema.Boolean.annotate({ "description": "Whether the assignment was removed." }) +}).annotate({ "description": "Confirmation payload returned after unassigning a role." }) +export type DoneEvent = { readonly "event": "done"; readonly "data": "[DONE]" } +export const DoneEvent = Schema.Struct({ "event": Schema.Literal("done"), "data": Schema.Literal("[DONE]") }).annotate({ + "description": "Occurs when a stream ends." +}) +export type Embedding = { + readonly "index": number + readonly "embedding": ReadonlyArray + readonly "object": "embedding" +} +export const Embedding = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the embedding in the list of embeddings." }).check( + Schema.isInt() + ), + "embedding": Schema.Array(Schema.Number.annotate({ "format": "float" }).check(Schema.isFinite())).annotate({ + "description": + "The embedding vector, which is a list of floats. The length of vector depends on the model as listed in the [embedding guide](/docs/guides/embeddings).\n" + }), + "object": Schema.Literal("embedding").annotate({ "description": "The object type, which is always \"embedding\"." }) +}).annotate({ "description": "Represents an embedding vector returned by embedding endpoint.\n" }) +export type Error = { + readonly "code": string | null + readonly "message": string + readonly "param": string | null + readonly "type": string +} +export const Error = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "message": Schema.String, + "param": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.String +}) +export type EvalApiError = { readonly "code": string; readonly "message": string } +export const EvalApiError = Schema.Struct({ + "code": Schema.String.annotate({ "description": "The error code." }), + "message": Schema.String.annotate({ "description": "The error message." }) +}).annotate({ "title": "EvalApiError", "description": "An object representing an error response from the Eval API.\n" }) +export type EvalGraderPython = { + readonly "type": "python" + readonly "name": string + readonly "source": string + readonly "image_tag"?: string + readonly "pass_threshold"?: number +} +export const EvalGraderPython = Schema.Struct({ + "type": Schema.Literal("python").annotate({ "description": "The object type, which is always `python`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "source": Schema.String.annotate({ "description": "The source code of the python script." }), + "image_tag": Schema.optionalKey( + Schema.String.annotate({ "description": "The image tag to use for the python script." }) + ), + "pass_threshold": Schema.optionalKey( + Schema.Number.annotate({ "description": "The threshold for the score." }).check(Schema.isFinite()) + ) +}).annotate({ + "title": "PythonGrader", + "description": "A PythonGrader object that runs a python script on the input.\n" +}) +export type EvalGraderStringCheck = { + readonly "type": "string_check" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "operation": "eq" | "ne" | "like" | "ilike" +} +export const EvalGraderStringCheck = Schema.Struct({ + "type": Schema.Literal("string_check").annotate({ + "description": "The object type, which is always `string_check`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The input text. This may include template strings." }), + "reference": Schema.String.annotate({ "description": "The reference text. This may include template strings." }), + "operation": Schema.Literals(["eq", "ne", "like", "ilike"]).annotate({ + "description": "The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`." + }) +}).annotate({ + "title": "StringCheckGrader", + "description": + "A StringCheckGrader object that performs a string comparison between input and reference using a specified operation.\n" +}) +export type EvalGraderTextSimilarity = { + readonly "type": "text_similarity" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "evaluation_metric": + | "cosine" + | "fuzzy_match" + | "bleu" + | "gleu" + | "meteor" + | "rouge_1" + | "rouge_2" + | "rouge_3" + | "rouge_4" + | "rouge_5" + | "rouge_l" + readonly "pass_threshold": number +} +export const EvalGraderTextSimilarity = Schema.Struct({ + "type": Schema.Literal("text_similarity").annotate({ "description": "The type of grader." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The text being graded." }), + "reference": Schema.String.annotate({ "description": "The text being graded against." }), + "evaluation_metric": Schema.Literals([ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l" + ]).annotate({ + "description": + "The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, \n`gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, \nor `rouge_l`.\n" + }), + "pass_threshold": Schema.Number.annotate({ "description": "The threshold for the score." }).check(Schema.isFinite()) +}).annotate({ + "title": "TextSimilarityGrader", + "description": "A TextSimilarityGrader object which grades text based on similarity metrics.\n" +}) +export type EvalItemContentOutputText = { readonly "type": "output_text"; readonly "text": string } +export const EvalItemContentOutputText = Schema.Struct({ + "type": Schema.Literal("output_text").annotate({ + "description": "The type of the output text. Always `output_text`.\n" + }), + "text": Schema.String.annotate({ "description": "The text output from the model.\n" }) +}).annotate({ "title": "Output text", "description": "A text output from the model.\n" }) +export type EvalItemContentText = string +export const EvalItemContentText = Schema.String.annotate({ + "title": "Text input", + "description": "A text input to the model.\n" +}) +export type EvalItemInputImage = { + readonly "type": "input_image" + readonly "image_url": string + readonly "detail"?: string +} +export const EvalItemInputImage = Schema.Struct({ + "type": Schema.Literal("input_image").annotate({ + "description": "The type of the image input. Always `input_image`.\n" + }), + "image_url": Schema.String.annotate({ "description": "The URL of the image input.\n", "format": "uri" }), + "detail": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The detail level of the image to be sent to the model. One of `high`, `low`, or `auto`. Defaults to `auto`.\n" + }) + ) +}).annotate({ "title": "Input image", "description": "An image input block used within EvalItem content arrays." }) +export type EvalJsonlFileContentSource = { + readonly "type": "file_content" + readonly "content": ReadonlyArray<{ readonly "item": {}; readonly "sample"?: {} }> +} +export const EvalJsonlFileContentSource = Schema.Struct({ + "type": Schema.Literal("file_content").annotate({ + "description": "The type of jsonl source. Always `file_content`." + }), + "content": Schema.Array(Schema.Struct({ "item": Schema.Struct({}), "sample": Schema.optionalKey(Schema.Struct({})) })) + .annotate({ "description": "The content of the jsonl file." }) +}).annotate({ "title": "EvalJsonlFileContentSource" }) +export type EvalJsonlFileIdSource = { readonly "type": "file_id"; readonly "id": string } +export const EvalJsonlFileIdSource = Schema.Struct({ + "type": Schema.Literal("file_id").annotate({ "description": "The type of jsonl source. Always `file_id`." }), + "id": Schema.String.annotate({ "description": "The identifier of the file." }) +}).annotate({ "title": "EvalJsonlFileIdSource" }) +export type EvalResponsesSource = { + readonly "type": "responses" + readonly "metadata"?: {} | null + readonly "model"?: string | null + readonly "instructions_search"?: string | null + readonly "created_after"?: number | null + readonly "created_before"?: number | null + readonly "reasoning_effort"?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | null | null + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "users"?: ReadonlyArray | null + readonly "tools"?: ReadonlyArray | null +} +export const EvalResponsesSource = Schema.Struct({ + "type": Schema.Literal("responses").annotate({ "description": "The type of run data source. Always `responses`." }), + "metadata": Schema.optionalKey( + Schema.Union([ + Schema.Struct({}).annotate({ + "description": "Metadata filter for the responses. This is a query parameter used to select responses." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The name of the model to find responses for. This is a query parameter used to select responses." + }), + Schema.Null + ]) + ), + "instructions_search": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "Optional string to search the 'instructions' field. This is a query parameter used to select responses." + }), + Schema.Null + ]) + ), + "created_after": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Only include items created after this timestamp (inclusive). This is a query parameter used to select responses." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]) + ), + "created_before": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Only include items created before this timestamp (inclusive). This is a query parameter used to select responses." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Constrains effort on reasoning for\n[reasoning models](https://platform.openai.com/docs/guides/reasoning).\nCurrently supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. Reducing\nreasoning effort can result in faster responses and fewer tokens used\non reasoning in a response.\n\n- `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool calls are supported for all reasoning values in gpt-5.1.\n- All models before `gpt-5.1` default to `medium` reasoning effort, and do not support `none`.\n- The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort.\n- `xhigh` is supported for all models after `gpt-5.1-codex-max`.\n" + }), + Schema.Null + ]).annotate({ + "description": "Optional reasoning effort parameter. This is a query parameter used to select responses." + }), + Schema.Null + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "Sampling temperature. This is a query parameter used to select responses." + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "Nucleus sampling parameter. This is a query parameter used to select responses." + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "users": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + "description": "List of user identifiers. This is a query parameter used to select responses." + }), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + "description": "List of tool names. This is a query parameter used to select responses." + }), + Schema.Null + ]) + ) +}).annotate({ + "title": "EvalResponsesSource", + "description": "A EvalResponsesSource object describing a run data source configuration.\n" +}) +export type EvalRunOutputItemResult = { + readonly "name": string + readonly "type"?: string + readonly "score": number + readonly "passed": boolean + readonly "sample"?: {} | null +} +export const EvalRunOutputItemResult = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "type": Schema.optionalKey( + Schema.String.annotate({ "description": "The grader type (for example, \"string-check-grader\")." }) + ), + "score": Schema.Number.annotate({ "description": "The numeric score produced by the grader." }).check( + Schema.isFinite() + ), + "passed": Schema.Boolean.annotate({ "description": "Whether the grader considered the output a pass." }), + "sample": Schema.optionalKey( + Schema.Union([Schema.Struct({}), Schema.Null]).annotate({ + "description": "Optional sample or intermediate data produced by the grader." + }) + ) +}).annotate({ + "title": "EvalRunOutputItemResult", + "description": "A single grader result for an evaluation run output item.\n" +}) +export type FileExpirationAfter = { readonly "anchor": "created_at"; readonly "seconds": number } +export const FileExpirationAfter = Schema.Struct({ + "anchor": Schema.Literal("created_at").annotate({ + "description": "Anchor timestamp after which the expiration policy applies. Supported anchors: `created_at`." + }), + "seconds": Schema.Number.annotate({ + "description": + "The number of seconds after the anchor time that the file will expire. Must be between 3600 (1 hour) and 2592000 (30 days).", + "format": "int64" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(3600)).check(Schema.isLessThanOrEqualTo(2592000)) +}).annotate({ + "title": "File expiration policy", + "description": + "The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted." +}) +export type FilePath = { readonly "type": "file_path"; readonly "file_id": string; readonly "index": number } +export const FilePath = Schema.Struct({ + "type": Schema.Literal("file_path").annotate({ "description": "The type of the file path. Always `file_path`.\n" }), + "file_id": Schema.String.annotate({ "description": "The ID of the file.\n" }), + "index": Schema.Number.annotate({ "description": "The index of the file in the list of files.\n" }).check( + Schema.isInt() + ) +}).annotate({ "title": "File path", "description": "A path to a file.\n" }) +export type FileSearchRanker = "auto" | "default_2024_08_21" +export const FileSearchRanker = Schema.Literals(["auto", "default_2024_08_21"]).annotate({ + "description": "The ranker to use for the file search. If not specified will use the `auto` ranker." +}) +export type FineTuneDPOHyperparameters = { + readonly "beta"?: "auto" | number + readonly "batch_size"?: "auto" | number + readonly "learning_rate_multiplier"?: "auto" | number + readonly "n_epochs"?: "auto" | number +} +export const FineTuneDPOHyperparameters = Schema.Struct({ + "beta": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isFinite()).check(Schema.isLessThanOrEqualTo(2)).check(Schema.isGreaterThan(0)) + ], { mode: "oneOf" }).annotate({ + "description": + "The beta value for the DPO method. A higher beta value will increase the weight of the penalty between the policy and reference model.\n" + }) + ), + "batch_size": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(256)) + ], { mode: "oneOf" }).annotate({ + "description": + "Number of examples in each batch. A larger batch size means that model parameters are updated less frequently, but with lower variance.\n" + }) + ), + "learning_rate_multiplier": Schema.optionalKey( + Schema.Union([Schema.Literal("auto"), Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThan(0))], { + mode: "oneOf" + }).annotate({ + "description": + "Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting.\n" + }) + ), + "n_epochs": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(50)) + ], { mode: "oneOf" }).annotate({ + "description": + "The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset.\n" + }) + ) +}).annotate({ "description": "The hyperparameters used for the DPO fine-tuning job." }) +export type FineTuneReinforcementHyperparameters = { + readonly "batch_size"?: "auto" | number + readonly "learning_rate_multiplier"?: "auto" | number + readonly "n_epochs"?: "auto" | number + readonly "reasoning_effort"?: "default" | "low" | "medium" | "high" + readonly "compute_multiplier"?: "auto" | number + readonly "eval_interval"?: "auto" | number + readonly "eval_samples"?: "auto" | number +} +export const FineTuneReinforcementHyperparameters = Schema.Struct({ + "batch_size": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(256)) + ], { mode: "oneOf" }).annotate({ + "description": + "Number of examples in each batch. A larger batch size means that model parameters are updated less frequently, but with lower variance.\n" + }) + ), + "learning_rate_multiplier": Schema.optionalKey( + Schema.Union([Schema.Literal("auto"), Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThan(0))], { + mode: "oneOf" + }).annotate({ + "description": + "Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting.\n" + }) + ), + "n_epochs": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(50)) + ], { mode: "oneOf" }).annotate({ + "description": + "The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset.\n" + }) + ), + "reasoning_effort": Schema.optionalKey( + Schema.Literals(["default", "low", "medium", "high"]).annotate({ "description": "Level of reasoning effort.\n" }) + ), + "compute_multiplier": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isFinite()).check(Schema.isLessThanOrEqualTo(10)).check(Schema.isGreaterThan(0.00001)) + ], { mode: "oneOf" }).annotate({ + "description": "Multiplier on amount of compute used for exploring search space during training.\n" + }) + ), + "eval_interval": Schema.optionalKey( + Schema.Union( + [Schema.Literal("auto"), Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1))], + { mode: "oneOf" } + ).annotate({ "description": "The number of training steps between evaluation runs.\n" }) + ), + "eval_samples": Schema.optionalKey( + Schema.Union( + [Schema.Literal("auto"), Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1))], + { mode: "oneOf" } + ).annotate({ "description": "Number of evaluation samples to generate per training step.\n" }) + ) +}).annotate({ "description": "The hyperparameters used for the reinforcement fine-tuning job." }) +export type FineTuneSupervisedHyperparameters = { + readonly "batch_size"?: "auto" | number + readonly "learning_rate_multiplier"?: "auto" | number + readonly "n_epochs"?: "auto" | number +} +export const FineTuneSupervisedHyperparameters = Schema.Struct({ + "batch_size": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(256)) + ], { mode: "oneOf" }).annotate({ + "description": + "Number of examples in each batch. A larger batch size means that model parameters are updated less frequently, but with lower variance.\n" + }) + ), + "learning_rate_multiplier": Schema.optionalKey( + Schema.Union([Schema.Literal("auto"), Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThan(0))], { + mode: "oneOf" + }).annotate({ + "description": + "Scaling factor for the learning rate. A smaller learning rate may be useful to avoid overfitting.\n" + }) + ), + "n_epochs": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(50)) + ], { mode: "oneOf" }).annotate({ + "description": + "The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset.\n" + }) + ) +}).annotate({ "description": "The hyperparameters used for the fine-tuning job." }) +export type FineTuningCheckpointPermission = { + readonly "id": string + readonly "created_at": number + readonly "project_id": string + readonly "object": "checkpoint.permission" +} +export const FineTuningCheckpointPermission = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The permission identifier, which can be referenced in the API endpoints." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the permission was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "project_id": Schema.String.annotate({ "description": "The project identifier that the permission is for." }), + "object": Schema.Literal("checkpoint.permission").annotate({ + "description": "The object type, which is always \"checkpoint.permission\"." + }) +}).annotate({ + "title": "FineTuningCheckpointPermission", + "description": "The `checkpoint.permission` object represents a permission for a fine-tuned model checkpoint.\n" +}) +export type FineTuningIntegration = { + readonly "type": "wandb" + readonly "wandb": { + readonly "project": string + readonly "name"?: string | null + readonly "entity"?: string | null + readonly "tags"?: ReadonlyArray + } +} +export const FineTuningIntegration = Schema.Struct({ + "type": Schema.Literal("wandb").annotate({ + "description": "The type of the integration being enabled for the fine-tuning job" + }), + "wandb": Schema.Struct({ + "project": Schema.String.annotate({ + "description": "The name of the project that the new run will be created under.\n" + }), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "A display name to set for the run. If not set, we will use the Job ID as the name.\n" + }), + Schema.Null + ]) + ), + "entity": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The entity to use for the run. This allows you to set the team or username of the WandB user that you would\nlike associated with the run. If not set, the default entity for the registered WandB API key is used.\n" + }), + Schema.Null + ]) + ), + "tags": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of tags to be attached to the newly created run. These tags are passed through directly to WandB. Some\ndefault tags are generated by OpenAI: \"openai/finetune\", \"openai/{base-model}\", \"openai/{ftjob-abcdef}\".\n" + }) + ) + }).annotate({ + "description": + "The settings for your integration with Weights and Biases. This payload specifies the project that\nmetrics will be sent to. Optionally, you can set an explicit display name for your run, add tags\nto your run, and set a default entity (team, username, etc) to be associated with your run.\n" + }) +}).annotate({ "title": "Fine-Tuning Job Integration" }) +export type FineTuningJobCheckpoint = { + readonly "id": string + readonly "created_at": number + readonly "fine_tuned_model_checkpoint": string + readonly "step_number": number + readonly "metrics": { + readonly "step"?: number + readonly "train_loss"?: number + readonly "train_mean_token_accuracy"?: number + readonly "valid_loss"?: number + readonly "valid_mean_token_accuracy"?: number + readonly "full_valid_loss"?: number + readonly "full_valid_mean_token_accuracy"?: number + } + readonly "fine_tuning_job_id": string + readonly "object": "fine_tuning.job.checkpoint" +} +export const FineTuningJobCheckpoint = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The checkpoint identifier, which can be referenced in the API endpoints." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the checkpoint was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "fine_tuned_model_checkpoint": Schema.String.annotate({ + "description": "The name of the fine-tuned checkpoint model that is created." + }), + "step_number": Schema.Number.annotate({ "description": "The step number that the checkpoint was created at." }).check( + Schema.isInt() + ), + "metrics": Schema.Struct({ + "step": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), + "train_loss": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), + "train_mean_token_accuracy": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), + "valid_loss": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), + "valid_mean_token_accuracy": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), + "full_valid_loss": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), + "full_valid_mean_token_accuracy": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) + }).annotate({ "description": "Metrics at the step number during the fine-tuning job." }), + "fine_tuning_job_id": Schema.String.annotate({ + "description": "The name of the fine-tuning job that this checkpoint was created from." + }), + "object": Schema.Literal("fine_tuning.job.checkpoint").annotate({ + "description": "The object type, which is always \"fine_tuning.job.checkpoint\"." + }) +}).annotate({ + "title": "FineTuningJobCheckpoint", + "description": + "The `fine_tuning.job.checkpoint` object represents a model checkpoint for a fine-tuning job that is ready to use.\n" +}) +export type FineTuningJobEvent = { + readonly "object": "fine_tuning.job.event" + readonly "id": string + readonly "created_at": number + readonly "level": "info" | "warn" | "error" + readonly "message": string + readonly "type"?: "message" | "metrics" + readonly "data"?: {} +} +export const FineTuningJobEvent = Schema.Struct({ + "object": Schema.Literal("fine_tuning.job.event").annotate({ + "description": "The object type, which is always \"fine_tuning.job.event\"." + }), + "id": Schema.String.annotate({ "description": "The object identifier." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the fine-tuning job was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "level": Schema.Literals(["info", "warn", "error"]).annotate({ "description": "The log level of the event." }), + "message": Schema.String.annotate({ "description": "The message of the event." }), + "type": Schema.optionalKey(Schema.Literals(["message", "metrics"]).annotate({ "description": "The type of event." })), + "data": Schema.optionalKey(Schema.Struct({}).annotate({ "description": "The data associated with the event." })) +}).annotate({ "description": "Fine-tuning job event object" }) +export type FunctionParameters = {} +export const FunctionParameters = Schema.Struct({}).annotate({ + "description": + "The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format. \n\nOmitting `parameters` defines a function with an empty parameter list." +}) +export type FunctionToolCall = { + readonly "id"?: string + readonly "type": "function_call" + readonly "call_id": string + readonly "namespace"?: string + readonly "name": string + readonly "arguments": string + readonly "status"?: "in_progress" | "completed" | "incomplete" +} +export const FunctionToolCall = Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The unique ID of the function tool call.\n" })), + "type": Schema.Literal("function_call").annotate({ + "description": "The type of the function tool call. Always `function_call`.\n" + }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the function tool call generated by the model.\n" + }), + "namespace": Schema.optionalKey(Schema.String.annotate({ "description": "The namespace of the function to run.\n" })), + "name": Schema.String.annotate({ "description": "The name of the function to run.\n" }), + "arguments": Schema.String.annotate({ "description": "A JSON string of the arguments to pass to the function.\n" }), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ) +}).annotate({ + "title": "Function tool call", + "description": + "A tool call to run a function. See the \n[function calling guide](/docs/guides/function-calling) for more information.\n" +}) +export type FunctionToolCallResource = { + readonly "id": string + readonly "type": "function_call" + readonly "call_id": string + readonly "namespace"?: string + readonly "name": string + readonly "arguments": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "created_by"?: string +} +export const FunctionToolCallResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the function tool call.\n" }), + "type": Schema.Literal("function_call").annotate({ + "description": "The type of the function tool call. Always `function_call`.\n" + }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the function tool call generated by the model.\n" + }), + "namespace": Schema.optionalKey(Schema.String.annotate({ "description": "The namespace of the function to run.\n" })), + "name": Schema.String.annotate({ "description": "The name of the function to run.\n" }), + "arguments": Schema.String.annotate({ "description": "A JSON string of the arguments to pass to the function.\n" }), + "status": Schema.Union([ + Schema.Literal("in_progress").annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + Schema.Literal("completed").annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + Schema.Literal("incomplete").annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item.\n" }) + ) +}).annotate({ + "title": "Function tool call", + "description": + "A tool call to run a function. See the \n[function calling guide](/docs/guides/function-calling) for more information.\n" +}) +export type GraderPython = { + readonly "type": "python" + readonly "name": string + readonly "source": string + readonly "image_tag"?: string +} +export const GraderPython = Schema.Struct({ + "type": Schema.Literal("python").annotate({ "description": "The object type, which is always `python`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "source": Schema.String.annotate({ "description": "The source code of the python script." }), + "image_tag": Schema.optionalKey( + Schema.String.annotate({ "description": "The image tag to use for the python script." }) + ) +}).annotate({ + "title": "PythonGrader", + "description": "A PythonGrader object that runs a python script on the input.\n" +}) +export type GraderStringCheck = { + readonly "type": "string_check" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "operation": "eq" | "ne" | "like" | "ilike" +} +export const GraderStringCheck = Schema.Struct({ + "type": Schema.Literal("string_check").annotate({ + "description": "The object type, which is always `string_check`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The input text. This may include template strings." }), + "reference": Schema.String.annotate({ "description": "The reference text. This may include template strings." }), + "operation": Schema.Literals(["eq", "ne", "like", "ilike"]).annotate({ + "description": "The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`." + }) +}).annotate({ + "title": "StringCheckGrader", + "description": + "A StringCheckGrader object that performs a string comparison between input and reference using a specified operation.\n" +}) +export type GraderTextSimilarity = { + readonly "type": "text_similarity" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "evaluation_metric": + | "cosine" + | "fuzzy_match" + | "bleu" + | "gleu" + | "meteor" + | "rouge_1" + | "rouge_2" + | "rouge_3" + | "rouge_4" + | "rouge_5" + | "rouge_l" +} +export const GraderTextSimilarity = Schema.Struct({ + "type": Schema.Literal("text_similarity").annotate({ "description": "The type of grader." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The text being graded." }), + "reference": Schema.String.annotate({ "description": "The text being graded against." }), + "evaluation_metric": Schema.Literals([ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l" + ]).annotate({ + "description": + "The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, \n`gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, \nor `rouge_l`.\n" + }) +}).annotate({ + "title": "TextSimilarityGrader", + "description": "A TextSimilarityGrader object which grades text based on similarity metrics.\n" +}) +export type Group = { + readonly "object": "group" + readonly "id": string + readonly "name": string + readonly "created_at": number + readonly "scim_managed": boolean +} +export const Group = Schema.Struct({ + "object": Schema.Literal("group").annotate({ "description": "Always `group`." }), + "id": Schema.String.annotate({ "description": "Identifier for the group." }), + "name": Schema.String.annotate({ "description": "Display name of the group." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the group was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "scim_managed": Schema.Boolean.annotate({ "description": "Whether the group is managed through SCIM." }) +}).annotate({ "description": "Summary information about a group returned in role assignment responses." }) +export type GroupDeletedResource = { + readonly "object": "group.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const GroupDeletedResource = Schema.Struct({ + "object": Schema.Literal("group.deleted").annotate({ "description": "Always `group.deleted`." }), + "id": Schema.String.annotate({ "description": "Identifier of the deleted group." }), + "deleted": Schema.Boolean.annotate({ "description": "Whether the group was deleted." }) +}).annotate({ "description": "Confirmation payload returned after deleting a group." }) +export type GroupResourceWithSuccess = { + readonly "id": string + readonly "name": string + readonly "created_at": number + readonly "is_scim_managed": boolean +} +export const GroupResourceWithSuccess = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier for the group." }), + "name": Schema.String.annotate({ "description": "Updated display name for the group." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the group was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "is_scim_managed": Schema.Boolean.annotate({ + "description": "Whether the group is managed through SCIM and controlled by your identity provider." + }) +}).annotate({ "description": "Response returned after updating a group." }) +export type GroupResponse = { + readonly "id": string + readonly "name": string + readonly "created_at": number + readonly "is_scim_managed": boolean + readonly "group_type": string +} +export const GroupResponse = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier for the group." }), + "name": Schema.String.annotate({ "description": "Display name of the group." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the group was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "is_scim_managed": Schema.Boolean.annotate({ + "description": "Whether the group is managed through SCIM and controlled by your identity provider." + }), + "group_type": Schema.String.annotate({ "description": "The type of the group." }) +}).annotate({ "description": "Details about an organization group." }) +export type GroupUser = { readonly "id": string; readonly "name": string; readonly "email": string | null } +export const GroupUser = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.String.annotate({ "description": "The name of the user." }), + "email": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The email address of the user." }) +}).annotate({ "description": "Represents an individual user returned when inspecting group membership." }) +export type GroupUserAssignment = { + readonly "object": "group.user" + readonly "user_id": string + readonly "group_id": string +} +export const GroupUserAssignment = Schema.Struct({ + "object": Schema.Literal("group.user").annotate({ "description": "Always `group.user`." }), + "user_id": Schema.String.annotate({ "description": "Identifier of the user that was added." }), + "group_id": Schema.String.annotate({ "description": "Identifier of the group the user was added to." }) +}).annotate({ "description": "Confirmation payload returned after adding a user to a group." }) +export type GroupUserDeletedResource = { readonly "object": "group.user.deleted"; readonly "deleted": boolean } +export const GroupUserDeletedResource = Schema.Struct({ + "object": Schema.Literal("group.user.deleted").annotate({ "description": "Always `group.user.deleted`." }), + "deleted": Schema.Boolean.annotate({ "description": "Whether the group membership was removed." }) +}).annotate({ "description": "Confirmation payload returned after removing a user from a group." }) +export type HostedToolPermission = { readonly "enabled": boolean } +export const HostedToolPermission = Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "description": "Whether the hosted tool is enabled for the project." }) +}).annotate({ "description": "Permission state for a single hosted tool on a project." }) +export type HostedToolPermissionUpdate = { readonly "enabled": boolean } +export const HostedToolPermissionUpdate = Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "description": "Whether to enable the hosted tool for the project." }) +}) +export type Image = { readonly "b64_json"?: string; readonly "url"?: string; readonly "revised_prompt"?: string } +export const Image = Schema.Struct({ + "b64_json": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The base64-encoded JSON of the generated image. Returned by default for the GPT image models, and only present if `response_format` is set to `b64_json` for `dall-e-2` and `dall-e-3`." + }) + ), + "url": Schema.optionalKey( + Schema.String.annotate({ + "description": + "When using `dall-e-2` or `dall-e-3`, the URL of the generated image if `response_format` is set to `url` (default value). Unsupported for the GPT image models.", + "format": "uri" + }) + ), + "revised_prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "For `dall-e-3` only, the revised prompt that was used to generate the image." + }) + ) +}).annotate({ "description": "Represents the content or the URL of an image generated by the OpenAI API." }) +export type ImageEditPartialImageEvent = { + readonly "type": "image_edit.partial_image" + readonly "b64_json": string + readonly "created_at": number + readonly "size": "1024x1024" | "1024x1536" | "1536x1024" | "auto" + readonly "quality": "low" | "medium" | "high" | "auto" + readonly "background": "transparent" | "opaque" | "auto" + readonly "output_format": "png" | "webp" | "jpeg" + readonly "partial_image_index": number +} +export const ImageEditPartialImageEvent = Schema.Struct({ + "type": Schema.Literal("image_edit.partial_image").annotate({ + "description": "The type of the event. Always `image_edit.partial_image`.\n" + }), + "b64_json": Schema.String.annotate({ + "description": "Base64-encoded partial image data, suitable for rendering as an image.\n" + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp when the event was created.\n", + "format": "unixtime" + }).check(Schema.isInt()), + "size": Schema.Literals(["1024x1024", "1024x1536", "1536x1024", "auto"]).annotate({ + "description": "The size of the requested edited image.\n" + }), + "quality": Schema.Literals(["low", "medium", "high", "auto"]).annotate({ + "description": "The quality setting for the requested edited image.\n" + }), + "background": Schema.Literals(["transparent", "opaque", "auto"]).annotate({ + "description": "The background setting for the requested edited image.\n" + }), + "output_format": Schema.Literals(["png", "webp", "jpeg"]).annotate({ + "description": "The output format for the requested edited image.\n" + }), + "partial_image_index": Schema.Number.annotate({ "description": "0-based index for the partial image (streaming).\n" }) + .check(Schema.isInt()) +}).annotate({ "description": "Emitted when a partial image is available during image editing streaming.\n" }) +export type ImageGenPartialImageEvent = { + readonly "type": "image_generation.partial_image" + readonly "b64_json": string + readonly "created_at": number + readonly "size": "1024x1024" | "1024x1536" | "1536x1024" | "auto" + readonly "quality": "low" | "medium" | "high" | "auto" + readonly "background": "transparent" | "opaque" | "auto" + readonly "output_format": "png" | "webp" | "jpeg" + readonly "partial_image_index": number +} +export const ImageGenPartialImageEvent = Schema.Struct({ + "type": Schema.Literal("image_generation.partial_image").annotate({ + "description": "The type of the event. Always `image_generation.partial_image`.\n" + }), + "b64_json": Schema.String.annotate({ + "description": "Base64-encoded partial image data, suitable for rendering as an image.\n" + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp when the event was created.\n", + "format": "unixtime" + }).check(Schema.isInt()), + "size": Schema.Literals(["1024x1024", "1024x1536", "1536x1024", "auto"]).annotate({ + "description": "The size of the requested image.\n" + }), + "quality": Schema.Literals(["low", "medium", "high", "auto"]).annotate({ + "description": "The quality setting for the requested image.\n" + }), + "background": Schema.Literals(["transparent", "opaque", "auto"]).annotate({ + "description": "The background setting for the requested image.\n" + }), + "output_format": Schema.Literals(["png", "webp", "jpeg"]).annotate({ + "description": "The output format for the requested image.\n" + }), + "partial_image_index": Schema.Number.annotate({ "description": "0-based index for the partial image (streaming).\n" }) + .check(Schema.isInt()) +}).annotate({ "description": "Emitted when a partial image is available during image generation streaming.\n" }) +export type ImageGenToolCall = { + readonly "type": "image_generation_call" + readonly "id": string + readonly "status": "in_progress" | "completed" | "generating" | "failed" + readonly "result": string | null +} +export const ImageGenToolCall = Schema.Struct({ + "type": Schema.Literal("image_generation_call").annotate({ + "description": "The type of the image generation call. Always `image_generation_call`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the image generation call.\n" }), + "status": Schema.Literals(["in_progress", "completed", "generating", "failed"]).annotate({ + "description": "The status of the image generation call.\n" + }), + "result": Schema.Union([ + Schema.String.annotate({ "description": "The generated image encoded in base64.\n" }), + Schema.Null + ]) +}).annotate({ "title": "Image generation call", "description": "An image generation request made by the model.\n" }) +export type ImageRefParam = { readonly "image_url": string; readonly "file_id"?: string } | { + readonly "file_id": string + readonly "image_url"?: string +} +export const ImageRefParam = Schema.Union([ + Schema.Struct({ + "image_url": Schema.String.annotate({ + "description": "A fully qualified URL or base64-encoded data URL.", + "format": "uri" + }).check(Schema.isMaxLength(20971520)), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The File API ID of an uploaded image to use as input." }) + ) + }).annotate({ + "description": + "Reference an input image by either URL or uploaded file ID.\nProvide exactly one of `image_url` or `file_id`.\n" + }), + Schema.Struct({ + "file_id": Schema.String.annotate({ "description": "The File API ID of an uploaded image to use as input." }), + "image_url": Schema.optionalKey( + Schema.String.annotate({ "description": "A fully qualified URL or base64-encoded data URL.", "format": "uri" }) + .check(Schema.isMaxLength(20971520)) + ) + }).annotate({ + "description": + "Reference an input image by either URL or uploaded file ID.\nProvide exactly one of `image_url` or `file_id`.\n" + }) +]) +export type ImagesUsage = { + readonly "total_tokens": number + readonly "input_tokens": number + readonly "output_tokens": number + readonly "input_tokens_details": { readonly "text_tokens": number; readonly "image_tokens": number } +} +export const ImagesUsage = Schema.Struct({ + "total_tokens": Schema.Number.annotate({ + "description": "The total number of tokens (images and text) used for the image generation.\n" + }).check(Schema.isInt()), + "input_tokens": Schema.Number.annotate({ + "description": "The number of tokens (images and text) in the input prompt." + }).check(Schema.isInt()), + "output_tokens": Schema.Number.annotate({ "description": "The number of image tokens in the output image." }).check( + Schema.isInt() + ), + "input_tokens_details": Schema.Struct({ + "text_tokens": Schema.Number.annotate({ "description": "The number of text tokens in the input prompt." }).check( + Schema.isInt() + ), + "image_tokens": Schema.Number.annotate({ "description": "The number of image tokens in the input prompt." }).check( + Schema.isInt() + ) + }).annotate({ "description": "The input tokens detailed information for the image generation." }) +}).annotate({ "description": "For the GPT image models only, the token usage information for the image generation.\n" }) +export type InputAudio = { + readonly "type": "input_audio" + readonly "input_audio": { readonly "data": string; readonly "format": "mp3" | "wav" } +} +export const InputAudio = Schema.Struct({ + "type": Schema.Literal("input_audio").annotate({ + "description": "The type of the input item. Always `input_audio`.\n" + }), + "input_audio": Schema.Struct({ + "data": Schema.String.annotate({ "description": "Base64-encoded audio data.\n" }), + "format": Schema.Literals(["mp3", "wav"]).annotate({ + "description": "The format of the audio data. Currently supported formats are `mp3` and\n`wav`.\n" + }) + }) +}).annotate({ "title": "Input audio", "description": "An audio input to the model.\n" }) +export type Invite = { + readonly "object": "organization.invite" + readonly "id": string + readonly "email": string + readonly "role": "owner" | "reader" + readonly "status": "accepted" | "expired" | "pending" + readonly "created_at": number + readonly "expires_at"?: number | null + readonly "accepted_at"?: number | null + readonly "projects": ReadonlyArray<{ readonly "id": string; readonly "role": "member" | "owner" }> +} +export const Invite = Schema.Struct({ + "object": Schema.Literal("organization.invite").annotate({ + "description": "The object type, which is always `organization.invite`" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "email": Schema.String.annotate({ "description": "The email address of the individual to whom the invite was sent" }), + "role": Schema.Literals(["owner", "reader"]).annotate({ "description": "`owner` or `reader`" }), + "status": Schema.Literals(["accepted", "expired", "pending"]).annotate({ + "description": "`accepted`,`expired`, or `pending`" + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the invite was sent.", + "format": "unixtime" + }).check(Schema.isInt()), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), Schema.Null]).annotate({ + "description": "The Unix timestamp (in seconds) of when the invite expires." + }) + ), + "accepted_at": Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), Schema.Null]).annotate({ + "description": "The Unix timestamp (in seconds) of when the invite was accepted." + }) + ), + "projects": Schema.Array( + Schema.Struct({ + "id": Schema.String.annotate({ "description": "Project's public ID" }), + "role": Schema.Literals(["member", "owner"]).annotate({ "description": "Project membership role" }) + }) + ).annotate({ "description": "The projects that were granted membership upon acceptance of the invite." }) +}).annotate({ "description": "Represents an individual `invite` to the organization." }) +export type InviteDeleteResponse = { + readonly "object": "organization.invite.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const InviteDeleteResponse = Schema.Struct({ + "object": Schema.Literal("organization.invite.deleted").annotate({ + "description": "The object type, which is always `organization.invite.deleted`" + }), + "id": Schema.String, + "deleted": Schema.Boolean +}) +export type InviteProjectGroupBody = { readonly "group_id": string; readonly "role": string } +export const InviteProjectGroupBody = Schema.Struct({ + "group_id": Schema.String.annotate({ "description": "Identifier of the group to add to the project." }), + "role": Schema.String.annotate({ "description": "Identifier of the project role to grant to the group." }) +}).annotate({ "description": "Request payload for granting a group access to a project." }) +export type InviteRequest = { + readonly "email": string + readonly "role": "reader" | "owner" + readonly "projects"?: ReadonlyArray<{ readonly "id": string; readonly "role": "member" | "owner" }> +} +export const InviteRequest = Schema.Struct({ + "email": Schema.String.annotate({ "description": "Send an email to this address" }), + "role": Schema.Literals(["reader", "owner"]).annotate({ "description": "`owner` or `reader`" }), + "projects": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "id": Schema.String.annotate({ "description": "Project's public ID" }), + "role": Schema.Literals(["member", "owner"]).annotate({ "description": "Project membership role" }) + }) + ).annotate({ + "description": + "An array of projects to which membership is granted at the same time the org invite is accepted. If omitted, the user will be invited to the default project for compatibility with legacy behavior. If empty list is passed, the user will not be invited to any projects, including the default one." + }) + ) +}) +export type LocalShellToolCallOutput = { + readonly "type": "local_shell_call_output" + readonly "id": string + readonly "output": string + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + readonly "call_id": unknown +} +export const LocalShellToolCallOutput = Schema.Struct({ + "type": Schema.Literal("local_shell_call_output").annotate({ + "description": "The type of the local shell tool call output. Always `local_shell_call_output`.\n" + }), + "id": Schema.String.annotate({ + "description": "The unique ID of the local shell tool call generated by the model.\n" + }), + "output": Schema.String.annotate({ "description": "A JSON string of the output of the local shell tool call.\n" }), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the item. One of `in_progress`, `completed`, or `incomplete`.\n" + }), + Schema.Null + ]) + ), + "call_id": Schema.Unknown +}).annotate({ "title": "Local shell call output", "description": "The output of a local shell tool call.\n" }) +export type LogProbProperties = { + readonly "token": string + readonly "logprob": number + readonly "bytes": ReadonlyArray +} +export const LogProbProperties = Schema.Struct({ + "token": Schema.String.annotate({ "description": "The token that was used to generate the log probability.\n" }), + "logprob": Schema.Number.annotate({ "description": "The log probability of the token.\n" }).check(Schema.isFinite()), + "bytes": Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": "The bytes that were used to generate the log probability.\n" + }) +}).annotate({ "description": "A log probability object.\n" }) +export type MCPApprovalRequest = { + readonly "type": "mcp_approval_request" + readonly "id": string + readonly "server_label": string + readonly "name": string + readonly "arguments": string +} +export const MCPApprovalRequest = Schema.Struct({ + "type": Schema.Literal("mcp_approval_request").annotate({ + "description": "The type of the item. Always `mcp_approval_request`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the approval request.\n" }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server making the request.\n" }), + "name": Schema.String.annotate({ "description": "The name of the tool to run.\n" }), + "arguments": Schema.String.annotate({ "description": "A JSON string of arguments for the tool.\n" }) +}).annotate({ "title": "MCP approval request", "description": "A request for human approval of a tool invocation.\n" }) +export type MCPApprovalResponseResource = { + readonly "type": "mcp_approval_response" + readonly "id": string + readonly "approval_request_id": string + readonly "approve": boolean + readonly "reason"?: string | null + readonly "request_id": unknown +} +export const MCPApprovalResponseResource = Schema.Struct({ + "type": Schema.Literal("mcp_approval_response").annotate({ + "description": "The type of the item. Always `mcp_approval_response`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the approval response\n" }), + "approval_request_id": Schema.String.annotate({ "description": "The ID of the approval request being answered.\n" }), + "approve": Schema.Boolean.annotate({ "description": "Whether the request was approved.\n" }), + "reason": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Optional reason for the decision.\n" }), Schema.Null]) + ), + "request_id": Schema.Unknown +}).annotate({ "title": "MCP approval response", "description": "A response to an MCP approval request.\n" }) +export type MCPListToolsTool = { + readonly "name": string + readonly "description"?: string | null + readonly "input_schema": {} + readonly "annotations"?: {} | null +} +export const MCPListToolsTool = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the tool.\n" }), + "description": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The description of the tool.\n" }), Schema.Null]) + ), + "input_schema": Schema.Struct({}).annotate({ "description": "The JSON schema describing the tool's input.\n" }), + "annotations": Schema.optionalKey( + Schema.Union([ + Schema.Struct({}).annotate({ "description": "Additional annotations about the tool.\n" }), + Schema.Null + ]) + ) +}).annotate({ "title": "MCP list tools tool", "description": "A tool available on an MCP server.\n" }) +export type MCPToolCall = { + readonly "type": "mcp_call" + readonly "id": string + readonly "server_label": string + readonly "name": string + readonly "arguments": string + readonly "output"?: string | null + readonly "error"?: string | null + readonly "status"?: "in_progress" | "completed" | "incomplete" | "calling" | "failed" + readonly "approval_request_id"?: string | null +} +export const MCPToolCall = Schema.Struct({ + "type": Schema.Literal("mcp_call").annotate({ "description": "The type of the item. Always `mcp_call`.\n" }), + "id": Schema.String.annotate({ "description": "The unique ID of the tool call.\n" }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server running the tool.\n" }), + "name": Schema.String.annotate({ "description": "The name of the tool that was run.\n" }), + "arguments": Schema.String.annotate({ "description": "A JSON string of the arguments passed to the tool.\n" }), + "output": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The output from the tool call.\n" }), Schema.Null]) + ), + "error": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The error from the tool call, if any.\n" }), Schema.Null]) + ), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete", "calling", "failed"]).annotate({ + "description": + "The status of the tool call. One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`.\n" + }) + ), + "approval_request_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "Unique identifier for the MCP tool call approval request.\nInclude this value in a subsequent `mcp_approval_response` input to approve or reject the corresponding tool call.\n" + }), + Schema.Null + ]) + ) +}).annotate({ "title": "MCP tool call", "description": "An invocation of a tool on an MCP server.\n" }) +export type MCPToolFilter = { readonly "tool_names"?: ReadonlyArray; readonly "read_only"?: boolean } +export const MCPToolFilter = Schema.Struct({ + "tool_names": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "title": "MCP allowed tools", "description": "List of allowed tool names." }) + ), + "read_only": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Indicates whether or not a tool modifies data or is read-only. If an\nMCP server is [annotated with `readOnlyHint`](https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations-readonlyhint),\nit will match this filter.\n" + }) + ) +}).annotate({ "title": "MCP tool filter", "description": "A filter object to specify which tools are allowed.\n" }) +export type MessageContentImageFileObject = { + readonly "type": "image_file" + readonly "image_file": { readonly "file_id": string; readonly "detail"?: "auto" | "low" | "high" } +} +export const MessageContentImageFileObject = Schema.Struct({ + "type": Schema.Literal("image_file").annotate({ "description": "Always `image_file`." }), + "image_file": Schema.Struct({ + "file_id": Schema.String.annotate({ + "description": + "The [File](/docs/api-reference/files) ID of the image in the message content. Set `purpose=\"vision\"` when uploading the File if you need to later display the file content." + }), + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high"]).annotate({ + "description": + "Specifies the detail level of the image if specified by the user. `low` uses fewer tokens, you can opt in to high resolution using `high`." + }) + ) + }) +}).annotate({ + "title": "Image file", + "description": "References an image [File](/docs/api-reference/files) in the content of a message." +}) +export type MessageContentImageUrlObject = { + readonly "type": "image_url" + readonly "image_url": { readonly "url": string; readonly "detail"?: "auto" | "low" | "high" } +} +export const MessageContentImageUrlObject = Schema.Struct({ + "type": Schema.Literal("image_url").annotate({ "description": "The type of the content part." }), + "image_url": Schema.Struct({ + "url": Schema.String.annotate({ + "description": "The external URL of the image, must be a supported image types: jpeg, jpg, png, gif, webp.", + "format": "uri" + }), + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high"]).annotate({ + "description": + "Specifies the detail level of the image. `low` uses fewer tokens, you can opt in to high resolution using `high`. Default value is `auto`" + }) + ) + }) +}).annotate({ "title": "Image URL", "description": "References an image URL in the content of a message." }) +export type MessageContentRefusalObject = { readonly "type": "refusal"; readonly "refusal": string } +export const MessageContentRefusalObject = Schema.Struct({ + "type": Schema.Literal("refusal").annotate({ "description": "Always `refusal`." }), + "refusal": Schema.String +}).annotate({ "title": "Refusal", "description": "The refusal content generated by the assistant." }) +export type MessageContentTextAnnotationsFileCitationObject = { + readonly "type": "file_citation" + readonly "text": string + readonly "file_citation": { readonly "file_id": string } + readonly "start_index": number + readonly "end_index": number +} +export const MessageContentTextAnnotationsFileCitationObject = Schema.Struct({ + "type": Schema.Literal("file_citation").annotate({ "description": "Always `file_citation`." }), + "text": Schema.String.annotate({ "description": "The text in the message content that needs to be replaced." }), + "file_citation": Schema.Struct({ + "file_id": Schema.String.annotate({ "description": "The ID of the specific File the citation is from." }) + }), + "start_index": Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "end_index": Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ + "title": "File citation", + "description": + "A citation within the message that points to a specific quote from a specific File associated with the assistant or the message. Generated when the assistant uses the \"file_search\" tool to search files." +}) +export type MessageContentTextAnnotationsFilePathObject = { + readonly "type": "file_path" + readonly "text": string + readonly "file_path": { readonly "file_id": string } + readonly "start_index": number + readonly "end_index": number +} +export const MessageContentTextAnnotationsFilePathObject = Schema.Struct({ + "type": Schema.Literal("file_path").annotate({ "description": "Always `file_path`." }), + "text": Schema.String.annotate({ "description": "The text in the message content that needs to be replaced." }), + "file_path": Schema.Struct({ + "file_id": Schema.String.annotate({ "description": "The ID of the file that was generated." }) + }), + "start_index": Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)), + "end_index": Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) +}).annotate({ + "title": "File path", + "description": + "A URL for the file that's generated when the assistant used the `code_interpreter` tool to generate a file." +}) +export type MessageDeltaContentImageFileObject = { + readonly "index": number + readonly "type": "image_file" + readonly "image_file"?: { readonly "file_id"?: string; readonly "detail"?: "auto" | "low" | "high" } +} +export const MessageDeltaContentImageFileObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the content part in the message." }).check( + Schema.isInt() + ), + "type": Schema.Literal("image_file").annotate({ "description": "Always `image_file`." }), + "image_file": Schema.optionalKey(Schema.Struct({ + "file_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The [File](/docs/api-reference/files) ID of the image in the message content. Set `purpose=\"vision\"` when uploading the File if you need to later display the file content." + }) + ), + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high"]).annotate({ + "description": + "Specifies the detail level of the image if specified by the user. `low` uses fewer tokens, you can opt in to high resolution using `high`." + }) + ) + })) +}).annotate({ + "title": "Image file", + "description": "References an image [File](/docs/api-reference/files) in the content of a message." +}) +export type MessageDeltaContentImageUrlObject = { + readonly "index": number + readonly "type": "image_url" + readonly "image_url"?: { readonly "url"?: string; readonly "detail"?: "auto" | "low" | "high" } +} +export const MessageDeltaContentImageUrlObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the content part in the message." }).check( + Schema.isInt() + ), + "type": Schema.Literal("image_url").annotate({ "description": "Always `image_url`." }), + "image_url": Schema.optionalKey(Schema.Struct({ + "url": Schema.optionalKey( + Schema.String.annotate({ + "description": "The URL of the image, must be a supported image types: jpeg, jpg, png, gif, webp.", + "format": "uri" + }) + ), + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high"]).annotate({ + "description": + "Specifies the detail level of the image. `low` uses fewer tokens, you can opt in to high resolution using `high`." + }) + ) + })) +}).annotate({ "title": "Image URL", "description": "References an image URL in the content of a message." }) +export type MessageDeltaContentRefusalObject = { + readonly "index": number + readonly "type": "refusal" + readonly "refusal"?: string +} +export const MessageDeltaContentRefusalObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the refusal part in the message." }).check( + Schema.isInt() + ), + "type": Schema.Literal("refusal").annotate({ "description": "Always `refusal`." }), + "refusal": Schema.optionalKey(Schema.String) +}).annotate({ "title": "Refusal", "description": "The refusal content that is part of a message." }) +export type MessageDeltaContentTextAnnotationsFileCitationObject = { + readonly "index": number + readonly "type": "file_citation" + readonly "text"?: string + readonly "file_citation"?: { readonly "file_id"?: string; readonly "quote"?: string } + readonly "start_index"?: number + readonly "end_index"?: number +} +export const MessageDeltaContentTextAnnotationsFileCitationObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the annotation in the text content part." }).check( + Schema.isInt() + ), + "type": Schema.Literal("file_citation").annotate({ "description": "Always `file_citation`." }), + "text": Schema.optionalKey( + Schema.String.annotate({ "description": "The text in the message content that needs to be replaced." }) + ), + "file_citation": Schema.optionalKey( + Schema.Struct({ + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the specific File the citation is from." }) + ), + "quote": Schema.optionalKey(Schema.String.annotate({ "description": "The specific quote in the file." })) + }) + ), + "start_index": Schema.optionalKey(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))), + "end_index": Schema.optionalKey(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))) +}).annotate({ + "title": "File citation", + "description": + "A citation within the message that points to a specific quote from a specific File associated with the assistant or the message. Generated when the assistant uses the \"file_search\" tool to search files." +}) +export type MessageDeltaContentTextAnnotationsFilePathObject = { + readonly "index": number + readonly "type": "file_path" + readonly "text"?: string + readonly "file_path"?: { readonly "file_id"?: string } + readonly "start_index"?: number + readonly "end_index"?: number +} +export const MessageDeltaContentTextAnnotationsFilePathObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the annotation in the text content part." }).check( + Schema.isInt() + ), + "type": Schema.Literal("file_path").annotate({ "description": "Always `file_path`." }), + "text": Schema.optionalKey( + Schema.String.annotate({ "description": "The text in the message content that needs to be replaced." }) + ), + "file_path": Schema.optionalKey( + Schema.Struct({ + "file_id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the file that was generated." })) + }) + ), + "start_index": Schema.optionalKey(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))), + "end_index": Schema.optionalKey(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))) +}).annotate({ + "title": "File path", + "description": + "A URL for the file that's generated when the assistant used the `code_interpreter` tool to generate a file." +}) +export type MessagePhase = "commentary" | "final_answer" +export const MessagePhase = Schema.Literals(["commentary", "final_answer"]).annotate({ + "description": + "Labels an `assistant` message as intermediate commentary (`commentary`) or the final answer (`final_answer`).\nFor models like `gpt-5.3-codex` and beyond, when sending follow-up requests, preserve and resend\nphase on all assistant messages — dropping it can degrade performance. Not used for user messages.\n" +}) +export type MessageRequestContentTextObject = { readonly "type": "text"; readonly "text": string } +export const MessageRequestContentTextObject = Schema.Struct({ + "type": Schema.Literal("text").annotate({ "description": "Always `text`." }), + "text": Schema.String.annotate({ "description": "Text content to be sent to the model" }) +}).annotate({ "title": "Text", "description": "The text content that is part of a message." }) +export type Metadata = {} | null +export const Metadata = Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null +]) +export type Model = { + readonly "id": string + readonly "created": number + readonly "object": "model" + readonly "owned_by": string + readonly [x: string]: unknown +} +export const Model = Schema.StructWithRest( + Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The model identifier, which can be referenced in the API endpoints." + }), + "created": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) when the model was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "object": Schema.Literal("model").annotate({ "description": "The object type, which is always \"model\"." }), + "owned_by": Schema.String.annotate({ "description": "The organization that owns the model." }) + }), + [Schema.Record(Schema.String, Schema.Json)] +).annotate({ "title": "Model", "description": "Describes an OpenAI model offering that can be used with the API." }) +export type ModelIdsShared = + | string + | "gpt-5.4" + | "gpt-5.4-mini" + | "gpt-5.4-nano" + | "gpt-5.4-mini-2026-03-17" + | "gpt-5.4-nano-2026-03-17" + | "gpt-5.3-chat-latest" + | "gpt-5.2" + | "gpt-5.2-2025-12-11" + | "gpt-5.2-chat-latest" + | "gpt-5.2-pro" + | "gpt-5.2-pro-2025-12-11" + | "gpt-5.1" + | "gpt-5.1-2025-11-13" + | "gpt-5.1-codex" + | "gpt-5.1-mini" + | "gpt-5.1-chat-latest" + | "gpt-5" + | "gpt-5-mini" + | "gpt-5-nano" + | "gpt-5-2025-08-07" + | "gpt-5-mini-2025-08-07" + | "gpt-5-nano-2025-08-07" + | "gpt-5-chat-latest" + | "gpt-4.1" + | "gpt-4.1-mini" + | "gpt-4.1-nano" + | "gpt-4.1-2025-04-14" + | "gpt-4.1-mini-2025-04-14" + | "gpt-4.1-nano-2025-04-14" + | "o4-mini" + | "o4-mini-2025-04-16" + | "o3" + | "o3-2025-04-16" + | "o3-mini" + | "o3-mini-2025-01-31" + | "o1" + | "o1-2024-12-17" + | "o1-preview" + | "o1-preview-2024-09-12" + | "o1-mini" + | "o1-mini-2024-09-12" + | "gpt-4o" + | "gpt-4o-2024-11-20" + | "gpt-4o-2024-08-06" + | "gpt-4o-2024-05-13" + | "gpt-4o-audio-preview" + | "gpt-4o-audio-preview-2024-10-01" + | "gpt-4o-audio-preview-2024-12-17" + | "gpt-4o-audio-preview-2025-06-03" + | "gpt-4o-mini-audio-preview" + | "gpt-4o-mini-audio-preview-2024-12-17" + | "gpt-4o-search-preview" + | "gpt-4o-mini-search-preview" + | "gpt-4o-search-preview-2025-03-11" + | "gpt-4o-mini-search-preview-2025-03-11" + | "chatgpt-4o-latest" + | "codex-mini-latest" + | "gpt-4o-mini" + | "gpt-4o-mini-2024-07-18" + | "gpt-4-turbo" + | "gpt-4-turbo-2024-04-09" + | "gpt-4-0125-preview" + | "gpt-4-turbo-preview" + | "gpt-4-1106-preview" + | "gpt-4-vision-preview" + | "gpt-4" + | "gpt-4-0314" + | "gpt-4-0613" + | "gpt-4-32k" + | "gpt-4-32k-0314" + | "gpt-4-32k-0613" + | "gpt-3.5-turbo" + | "gpt-3.5-turbo-16k" + | "gpt-3.5-turbo-0301" + | "gpt-3.5-turbo-0613" + | "gpt-3.5-turbo-1106" + | "gpt-3.5-turbo-0125" + | "gpt-3.5-turbo-16k-0613" +export const ModelIdsShared = Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-mini-2026-03-17", + "gpt-5.4-nano-2026-03-17", + "gpt-5.3-chat-latest", + "gpt-5.2", + "gpt-5.2-2025-12-11", + "gpt-5.2-chat-latest", + "gpt-5.2-pro", + "gpt-5.2-pro-2025-12-11", + "gpt-5.1", + "gpt-5.1-2025-11-13", + "gpt-5.1-codex", + "gpt-5.1-mini", + "gpt-5.1-chat-latest", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-5-chat-latest", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o4-mini", + "o4-mini-2025-04-16", + "o3", + "o3-2025-04-16", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "o1-preview", + "o1-preview-2024-09-12", + "o1-mini", + "o1-mini-2024-09-12", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-audio-preview", + "gpt-4o-audio-preview-2024-10-01", + "gpt-4o-audio-preview-2024-12-17", + "gpt-4o-audio-preview-2025-06-03", + "gpt-4o-mini-audio-preview", + "gpt-4o-mini-audio-preview-2024-12-17", + "gpt-4o-search-preview", + "gpt-4o-mini-search-preview", + "gpt-4o-search-preview-2025-03-11", + "gpt-4o-mini-search-preview-2025-03-11", + "chatgpt-4o-latest", + "codex-mini-latest", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613" + ]) +]) +export type ModifyCertificateRequest = { readonly "name"?: string } +export const ModifyCertificateRequest = Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The updated name for the certificate" })) +}) +export type NoiseReductionType = "near_field" | "far_field" +export const NoiseReductionType = Schema.Literals(["near_field", "far_field"]).annotate({ + "description": + "Type of noise reduction. `near_field` is for close-talking microphones such as headphones, `far_field` is for far-field microphones such as laptop or conference room microphones.\n" +}) +export type OpenAIFile = { + readonly "id": string + readonly "bytes": number + readonly "created_at": number + readonly "expires_at"?: number | null + readonly "filename": string + readonly "object": "file" + readonly "purpose": + | "assistants" + | "assistants_output" + | "batch" + | "batch_output" + | "fine-tune" + | "fine-tune-results" + | "vision" + | "user_data" + readonly "status": "uploaded" | "processed" | "error" + readonly "status_details"?: string | null + readonly [x: string]: unknown +} +export const OpenAIFile = Schema.StructWithRest( + Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The file identifier, which can be referenced in the API endpoints." + }), + "bytes": Schema.Number.annotate({ "description": "The size of the file, in bytes." }).check(Schema.isInt()), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the file was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "expires_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the file will expire.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "filename": Schema.String.annotate({ "description": "The name of the file." }), + "object": Schema.Literal("file").annotate({ "description": "The object type, which is always `file`." }), + "purpose": Schema.Literals([ + "assistants", + "assistants_output", + "batch", + "batch_output", + "fine-tune", + "fine-tune-results", + "vision", + "user_data" + ]).annotate({ + "description": + "The intended purpose of the file. Supported values are `assistants`, `assistants_output`, `batch`, `batch_output`, `fine-tune`, `fine-tune-results`, `vision`, and `user_data`." + }), + "status": Schema.Literals(["uploaded", "processed", "error"]).annotate({ + "description": + "Deprecated. The current status of the file, which can be either `uploaded`, `processed`, or `error`." + }), + "status_details": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Deprecated. For details on why a fine-tuning training file failed validation, see the `error` field on `fine_tuning.job`." + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json)] +).annotate({ + "title": "OpenAIFile", + "description": "The `File` object represents a document that has been uploaded to OpenAI." +}) +export type OrganizationCertificate = { + readonly "object": "organization.certificate" + readonly "id": string + readonly "name": string | null + readonly "created_at": number + readonly "certificate_details": { readonly "valid_at"?: number; readonly "expires_at"?: number } + readonly "active": boolean +} +export const OrganizationCertificate = Schema.Struct({ + "object": Schema.Literal("organization.certificate").annotate({ + "description": "The object type, which is always `organization.certificate`." + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the certificate." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate was uploaded.", + "format": "unixtime" + }).check(Schema.isInt()), + "certificate_details": Schema.Struct({ + "valid_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate becomes valid.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate expires.", + "format": "unixtime" + }).check(Schema.isInt()) + ) + }), + "active": Schema.Boolean.annotate({ + "description": "Whether the certificate is currently active at the organization level." + }) +}).annotate({ "description": "Represents an individual certificate configured at the organization level." }) +export type OrganizationProjectCertificate = { + readonly "object": "organization.project.certificate" + readonly "id": string + readonly "name": string | null + readonly "created_at": number + readonly "certificate_details": { readonly "valid_at"?: number; readonly "expires_at"?: number } + readonly "active": boolean +} +export const OrganizationProjectCertificate = Schema.Struct({ + "object": Schema.Literal("organization.project.certificate").annotate({ + "description": "The object type, which is always `organization.project.certificate`." + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the certificate." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate was uploaded.", + "format": "unixtime" + }).check(Schema.isInt()), + "certificate_details": Schema.Struct({ + "valid_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate becomes valid.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the certificate expires.", + "format": "unixtime" + }).check(Schema.isInt()) + ) + }), + "active": Schema.Boolean.annotate({ + "description": "Whether the certificate is currently active at the project level." + }) +}).annotate({ "description": "Represents an individual certificate configured at the project level." }) +export type ParallelToolCalls = boolean +export const ParallelToolCalls = Schema.Boolean.annotate({ + "description": + "Whether to enable [parallel function calling](/docs/guides/function-calling#configuring-parallel-function-calling) during tool use." +}) +export type PartialImages = number | null +export const PartialImages = Schema.Union([ + Schema.Number.annotate({ + "description": + "The number of partial images to generate. This parameter is used for\nstreaming responses that return partial images. Value must be between 0 and 3.\nWhen set to 0, the response will be a single image sent in one streaming event.\n\nNote that the final image may be sent before the full number of partial images\nare generated if the full image is generated more quickly.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(3)), + Schema.Null +]) +export type Project = { + readonly "id": string + readonly "object": "organization.project" + readonly "name"?: string | null + readonly "created_at": number + readonly "archived_at"?: number | null + readonly "status"?: string | null + readonly "external_key_id"?: string | null +} +export const Project = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "object": Schema.Literal("organization.project").annotate({ + "description": "The object type, which is always `organization.project`" + }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The name of the project. This appears in reporting." + }) + ), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the project was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "archived_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the project was archived or `null`.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "status": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "`active` or `archived`" }) + ), + "external_key_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The external key associated with the project." + }) + ) +}).annotate({ "description": "Represents an individual project." }) +export type ProjectApiKeyDeleteResponse = { + readonly "object": "organization.project.api_key.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const ProjectApiKeyDeleteResponse = Schema.Struct({ + "object": Schema.Literal("organization.project.api_key.deleted"), + "id": Schema.String, + "deleted": Schema.Boolean +}) +export type ProjectApiKeyOwnerServiceAccount = { + readonly "id": string + readonly "name": string + readonly "created_at": number + readonly "role": string +} +export const ProjectApiKeyOwnerServiceAccount = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.String.annotate({ "description": "The name of the service account." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the service account was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "role": Schema.String.annotate({ "description": "The service account's project role." }) +}).annotate({ "description": "The service account that owns a project API key." }) +export type ProjectApiKeyOwnerUser = { + readonly "id": string + readonly "email": string + readonly "name": string + readonly "created_at": number + readonly "role": string +} +export const ProjectApiKeyOwnerUser = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "email": Schema.String.annotate({ "description": "The email address of the user." }), + "name": Schema.String.annotate({ "description": "The name of the user." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the user was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "role": Schema.String.annotate({ "description": "The user's project role." }) +}).annotate({ "description": "The user that owns a project API key." }) +export type ProjectCreateRequest = { + readonly "name": string + readonly "geography"?: string | null + readonly "external_key_id"?: string | null +} +export const ProjectCreateRequest = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The friendly name of the project, this name appears in reports." }), + "geography": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Create the project with the specified data residency region. Your organization must have access to Data residency functionality in order to use. See [data residency controls](/docs/guides/your-data#data-residency-controls) to review the functionality and limitations of setting this field." + }) + ), + "external_key_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "External key ID to associate with the project." + }) + ) +}) +export type ProjectGroup = { + readonly "object": "project.group" + readonly "project_id": string + readonly "group_id": string + readonly "group_name": string + readonly "group_type": string + readonly "created_at": number +} +export const ProjectGroup = Schema.Struct({ + "object": Schema.Literal("project.group").annotate({ "description": "Always `project.group`." }), + "project_id": Schema.String.annotate({ "description": "Identifier of the project." }), + "group_id": Schema.String.annotate({ "description": "Identifier of the group that has access to the project." }), + "group_name": Schema.String.annotate({ "description": "Display name of the group." }), + "group_type": Schema.String.annotate({ "description": "The type of the group." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the group was granted project access.", + "format": "unixtime" + }).check(Schema.isInt()) +}).annotate({ "description": "Details about a group's membership in a project." }) +export type ProjectGroupDeletedResource = { readonly "object": "project.group.deleted"; readonly "deleted": boolean } +export const ProjectGroupDeletedResource = Schema.Struct({ + "object": Schema.Literal("project.group.deleted").annotate({ "description": "Always `project.group.deleted`." }), + "deleted": Schema.Boolean.annotate({ "description": "Whether the group membership in the project was removed." }) +}).annotate({ "description": "Confirmation payload returned after removing a group from a project." }) +export type ProjectModelPermissions = { + readonly "object": "project.model_permissions" + readonly "mode": "allow_list" | "deny_list" + readonly "model_ids": ReadonlyArray +} +export const ProjectModelPermissions = Schema.Struct({ + "object": Schema.Literal("project.model_permissions").annotate({ + "description": "The object type, which is always `project.model_permissions`." + }), + "mode": Schema.Literals(["allow_list", "deny_list"]).annotate({ + "description": "Whether the project uses an allowlist or a denylist." + }), + "model_ids": Schema.Array(Schema.String).annotate({ + "description": "The model IDs included in the model permissions policy." + }) +}).annotate({ "description": "Represents the model allowlist or denylist policy for a project." }) +export type ProjectModelPermissionsDeleteResponse = { + readonly "object": "project.model_permissions.deleted" + readonly "deleted": boolean +} +export const ProjectModelPermissionsDeleteResponse = Schema.Struct({ + "object": Schema.Literal("project.model_permissions.deleted").annotate({ + "description": "The object type, which is always `project.model_permissions.deleted`." + }), + "deleted": Schema.Boolean.annotate({ "description": "Whether the project model permissions were deleted." }) +}).annotate({ "description": "Confirmation payload returned after deleting project model permissions." }) +export type ProjectModelPermissionsUpdateRequest = { + readonly "mode": "allow_list" | "deny_list" + readonly "model_ids": ReadonlyArray +} +export const ProjectModelPermissionsUpdateRequest = Schema.Struct({ + "mode": Schema.Literals(["allow_list", "deny_list"]).annotate({ + "description": "The model permissions mode to apply." + }), + "model_ids": Schema.Array(Schema.String).annotate({ + "description": "The model IDs included in this permissions policy." + }) +}) +export type ProjectRateLimit = { + readonly "object": "project.rate_limit" + readonly "id": string + readonly "model": string + readonly "max_requests_per_1_minute": number + readonly "max_tokens_per_1_minute": number + readonly "max_images_per_1_minute"?: number + readonly "max_audio_megabytes_per_1_minute"?: number + readonly "max_requests_per_1_day"?: number + readonly "batch_1_day_max_input_tokens"?: number +} +export const ProjectRateLimit = Schema.Struct({ + "object": Schema.Literal("project.rate_limit").annotate({ + "description": "The object type, which is always `project.rate_limit`" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "model": Schema.String.annotate({ "description": "The model this rate limit applies to." }), + "max_requests_per_1_minute": Schema.Number.annotate({ "description": "The maximum requests per minute." }).check( + Schema.isInt() + ), + "max_tokens_per_1_minute": Schema.Number.annotate({ "description": "The maximum tokens per minute." }).check( + Schema.isInt() + ), + "max_images_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum images per minute. Only present for relevant models." }).check( + Schema.isInt() + ) + ), + "max_audio_megabytes_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum audio megabytes per minute. Only present for relevant models." + }).check(Schema.isInt()) + ), + "max_requests_per_1_day": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum requests per day. Only present for relevant models." }).check( + Schema.isInt() + ) + ), + "batch_1_day_max_input_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum batch input tokens per day. Only present for relevant models." + }).check(Schema.isInt()) + ) +}).annotate({ "description": "Represents a project rate limit config." }) +export type ProjectRateLimitUpdateRequest = { + readonly "max_requests_per_1_minute"?: number + readonly "max_tokens_per_1_minute"?: number + readonly "max_images_per_1_minute"?: number + readonly "max_audio_megabytes_per_1_minute"?: number + readonly "max_requests_per_1_day"?: number + readonly "batch_1_day_max_input_tokens"?: number +} +export const ProjectRateLimitUpdateRequest = Schema.Struct({ + "max_requests_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum requests per minute." }).check(Schema.isInt()) + ), + "max_tokens_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum tokens per minute." }).check(Schema.isInt()) + ), + "max_images_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum images per minute. Only relevant for certain models." }).check( + Schema.isInt() + ) + ), + "max_audio_megabytes_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum audio megabytes per minute. Only relevant for certain models." + }).check(Schema.isInt()) + ), + "max_requests_per_1_day": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum requests per day. Only relevant for certain models." }).check( + Schema.isInt() + ) + ), + "batch_1_day_max_input_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum batch input tokens per day. Only relevant for certain models." + }).check(Schema.isInt()) + ) +}) +export type ProjectServiceAccount = { + readonly "object": "organization.project.service_account" + readonly "id": string + readonly "name": string + readonly "role": "owner" | "member" + readonly "created_at": number +} +export const ProjectServiceAccount = Schema.Struct({ + "object": Schema.Literal("organization.project.service_account").annotate({ + "description": "The object type, which is always `organization.project.service_account`" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.String.annotate({ "description": "The name of the service account" }), + "role": Schema.Literals(["owner", "member"]).annotate({ "description": "`owner` or `member`" }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the service account was created", + "format": "unixtime" + }).check(Schema.isInt()) +}).annotate({ "description": "Represents an individual service account in a project." }) +export type ProjectServiceAccountApiKey = { + readonly "object": "organization.project.service_account.api_key" + readonly "value": string + readonly "name": string + readonly "created_at": number + readonly "id": string +} +export const ProjectServiceAccountApiKey = Schema.Struct({ + "object": Schema.Literal("organization.project.service_account.api_key").annotate({ + "description": "The object type, which is always `organization.project.service_account.api_key`" + }), + "value": Schema.String, + "name": Schema.String, + "created_at": Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), + "id": Schema.String +}) +export type ProjectServiceAccountCreateRequest = { readonly "name": string } +export const ProjectServiceAccountCreateRequest = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the service account being created." }) +}) +export type ProjectServiceAccountDeleteResponse = { + readonly "object": "organization.project.service_account.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const ProjectServiceAccountDeleteResponse = Schema.Struct({ + "object": Schema.Literal("organization.project.service_account.deleted"), + "id": Schema.String, + "deleted": Schema.Boolean +}) +export type ProjectUpdateRequest = { + readonly "name"?: string | null + readonly "external_key_id"?: string | null + readonly "geography"?: string | null +} +export const ProjectUpdateRequest = Schema.Struct({ + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The updated name of the project, this name appears in reports." + }) + ), + "external_key_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "External key ID to associate with the project." + }) + ), + "geography": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Geography for the project." }) + ) +}) +export type ProjectUser = { + readonly "object": "organization.project.user" + readonly "id": string + readonly "name"?: string | null + readonly "email"?: string | null + readonly "role": string + readonly "added_at": number +} +export const ProjectUser = Schema.Struct({ + "object": Schema.Literal("organization.project.user").annotate({ + "description": "The object type, which is always `organization.project.user`" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the user" }) + ), + "email": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The email address of the user" }) + ), + "role": Schema.String.annotate({ "description": "`owner` or `member`" }), + "added_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the project was added.", + "format": "unixtime" + }).check(Schema.isInt()) +}).annotate({ "description": "Represents an individual user in a project." }) +export type ProjectUserCreateRequest = { + readonly "user_id"?: string | null + readonly "email"?: string | null + readonly "role": string +} +export const ProjectUserCreateRequest = Schema.Struct({ + "user_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The ID of the user." }) + ), + "email": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Email of the user to add." }) + ), + "role": Schema.String.annotate({ "description": "`owner` or `member`" }) +}) +export type ProjectUserDeleteResponse = { + readonly "object": "organization.project.user.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const ProjectUserDeleteResponse = Schema.Struct({ + "object": Schema.Literal("organization.project.user.deleted"), + "id": Schema.String, + "deleted": Schema.Boolean +}) +export type ProjectUserUpdateRequest = { readonly "role"?: string | null } +export const ProjectUserUpdateRequest = Schema.Struct({ + "role": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "`owner` or `member`" }) + ) +}) +export type PublicAssignOrganizationGroupRoleBody = { readonly "role_id": string } +export const PublicAssignOrganizationGroupRoleBody = Schema.Struct({ + "role_id": Schema.String.annotate({ "description": "Identifier of the role to assign." }) +}).annotate({ "description": "Request payload for assigning a role to a group or user." }) +export type PublicCreateOrganizationRoleBody = { + readonly "role_name": string + readonly "permissions": ReadonlyArray + readonly "description"?: string | null +} +export const PublicCreateOrganizationRoleBody = Schema.Struct({ + "role_name": Schema.String.annotate({ "description": "Unique name for the role." }), + "permissions": Schema.Array(Schema.String).annotate({ "description": "Permissions to grant to the role." }), + "description": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Optional description of the role." }) + ) +}).annotate({ "description": "Request payload for creating a custom role." }) +export type PublicUpdateOrganizationRoleBody = { + readonly "permissions"?: ReadonlyArray | null + readonly "description"?: string | null + readonly "role_name"?: string | null +} +export const PublicUpdateOrganizationRoleBody = Schema.Struct({ + "permissions": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "Updated set of permissions for the role." + }) + ), + "description": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "New description for the role." }) + ), + "role_name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "New name for the role." }) + ) +}).annotate({ "description": "Request payload for updating an existing role." }) +export type RealtimeAudioFormats = { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" +} | { readonly "type"?: "audio/pcma" } +export const RealtimeAudioFormats = Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) +]) +export type RealtimeCallReferRequest = { readonly "target_uri": string } +export const RealtimeCallReferRequest = Schema.Struct({ + "target_uri": Schema.String.annotate({ + "description": + "URI that should appear in the SIP Refer-To header. Supports values like\n`tel:+14155550123` or `sip:agent@example.com`." + }) +}).annotate({ + "title": "Realtime call refer request", + "description": "Parameters required to transfer a SIP call to a new destination using the\nRealtime API." +}) +export type RealtimeCallRejectRequest = { readonly "status_code"?: number } +export const RealtimeCallRejectRequest = Schema.Struct({ + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "SIP response code to send back to the caller. Defaults to `603` (Decline)\nwhen omitted." + }).check(Schema.isInt()) + ) +}).annotate({ + "title": "Realtime call reject request", + "description": "Parameters used to decline an incoming SIP call handled by the Realtime API." +}) +export type RealtimeClientEventConversationItemDelete = { + readonly "event_id"?: string + readonly "type": "conversation.item.delete" + readonly "item_id": string +} +export const RealtimeClientEventConversationItemDelete = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("conversation.item.delete").annotate({ + "description": "The event type, must be `conversation.item.delete`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item to delete." }) +}).annotate({ + "description": + "Send this event when you want to remove any item from the conversation \nhistory. The server will respond with a `conversation.item.deleted` event, \nunless the item does not exist in the conversation history, in which case the \nserver will respond with an error.\n" +}) +export type RealtimeClientEventConversationItemRetrieve = { + readonly "event_id"?: string + readonly "type": "conversation.item.retrieve" + readonly "item_id": string +} +export const RealtimeClientEventConversationItemRetrieve = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("conversation.item.retrieve").annotate({ + "description": "The event type, must be `conversation.item.retrieve`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item to retrieve." }) +}).annotate({ + "description": + "Send this event when you want to retrieve the server's representation of a specific item in the conversation history. This is useful, for example, to inspect user audio after noise cancellation and VAD.\nThe server will respond with a `conversation.item.retrieved` event, \nunless the item does not exist in the conversation history, in which case the \nserver will respond with an error.\n" +}) +export type RealtimeClientEventConversationItemTruncate = { + readonly "event_id"?: string + readonly "type": "conversation.item.truncate" + readonly "item_id": string + readonly "content_index": number + readonly "audio_end_ms": number +} +export const RealtimeClientEventConversationItemTruncate = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("conversation.item.truncate").annotate({ + "description": "The event type, must be `conversation.item.truncate`." + }), + "item_id": Schema.String.annotate({ + "description": + "The ID of the assistant message item to truncate. Only assistant message \nitems can be truncated.\n" + }), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part to truncate. Set this to `0`." + }).check(Schema.isInt()), + "audio_end_ms": Schema.Number.annotate({ + "description": + "Inclusive duration up to which audio is truncated, in milliseconds. If \nthe audio_end_ms is greater than the actual audio duration, the server \nwill respond with an error.\n" + }).check(Schema.isInt()) +}).annotate({ + "description": + "Send this event to truncate a previous assistant message’s audio. The server \nwill produce audio faster than realtime, so this event is useful when the user \ninterrupts to truncate audio that has already been sent to the client but not \nyet played. This will synchronize the server's understanding of the audio with \nthe client's playback.\n\nTruncating audio will delete the server-side text transcript to ensure there \nis not text in the context that hasn't been heard by the user.\n\nIf successful, the server will respond with a `conversation.item.truncated` \nevent. \n" +}) +export type RealtimeClientEventInputAudioBufferAppend = { + readonly "event_id"?: string + readonly "type": "input_audio_buffer.append" + readonly "audio": string +} +export const RealtimeClientEventInputAudioBufferAppend = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("input_audio_buffer.append").annotate({ + "description": "The event type, must be `input_audio_buffer.append`." + }), + "audio": Schema.String.annotate({ + "description": + "Base64-encoded audio bytes. This must be in the format specified by the \n`input_audio_format` field in the session configuration.\n" + }) +}).annotate({ + "description": + "Send this event to append audio bytes to the input audio buffer. The audio \nbuffer is temporary storage you can write to and later commit. A \"commit\" will create a new\nuser message item in the conversation history from the buffer content and clear the buffer.\nInput audio transcription (if enabled) will be generated when the buffer is committed.\n\nIf VAD is enabled the audio buffer is used to detect speech and the server will decide \nwhen to commit. When Server VAD is disabled, you must commit the audio buffer\nmanually. Input audio noise reduction operates on writes to the audio buffer.\n\nThe client may choose how much audio to place in each event up to a maximum \nof 15 MiB, for example streaming smaller chunks from the client may allow the \nVAD to be more responsive. Unlike most other client events, the server will \nnot send a confirmation response to this event.\n" +}) +export type RealtimeClientEventInputAudioBufferClear = { + readonly "event_id"?: string + readonly "type": "input_audio_buffer.clear" +} +export const RealtimeClientEventInputAudioBufferClear = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("input_audio_buffer.clear").annotate({ + "description": "The event type, must be `input_audio_buffer.clear`." + }) +}).annotate({ + "description": + "Send this event to clear the audio bytes in the buffer. The server will \nrespond with an `input_audio_buffer.cleared` event.\n" +}) +export type RealtimeClientEventInputAudioBufferCommit = { + readonly "event_id"?: string + readonly "type": "input_audio_buffer.commit" +} +export const RealtimeClientEventInputAudioBufferCommit = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("input_audio_buffer.commit").annotate({ + "description": "The event type, must be `input_audio_buffer.commit`." + }) +}).annotate({ + "description": + "Send this event to commit the user input audio buffer, which will create a new user message item in the conversation. This event will produce an error if the input audio buffer is empty. When in Server VAD mode, the client does not need to send this event, the server will commit the audio buffer automatically.\n\nCommitting the input audio buffer will trigger input audio transcription (if enabled in session configuration), but it will not create a response from the model. The server will respond with an `input_audio_buffer.committed` event.\n" +}) +export type RealtimeClientEventOutputAudioBufferClear = { + readonly "event_id"?: string + readonly "type": "output_audio_buffer.clear" +} +export const RealtimeClientEventOutputAudioBufferClear = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The unique ID of the client event used for error handling." }) + ), + "type": Schema.Literal("output_audio_buffer.clear").annotate({ + "description": "The event type, must be `output_audio_buffer.clear`." + }) +}).annotate({ + "description": + "**WebRTC/SIP Only:** Emit to cut off the current audio response. This will trigger the server to\nstop generating audio and emit a `output_audio_buffer.cleared` event. This\nevent should be preceded by a `response.cancel` client event to stop the\ngeneration of the current response.\n[Learn more](/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc).\n" +}) +export type RealtimeClientEventResponseCancel = { + readonly "event_id"?: string + readonly "type": "response.cancel" + readonly "response_id"?: string +} +export const RealtimeClientEventResponseCancel = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("response.cancel").annotate({ "description": "The event type, must be `response.cancel`." }), + "response_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A specific response ID to cancel - if not provided, will cancel an \nin-progress response in the default conversation.\n" + }) + ) +}).annotate({ + "description": + "Send this event to cancel an in-progress response. The server will respond \nwith a `response.done` event with a status of `response.status=cancelled`. If \nthere is no response to cancel, the server will respond with an error. It's safe\nto call `response.cancel` even if no response is in progress, an error will be\nreturned the session will remain unaffected.\n" +}) +export type RealtimeConversationItemFunctionCall = { + readonly "id"?: string + readonly "object"?: "realtime.item" + readonly "type": "function_call" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "call_id"?: string + readonly "name": string + readonly "arguments": string +} +export const RealtimeConversationItemFunctionCall = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The unique ID of the item. This may be provided by the client or generated by the server." + }) + ), + "object": Schema.optionalKey( + Schema.Literal("realtime.item").annotate({ + "description": + "Identifier for the API object being returned - always `realtime.item`. Optional when creating a new item." + }) + ), + "type": Schema.Literal("function_call").annotate({ "description": "The type of the item. Always `function_call`." }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "incomplete", "in_progress"]).annotate({ + "description": "The status of the item. Has no effect on the conversation." + }) + ), + "call_id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the function call." })), + "name": Schema.String.annotate({ "description": "The name of the function being called." }), + "arguments": Schema.String.annotate({ + "description": + "The arguments of the function call. This is a JSON-encoded string representing the arguments passed to the function, for example `{\"arg1\": \"value1\", \"arg2\": 42}`." + }) +}).annotate({ + "title": "Realtime function call item", + "description": "A function call item in a Realtime conversation." +}) +export type RealtimeConversationItemFunctionCallOutput = { + readonly "id"?: string + readonly "object"?: "realtime.item" + readonly "type": "function_call_output" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "call_id": string + readonly "output": string +} +export const RealtimeConversationItemFunctionCallOutput = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The unique ID of the item. This may be provided by the client or generated by the server." + }) + ), + "object": Schema.optionalKey( + Schema.Literal("realtime.item").annotate({ + "description": + "Identifier for the API object being returned - always `realtime.item`. Optional when creating a new item." + }) + ), + "type": Schema.Literal("function_call_output").annotate({ + "description": "The type of the item. Always `function_call_output`." + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "incomplete", "in_progress"]).annotate({ + "description": "The status of the item. Has no effect on the conversation." + }) + ), + "call_id": Schema.String.annotate({ "description": "The ID of the function call this output is for." }), + "output": Schema.String.annotate({ + "description": + "The output of the function call, this is free text and can contain any information or simply be empty." + }) +}).annotate({ + "title": "Realtime function call output item", + "description": "A function call output item in a Realtime conversation." +}) +export type RealtimeConversationItemMessageAssistant = { + readonly "id"?: string + readonly "object"?: "realtime.item" + readonly "type": "message" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "role": "assistant" + readonly "content": ReadonlyArray< + { + readonly "type"?: "output_text" | "output_audio" + readonly "text"?: string + readonly "audio"?: string + readonly "transcript"?: string + } + > +} +export const RealtimeConversationItemMessageAssistant = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The unique ID of the item. This may be provided by the client or generated by the server." + }) + ), + "object": Schema.optionalKey( + Schema.Literal("realtime.item").annotate({ + "description": + "Identifier for the API object being returned - always `realtime.item`. Optional when creating a new item." + }) + ), + "type": Schema.Literal("message").annotate({ "description": "The type of the item. Always `message`." }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "incomplete", "in_progress"]).annotate({ + "description": "The status of the item. Has no effect on the conversation." + }) + ), + "role": Schema.Literal("assistant").annotate({ + "description": "The role of the message sender. Always `assistant`." + }), + "content": Schema.Array(Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["output_text", "output_audio"]).annotate({ + "description": + "The content type, `output_text` or `output_audio` depending on the session `output_modalities` configuration." + }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The text content." })), + "audio": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Base64-encoded audio bytes, these will be parsed as the format specified in the session output audio type configuration. This defaults to PCM 16-bit 24kHz mono if not specified." + }) + ), + "transcript": Schema.optionalKey( + Schema.String.annotate({ + "description": "The transcript of the audio content, this will always be present if the output type is `audio`." + }) + ) + })).annotate({ "description": "The content of the message." }) +}).annotate({ + "title": "Realtime assistant message item", + "description": "An assistant message item in a Realtime conversation." +}) +export type RealtimeConversationItemMessageSystem = { + readonly "id"?: string + readonly "object"?: "realtime.item" + readonly "type": "message" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "role": "system" + readonly "content": ReadonlyArray<{ readonly "type"?: "input_text"; readonly "text"?: string }> +} +export const RealtimeConversationItemMessageSystem = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The unique ID of the item. This may be provided by the client or generated by the server." + }) + ), + "object": Schema.optionalKey( + Schema.Literal("realtime.item").annotate({ + "description": + "Identifier for the API object being returned - always `realtime.item`. Optional when creating a new item." + }) + ), + "type": Schema.Literal("message").annotate({ "description": "The type of the item. Always `message`." }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "incomplete", "in_progress"]).annotate({ + "description": "The status of the item. Has no effect on the conversation." + }) + ), + "role": Schema.Literal("system").annotate({ "description": "The role of the message sender. Always `system`." }), + "content": Schema.Array( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("input_text").annotate({ + "description": "The content type. Always `input_text` for system messages." + }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The text content." })) + }) + ).annotate({ "description": "The content of the message." }) +}).annotate({ + "title": "Realtime system message item", + "description": + "A system message in a Realtime conversation can be used to provide additional context or instructions to the model. This is similar but distinct from the instruction prompt provided at the start of a conversation, as system messages can be added at any point in the conversation. For major changes to the conversation's behavior, use instructions, but for smaller updates (e.g. \"the user is now asking about a different topic\"), use system messages." +}) +export type RealtimeConversationItemMessageUser = { + readonly "id"?: string + readonly "object"?: "realtime.item" + readonly "type": "message" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "role": "user" + readonly "content": ReadonlyArray< + { + readonly "type"?: "input_text" | "input_audio" | "input_image" + readonly "text"?: string + readonly "audio"?: string + readonly "image_url"?: string + readonly "detail"?: "auto" | "low" | "high" + readonly "transcript"?: string + } + > +} +export const RealtimeConversationItemMessageUser = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The unique ID of the item. This may be provided by the client or generated by the server." + }) + ), + "object": Schema.optionalKey( + Schema.Literal("realtime.item").annotate({ + "description": + "Identifier for the API object being returned - always `realtime.item`. Optional when creating a new item." + }) + ), + "type": Schema.Literal("message").annotate({ "description": "The type of the item. Always `message`." }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "incomplete", "in_progress"]).annotate({ + "description": "The status of the item. Has no effect on the conversation." + }) + ), + "role": Schema.Literal("user").annotate({ "description": "The role of the message sender. Always `user`." }), + "content": Schema.Array(Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["input_text", "input_audio", "input_image"]).annotate({ + "description": "The content type (`input_text`, `input_audio`, or `input_image`)." + }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The text content (for `input_text`)." })), + "audio": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Base64-encoded audio bytes (for `input_audio`), these will be parsed as the format specified in the session input audio type configuration. This defaults to PCM 16-bit 24kHz mono if not specified." + }) + ), + "image_url": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Base64-encoded image bytes (for `input_image`) as a data URI. For example `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...`. Supported formats are PNG and JPEG.", + "format": "uri" + }) + ), + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high"]).annotate({ + "description": "The detail level of the image (for `input_image`). `auto` will default to `high`." + }) + ), + "transcript": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Transcript of the audio (for `input_audio`). This is not sent to the model, but will be attached to the message item for reference." + }) + ) + })).annotate({ "description": "The content of the message." }) +}).annotate({ "title": "Realtime user message item", "description": "A user message item in a Realtime conversation." }) +export type RealtimeFunctionTool = { + readonly "type"?: "function" + readonly "name"?: string + readonly "description"?: string + readonly "parameters"?: {} +} +export const RealtimeFunctionTool = Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("function").annotate({ "description": "The type of the tool, i.e. `function`." }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the function." })), + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The description of the function, including guidance on when and how\nto call it, and guidance about what to tell the user when calling\n(if anything).\n" + }) + ), + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Parameters of the function in JSON Schema." }) + ) +}).annotate({ "title": "Function tool" }) +export type RealtimeMCPApprovalRequest = { + readonly "type": "mcp_approval_request" + readonly "id": string + readonly "server_label": string + readonly "name": string + readonly "arguments": string +} +export const RealtimeMCPApprovalRequest = Schema.Struct({ + "type": Schema.Literal("mcp_approval_request").annotate({ + "description": "The type of the item. Always `mcp_approval_request`." + }), + "id": Schema.String.annotate({ "description": "The unique ID of the approval request." }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server making the request." }), + "name": Schema.String.annotate({ "description": "The name of the tool to run." }), + "arguments": Schema.String.annotate({ "description": "A JSON string of arguments for the tool." }) +}).annotate({ + "title": "Realtime MCP approval request", + "description": "A Realtime item requesting human approval of a tool invocation.\n" +}) +export type RealtimeMCPApprovalResponse = { + readonly "type": "mcp_approval_response" + readonly "id": string + readonly "approval_request_id": string + readonly "approve": boolean + readonly "reason"?: string | null +} +export const RealtimeMCPApprovalResponse = Schema.Struct({ + "type": Schema.Literal("mcp_approval_response").annotate({ + "description": "The type of the item. Always `mcp_approval_response`." + }), + "id": Schema.String.annotate({ "description": "The unique ID of the approval response." }), + "approval_request_id": Schema.String.annotate({ "description": "The ID of the approval request being answered." }), + "approve": Schema.Boolean.annotate({ "description": "Whether the request was approved." }), + "reason": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Optional reason for the decision." }), Schema.Null]) + ) +}).annotate({ + "title": "Realtime MCP approval response", + "description": "A Realtime item responding to an MCP approval request.\n" +}) +export type RealtimeMCPHTTPError = { + readonly "type": "http_error" + readonly "code": number + readonly "message": string +} +export const RealtimeMCPHTTPError = Schema.Struct({ + "type": Schema.Literal("http_error"), + "code": Schema.Number.check(Schema.isInt()), + "message": Schema.String +}).annotate({ "title": "Realtime MCP HTTP error" }) +export type RealtimeMCPProtocolError = { + readonly "type": "protocol_error" + readonly "code": number + readonly "message": string +} +export const RealtimeMCPProtocolError = Schema.Struct({ + "type": Schema.Literal("protocol_error"), + "code": Schema.Number.check(Schema.isInt()), + "message": Schema.String +}).annotate({ "title": "Realtime MCP protocol error" }) +export type RealtimeMCPToolExecutionError = { readonly "type": "tool_execution_error"; readonly "message": string } +export const RealtimeMCPToolExecutionError = Schema.Struct({ + "type": Schema.Literal("tool_execution_error"), + "message": Schema.String +}).annotate({ "title": "Realtime MCP tool execution error" }) +export type RealtimeReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh" +export const RealtimeReasoningEffort = Schema.Literals(["minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": "Constrains effort on reasoning for reasoning-capable Realtime models such as\n`gpt-realtime-2`.\n" +}) +export type RealtimeServerEventConversationCreated = { + readonly "event_id": string + readonly "type": "conversation.created" + readonly "conversation": { readonly "id"?: string; readonly "object"?: string } +} +export const RealtimeServerEventConversationCreated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.created").annotate({ + "description": "The event type, must be `conversation.created`." + }), + "conversation": Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The unique ID of the conversation." })), + "object": Schema.optionalKey( + Schema.String.annotate({ "description": "The object type, must be `realtime.conversation`." }) + ) + }).annotate({ "description": "The conversation resource." }) +}).annotate({ "description": "Returned when a conversation is created. Emitted right after session creation.\n" }) +export type RealtimeServerEventConversationItemDeleted = { + readonly "event_id": string + readonly "type": "conversation.item.deleted" + readonly "item_id": string +} +export const RealtimeServerEventConversationItemDeleted = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.deleted").annotate({ + "description": "The event type, must be `conversation.item.deleted`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item that was deleted." }) +}).annotate({ + "description": + "Returned when an item in the conversation is deleted by the client with a \n`conversation.item.delete` event. This event is used to synchronize the \nserver's understanding of the conversation history with the client's view.\n" +}) +export type RealtimeServerEventConversationItemInputAudioTranscriptionFailed = { + readonly "event_id": string + readonly "type": "conversation.item.input_audio_transcription.failed" + readonly "item_id": string + readonly "content_index": number + readonly "error": { + readonly "type"?: string + readonly "code"?: string + readonly "message"?: string + readonly "param"?: string + } +} +export const RealtimeServerEventConversationItemInputAudioTranscriptionFailed = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.input_audio_transcription.failed").annotate({ + "description": "The event type, must be\n`conversation.item.input_audio_transcription.failed`.\n" + }), + "item_id": Schema.String.annotate({ "description": "The ID of the user message item." }), + "content_index": Schema.Number.annotate({ "description": "The index of the content part containing the audio." }) + .check(Schema.isInt()), + "error": Schema.Struct({ + "type": Schema.optionalKey(Schema.String.annotate({ "description": "The type of error." })), + "code": Schema.optionalKey(Schema.String.annotate({ "description": "Error code, if any." })), + "message": Schema.optionalKey(Schema.String.annotate({ "description": "A human-readable error message." })), + "param": Schema.optionalKey(Schema.String.annotate({ "description": "Parameter related to the error, if any." })) + }).annotate({ "description": "Details of the transcription error." }) +}).annotate({ + "description": + "Returned when input audio transcription is configured, and a transcription \nrequest for a user message failed. These events are separate from other \n`error` events so that the client can identify the related Item.\n" +}) +export type RealtimeServerEventConversationItemInputAudioTranscriptionSegment = { + readonly "event_id": string + readonly "type": "conversation.item.input_audio_transcription.segment" + readonly "item_id": string + readonly "content_index": number + readonly "text": string + readonly "id": string + readonly "speaker": string + readonly "start": number + readonly "end": number +} +export const RealtimeServerEventConversationItemInputAudioTranscriptionSegment = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.input_audio_transcription.segment").annotate({ + "description": "The event type, must be `conversation.item.input_audio_transcription.segment`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item containing the input audio content." }), + "content_index": Schema.Number.annotate({ + "description": "The index of the input audio content part within the item." + }).check(Schema.isInt()), + "text": Schema.String.annotate({ "description": "The text for this segment." }), + "id": Schema.String.annotate({ "description": "The segment identifier." }), + "speaker": Schema.String.annotate({ "description": "The detected speaker label for this segment." }), + "start": Schema.Number.annotate({ "description": "Start time of the segment in seconds.", "format": "double" }).check( + Schema.isFinite() + ), + "end": Schema.Number.annotate({ "description": "End time of the segment in seconds.", "format": "double" }).check( + Schema.isFinite() + ) +}).annotate({ "description": "Returned when an input audio transcription segment is identified for an item." }) +export type RealtimeServerEventConversationItemTruncated = { + readonly "event_id": string + readonly "type": "conversation.item.truncated" + readonly "item_id": string + readonly "content_index": number + readonly "audio_end_ms": number +} +export const RealtimeServerEventConversationItemTruncated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.truncated").annotate({ + "description": "The event type, must be `conversation.item.truncated`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the assistant message item that was truncated." }), + "content_index": Schema.Number.annotate({ "description": "The index of the content part that was truncated." }).check( + Schema.isInt() + ), + "audio_end_ms": Schema.Number.annotate({ + "description": "The duration up to which the audio was truncated, in milliseconds.\n" + }).check(Schema.isInt()) +}).annotate({ + "description": + "Returned when an earlier assistant audio message item is truncated by the \nclient with a `conversation.item.truncate` event. This event is used to \nsynchronize the server's understanding of the audio with the client's playback.\n\nThis action will truncate the audio and remove the server-side text transcript \nto ensure there is no text in the context that hasn't been heard by the user.\n" +}) +export type RealtimeServerEventError = { + readonly "event_id": string + readonly "type": "error" + readonly "error": { + readonly "type": string + readonly "code"?: string | null + readonly "message": string + readonly "param"?: string | null + readonly "event_id"?: string | null + } +} +export const RealtimeServerEventError = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("error").annotate({ "description": "The event type, must be `error`." }), + "error": Schema.Struct({ + "type": Schema.String.annotate({ + "description": "The type of error (e.g., \"invalid_request_error\", \"server_error\").\n" + }), + "code": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Error code, if any." }), Schema.Null]) + ), + "message": Schema.String.annotate({ "description": "A human-readable error message." }), + "param": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Parameter related to the error, if any." }), Schema.Null]) + ), + "event_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The event_id of the client event that caused the error, if applicable.\n" + }), + Schema.Null + ]) + ) + }).annotate({ "description": "Details of the error." }) +}).annotate({ + "description": + "Returned when an error occurs, which could be a client problem or a server\nproblem. Most errors are recoverable and the session will stay open, we\nrecommend to implementors to monitor and log error messages by default.\n" +}) +export type RealtimeServerEventInputAudioBufferCleared = { + readonly "event_id": string + readonly "type": "input_audio_buffer.cleared" +} +export const RealtimeServerEventInputAudioBufferCleared = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("input_audio_buffer.cleared").annotate({ + "description": "The event type, must be `input_audio_buffer.cleared`." + }) +}).annotate({ + "description": + "Returned when the input audio buffer is cleared by the client with a \n`input_audio_buffer.clear` event.\n" +}) +export type RealtimeServerEventInputAudioBufferCommitted = { + readonly "event_id": string + readonly "type": "input_audio_buffer.committed" + readonly "previous_item_id"?: string | null + readonly "item_id": string +} +export const RealtimeServerEventInputAudioBufferCommitted = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("input_audio_buffer.committed").annotate({ + "description": "The event type, must be `input_audio_buffer.committed`." + }), + "previous_item_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The ID of the preceding item after which the new item will be inserted.\nCan be `null` if the item has no predecessor.\n" + }), + Schema.Null + ]) + ), + "item_id": Schema.String.annotate({ "description": "The ID of the user message item that will be created." }) +}).annotate({ + "description": + "Returned when an input audio buffer is committed, either by the client or\nautomatically in server VAD mode. The `item_id` property is the ID of the user\nmessage item that will be created, thus a `conversation.item.created` event\nwill also be sent to the client.\n" +}) +export type RealtimeServerEventInputAudioBufferDtmfEventReceived = { + readonly "type": "input_audio_buffer.dtmf_event_received" + readonly "event": string + readonly "received_at": number +} +export const RealtimeServerEventInputAudioBufferDtmfEventReceived = Schema.Struct({ + "type": Schema.Literal("input_audio_buffer.dtmf_event_received").annotate({ + "description": "The event type, must be `input_audio_buffer.dtmf_event_received`." + }), + "event": Schema.String.annotate({ "description": "The telephone keypad that was pressed by the user." }), + "received_at": Schema.Number.annotate({ + "description": "UTC Unix Timestamp when DTMF Event was received by server.\n" + }).check(Schema.isInt()) +}).annotate({ + "description": + "**SIP Only:** Returned when an DTMF event is received. A DTMF event is a message that\nrepresents a telephone keypad press (0–9, *, #, A–D). The `event` property\nis the keypad that the user press. The `received_at` is the UTC Unix Timestamp\nthat the server received the event.\n" +}) +export type RealtimeServerEventInputAudioBufferSpeechStarted = { + readonly "event_id": string + readonly "type": "input_audio_buffer.speech_started" + readonly "audio_start_ms": number + readonly "item_id": string +} +export const RealtimeServerEventInputAudioBufferSpeechStarted = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("input_audio_buffer.speech_started").annotate({ + "description": "The event type, must be `input_audio_buffer.speech_started`." + }), + "audio_start_ms": Schema.Number.annotate({ + "description": + "Milliseconds from the start of all audio written to the buffer during the \nsession when speech was first detected. This will correspond to the \nbeginning of audio sent to the model, and thus includes the \n`prefix_padding_ms` configured in the Session.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The ID of the user message item that will be created when speech stops.\n" + }) +}).annotate({ + "description": + "Sent by the server when in `server_vad` mode to indicate that speech has been \ndetected in the audio buffer. This can happen any time audio is added to the \nbuffer (unless speech is already detected). The client may want to use this \nevent to interrupt audio playback or provide visual feedback to the user. \n\nThe client should expect to receive a `input_audio_buffer.speech_stopped` event \nwhen speech stops. The `item_id` property is the ID of the user message item \nthat will be created when speech stops and will also be included in the \n`input_audio_buffer.speech_stopped` event (unless the client manually commits \nthe audio buffer during VAD activation).\n" +}) +export type RealtimeServerEventInputAudioBufferSpeechStopped = { + readonly "event_id": string + readonly "type": "input_audio_buffer.speech_stopped" + readonly "audio_end_ms": number + readonly "item_id": string +} +export const RealtimeServerEventInputAudioBufferSpeechStopped = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("input_audio_buffer.speech_stopped").annotate({ + "description": "The event type, must be `input_audio_buffer.speech_stopped`." + }), + "audio_end_ms": Schema.Number.annotate({ + "description": + "Milliseconds since the session started when speech stopped. This will \ncorrespond to the end of audio sent to the model, and thus includes the \n`min_silence_duration_ms` configured in the Session.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ "description": "The ID of the user message item that will be created." }) +}).annotate({ + "description": + "Returned in `server_vad` mode when the server detects the end of speech in \nthe audio buffer. The server will also send an `conversation.item.created` \nevent with the user message item that is created from the audio buffer.\n" +}) +export type RealtimeServerEventInputAudioBufferTimeoutTriggered = { + readonly "event_id": string + readonly "type": "input_audio_buffer.timeout_triggered" + readonly "audio_start_ms": number + readonly "audio_end_ms": number + readonly "item_id": string +} +export const RealtimeServerEventInputAudioBufferTimeoutTriggered = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("input_audio_buffer.timeout_triggered").annotate({ + "description": "The event type, must be `input_audio_buffer.timeout_triggered`." + }), + "audio_start_ms": Schema.Number.annotate({ + "description": + "Millisecond offset of audio written to the input audio buffer that was after the playback time of the last model response." + }).check(Schema.isInt()), + "audio_end_ms": Schema.Number.annotate({ + "description": + "Millisecond offset of audio written to the input audio buffer at the time the timeout was triggered." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ "description": "The ID of the item associated with this segment." }) +}).annotate({ + "description": + "Returned when the Server VAD timeout is triggered for the input audio buffer. This is configured\nwith `idle_timeout_ms` in the `turn_detection` settings of the session, and it indicates that\nthere hasn't been any speech detected for the configured duration.\n\nThe `audio_start_ms` and `audio_end_ms` fields indicate the segment of audio after the last\nmodel response up to the triggering time, as an offset from the beginning of audio written\nto the input audio buffer. This means it demarcates the segment of audio that was silent and\nthe difference between the start and end values will roughly match the configured timeout.\n\nThe empty audio will be committed to the conversation as an `input_audio` item (there will be a\n`input_audio_buffer.committed` event) and a model response will be generated. There may be speech\nthat didn't trigger VAD but is still detected by the model, so the model may respond with\nsomething relevant to the conversation or a prompt to continue speaking.\n" +}) +export type RealtimeServerEventMCPListToolsCompleted = { + readonly "event_id": string + readonly "type": "mcp_list_tools.completed" + readonly "item_id": string +} +export const RealtimeServerEventMCPListToolsCompleted = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("mcp_list_tools.completed").annotate({ + "description": "The event type, must be `mcp_list_tools.completed`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP list tools item." }) +}).annotate({ "description": "Returned when listing MCP tools has completed for an item." }) +export type RealtimeServerEventMCPListToolsFailed = { + readonly "event_id": string + readonly "type": "mcp_list_tools.failed" + readonly "item_id": string +} +export const RealtimeServerEventMCPListToolsFailed = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("mcp_list_tools.failed").annotate({ + "description": "The event type, must be `mcp_list_tools.failed`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP list tools item." }) +}).annotate({ "description": "Returned when listing MCP tools has failed for an item." }) +export type RealtimeServerEventMCPListToolsInProgress = { + readonly "event_id": string + readonly "type": "mcp_list_tools.in_progress" + readonly "item_id": string +} +export const RealtimeServerEventMCPListToolsInProgress = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("mcp_list_tools.in_progress").annotate({ + "description": "The event type, must be `mcp_list_tools.in_progress`." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP list tools item." }) +}).annotate({ "description": "Returned when listing MCP tools is in progress for an item." }) +export type RealtimeServerEventOutputAudioBufferCleared = { + readonly "event_id": string + readonly "type": "output_audio_buffer.cleared" + readonly "response_id": string +} +export const RealtimeServerEventOutputAudioBufferCleared = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("output_audio_buffer.cleared").annotate({ + "description": "The event type, must be `output_audio_buffer.cleared`." + }), + "response_id": Schema.String.annotate({ "description": "The unique ID of the response that produced the audio." }) +}).annotate({ + "description": + "**WebRTC/SIP Only:** Emitted when the output audio buffer is cleared. This happens either in VAD\nmode when the user has interrupted (`input_audio_buffer.speech_started`),\nor when the client has emitted the `output_audio_buffer.clear` event to manually\ncut off the current audio response.\n[Learn more](/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc).\n" +}) +export type RealtimeServerEventOutputAudioBufferStarted = { + readonly "event_id": string + readonly "type": "output_audio_buffer.started" + readonly "response_id": string +} +export const RealtimeServerEventOutputAudioBufferStarted = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("output_audio_buffer.started").annotate({ + "description": "The event type, must be `output_audio_buffer.started`." + }), + "response_id": Schema.String.annotate({ "description": "The unique ID of the response that produced the audio." }) +}).annotate({ + "description": + "**WebRTC/SIP Only:** Emitted when the server begins streaming audio to the client. This event is\nemitted after an audio content part has been added (`response.content_part.added`)\nto the response.\n[Learn more](/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc).\n" +}) +export type RealtimeServerEventOutputAudioBufferStopped = { + readonly "event_id": string + readonly "type": "output_audio_buffer.stopped" + readonly "response_id": string +} +export const RealtimeServerEventOutputAudioBufferStopped = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("output_audio_buffer.stopped").annotate({ + "description": "The event type, must be `output_audio_buffer.stopped`." + }), + "response_id": Schema.String.annotate({ "description": "The unique ID of the response that produced the audio." }) +}).annotate({ + "description": + "**WebRTC/SIP Only:** Emitted when the output audio buffer has been completely drained on the server,\nand no more audio is forthcoming. This event is emitted after the full response\ndata has been sent to the client (`response.done`).\n[Learn more](/docs/guides/realtime-conversations#client-and-server-events-for-audio-in-webrtc).\n" +}) +export type RealtimeServerEventRateLimitsUpdated = { + readonly "event_id": string + readonly "type": "rate_limits.updated" + readonly "rate_limits": ReadonlyArray< + { + readonly "name"?: "requests" | "tokens" + readonly "limit"?: number + readonly "remaining"?: number + readonly "reset_seconds"?: number + } + > +} +export const RealtimeServerEventRateLimitsUpdated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("rate_limits.updated").annotate({ + "description": "The event type, must be `rate_limits.updated`." + }), + "rate_limits": Schema.Array(Schema.Struct({ + "name": Schema.optionalKey( + Schema.Literals(["requests", "tokens"]).annotate({ + "description": "The name of the rate limit (`requests`, `tokens`).\n" + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum allowed value for the rate limit." }).check(Schema.isInt()) + ), + "remaining": Schema.optionalKey( + Schema.Number.annotate({ "description": "The remaining value before the limit is reached." }).check( + Schema.isInt() + ) + ), + "reset_seconds": Schema.optionalKey( + Schema.Number.annotate({ "description": "Seconds until the rate limit resets." }).check(Schema.isFinite()) + ) + })).annotate({ "description": "List of rate limit information." }) +}).annotate({ + "description": + "Emitted at the beginning of a Response to indicate the updated rate limits. \nWhen a Response is created some tokens will be \"reserved\" for the output \ntokens, the rate limits shown here reflect that reservation, which is then \nadjusted accordingly once the Response is completed.\n" +}) +export type RealtimeServerEventResponseAudioDelta = { + readonly "event_id": string + readonly "type": "response.output_audio.delta" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "delta": string +} +export const RealtimeServerEventResponseAudioDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_audio.delta").annotate({ + "description": "The event type, must be `response.output_audio.delta`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "Base64-encoded audio data delta." }) +}).annotate({ "description": "Returned when the model-generated audio is updated." }) +export type RealtimeServerEventResponseAudioDone = { + readonly "event_id": string + readonly "type": "response.output_audio.done" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number +} +export const RealtimeServerEventResponseAudioDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_audio.done").annotate({ + "description": "The event type, must be `response.output_audio.done`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()) +}).annotate({ + "description": + "Returned when the model-generated audio is done. Also emitted when a Response\nis interrupted, incomplete, or cancelled.\n" +}) +export type RealtimeServerEventResponseAudioTranscriptDelta = { + readonly "event_id": string + readonly "type": "response.output_audio_transcript.delta" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "delta": string +} +export const RealtimeServerEventResponseAudioTranscriptDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_audio_transcript.delta").annotate({ + "description": "The event type, must be `response.output_audio_transcript.delta`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "The transcript delta." }) +}).annotate({ "description": "Returned when the model-generated transcription of audio output is updated.\n" }) +export type RealtimeServerEventResponseAudioTranscriptDone = { + readonly "event_id": string + readonly "type": "response.output_audio_transcript.done" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "transcript": string +} +export const RealtimeServerEventResponseAudioTranscriptDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_audio_transcript.done").annotate({ + "description": "The event type, must be `response.output_audio_transcript.done`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()), + "transcript": Schema.String.annotate({ "description": "The final transcript of the audio." }) +}).annotate({ + "description": + "Returned when the model-generated transcription of audio output is done\nstreaming. Also emitted when a Response is interrupted, incomplete, or\ncancelled.\n" +}) +export type RealtimeServerEventResponseContentPartAdded = { + readonly "event_id": string + readonly "type": "response.content_part.added" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "part": { + readonly "type"?: "audio" | "text" + readonly "text"?: string + readonly "audio"?: string + readonly "transcript"?: string + } +} +export const RealtimeServerEventResponseContentPartAdded = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.content_part.added").annotate({ + "description": "The event type, must be `response.content_part.added`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item to which the content part was added." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()), + "part": Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["audio", "text"]).annotate({ "description": "The content type (\"text\", \"audio\")." }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The text content (if type is \"text\")." })), + "audio": Schema.optionalKey( + Schema.String.annotate({ "description": "Base64-encoded audio data (if type is \"audio\")." }) + ), + "transcript": Schema.optionalKey( + Schema.String.annotate({ "description": "The transcript of the audio (if type is \"audio\")." }) + ) + }).annotate({ "description": "The content part that was added." }) +}).annotate({ + "description": "Returned when a new content part is added to an assistant message item during\nresponse generation.\n" +}) +export type RealtimeServerEventResponseContentPartDone = { + readonly "event_id": string + readonly "type": "response.content_part.done" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "part": { + readonly "type"?: "audio" | "text" + readonly "text"?: string + readonly "audio"?: string + readonly "transcript"?: string + } +} +export const RealtimeServerEventResponseContentPartDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.content_part.done").annotate({ + "description": "The event type, must be `response.content_part.done`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()), + "part": Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["audio", "text"]).annotate({ "description": "The content type (\"text\", \"audio\")." }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The text content (if type is \"text\")." })), + "audio": Schema.optionalKey( + Schema.String.annotate({ "description": "Base64-encoded audio data (if type is \"audio\")." }) + ), + "transcript": Schema.optionalKey( + Schema.String.annotate({ "description": "The transcript of the audio (if type is \"audio\")." }) + ) + }).annotate({ "description": "The content part that is done." }) +}).annotate({ + "description": + "Returned when a content part is done streaming in an assistant message item.\nAlso emitted when a Response is interrupted, incomplete, or cancelled.\n" +}) +export type RealtimeServerEventResponseFunctionCallArgumentsDelta = { + readonly "event_id": string + readonly "type": "response.function_call_arguments.delta" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "call_id": string + readonly "delta": string +} +export const RealtimeServerEventResponseFunctionCallArgumentsDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.function_call_arguments.delta").annotate({ + "description": "The event type, must be `response.function_call_arguments.delta`.\n" + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the function call item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "call_id": Schema.String.annotate({ "description": "The ID of the function call." }), + "delta": Schema.String.annotate({ "description": "The arguments delta as a JSON string." }) +}).annotate({ "description": "Returned when the model-generated function call arguments are updated.\n" }) +export type RealtimeServerEventResponseFunctionCallArgumentsDone = { + readonly "event_id": string + readonly "type": "response.function_call_arguments.done" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "call_id": string + readonly "name": string + readonly "arguments": string +} +export const RealtimeServerEventResponseFunctionCallArgumentsDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.function_call_arguments.done").annotate({ + "description": "The event type, must be `response.function_call_arguments.done`.\n" + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the function call item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "call_id": Schema.String.annotate({ "description": "The ID of the function call." }), + "name": Schema.String.annotate({ "description": "The name of the function that was called." }), + "arguments": Schema.String.annotate({ "description": "The final arguments as a JSON string." }) +}).annotate({ + "description": + "Returned when the model-generated function call arguments are done streaming.\nAlso emitted when a Response is interrupted, incomplete, or cancelled.\n" +}) +export type RealtimeServerEventResponseMCPCallArgumentsDelta = { + readonly "event_id": string + readonly "type": "response.mcp_call_arguments.delta" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "delta": string + readonly "obfuscation"?: string | null +} +export const RealtimeServerEventResponseMCPCallArgumentsDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.mcp_call_arguments.delta").annotate({ + "description": "The event type, must be `response.mcp_call_arguments.delta`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "delta": Schema.String.annotate({ "description": "The JSON-encoded arguments delta." }), + "obfuscation": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "If present, indicates the delta text was obfuscated." }), + Schema.Null + ]) + ) +}).annotate({ "description": "Returned when MCP tool call arguments are updated during response generation." }) +export type RealtimeServerEventResponseMCPCallArgumentsDone = { + readonly "event_id": string + readonly "type": "response.mcp_call_arguments.done" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "arguments": string +} +export const RealtimeServerEventResponseMCPCallArgumentsDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.mcp_call_arguments.done").annotate({ + "description": "The event type, must be `response.mcp_call_arguments.done`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "arguments": Schema.String.annotate({ "description": "The final JSON-encoded arguments string." }) +}).annotate({ "description": "Returned when MCP tool call arguments are finalized during response generation." }) +export type RealtimeServerEventResponseMCPCallCompleted = { + readonly "event_id": string + readonly "type": "response.mcp_call.completed" + readonly "output_index": number + readonly "item_id": string +} +export const RealtimeServerEventResponseMCPCallCompleted = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.mcp_call.completed").annotate({ + "description": "The event type, must be `response.mcp_call.completed`." + }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item." }) +}).annotate({ "description": "Returned when an MCP tool call has completed successfully." }) +export type RealtimeServerEventResponseMCPCallFailed = { + readonly "event_id": string + readonly "type": "response.mcp_call.failed" + readonly "output_index": number + readonly "item_id": string +} +export const RealtimeServerEventResponseMCPCallFailed = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.mcp_call.failed").annotate({ + "description": "The event type, must be `response.mcp_call.failed`." + }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item." }) +}).annotate({ "description": "Returned when an MCP tool call has failed." }) +export type RealtimeServerEventResponseMCPCallInProgress = { + readonly "event_id": string + readonly "type": "response.mcp_call.in_progress" + readonly "output_index": number + readonly "item_id": string +} +export const RealtimeServerEventResponseMCPCallInProgress = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.mcp_call.in_progress").annotate({ + "description": "The event type, must be `response.mcp_call.in_progress`." + }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item." }) +}).annotate({ "description": "Returned when an MCP tool call has started and is in progress." }) +export type RealtimeServerEventResponseTextDelta = { + readonly "event_id": string + readonly "type": "response.output_text.delta" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "delta": string +} +export const RealtimeServerEventResponseTextDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_text.delta").annotate({ + "description": "The event type, must be `response.output_text.delta`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "The text delta." }) +}).annotate({ "description": "Returned when the text value of an \"output_text\" content part is updated." }) +export type RealtimeServerEventResponseTextDone = { + readonly "event_id": string + readonly "type": "response.output_text.done" + readonly "response_id": string + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "text": string +} +export const RealtimeServerEventResponseTextDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_text.done").annotate({ + "description": "The event type, must be `response.output_text.done`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the response." }), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the response." }).check( + Schema.isInt() + ), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part in the item's content array." + }).check(Schema.isInt()), + "text": Schema.String.annotate({ "description": "The final text content." }) +}).annotate({ + "description": + "Returned when the text value of an \"output_text\" content part is done streaming. Also\nemitted when a Response is interrupted, incomplete, or cancelled.\n" +}) +export type RealtimeTranscriptionSessionCreateResponse = { + readonly "client_secret": { readonly "value": string; readonly "expires_at": number } + readonly "modalities"?: ReadonlyArray<"text" | "audio"> + readonly "input_audio_format"?: string + readonly "input_audio_transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + } + readonly "turn_detection"?: { + readonly "type"?: string + readonly "threshold"?: number + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + } +} +export const RealtimeTranscriptionSessionCreateResponse = Schema.Struct({ + "client_secret": Schema.Struct({ + "value": Schema.String.annotate({ + "description": + "Ephemeral key usable in client environments to authenticate connections\nto the Realtime API. Use this in client-side environments rather than\na standard API token, which should only be used server-side.\n" + }), + "expires_at": Schema.Number.annotate({ + "description": "Timestamp for when the token expires. Currently, all tokens expire\nafter one minute.\n", + "format": "unixtime" + }).check(Schema.isInt()) + }).annotate({ + "description": + "Ephemeral key returned by the API. Only present when the session is\ncreated on the server via REST API.\n" + }), + "modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": "The set of modalities the model can respond with. To disable audio,\nset this to [\"text\"].\n" + }) + ), + "input_audio_format": Schema.optionalKey( + Schema.String.annotate({ + "description": "The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\n" + }) + ), + "input_audio_transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model used for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`.\n" + }) + ), + "language": Schema.optionalKey(Schema.String.annotate({ "description": "The language of the input audio.\n" })), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt configured for input audio transcription, when present.\n" + }) + ) + }).annotate({ "description": "Configuration of the transcription model.\n" }) + ), + "turn_detection": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.String.annotate({ "description": "Type of turn detection, only `server_vad` is currently supported.\n" }) + ), + "threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A\nhigher threshold will require louder audio to activate the model, and\nthus might perform better in noisy environments.\n" + }).check(Schema.isFinite()) + ), + "prefix_padding_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Amount of audio to include before the VAD detected speech (in\nmilliseconds). Defaults to 300ms.\n" + }).check(Schema.isInt()) + ), + "silence_duration_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Duration of silence to detect speech stop (in milliseconds). Defaults\nto 500ms. With shorter values the model will respond more quickly,\nbut may jump in on short pauses from the user.\n" + }).check(Schema.isInt()) + ) + }).annotate({ + "description": + "Configuration for turn detection. Can be set to `null` to turn off. Server\nVAD means that the model will detect the start and end of speech based on\naudio volume and respond at the end of user speech.\n" + }) + ) +}).annotate({ + "description": + "A new Realtime transcription session configuration.\n\nWhen a session is created on the server via REST API, the session object\nalso contains an ephemeral key. Default TTL for keys is 10 minutes. This\nproperty is not present when a session is updated via the WebSocket API.\n" +}) +export type RealtimeTranslationClientEventInputAudioBufferAppend = { + readonly "event_id"?: string + readonly "type": "session.input_audio_buffer.append" + readonly "audio": string +} +export const RealtimeTranslationClientEventInputAudioBufferAppend = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("session.input_audio_buffer.append").annotate({ + "description": "The event type, must be `session.input_audio_buffer.append`." + }), + "audio": Schema.String.annotate({ "description": "Base64-encoded 24 kHz PCM16 mono audio bytes." }) +}).annotate({ + "description": + "Send this event to append audio bytes to the translation session input audio buffer.\n\nWebSocket translation sessions accept base64-encoded 24 kHz PCM16 mono\nlittle-endian raw audio bytes. Unsupported websocket audio formats return a\nvalidation error because lower-quality audio materially degrades translation\nquality.\n\nTranslation consumes 200 ms engine frames. For best realtime behavior, append\naudio in 200 ms chunks. If a chunk is shorter, the server buffers it until it\nhas enough audio for one frame. If a chunk is longer, the server splits it into\n200 ms frames and enqueues them back-to-back.\n\nKeep appending silence while the session is active. If a client stops sending\naudio and later resumes, model time treats the resumed audio as contiguous with\nthe previous audio rather than as a real-world pause.\n" +}) +export type RealtimeTranslationClientEventSessionClose = { + readonly "event_id"?: string + readonly "type": "session.close" +} +export const RealtimeTranslationClientEventSessionClose = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("session.close").annotate({ "description": "The event type, must be `session.close`." }) +}).annotate({ + "description": + "Gracefully close the realtime translation session. The server flushes pending\ninput audio and emits any remaining translated output before closing the\nsession.\n" +}) +export type RealtimeTranslationServerEventSessionClosed = { + readonly "event_id": string + readonly "type": "session.closed" +} +export const RealtimeTranslationServerEventSessionClosed = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.closed").annotate({ "description": "The event type, must be `session.closed`." }) +}).annotate({ "description": "Returned when a realtime translation session is closed.\n" }) +export type RealtimeTranslationServerEventSessionInputTranscriptDelta = { + readonly "event_id": string + readonly "type": "session.input_transcript.delta" + readonly "delta": string + readonly "elapsed_ms"?: number | null +} +export const RealtimeTranslationServerEventSessionInputTranscriptDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.input_transcript.delta").annotate({ + "description": "The event type, must be `session.input_transcript.delta`." + }), + "delta": Schema.String.annotate({ "description": "Append-only source-language transcript text." }), + "elapsed_ms": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "Timing metadata for stream alignment, derived from the translation frame\nwhen available. It advances in 200 ms increments, but multiple transcript\ndeltas may share the same `elapsed_ms`. Treat it as alignment metadata,\nnot a unique transcript-delta identifier.\n" + }).check(Schema.isInt()), + Schema.Null + ])) +}).annotate({ + "description": + "Returned when optional source-language transcript text is available. This event\nis emitted only when `audio.input.transcription` is configured.\n\nTranscript deltas are append-only text fragments. Clients should not insert\nunconditional spaces between deltas.\n" +}) +export type RealtimeTranslationServerEventSessionOutputAudioDelta = { + readonly "event_id": string + readonly "type": "session.output_audio.delta" + readonly "delta": string + readonly "sample_rate"?: number + readonly "channels"?: number + readonly "format"?: "pcm16" + readonly "elapsed_ms"?: number | null +} +export const RealtimeTranslationServerEventSessionOutputAudioDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.output_audio.delta").annotate({ + "description": "The event type, must be `session.output_audio.delta`." + }), + "delta": Schema.String.annotate({ "description": "Base64-encoded translated audio data." }), + "sample_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sample rate of the audio delta." }).check(Schema.isInt()) + ), + "channels": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of audio channels." }).check(Schema.isInt()) + ), + "format": Schema.optionalKey(Schema.Literal("pcm16").annotate({ "description": "Audio encoding for `delta`." })), + "elapsed_ms": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Timing metadata for stream alignment, derived from the translation frame\nwhen available. Treat `elapsed_ms` as alignment metadata, not a unique\nevent identifier.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) +}).annotate({ + "description": + "Returned when translated output audio is available. Output audio deltas are\n200 ms frames of PCM16 audio.\n" +}) +export type RealtimeTranslationServerEventSessionOutputTranscriptDelta = { + readonly "event_id": string + readonly "type": "session.output_transcript.delta" + readonly "delta": string + readonly "elapsed_ms"?: number | null +} +export const RealtimeTranslationServerEventSessionOutputTranscriptDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.output_transcript.delta").annotate({ + "description": "The event type, must be `session.output_transcript.delta`." + }), + "delta": Schema.String.annotate({ "description": "Append-only transcript text for the translated output audio." }), + "elapsed_ms": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "Timing metadata for stream alignment, derived from the translation frame\nwhen available. It advances in 200 ms increments, but multiple transcript\ndeltas may share the same `elapsed_ms`. Treat it as alignment metadata,\nnot a unique transcript-delta identifier.\n" + }).check(Schema.isInt()), + Schema.Null + ])) +}).annotate({ + "description": + "Returned when translated transcript text is available.\n\nTranscript deltas are append-only text fragments. Clients should not insert\nunconditional spaces between deltas.\n" +}) +export type RealtimeTruncation = "auto" | "disabled" | { + readonly "type": "retention_ratio" + readonly "retention_ratio": number + readonly "token_limits"?: { readonly "post_instructions"?: number } +} +export const RealtimeTruncation = Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the session. `auto` is the default truncation strategy. `disabled` will disable truncation and emit errors when the conversation exceeds the input token limit." + }), + Schema.Struct({ + "type": Schema.Literal("retention_ratio").annotate({ "description": "Use retention ratio truncation." }), + "retention_ratio": Schema.Number.annotate({ + "description": + "Fraction of post-instruction conversation tokens to retain (`0.0` - `1.0`) when the conversation exceeds the input token limit. Setting this to `0.8` means that messages will be dropped until 80% of the maximum allowed tokens are used. This helps reduce the frequency of truncations and improve cache rates.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + "token_limits": Schema.optionalKey( + Schema.Struct({ + "post_instructions": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum tokens allowed in the conversation after instructions (which including tool definitions). For example, setting this to 5,000 would mean that truncation would occur when the conversation exceeds 5,000 tokens after instructions. This cannot be higher than the model's context window size minus the maximum output tokens." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) + ) + }).annotate({ + "description": + "Optional custom token limits for this truncation strategy. If not provided, the model's default token limits will be used." + }) + ) + }).annotate({ + "title": "Retention ratio truncation", + "description": + "Retain a fraction of the conversation tokens when the conversation exceeds the input token limit. This allows you to amortize truncations across multiple turns, which can help improve cached token usage." + }) +], { mode: "oneOf" }).annotate({ + "title": "Realtime Truncation Controls", + "description": + "When the number of tokens in a conversation exceeds the model's input token limit, the conversation be truncated, meaning messages (starting from the oldest) will not be included in the model's context. A 32k context model with 4,096 max output tokens can only include 28,224 tokens in the context before truncation occurs.\n\nClients can configure truncation behavior to truncate with a lower max token limit, which is an effective way to control token usage and cost.\n\nTruncation will reduce the number of cached tokens on the next turn (busting the cache), since messages are dropped from the beginning of the context. However, clients can also configure truncation to retain messages up to a fraction of the maximum context size, which will reduce the need for future truncations and thus improve the cache rate.\n\nTruncation can be disabled entirely, which means the server will never truncate but would instead return an error if the conversation exceeds the model's input token limit.\n" +}) +export type RealtimeTurnDetection = { + readonly "type": "server_vad" + readonly "threshold"?: number + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + readonly "create_response"?: boolean + readonly "interrupt_response"?: boolean + readonly "idle_timeout_ms"?: number | null +} | { + readonly "type": "semantic_vad" + readonly "eagerness"?: "low" | "medium" | "high" | "auto" + readonly "create_response"?: boolean + readonly "interrupt_response"?: boolean +} | null +export const RealtimeTurnDetection = Schema.Union([ + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("server_vad").annotate({ + "description": "Type of turn detection, `server_vad` to turn on simple Server VAD.\n" + }), + "threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Used only for `server_vad` mode. Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A\nhigher threshold will require louder audio to activate the model, and\nthus might perform better in noisy environments.\n" + }).check(Schema.isFinite()) + ), + "prefix_padding_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Used only for `server_vad` mode. Amount of audio to include before the VAD detected speech (in\nmilliseconds). Defaults to 300ms.\n" + }).check(Schema.isInt()) + ), + "silence_duration_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Used only for `server_vad` mode. Duration of silence to detect speech stop (in milliseconds). Defaults\nto 500ms. With shorter values the model will respond more quickly,\nbut may jump in on short pauses from the user.\n" + }).check(Schema.isInt()) + ), + "create_response": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "Whether or not to automatically generate a response when a VAD stop event occurs. If `interrupt_response` is set to `false` this may fail to create a response if the model is already responding.\n\nIf both `create_response` and `interrupt_response` are set to `false`, the model will never respond automatically but VAD events will still be emitted.\n" + })), + "interrupt_response": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "Whether or not to automatically interrupt (cancel) any ongoing response with output to the default\nconversation (i.e. `conversation` of `auto`) when a VAD start event occurs. If `true` then the response will be cancelled, otherwise it will continue until complete.\n\nIf both `create_response` and `interrupt_response` are set to `false`, the model will never respond automatically but VAD events will still be emitted.\n" + })), + "idle_timeout_ms": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "Optional timeout after which a model response will be triggered automatically. This is\nuseful for situations in which a long pause from the user is unexpected, such as a phone\ncall. The model will effectively prompt the user to continue the conversation based\non the current context.\n\nThe timeout value will be applied after the last model response's audio has finished playing,\ni.e. it's set to the `response.done` time plus audio playback duration.\n\nAn `input_audio_buffer.timeout_triggered` event (plus events\nassociated with the Response) will be emitted when the timeout is reached.\nIdle timeout is currently only supported for `server_vad` mode.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(5000)).check(Schema.isLessThanOrEqualTo(30000)), + Schema.Null + ])) + }).annotate({ + "title": "Server VAD", + "description": + "Server-side voice activity detection (VAD) which flips on when user speech is detected and off after a period of silence." + }), + Schema.Struct({ + "type": Schema.Literal("semantic_vad").annotate({ + "description": "Type of turn detection, `semantic_vad` to turn on Semantic VAD.\n" + }), + "eagerness": Schema.optionalKey( + Schema.Literals(["low", "medium", "high", "auto"]).annotate({ + "description": + "Used only for `semantic_vad` mode. The eagerness of the model to respond. `low` will wait longer for the user to continue speaking, `high` will respond more quickly. `auto` is the default and is equivalent to `medium`. `low`, `medium`, and `high` have max timeouts of 8s, 4s, and 2s respectively.\n" + }) + ), + "create_response": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Whether or not to automatically generate a response when a VAD stop event occurs.\n" + }) + ), + "interrupt_response": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether or not to automatically interrupt any ongoing response with output to the default\nconversation (i.e. `conversation` of `auto`) when a VAD start event occurs.\n" + }) + ) + }).annotate({ + "title": "Semantic VAD", + "description": + "Server-side semantic turn detection which uses a model to determine when the user has finished speaking." + }) + ], { mode: "oneOf" }).annotate({ + "title": "Realtime Turn Detection", + "description": + "Configuration for turn detection, ether Server VAD or Semantic VAD. This can be set to `null` to turn off, in which case the client must manually trigger model response.\n\nServer VAD means that the model will detect the start and end of speech based on audio volume and respond at the end of user speech.\n\nSemantic VAD is more advanced and uses a turn detection model (in conjunction with VAD) to semantically estimate whether the user has finished speaking, then dynamically sets a timeout based on this probability. For example, if user audio trails off with \"uhhm\", the model will score a low probability of turn end and wait longer for the user to continue speaking. This can be useful for more natural conversations, but may have a higher latency.\n\nFor `gpt-realtime-whisper` transcription sessions, turn detection must be\nset to `null`; VAD is not supported.\n" + }), + Schema.Null +]) +export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | null +export const ReasoningEffort = Schema.Union([ + Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Constrains effort on reasoning for\n[reasoning models](https://platform.openai.com/docs/guides/reasoning).\nCurrently supported values are `none`, `minimal`, `low`, `medium`, `high`, and `xhigh`. Reducing\nreasoning effort can result in faster responses and fewer tokens used\non reasoning in a response.\n\n- `gpt-5.1` defaults to `none`, which does not perform reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, `medium`, and `high`. Tool calls are supported for all reasoning values in gpt-5.1.\n- All models before `gpt-5.1` default to `medium` reasoning effort, and do not support `none`.\n- The `gpt-5-pro` model defaults to (and only supports) `high` reasoning effort.\n- `xhigh` is supported for all models after `gpt-5.1-codex-max`.\n" + }), + Schema.Null +]) +export type ResponseAudioDeltaEvent = { + readonly "type": "response.audio.delta" + readonly "sequence_number": number + readonly "delta": string +} +export const ResponseAudioDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.audio.delta").annotate({ + "description": "The type of the event. Always `response.audio.delta`.\n" + }), + "sequence_number": Schema.Number.annotate({ + "description": "A sequence number for this chunk of the stream response.\n" + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "A chunk of Base64 encoded response audio bytes.\n" }) +}).annotate({ "description": "Emitted when there is a partial audio response." }) +export type ResponseAudioDoneEvent = { + readonly "type": "response.audio.done" + readonly "sequence_number": number + readonly "response_id": unknown +} +export const ResponseAudioDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.audio.done").annotate({ + "description": "The type of the event. Always `response.audio.done`.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of the delta.\n" }).check( + Schema.isInt() + ), + "response_id": Schema.Unknown +}).annotate({ "description": "Emitted when the audio response is complete." }) +export type ResponseAudioTranscriptDeltaEvent = { + readonly "type": "response.audio.transcript.delta" + readonly "delta": string + readonly "sequence_number": number + readonly "response_id": unknown +} +export const ResponseAudioTranscriptDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.audio.transcript.delta").annotate({ + "description": "The type of the event. Always `response.audio.transcript.delta`.\n" + }), + "delta": Schema.String.annotate({ "description": "The partial transcript of the audio response.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "response_id": Schema.Unknown +}).annotate({ "description": "Emitted when there is a partial transcript of audio." }) +export type ResponseAudioTranscriptDoneEvent = { + readonly "type": "response.audio.transcript.done" + readonly "sequence_number": number + readonly "response_id": unknown +} +export const ResponseAudioTranscriptDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.audio.transcript.done").annotate({ + "description": "The type of the event. Always `response.audio.transcript.done`.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "response_id": Schema.Unknown +}).annotate({ "description": "Emitted when the full audio transcript is completed." }) +export type ResponseCodeInterpreterCallCodeDeltaEvent = { + readonly "type": "response.code_interpreter_call_code.delta" + readonly "output_index": number + readonly "item_id": string + readonly "delta": string + readonly "sequence_number": number +} +export const ResponseCodeInterpreterCallCodeDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.code_interpreter_call_code.delta").annotate({ + "description": "The type of the event. Always `response.code_interpreter_call_code.delta`." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response for which the code is being streamed." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ "description": "The unique identifier of the code interpreter tool call item." }), + "delta": Schema.String.annotate({ + "description": "The partial code snippet being streamed by the code interpreter." + }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of this event, used to order streaming events." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when a partial code snippet is streamed by the code interpreter." }) +export type ResponseCodeInterpreterCallCodeDoneEvent = { + readonly "type": "response.code_interpreter_call_code.done" + readonly "output_index": number + readonly "item_id": string + readonly "code": string + readonly "sequence_number": number +} +export const ResponseCodeInterpreterCallCodeDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.code_interpreter_call_code.done").annotate({ + "description": "The type of the event. Always `response.code_interpreter_call_code.done`." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response for which the code is finalized." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ "description": "The unique identifier of the code interpreter tool call item." }), + "code": Schema.String.annotate({ "description": "The final code snippet output by the code interpreter." }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of this event, used to order streaming events." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when the code snippet is finalized by the code interpreter." }) +export type ResponseCodeInterpreterCallCompletedEvent = { + readonly "type": "response.code_interpreter_call.completed" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseCodeInterpreterCallCompletedEvent = Schema.Struct({ + "type": Schema.Literal("response.code_interpreter_call.completed").annotate({ + "description": "The type of the event. Always `response.code_interpreter_call.completed`." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response for which the code interpreter call is completed." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ "description": "The unique identifier of the code interpreter tool call item." }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of this event, used to order streaming events." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when the code interpreter call is completed." }) +export type ResponseCodeInterpreterCallInProgressEvent = { + readonly "type": "response.code_interpreter_call.in_progress" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseCodeInterpreterCallInProgressEvent = Schema.Struct({ + "type": Schema.Literal("response.code_interpreter_call.in_progress").annotate({ + "description": "The type of the event. Always `response.code_interpreter_call.in_progress`." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response for which the code interpreter call is in progress." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ "description": "The unique identifier of the code interpreter tool call item." }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of this event, used to order streaming events." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when a code interpreter call is in progress." }) +export type ResponseCodeInterpreterCallInterpretingEvent = { + readonly "type": "response.code_interpreter_call.interpreting" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseCodeInterpreterCallInterpretingEvent = Schema.Struct({ + "type": Schema.Literal("response.code_interpreter_call.interpreting").annotate({ + "description": "The type of the event. Always `response.code_interpreter_call.interpreting`." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response for which the code interpreter is interpreting code." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ "description": "The unique identifier of the code interpreter tool call item." }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of this event, used to order streaming events." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when the code interpreter is actively interpreting the code snippet." }) +export type ResponseCustomToolCallInputDeltaEvent = { + readonly "type": "response.custom_tool_call_input.delta" + readonly "sequence_number": number + readonly "output_index": number + readonly "item_id": string + readonly "delta": string +} +export const ResponseCustomToolCallInputDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.custom_tool_call_input.delta").annotate({ + "description": "The event type identifier." + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "output_index": Schema.Number.annotate({ "description": "The index of the output this delta applies to." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ + "description": "Unique identifier for the API item associated with this event." + }), + "delta": Schema.String.annotate({ "description": "The incremental input data (delta) for the custom tool call." }) +}).annotate({ + "title": "ResponseCustomToolCallInputDelta", + "description": "Event representing a delta (partial update) to the input of a custom tool call.\n" +}) +export type ResponseCustomToolCallInputDoneEvent = { + readonly "type": "response.custom_tool_call_input.done" + readonly "sequence_number": number + readonly "output_index": number + readonly "item_id": string + readonly "input": string +} +export const ResponseCustomToolCallInputDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.custom_tool_call_input.done").annotate({ + "description": "The event type identifier." + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "output_index": Schema.Number.annotate({ "description": "The index of the output this event applies to." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ + "description": "Unique identifier for the API item associated with this event." + }), + "input": Schema.String.annotate({ "description": "The complete input data for the custom tool call." }) +}).annotate({ + "title": "ResponseCustomToolCallInputDone", + "description": "Event indicating that input for a custom tool call is complete.\n" +}) +export type ResponseErrorCode = + | "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" +export const ResponseErrorCode = 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" +]).annotate({ "description": "The error code for the response.\n" }) +export type ResponseErrorEvent = { + readonly "type": "error" + readonly "code": string | null + readonly "message": string + readonly "param": string | null + readonly "sequence_number": number +} +export const ResponseErrorEvent = Schema.Struct({ + "type": Schema.Literal("error").annotate({ "description": "The type of the event. Always `error`.\n" }), + "code": Schema.Union([Schema.String.annotate({ "description": "The error code.\n" }), Schema.Null]), + "message": Schema.String.annotate({ "description": "The error message.\n" }), + "param": Schema.Union([Schema.String.annotate({ "description": "The error parameter.\n" }), Schema.Null]), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when an error occurs." }) +export type ResponseFileSearchCallCompletedEvent = { + readonly "type": "response.file_search_call.completed" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseFileSearchCallCompletedEvent = Schema.Struct({ + "type": Schema.Literal("response.file_search_call.completed").annotate({ + "description": "The type of the event. Always `response.file_search_call.completed`.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the file search call is initiated.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the file search call is initiated.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a file search call is completed (results found)." }) +export type ResponseFileSearchCallInProgressEvent = { + readonly "type": "response.file_search_call.in_progress" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseFileSearchCallInProgressEvent = Schema.Struct({ + "type": Schema.Literal("response.file_search_call.in_progress").annotate({ + "description": "The type of the event. Always `response.file_search_call.in_progress`.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the file search call is initiated.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the file search call is initiated.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a file search call is initiated." }) +export type ResponseFileSearchCallSearchingEvent = { + readonly "type": "response.file_search_call.searching" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseFileSearchCallSearchingEvent = Schema.Struct({ + "type": Schema.Literal("response.file_search_call.searching").annotate({ + "description": "The type of the event. Always `response.file_search_call.searching`.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the file search call is searching.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the file search call is initiated.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a file search is currently searching." }) +export type ResponseFormatJsonObject = { readonly "type": "json_object" } +export const ResponseFormatJsonObject = Schema.Struct({ + "type": Schema.Literal("json_object").annotate({ + "description": "The type of response format being defined. Always `json_object`." + }) +}).annotate({ + "title": "JSON object", + "description": + "JSON object response format. An older method of generating JSON responses.\nUsing `json_schema` is recommended for models that support it. Note that the\nmodel will not generate JSON without a system or user message instructing it\nto do so.\n" +}) +export type ResponseFormatJsonSchemaSchema = {} +export const ResponseFormatJsonSchemaSchema = Schema.Struct({}).annotate({ + "title": "JSON schema", + "description": + "The schema for the response format, described as a JSON Schema object.\nLearn how to build JSON schemas [here](https://json-schema.org/).\n" +}) +export type ResponseFormatText = { readonly "type": "text" } +export const ResponseFormatText = Schema.Struct({ + "type": Schema.Literal("text").annotate({ + "description": "The type of response format being defined. Always `text`." + }) +}).annotate({ "title": "Text", "description": "Default response format. Used to generate text responses.\n" }) +export type ResponseFunctionCallArgumentsDeltaEvent = { + readonly "type": "response.function_call_arguments.delta" + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "delta": string +} +export const ResponseFunctionCallArgumentsDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.function_call_arguments.delta").annotate({ + "description": "The type of the event. Always `response.function_call_arguments.delta`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the function-call arguments delta is added to.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the function-call arguments delta is added to.\n" + }).check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "delta": Schema.String.annotate({ "description": "The function-call arguments delta that is added.\n" }) +}).annotate({ "description": "Emitted when there is a partial function-call arguments delta." }) +export type ResponseFunctionCallArgumentsDoneEvent = { + readonly "type": "response.function_call_arguments.done" + readonly "item_id": string + readonly "name"?: string + readonly "output_index": number + readonly "sequence_number": number + readonly "arguments": string +} +export const ResponseFunctionCallArgumentsDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.function_call_arguments.done"), + "item_id": Schema.String.annotate({ "description": "The ID of the item." }), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the function that was called." })), + "output_index": Schema.Number.annotate({ "description": "The index of the output item." }).check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "arguments": Schema.String.annotate({ "description": "The function-call arguments." }) +}).annotate({ "description": "Emitted when function-call arguments are finalized." }) +export type ResponseImageGenCallCompletedEvent = { + readonly "type": "response.image_generation_call.completed" + readonly "output_index": number + readonly "sequence_number": number + readonly "item_id": string +} +export const ResponseImageGenCallCompletedEvent = Schema.Struct({ + "type": Schema.Literal("response.image_generation_call.completed").annotate({ + "description": "The type of the event. Always 'response.image_generation_call.completed'." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the image generation item being processed." + }) +}).annotate({ + "title": "ResponseImageGenCallCompletedEvent", + "description": "Emitted when an image generation tool call has completed and the final image is available.\n" +}) +export type ResponseImageGenCallGeneratingEvent = { + readonly "type": "response.image_generation_call.generating" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseImageGenCallGeneratingEvent = Schema.Struct({ + "type": Schema.Literal("response.image_generation_call.generating").annotate({ + "description": "The type of the event. Always 'response.image_generation_call.generating'." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the image generation item being processed." + }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of the image generation item being processed." + }).check(Schema.isInt()) +}).annotate({ + "title": "ResponseImageGenCallGeneratingEvent", + "description": "Emitted when an image generation tool call is actively generating an image (intermediate state).\n" +}) +export type ResponseImageGenCallInProgressEvent = { + readonly "type": "response.image_generation_call.in_progress" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseImageGenCallInProgressEvent = Schema.Struct({ + "type": Schema.Literal("response.image_generation_call.in_progress").annotate({ + "description": "The type of the event. Always 'response.image_generation_call.in_progress'." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the image generation item being processed." + }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of the image generation item being processed." + }).check(Schema.isInt()) +}).annotate({ + "title": "ResponseImageGenCallInProgressEvent", + "description": "Emitted when an image generation tool call is in progress.\n" +}) +export type ResponseImageGenCallPartialImageEvent = { + readonly "type": "response.image_generation_call.partial_image" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number + readonly "partial_image_index": number + readonly "partial_image_b64": string +} +export const ResponseImageGenCallPartialImageEvent = Schema.Struct({ + "type": Schema.Literal("response.image_generation_call.partial_image").annotate({ + "description": "The type of the event. Always 'response.image_generation_call.partial_image'." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the image generation item being processed." + }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of the image generation item being processed." + }).check(Schema.isInt()), + "partial_image_index": Schema.Number.annotate({ + "description": "0-based index for the partial image (backend is 1-based, but this is 0-based for the user)." + }).check(Schema.isInt()), + "partial_image_b64": Schema.String.annotate({ + "description": "Base64-encoded partial image data, suitable for rendering as an image." + }) +}).annotate({ + "title": "ResponseImageGenCallPartialImageEvent", + "description": "Emitted when a partial image is available during image generation streaming.\n" +}) +export type ResponseLogProb = { + readonly "token": string + readonly "logprob": number + readonly "top_logprobs"?: ReadonlyArray<{ readonly "token"?: string; readonly "logprob"?: number }> +} +export const ResponseLogProb = Schema.Struct({ + "token": Schema.String.annotate({ "description": "A possible text token." }), + "logprob": Schema.Number.annotate({ "description": "The log probability of this token.\n" }).check(Schema.isFinite()), + "top_logprobs": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "token": Schema.optionalKey(Schema.String.annotate({ "description": "A possible text token." })), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "description": "The log probability of this token." }).check(Schema.isFinite()) + ) + }) + ).annotate({ "description": "The log probabilities of up to 20 of the most likely tokens.\n" }) + ) +}).annotate({ + "description": + "A logprob is the logarithmic probability that the model assigns to producing \na particular token at a given position in the sequence. Less-negative (higher) \nlogprob values indicate greater model confidence in that token choice.\n" +}) +export type ResponseMCPCallArgumentsDeltaEvent = { + readonly "type": "response.mcp_call_arguments.delta" + readonly "output_index": number + readonly "item_id": string + readonly "delta": string + readonly "sequence_number": number +} +export const ResponseMCPCallArgumentsDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_call_arguments.delta").annotate({ + "description": "The type of the event. Always 'response.mcp_call_arguments.delta'." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the MCP tool call item being processed." + }), + "delta": Schema.String.annotate({ + "description": "A JSON string containing the partial update to the arguments for the MCP tool call.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "ResponseMCPCallArgumentsDeltaEvent", + "description": "Emitted when there is a delta (partial update) to the arguments of an MCP tool call.\n" +}) +export type ResponseMCPCallArgumentsDoneEvent = { + readonly "type": "response.mcp_call_arguments.done" + readonly "output_index": number + readonly "item_id": string + readonly "arguments": string + readonly "sequence_number": number +} +export const ResponseMCPCallArgumentsDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_call_arguments.done").annotate({ + "description": "The type of the event. Always 'response.mcp_call_arguments.done'." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the MCP tool call item being processed." + }), + "arguments": Schema.String.annotate({ + "description": "A JSON string containing the finalized arguments for the MCP tool call.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "ResponseMCPCallArgumentsDoneEvent", + "description": "Emitted when the arguments for an MCP tool call are finalized.\n" +}) +export type ResponseMCPCallCompletedEvent = { + readonly "type": "response.mcp_call.completed" + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number +} +export const ResponseMCPCallCompletedEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_call.completed").annotate({ + "description": "The type of the event. Always 'response.mcp_call.completed'." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item that completed." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item that completed." }).check( + Schema.isInt() + ), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "ResponseMCPCallCompletedEvent", + "description": "Emitted when an MCP tool call has completed successfully.\n" +}) +export type ResponseMCPCallFailedEvent = { + readonly "type": "response.mcp_call.failed" + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number +} +export const ResponseMCPCallFailedEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_call.failed").annotate({ + "description": "The type of the event. Always 'response.mcp_call.failed'." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item that failed." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item that failed." }).check( + Schema.isInt() + ), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "title": "ResponseMCPCallFailedEvent", "description": "Emitted when an MCP tool call has failed.\n" }) +export type ResponseMCPCallInProgressEvent = { + readonly "type": "response.mcp_call.in_progress" + readonly "sequence_number": number + readonly "output_index": number + readonly "item_id": string +} +export const ResponseMCPCallInProgressEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_call.in_progress").annotate({ + "description": "The type of the event. Always 'response.mcp_call.in_progress'." + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the MCP tool call item being processed." + }) +}).annotate({ + "title": "ResponseMCPCallInProgressEvent", + "description": "Emitted when an MCP tool call is in progress.\n" +}) +export type ResponseMCPListToolsCompletedEvent = { + readonly "type": "response.mcp_list_tools.completed" + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number +} +export const ResponseMCPListToolsCompletedEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_list_tools.completed").annotate({ + "description": "The type of the event. Always 'response.mcp_list_tools.completed'." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item that produced this output." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item that was processed." }).check( + Schema.isInt() + ), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "ResponseMCPListToolsCompletedEvent", + "description": "Emitted when the list of available MCP tools has been successfully retrieved.\n" +}) +export type ResponseMCPListToolsFailedEvent = { + readonly "type": "response.mcp_list_tools.failed" + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number +} +export const ResponseMCPListToolsFailedEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_list_tools.failed").annotate({ + "description": "The type of the event. Always 'response.mcp_list_tools.failed'." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item that failed." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item that failed." }).check( + Schema.isInt() + ), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "ResponseMCPListToolsFailedEvent", + "description": "Emitted when the attempt to list available MCP tools has failed.\n" +}) +export type ResponseMCPListToolsInProgressEvent = { + readonly "type": "response.mcp_list_tools.in_progress" + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number +} +export const ResponseMCPListToolsInProgressEvent = Schema.Struct({ + "type": Schema.Literal("response.mcp_list_tools.in_progress").annotate({ + "description": "The type of the event. Always 'response.mcp_list_tools.in_progress'." + }), + "item_id": Schema.String.annotate({ "description": "The ID of the MCP tool call item that is being processed." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item that is being processed." }) + .check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "ResponseMCPListToolsInProgressEvent", + "description": "Emitted when the system is in the process of retrieving the list of available MCP tools.\n" +}) +export type ResponseModalities = ReadonlyArray<"text" | "audio"> | null +export const ResponseModalities = Schema.Union([ + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "Output types that you would like the model to generate.\nMost models are capable of generating text, which is the default:\n\n`[\"text\"]`\n\nThe `gpt-4o-audio-preview` model can also be used to\n[generate audio](/docs/guides/audio). To request that this model generate\nboth text and audio responses, you can use:\n\n`[\"text\", \"audio\"]`\n" + }), + Schema.Null +]) +export type ResponseOutputTextAnnotationAddedEvent = { + readonly "type": "response.output_text.annotation.added" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "annotation_index": number + readonly "sequence_number": number + readonly "annotation": {} +} +export const ResponseOutputTextAnnotationAddedEvent = Schema.Struct({ + "type": Schema.Literal("response.output_text.annotation.added").annotate({ + "description": "The type of the event. Always 'response.output_text.annotation.added'." + }), + "item_id": Schema.String.annotate({ + "description": "The unique identifier of the item to which the annotation is being added." + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item in the response's output array." + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ "description": "The index of the content part within the output item." }) + .check(Schema.isInt()), + "annotation_index": Schema.Number.annotate({ "description": "The index of the annotation within the content part." }) + .check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "annotation": Schema.Struct({}).annotate({ + "description": "The annotation object being added. (See annotation schema for details.)" + }) +}).annotate({ + "title": "ResponseOutputTextAnnotationAddedEvent", + "description": "Emitted when an annotation is added to output text content.\n" +}) +export type ResponsePromptVariables = {} | null +export const ResponsePromptVariables = Schema.Union([ + Schema.Struct({}).annotate({ + "title": "Prompt Variables", + "description": + "Optional map of values to substitute in for variables in your\nprompt. The substitution values can either be strings, or other\nResponse input types like images or files.\n" + }), + Schema.Null +]) +export type ResponseReasoningSummaryPartAddedEvent = { + readonly "type": "response.reasoning_summary_part.added" + readonly "item_id": string + readonly "output_index": number + readonly "summary_index": number + readonly "sequence_number": number + readonly "part": { readonly "type": "summary_text"; readonly "text": string } +} +export const ResponseReasoningSummaryPartAddedEvent = Schema.Struct({ + "type": Schema.Literal("response.reasoning_summary_part.added").annotate({ + "description": "The type of the event. Always `response.reasoning_summary_part.added`.\n" + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item this summary part is associated with.\n" }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item this summary part is associated with.\n" + }).check(Schema.isInt()), + "summary_index": Schema.Number.annotate({ + "description": "The index of the summary part within the reasoning summary.\n" + }).check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ), + "part": Schema.Struct({ + "type": Schema.Literal("summary_text").annotate({ + "description": "The type of the summary part. Always `summary_text`." + }), + "text": Schema.String.annotate({ "description": "The text of the summary part." }) + }).annotate({ "description": "The summary part that was added.\n" }) +}).annotate({ "description": "Emitted when a new reasoning summary part is added." }) +export type ResponseReasoningSummaryPartDoneEvent = { + readonly "type": "response.reasoning_summary_part.done" + readonly "item_id": string + readonly "output_index": number + readonly "summary_index": number + readonly "sequence_number": number + readonly "part": { readonly "type": "summary_text"; readonly "text": string } +} +export const ResponseReasoningSummaryPartDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.reasoning_summary_part.done").annotate({ + "description": "The type of the event. Always `response.reasoning_summary_part.done`.\n" + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item this summary part is associated with.\n" }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item this summary part is associated with.\n" + }).check(Schema.isInt()), + "summary_index": Schema.Number.annotate({ + "description": "The index of the summary part within the reasoning summary.\n" + }).check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ), + "part": Schema.Struct({ + "type": Schema.Literal("summary_text").annotate({ + "description": "The type of the summary part. Always `summary_text`." + }), + "text": Schema.String.annotate({ "description": "The text of the summary part." }) + }).annotate({ "description": "The completed summary part.\n" }) +}).annotate({ "description": "Emitted when a reasoning summary part is completed." }) +export type ResponseReasoningSummaryTextDeltaEvent = { + readonly "type": "response.reasoning_summary_text.delta" + readonly "item_id": string + readonly "output_index": number + readonly "summary_index": number + readonly "delta": string + readonly "sequence_number": number +} +export const ResponseReasoningSummaryTextDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.reasoning_summary_text.delta").annotate({ + "description": "The type of the event. Always `response.reasoning_summary_text.delta`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the item this summary text delta is associated with.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item this summary text delta is associated with.\n" + }).check(Schema.isInt()), + "summary_index": Schema.Number.annotate({ + "description": "The index of the summary part within the reasoning summary.\n" + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "The text delta that was added to the summary.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a delta is added to a reasoning summary text." }) +export type ResponseReasoningSummaryTextDoneEvent = { + readonly "type": "response.reasoning_summary_text.done" + readonly "item_id": string + readonly "output_index": number + readonly "summary_index": number + readonly "text": string + readonly "sequence_number": number +} +export const ResponseReasoningSummaryTextDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.reasoning_summary_text.done").annotate({ + "description": "The type of the event. Always `response.reasoning_summary_text.done`.\n" + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item this summary text is associated with.\n" }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item this summary text is associated with.\n" + }).check(Schema.isInt()), + "summary_index": Schema.Number.annotate({ + "description": "The index of the summary part within the reasoning summary.\n" + }).check(Schema.isInt()), + "text": Schema.String.annotate({ "description": "The full text of the completed reasoning summary.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a reasoning summary text is completed." }) +export type ResponseReasoningTextDeltaEvent = { + readonly "type": "response.reasoning_text.delta" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "delta": string + readonly "sequence_number": number +} +export const ResponseReasoningTextDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.reasoning_text.delta").annotate({ + "description": "The type of the event. Always `response.reasoning_text.delta`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the item this reasoning text delta is associated with.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item this reasoning text delta is associated with.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ + "description": "The index of the reasoning content part this delta is associated with.\n" + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "The text delta that was added to the reasoning content.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a delta is added to a reasoning text." }) +export type ResponseReasoningTextDoneEvent = { + readonly "type": "response.reasoning_text.done" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "text": string + readonly "sequence_number": number +} +export const ResponseReasoningTextDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.reasoning_text.done").annotate({ + "description": "The type of the event. Always `response.reasoning_text.done`.\n" + }), + "item_id": Schema.String.annotate({ "description": "The ID of the item this reasoning text is associated with.\n" }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item this reasoning text is associated with.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ "description": "The index of the reasoning content part.\n" }).check( + Schema.isInt() + ), + "text": Schema.String.annotate({ "description": "The full text of the completed reasoning content.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a reasoning text is completed." }) +export type ResponseRefusalDeltaEvent = { + readonly "type": "response.refusal.delta" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "delta": string + readonly "sequence_number": number +} +export const ResponseRefusalDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.refusal.delta").annotate({ + "description": "The type of the event. Always `response.refusal.delta`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the refusal text is added to.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the refusal text is added to.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part that the refusal text is added to.\n" + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "The refusal text that is added.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when there is a partial refusal text." }) +export type ResponseRefusalDoneEvent = { + readonly "type": "response.refusal.done" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "refusal": string + readonly "sequence_number": number +} +export const ResponseRefusalDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.refusal.done").annotate({ + "description": "The type of the event. Always `response.refusal.done`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the refusal text is finalized.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the refusal text is finalized.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part that the refusal text is finalized.\n" + }).check(Schema.isInt()), + "refusal": Schema.String.annotate({ "description": "The refusal text that is finalized.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when refusal text is finalized." }) +export type ResponseStreamOptions = { readonly "include_obfuscation"?: boolean } | null +export const ResponseStreamOptions = Schema.Union([ + Schema.Struct({ + "include_obfuscation": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "When true, stream obfuscation will be enabled. Stream obfuscation adds\nrandom characters to an `obfuscation` field on streaming delta events to\nnormalize payload sizes as a mitigation to certain side-channel attacks.\nThese obfuscation fields are included by default, but add a small amount\nof overhead to the data stream. You can set `include_obfuscation` to\nfalse to optimize for bandwidth if you trust the network links between\nyour application and the OpenAI API.\n" + })) + }).annotate({ "description": "Options for streaming responses. Only set this when you set `stream: true`.\n" }), + Schema.Null +]) +export type ResponseUsage = { + 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 ResponseUsage = Schema.Struct({ + "input_tokens": Schema.Number.annotate({ "description": "The number of input tokens." }).check(Schema.isInt()), + "input_tokens_details": Schema.Struct({ + "cached_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that were retrieved from the cache. \n[More on prompt caching](/docs/guides/prompt-caching).\n" + }).check(Schema.isInt()) + }).annotate({ "description": "A detailed breakdown of the input tokens." }), + "output_tokens": Schema.Number.annotate({ "description": "The number of output tokens." }).check(Schema.isInt()), + "output_tokens_details": Schema.Struct({ + "reasoning_tokens": Schema.Number.annotate({ "description": "The number of reasoning tokens." }).check( + Schema.isInt() + ) + }).annotate({ "description": "A detailed breakdown of the output tokens." }), + "total_tokens": Schema.Number.annotate({ "description": "The total number of tokens used." }).check(Schema.isInt()) +}).annotate({ + "description": + "Represents token usage details including input tokens, output tokens,\na breakdown of output tokens, and the total tokens used.\n" +}) +export type ResponseWebSearchCallCompletedEvent = { + readonly "type": "response.web_search_call.completed" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseWebSearchCallCompletedEvent = Schema.Struct({ + "type": Schema.Literal("response.web_search_call.completed").annotate({ + "description": "The type of the event. Always `response.web_search_call.completed`.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the web search call is associated with.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "Unique ID for the output item associated with the web search call.\n" + }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of the web search call being processed." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when a web search call is completed." }) +export type ResponseWebSearchCallInProgressEvent = { + readonly "type": "response.web_search_call.in_progress" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseWebSearchCallInProgressEvent = Schema.Struct({ + "type": Schema.Literal("response.web_search_call.in_progress").annotate({ + "description": "The type of the event. Always `response.web_search_call.in_progress`.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the web search call is associated with.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "Unique ID for the output item associated with the web search call.\n" + }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of the web search call being processed." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when a web search call is initiated." }) +export type ResponseWebSearchCallSearchingEvent = { + readonly "type": "response.web_search_call.searching" + readonly "output_index": number + readonly "item_id": string + readonly "sequence_number": number +} +export const ResponseWebSearchCallSearchingEvent = Schema.Struct({ + "type": Schema.Literal("response.web_search_call.searching").annotate({ + "description": "The type of the event. Always `response.web_search_call.searching`.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the web search call is associated with.\n" + }).check(Schema.isInt()), + "item_id": Schema.String.annotate({ + "description": "Unique ID for the output item associated with the web search call.\n" + }), + "sequence_number": Schema.Number.annotate({ + "description": "The sequence number of the web search call being processed." + }).check(Schema.isInt()) +}).annotate({ "description": "Emitted when a web search call is executing." }) +export type Role = { + readonly "object": "role" + readonly "id": string + readonly "name": string + readonly "description": string | null + readonly "permissions": ReadonlyArray + readonly "resource_type": string + readonly "predefined_role": boolean +} +export const Role = Schema.Struct({ + "object": Schema.Literal("role").annotate({ "description": "Always `role`." }), + "id": Schema.String.annotate({ "description": "Identifier for the role." }), + "name": Schema.String.annotate({ "description": "Unique name for the role." }), + "description": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Optional description of the role." + }), + "permissions": Schema.Array(Schema.String).annotate({ "description": "Permissions granted by the role." }), + "resource_type": Schema.String.annotate({ + "description": "Resource type the role is bound to (for example `api.organization` or `api.project`)." + }), + "predefined_role": Schema.Boolean.annotate({ "description": "Whether the role is predefined and managed by OpenAI." }) +}).annotate({ "description": "Details about a role that can be assigned through the public Roles API." }) +export type RoleDeletedResource = { + readonly "object": "role.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const RoleDeletedResource = Schema.Struct({ + "object": Schema.Literal("role.deleted").annotate({ "description": "Always `role.deleted`." }), + "id": Schema.String.annotate({ "description": "Identifier of the deleted role." }), + "deleted": Schema.Boolean.annotate({ "description": "Whether the role was deleted." }) +}).annotate({ "description": "Confirmation payload returned after deleting a role." }) +export type RunCompletionUsage = { + readonly "completion_tokens": number + readonly "prompt_tokens": number + readonly "total_tokens": number +} | null +export const RunCompletionUsage = Schema.Union([ + Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ + "description": "Number of completion tokens used over the course of the run." + }).check(Schema.isInt()), + "prompt_tokens": Schema.Number.annotate({ + "description": "Number of prompt tokens used over the course of the run." + }).check(Schema.isInt()), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used (prompt + completion)." }) + .check(Schema.isInt()) + }).annotate({ + "description": + "Usage statistics related to the run. This value will be `null` if the run is not in a terminal state (i.e. `in_progress`, `queued`, etc.)." + }), + Schema.Null +]) +export type RunGraderResponse = { + readonly "reward": number + readonly "metadata": { + readonly "name": string + readonly "type": string + readonly "errors": { + readonly "formula_parse_error": boolean + readonly "sample_parse_error": boolean + readonly "truncated_observation_error": boolean + readonly "unresponsive_reward_error": boolean + readonly "invalid_variable_error": boolean + readonly "other_error": boolean + readonly "python_grader_server_error": boolean + readonly "python_grader_server_error_type": string | null + readonly "python_grader_runtime_error": boolean + readonly "python_grader_runtime_error_details": string | null + readonly "model_grader_server_error": boolean + readonly "model_grader_refusal_error": boolean + readonly "model_grader_parse_error": boolean + readonly "model_grader_server_error_details": string | null + } + readonly "execution_time": number + readonly "scores": {} + readonly "token_usage": number | null + readonly "sampled_model_name": string | null + } + readonly "sub_rewards": {} + readonly "model_grader_token_usage_per_model": {} +} +export const RunGraderResponse = Schema.Struct({ + "reward": Schema.Number.check(Schema.isFinite()), + "metadata": Schema.Struct({ + "name": Schema.String, + "type": Schema.String, + "errors": Schema.Struct({ + "formula_parse_error": Schema.Boolean, + "sample_parse_error": Schema.Boolean, + "truncated_observation_error": Schema.Boolean, + "unresponsive_reward_error": Schema.Boolean, + "invalid_variable_error": Schema.Boolean, + "other_error": Schema.Boolean, + "python_grader_server_error": Schema.Boolean, + "python_grader_server_error_type": Schema.Union([Schema.String, Schema.Null]), + "python_grader_runtime_error": Schema.Boolean, + "python_grader_runtime_error_details": Schema.Union([Schema.String, Schema.Null]), + "model_grader_server_error": Schema.Boolean, + "model_grader_refusal_error": Schema.Boolean, + "model_grader_parse_error": Schema.Boolean, + "model_grader_server_error_details": Schema.Union([Schema.String, Schema.Null]) + }), + "execution_time": Schema.Number.check(Schema.isFinite()), + "scores": Schema.Struct({}), + "token_usage": Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]), + "sampled_model_name": Schema.Union([Schema.String, Schema.Null]) + }), + "sub_rewards": Schema.Struct({}), + "model_grader_token_usage_per_model": Schema.Struct({}) +}) +export type RunStepCompletionUsage = { + readonly "completion_tokens": number + readonly "prompt_tokens": number + readonly "total_tokens": number +} | null +export const RunStepCompletionUsage = Schema.Union([ + Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ + "description": "Number of completion tokens used over the course of the run step." + }).check(Schema.isInt()), + "prompt_tokens": Schema.Number.annotate({ + "description": "Number of prompt tokens used over the course of the run step." + }).check(Schema.isInt()), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used (prompt + completion)." }) + .check(Schema.isInt()) + }).annotate({ + "description": + "Usage statistics related to the run step. This value will be `null` while the run step's status is `in_progress`." + }), + Schema.Null +]) +export type RunStepDeltaStepDetailsToolCallsCodeObject = { + readonly "index": number + readonly "id"?: string + readonly "type": "code_interpreter" + readonly "code_interpreter"?: { + readonly "input"?: string + readonly "outputs"?: ReadonlyArray< + { readonly "index": number; readonly "type": "logs"; readonly "logs"?: string } | { + readonly "index": number + readonly "type": "image" + readonly "image"?: { readonly "file_id"?: string } + } + > + } +} +export const RunStepDeltaStepDetailsToolCallsCodeObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the tool call in the tool calls array." }).check( + Schema.isInt() + ), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the tool call." })), + "type": Schema.Literal("code_interpreter").annotate({ + "description": "The type of tool call. This is always going to be `code_interpreter` for this type of tool call." + }), + "code_interpreter": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey( + Schema.String.annotate({ "description": "The input to the Code Interpreter tool call." }) + ), + "outputs": Schema.optionalKey( + Schema.Array(Schema.Union([ + Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the output in the outputs array." }).check( + Schema.isInt() + ), + "type": Schema.Literal("logs").annotate({ "description": "Always `logs`." }), + "logs": Schema.optionalKey( + Schema.String.annotate({ "description": "The text output from the Code Interpreter tool call." }) + ) + }).annotate({ + "title": "Code interpreter log output", + "description": "Text output from the Code Interpreter tool call as part of a run step." + }), + Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the output in the outputs array." }).check( + Schema.isInt() + ), + "type": Schema.Literal("image").annotate({ "description": "Always `image`." }), + "image": Schema.optionalKey( + Schema.Struct({ + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The [file](/docs/api-reference/files) ID of the image." }) + ) + }) + ) + }).annotate({ "title": "Code interpreter image output" }) + ], { mode: "oneOf" })).annotate({ + "description": + "The outputs from the Code Interpreter tool call. Code Interpreter can output one or more items, including text (`logs`) or images (`image`). Each of these are represented by a different object type." + }) + ) + }).annotate({ "description": "The Code Interpreter tool call definition." }) + ) +}).annotate({ + "title": "Code interpreter tool call", + "description": "Details of the Code Interpreter tool call the run step was involved in." +}) +export type RunStepDeltaStepDetailsToolCallsFileSearchObject = { + readonly "index": number + readonly "id"?: string + readonly "type": "file_search" + readonly "file_search": {} +} +export const RunStepDeltaStepDetailsToolCallsFileSearchObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the tool call in the tool calls array." }).check( + Schema.isInt() + ), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the tool call object." })), + "type": Schema.Literal("file_search").annotate({ + "description": "The type of tool call. This is always going to be `file_search` for this type of tool call." + }), + "file_search": Schema.Struct({}).annotate({ "description": "For now, this is always going to be an empty object." }) +}).annotate({ "title": "File search tool call" }) +export type RunStepDeltaStepDetailsToolCallsFunctionObject = { + readonly "index": number + readonly "id"?: string + readonly "type": "function" + readonly "function"?: { readonly "name"?: string; readonly "arguments"?: string; readonly "output"?: string | null } +} +export const RunStepDeltaStepDetailsToolCallsFunctionObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the tool call in the tool calls array." }).check( + Schema.isInt() + ), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the tool call object." })), + "type": Schema.Literal("function").annotate({ + "description": "The type of tool call. This is always going to be `function` for this type of tool call." + }), + "function": Schema.optionalKey( + Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the function." })), + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The arguments passed to the function." }) + ), + "output": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The output of the function. This will be `null` if the outputs have not been [submitted](/docs/api-reference/runs/submitToolOutputs) yet." + }), + Schema.Null + ]) + ) + }).annotate({ "description": "The definition of the function that was called." }) + ) +}).annotate({ "title": "Function tool call" }) +export type RunStepDetailsToolCallsCodeObject = { + readonly "id": string + readonly "type": "code_interpreter" + readonly "code_interpreter": { + readonly "input": string + readonly "outputs": ReadonlyArray< + { readonly "type": "logs"; readonly "logs": string } | { + readonly "type": "image" + readonly "image": { readonly "file_id": string } + } + > + } +} +export const RunStepDetailsToolCallsCodeObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The ID of the tool call." }), + "type": Schema.Literal("code_interpreter").annotate({ + "description": "The type of tool call. This is always going to be `code_interpreter` for this type of tool call." + }), + "code_interpreter": Schema.Struct({ + "input": Schema.String.annotate({ "description": "The input to the Code Interpreter tool call." }), + "outputs": Schema.Array( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("logs").annotate({ "description": "Always `logs`." }), + "logs": Schema.String.annotate({ "description": "The text output from the Code Interpreter tool call." }) + }).annotate({ + "title": "Code Interpreter log output", + "description": "Text output from the Code Interpreter tool call as part of a run step." + }), + Schema.Struct({ + "type": Schema.Literal("image").annotate({ "description": "Always `image`." }), + "image": Schema.Struct({ + "file_id": Schema.String.annotate({ + "description": "The [file](/docs/api-reference/files) ID of the image." + }) + }) + }).annotate({ "title": "Code Interpreter image output" }) + ], { mode: "oneOf" }) + ).annotate({ + "description": + "The outputs from the Code Interpreter tool call. Code Interpreter can output one or more items, including text (`logs`) or images (`image`). Each of these are represented by a different object type." + }) + }).annotate({ "description": "The Code Interpreter tool call definition." }) +}).annotate({ + "title": "Code Interpreter tool call", + "description": "Details of the Code Interpreter tool call the run step was involved in." +}) +export type RunStepDetailsToolCallsFileSearchResultObject = { + readonly "file_id": string + readonly "file_name": string + readonly "score": number + readonly "content"?: ReadonlyArray<{ readonly "type"?: "text"; readonly "text"?: string }> +} +export const RunStepDetailsToolCallsFileSearchResultObject = Schema.Struct({ + "file_id": Schema.String.annotate({ "description": "The ID of the file that result was found in." }), + "file_name": Schema.String.annotate({ "description": "The name of the file that result was found in." }), + "score": Schema.Number.annotate({ + "description": "The score of the result. All values must be a floating point number between 0 and 1." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + "content": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "type": Schema.optionalKey(Schema.Literal("text").annotate({ "description": "The type of the content." })), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The text content of the file." })) + }) + ).annotate({ + "description": + "The content of the result that was found. The content is only included if requested via the include query parameter." + }) + ) +}).annotate({ "title": "File search tool call result", "description": "A result instance of the file search." }) +export type RunStepDetailsToolCallsFunctionObject = { + readonly "id": string + readonly "type": "function" + readonly "function": { readonly "name": string; readonly "arguments": string; readonly "output": string | null } +} +export const RunStepDetailsToolCallsFunctionObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The ID of the tool call object." }), + "type": Schema.Literal("function").annotate({ + "description": "The type of tool call. This is always going to be `function` for this type of tool call." + }), + "function": Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the function." }), + "arguments": Schema.String.annotate({ "description": "The arguments passed to the function." }), + "output": Schema.Union([ + Schema.String.annotate({ + "description": + "The output of the function. This will be `null` if the outputs have not been [submitted](/docs/api-reference/runs/submitToolOutputs) yet." + }), + Schema.Null + ]) + }).annotate({ "description": "The definition of the function that was called." }) +}).annotate({ "title": "Function tool call" }) +export type RunToolCallObject = { + readonly "id": string + readonly "type": "function" + readonly "function": { readonly "name": string; readonly "arguments": string } +} +export const RunToolCallObject = Schema.Struct({ + "id": Schema.String.annotate({ + "description": + "The ID of the tool call. This ID must be referenced when you submit the tool outputs in using the [Submit tool outputs to run](/docs/api-reference/runs/submitToolOutputs) endpoint." + }), + "type": Schema.Literal("function").annotate({ + "description": "The type of tool call the output is required for. For now, this is always `function`." + }), + "function": Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the function." }), + "arguments": Schema.String.annotate({ + "description": "The arguments that the model expects you to pass to the function." + }) + }).annotate({ "description": "The function definition." }) +}).annotate({ "description": "Tool call objects" }) +export type ServiceTier = "auto" | "default" | "flex" | "scale" | "priority" | null +export const ServiceTier = Schema.Union([ + Schema.Literals(["auto", "default", "flex", "scale", "priority"]).annotate({ + "description": + "Specifies the processing type used for serving the request.\n - If set to 'auto', then the request will be processed with the service tier configured in the Project settings. Unless otherwise configured, the Project will use 'default'.\n - If set to 'default', then the request will be processed with the standard pricing and performance for the selected model.\n - If set to '[flex](/docs/guides/flex-processing)' or '[priority](https://openai.com/api-priority-processing/)', then the request will be processed with the corresponding service tier.\n - When not set, the default behavior is 'auto'.\n\n When the `service_tier` parameter is set, the response body will include the `service_tier` value based on the processing mode actually used to serve the request. This response value may be different from the value set in the parameter.\n" + }), + Schema.Null +]) +export type SpeechAudioDeltaEvent = { readonly "type": "speech.audio.delta"; readonly "audio": string } +export const SpeechAudioDeltaEvent = Schema.Struct({ + "type": Schema.Literal("speech.audio.delta").annotate({ + "description": "The type of the event. Always `speech.audio.delta`.\n" + }), + "audio": Schema.String.annotate({ "description": "A chunk of Base64-encoded audio data.\n" }) +}).annotate({ "description": "Emitted for each chunk of audio data generated during speech synthesis." }) +export type SpeechAudioDoneEvent = { + readonly "type": "speech.audio.done" + readonly "usage": { + readonly "input_tokens": number + readonly "output_tokens": number + readonly "total_tokens": number + } +} +export const SpeechAudioDoneEvent = Schema.Struct({ + "type": Schema.Literal("speech.audio.done").annotate({ + "description": "The type of the event. Always `speech.audio.done`.\n" + }), + "usage": Schema.Struct({ + "input_tokens": Schema.Number.annotate({ "description": "Number of input tokens in the prompt." }).check( + Schema.isInt() + ), + "output_tokens": Schema.Number.annotate({ "description": "Number of output tokens generated." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used (input + output)." }).check( + Schema.isInt() + ) + }).annotate({ "description": "Token usage statistics for the request.\n" }) +}).annotate({ "description": "Emitted when the speech synthesis is complete and all audio has been streamed." }) +export type StaticChunkingStrategy = { + readonly "max_chunk_size_tokens": number + readonly "chunk_overlap_tokens": number +} +export const StaticChunkingStrategy = Schema.Struct({ + "max_chunk_size_tokens": Schema.Number.annotate({ + "description": + "The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(100)).check(Schema.isLessThanOrEqualTo(4096)), + "chunk_overlap_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that overlap between chunks. The default value is `400`.\n\nNote that the overlap must not exceed half of `max_chunk_size_tokens`.\n" + }).check(Schema.isInt()) +}) +export type StopConfiguration = string | null | ReadonlyArray | null +export const StopConfiguration = Schema.Union([ + Schema.Union([ + Schema.Union([Schema.String, Schema.Null]), + Schema.Array(Schema.String).check(Schema.isMinLength(1)).check(Schema.isMaxLength(4)) + ], { mode: "oneOf" }).annotate({ + "description": + "Not supported with latest reasoning models `o3` and `o4-mini`.\n\nUp to 4 sequences where the API will stop generating further tokens. The\nreturned text will not contain the stop sequence.\n" + }), + Schema.Null +]) +export type SubmitToolOutputsRunRequest = { + readonly "tool_outputs": ReadonlyArray<{ readonly "tool_call_id"?: string; readonly "output"?: string }> + readonly "stream"?: boolean | null +} +export const SubmitToolOutputsRunRequest = Schema.Struct({ + "tool_outputs": Schema.Array(Schema.Struct({ + "tool_call_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The ID of the tool call in the `required_action` object within the run object the output is being submitted for." + }) + ), + "output": Schema.optionalKey( + Schema.String.annotate({ "description": "The output of the tool call to be submitted to continue the run." }) + ) + })).annotate({ "description": "A list of tools for which the outputs are being submitted." }), + "stream": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": + "If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n" + }), + Schema.Null + ]) + ) +}) +export type ToggleCertificatesRequest = { readonly "certificate_ids": ReadonlyArray } +export const ToggleCertificatesRequest = Schema.Struct({ + "certificate_ids": Schema.Array(Schema.String).check(Schema.isMinLength(1)).check(Schema.isMaxLength(10)) +}) +export type ToolChoiceAllowed = { + readonly "type": "allowed_tools" + readonly "mode": "auto" | "required" + readonly "tools": ReadonlyArray<{}> +} +export const ToolChoiceAllowed = Schema.Struct({ + "type": Schema.Literal("allowed_tools").annotate({ + "description": "Allowed tool configuration type. Always `allowed_tools`." + }), + "mode": Schema.Literals(["auto", "required"]).annotate({ + "description": + "Constrains the tools available to the model to a pre-defined set.\n\n`auto` allows the model to pick from among the allowed tools and generate a\nmessage.\n\n`required` requires the model to call one or more of the allowed tools.\n" + }), + "tools": Schema.Array( + Schema.Struct({}).annotate({ "description": "A tool definition that the model should be allowed to call.\n" }) + ).annotate({ + "description": + "A list of tool definitions that the model should be allowed to call.\n\nFor the Responses API, the list of tool definitions might look like:\n```json\n[\n { \"type\": \"function\", \"name\": \"get_weather\" },\n { \"type\": \"mcp\", \"server_label\": \"deepwiki\" },\n { \"type\": \"image_generation\" }\n]\n```\n" + }) +}).annotate({ + "title": "Allowed tools", + "description": "Constrains the tools available to the model to a pre-defined set.\n" +}) +export type ToolChoiceCustom = { readonly "type": "custom"; readonly "name": string } +export const ToolChoiceCustom = Schema.Struct({ + "type": Schema.Literal("custom").annotate({ "description": "For custom tool calling, the type is always `custom`." }), + "name": Schema.String.annotate({ "description": "The name of the custom tool to call." }) +}).annotate({ + "title": "Custom tool", + "description": "Use this option to force the model to call a specific custom tool.\n" +}) +export type ToolChoiceFunction = { readonly "type": "function"; readonly "name": string } +export const ToolChoiceFunction = Schema.Struct({ + "type": Schema.Literal("function").annotate({ + "description": "For function calling, the type is always `function`." + }), + "name": Schema.String.annotate({ "description": "The name of the function to call." }) +}).annotate({ + "title": "Function tool", + "description": "Use this option to force the model to call a specific function.\n" +}) +export type ToolChoiceMCP = { readonly "type": "mcp"; readonly "server_label": string; readonly "name"?: string | null } +export const ToolChoiceMCP = Schema.Struct({ + "type": Schema.Literal("mcp").annotate({ "description": "For MCP tools, the type is always `mcp`." }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server to use.\n" }), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The name of the tool to call on the server.\n" }), + Schema.Null + ]) + ) +}).annotate({ + "title": "MCP tool", + "description": "Use this option to force the model to call a specific tool on a remote MCP server.\n" +}) +export type ToolChoiceOptions = "none" | "auto" | "required" +export const ToolChoiceOptions = Schema.Literals(["none", "auto", "required"]).annotate({ + "title": "Tool choice mode", + "description": + "Controls which (if any) tool is called by the model.\n\n`none` means the model will not call any tool and instead generates a message.\n\n`auto` means the model can pick between generating a message or calling one or\nmore tools.\n\n`required` means the model must call one or more tools.\n" +}) +export type ToolChoiceTypes = { + readonly "type": + | "file_search" + | "web_search_preview" + | "computer" + | "computer_use_preview" + | "computer_use" + | "web_search_preview_2025_03_11" + | "image_generation" + | "code_interpreter" +} +export const ToolChoiceTypes = Schema.Struct({ + "type": Schema.Literals([ + "file_search", + "web_search_preview", + "computer", + "computer_use_preview", + "computer_use", + "web_search_preview_2025_03_11", + "image_generation", + "code_interpreter" + ]).annotate({ + "description": + "The type of hosted tool the model should to use. Learn more about\n[built-in tools](/docs/guides/tools).\n\nAllowed values are:\n- `file_search`\n- `web_search_preview`\n- `computer`\n- `computer_use_preview`\n- `computer_use`\n- `code_interpreter`\n- `image_generation`\n" + }) +}).annotate({ + "title": "Hosted tool", + "description": + "Indicates that the model should use a built-in tool to generate a response.\n[Learn more about built-in tools](/docs/guides/tools).\n" +}) +export type TranscriptTextDeltaEvent = { + readonly "type": "transcript.text.delta" + readonly "delta": string + readonly "logprobs"?: ReadonlyArray< + { readonly "token"?: string; readonly "logprob"?: number; readonly "bytes"?: ReadonlyArray } + > + readonly "segment_id"?: string +} +export const TranscriptTextDeltaEvent = Schema.Struct({ + "type": Schema.Literal("transcript.text.delta").annotate({ + "description": "The type of the event. Always `transcript.text.delta`.\n" + }), + "delta": Schema.String.annotate({ "description": "The text delta that was additionally transcribed.\n" }), + "logprobs": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "token": Schema.optionalKey( + Schema.String.annotate({ "description": "The token that was used to generate the log probability.\n" }) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "description": "The log probability of the token.\n" }).check(Schema.isFinite()) + ), + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": "The bytes that were used to generate the log probability.\n" + }) + ) + })).annotate({ + "description": + "The log probabilities of the delta. Only included if you [create a transcription](/docs/api-reference/audio/create-transcription) with the `include[]` parameter set to `logprobs`.\n" + }) + ), + "segment_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Identifier of the diarized segment that this delta belongs to. Only present when using `gpt-4o-transcribe-diarize`.\n" + }) + ) +}).annotate({ + "description": + "Emitted when there is an additional text delta. This is also the first event emitted when the transcription starts. Only emitted when you [create a transcription](/docs/api-reference/audio/create-transcription) with the `Stream` parameter set to `true`." +}) +export type TranscriptTextSegmentEvent = { + readonly "type": "transcript.text.segment" + readonly "id": string + readonly "start": number + readonly "end": number + readonly "text": string + readonly "speaker": string +} +export const TranscriptTextSegmentEvent = Schema.Struct({ + "type": Schema.Literal("transcript.text.segment").annotate({ + "description": "The type of the event. Always `transcript.text.segment`." + }), + "id": Schema.String.annotate({ "description": "Unique identifier for the segment." }), + "start": Schema.Number.annotate({ "description": "Start timestamp of the segment in seconds.", "format": "double" }) + .check(Schema.isFinite()), + "end": Schema.Number.annotate({ "description": "End timestamp of the segment in seconds.", "format": "double" }) + .check(Schema.isFinite()), + "text": Schema.String.annotate({ "description": "Transcript text for this segment." }), + "speaker": Schema.String.annotate({ "description": "Speaker label for this segment." }) +}).annotate({ + "description": + "Emitted when a diarized transcription returns a completed segment with speaker information. Only emitted when you [create a transcription](/docs/api-reference/audio/create-transcription) with `stream` set to `true` and `response_format` set to `diarized_json`.\n" +}) +export type TranscriptTextUsageDuration = { readonly "type": "duration"; readonly "seconds": number } +export const TranscriptTextUsageDuration = Schema.Struct({ + "type": Schema.Literal("duration").annotate({ + "description": "The type of the usage object. Always `duration` for this variant." + }), + "seconds": Schema.Number.annotate({ "description": "Duration of the input audio in seconds.", "format": "double" }) + .check(Schema.isFinite()) +}).annotate({ "title": "Duration Usage", "description": "Usage statistics for models billed by audio input duration." }) +export type TranscriptTextUsageTokens = { + readonly "type": "tokens" + readonly "input_tokens": number + readonly "input_token_details"?: { readonly "text_tokens"?: number; readonly "audio_tokens"?: number } + readonly "output_tokens": number + readonly "total_tokens": number +} +export const TranscriptTextUsageTokens = Schema.Struct({ + "type": Schema.Literal("tokens").annotate({ + "description": "The type of the usage object. Always `tokens` for this variant." + }), + "input_tokens": Schema.Number.annotate({ "description": "Number of input tokens billed for this request." }).check( + Schema.isInt() + ), + "input_token_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of text tokens billed for this request." }).check( + Schema.isInt() + ) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of audio tokens billed for this request." }).check( + Schema.isInt() + ) + ) + }).annotate({ "description": "Details about the input tokens billed for this request." }) + ), + "output_tokens": Schema.Number.annotate({ "description": "Number of output tokens generated." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used (input + output)." }).check( + Schema.isInt() + ) +}).annotate({ "title": "Token Usage", "description": "Usage statistics for models billed by token usage." }) +export type TranscriptionDiarizedSegment = { + readonly "type": "transcript.text.segment" + readonly "id": string + readonly "start": number + readonly "end": number + readonly "text": string + readonly "speaker": string +} +export const TranscriptionDiarizedSegment = Schema.Struct({ + "type": Schema.Literal("transcript.text.segment").annotate({ + "description": "The type of the segment. Always `transcript.text.segment`.\n" + }), + "id": Schema.String.annotate({ "description": "Unique identifier for the segment." }), + "start": Schema.Number.annotate({ "description": "Start timestamp of the segment in seconds.", "format": "double" }) + .check(Schema.isFinite()), + "end": Schema.Number.annotate({ "description": "End timestamp of the segment in seconds.", "format": "double" }) + .check(Schema.isFinite()), + "text": Schema.String.annotate({ "description": "Transcript text for this segment." }), + "speaker": Schema.String.annotate({ + "description": + "Speaker label for this segment. When known speakers are provided, the label matches `known_speaker_names[]`. Otherwise speakers are labeled sequentially using capital letters (`A`, `B`, ...).\n" + }) +}).annotate({ "description": "A segment of diarized transcript text with speaker metadata." }) +export type TranscriptionInclude = "logprobs" +export const TranscriptionInclude = Schema.Literal("logprobs") +export type TranscriptionSegment = { + readonly "id": number + readonly "seek": number + readonly "start": number + readonly "end": number + readonly "text": string + readonly "tokens": ReadonlyArray + readonly "temperature": number + readonly "avg_logprob": number + readonly "compression_ratio": number + readonly "no_speech_prob": number +} +export const TranscriptionSegment = Schema.Struct({ + "id": Schema.Number.annotate({ "description": "Unique identifier of the segment." }).check(Schema.isInt()), + "seek": Schema.Number.annotate({ "description": "Seek offset of the segment." }).check(Schema.isInt()), + "start": Schema.Number.annotate({ "description": "Start time of the segment in seconds.", "format": "double" }).check( + Schema.isFinite() + ), + "end": Schema.Number.annotate({ "description": "End time of the segment in seconds.", "format": "double" }).check( + Schema.isFinite() + ), + "text": Schema.String.annotate({ "description": "Text content of the segment." }), + "tokens": Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": "Array of token IDs for the text content." + }), + "temperature": Schema.Number.annotate({ + "description": "Temperature parameter used for generating the segment.", + "format": "float" + }).check(Schema.isFinite()), + "avg_logprob": Schema.Number.annotate({ + "description": "Average logprob of the segment. If the value is lower than -1, consider the logprobs failed.", + "format": "float" + }).check(Schema.isFinite()), + "compression_ratio": Schema.Number.annotate({ + "description": + "Compression ratio of the segment. If the value is greater than 2.4, consider the compression failed.", + "format": "float" + }).check(Schema.isFinite()), + "no_speech_prob": Schema.Number.annotate({ + "description": + "Probability of no speech in the segment. If the value is higher than 1.0 and the `avg_logprob` is below -1, consider this segment silent.", + "format": "float" + }).check(Schema.isFinite()) +}) +export type TranscriptionWord = { readonly "word": string; readonly "start": number; readonly "end": number } +export const TranscriptionWord = Schema.Struct({ + "word": Schema.String.annotate({ "description": "The text content of the word." }), + "start": Schema.Number.annotate({ "description": "Start time of the word in seconds.", "format": "double" }).check( + Schema.isFinite() + ), + "end": Schema.Number.annotate({ "description": "End time of the word in seconds.", "format": "double" }).check( + Schema.isFinite() + ) +}) +export type UpdateGroupBody = { readonly "name": string } +export const UpdateGroupBody = Schema.Struct({ + "name": Schema.String.annotate({ "description": "New display name for the group." }).check(Schema.isMinLength(1)) + .check(Schema.isMaxLength(255)) +}).annotate({ "description": "Request payload for updating the details of an existing group." }) +export type UpdateVoiceConsentRequest = { readonly "name": string } +export const UpdateVoiceConsentRequest = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The updated label for this consent recording." }) +}) +export type Upload = { + readonly "id": string + readonly "created_at": number + readonly "filename": string + readonly "bytes": number + readonly "purpose": string + readonly "status": "pending" | "completed" | "cancelled" | "expired" + readonly "expires_at": number + readonly "object"?: "upload" + readonly "file"?: { + readonly "id": string + readonly "bytes": number + readonly "created_at": number + readonly "expires_at"?: number | null + readonly "filename": string + readonly "object": "file" + readonly "purpose": + | "assistants" + | "assistants_output" + | "batch" + | "batch_output" + | "fine-tune" + | "fine-tune-results" + | "vision" + | "user_data" + readonly "status": "uploaded" | "processed" | "error" + readonly "status_details"?: string | null + readonly [x: string]: unknown + } +} +export const Upload = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The Upload unique identifier, which can be referenced in API endpoints." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the Upload was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "filename": Schema.String.annotate({ "description": "The name of the file to be uploaded." }), + "bytes": Schema.Number.annotate({ "description": "The intended number of bytes to be uploaded." }).check( + Schema.isInt() + ), + "purpose": Schema.String.annotate({ + "description": + "The intended purpose of the file. [Please refer here](/docs/api-reference/files/object#files/object-purpose) for acceptable values." + }), + "status": Schema.Literals(["pending", "completed", "cancelled", "expired"]).annotate({ + "description": "The status of the Upload." + }), + "expires_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the Upload will expire.", + "format": "unixtime" + }).check(Schema.isInt()), + "object": Schema.optionalKey( + Schema.Literal("upload").annotate({ "description": "The object type, which is always \"upload\"." }) + ), + "file": Schema.optionalKey(Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The file identifier, which can be referenced in the API endpoints." + }), + "bytes": Schema.Number.annotate({ "description": "The size of the file, in bytes." }).check(Schema.isInt()), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the file was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "expires_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the file will expire.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "filename": Schema.String.annotate({ "description": "The name of the file." }), + "object": Schema.Literal("file").annotate({ "description": "The object type, which is always `file`." }), + "purpose": Schema.Literals([ + "assistants", + "assistants_output", + "batch", + "batch_output", + "fine-tune", + "fine-tune-results", + "vision", + "user_data" + ]).annotate({ + "description": + "The intended purpose of the file. Supported values are `assistants`, `assistants_output`, `batch`, `batch_output`, `fine-tune`, `fine-tune-results`, `vision`, and `user_data`." + }), + "status": Schema.Literals(["uploaded", "processed", "error"]).annotate({ + "description": + "Deprecated. The current status of the file, which can be either `uploaded`, `processed`, or `error`." + }), + "status_details": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Deprecated. For details on why a fine-tuning training file failed validation, see the `error` field on `fine_tuning.job`." + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json)] + ).annotate({ + "description": "The `File` object represents a document that has been uploaded to OpenAI.", + "title": "OpenAIFile" + }) + ])) +}).annotate({ "title": "Upload", "description": "The Upload object can accept byte chunks in the form of Parts.\n" }) +export type UploadCertificateRequest = { readonly "name"?: string; readonly "certificate": string } +export const UploadCertificateRequest = Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "An optional name for the certificate" })), + "certificate": Schema.String.annotate({ "description": "The certificate content in PEM format" }) +}) +export type UploadPart = { + readonly "id": string + readonly "created_at": number + readonly "upload_id": string + readonly "object": "upload.part" +} +export const UploadPart = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The upload Part unique identifier, which can be referenced in API endpoints." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the Part was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "upload_id": Schema.String.annotate({ "description": "The ID of the Upload object that this Part was added to." }), + "object": Schema.Literal("upload.part").annotate({ "description": "The object type, which is always `upload.part`." }) +}).annotate({ + "title": "UploadPart", + "description": "The upload Part represents a chunk of bytes we can add to an Upload object.\n" +}) +export type UsageAudioSpeechesResult = { + readonly "object": "organization.usage.audio_speeches.result" + readonly "characters": number + readonly "num_model_requests": number + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "model"?: string | null +} +export const UsageAudioSpeechesResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.audio_speeches.result"), + "characters": Schema.Number.annotate({ "description": "The number of characters processed." }).check(Schema.isInt()), + "num_model_requests": Schema.Number.annotate({ "description": "The count of requests made to the model." }).check( + Schema.isInt() + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=model`, this field provides the model name of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated audio speeches usage details of the specific time bucket." }) +export type UsageAudioTranscriptionsResult = { + readonly "object": "organization.usage.audio_transcriptions.result" + readonly "seconds": number + readonly "num_model_requests": number + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "model"?: string | null +} +export const UsageAudioTranscriptionsResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.audio_transcriptions.result"), + "seconds": Schema.Number.annotate({ "description": "The number of seconds processed.", "format": "int64" }).check( + Schema.isInt() + ), + "num_model_requests": Schema.Number.annotate({ "description": "The count of requests made to the model." }).check( + Schema.isInt() + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=model`, this field provides the model name of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated audio transcriptions usage details of the specific time bucket." }) +export type UsageCodeInterpreterSessionsResult = { + readonly "object": "organization.usage.code_interpreter_sessions.result" + readonly "num_sessions": number + readonly "project_id"?: string | null +} +export const UsageCodeInterpreterSessionsResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.code_interpreter_sessions.result"), + "num_sessions": Schema.Number.annotate({ "description": "The number of code interpreter sessions." }).check( + Schema.isInt() + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated code interpreter sessions usage details of the specific time bucket." }) +export type UsageCompletionsResult = { + readonly "object": "organization.usage.completions.result" + readonly "input_tokens": number + readonly "input_cached_tokens"?: number + readonly "output_tokens": number + readonly "input_audio_tokens"?: number + readonly "output_audio_tokens"?: number + readonly "num_model_requests": number + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "model"?: string | null + readonly "batch"?: boolean | null + readonly "service_tier"?: string | null +} +export const UsageCompletionsResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.completions.result"), + "input_tokens": Schema.Number.annotate({ + "description": + "The aggregated number of text input tokens used, including cached tokens. For customers subscribe to scale tier, this includes scale tier tokens." + }).check(Schema.isInt()), + "input_cached_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The aggregated number of text input tokens that has been cached from previous requests. For customers subscribe to scale tier, this includes scale tier tokens." + }).check(Schema.isInt()) + ), + "output_tokens": Schema.Number.annotate({ + "description": + "The aggregated number of text output tokens used. For customers subscribe to scale tier, this includes scale tier tokens." + }).check(Schema.isInt()), + "input_audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The aggregated number of audio input tokens used, including cached tokens." + }).check(Schema.isInt()) + ), + "output_audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The aggregated number of audio output tokens used." }).check( + Schema.isInt() + ) + ), + "num_model_requests": Schema.Number.annotate({ "description": "The count of requests made to the model." }).check( + Schema.isInt() + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=model`, this field provides the model name of the grouped usage result." + }), + Schema.Null + ]) + ), + "batch": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": "When `group_by=batch`, this field tells whether the grouped usage result is batch or not." + }), + Schema.Null + ]) + ), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=service_tier`, this field provides the service tier of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated completions usage details of the specific time bucket." }) +export type UsageEmbeddingsResult = { + readonly "object": "organization.usage.embeddings.result" + readonly "input_tokens": number + readonly "num_model_requests": number + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "model"?: string | null +} +export const UsageEmbeddingsResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.embeddings.result"), + "input_tokens": Schema.Number.annotate({ "description": "The aggregated number of input tokens used." }).check( + Schema.isInt() + ), + "num_model_requests": Schema.Number.annotate({ "description": "The count of requests made to the model." }).check( + Schema.isInt() + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=model`, this field provides the model name of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated embeddings usage details of the specific time bucket." }) +export type UsageFileSearchCallsResult = { + readonly "object": "organization.usage.file_searches.result" + readonly "num_requests": number + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "vector_store_id"?: string | null +} +export const UsageFileSearchCallsResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.file_searches.result"), + "num_requests": Schema.Number.annotate({ "description": "The count of file search calls." }).check(Schema.isInt()), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "vector_store_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "When `group_by=vector_store_id`, this field provides the vector store ID of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated file search calls usage details of the specific time bucket." }) +export type UsageImagesResult = { + readonly "object": "organization.usage.images.result" + readonly "images": number + readonly "num_model_requests": number + readonly "source"?: string | null + readonly "size"?: string | null + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "model"?: string | null +} +export const UsageImagesResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.images.result"), + "images": Schema.Number.annotate({ "description": "The number of images processed." }).check(Schema.isInt()), + "num_model_requests": Schema.Number.annotate({ "description": "The count of requests made to the model." }).check( + Schema.isInt() + ), + "source": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "When `group_by=source`, this field provides the source of the grouped usage result, possible values are `image.generation`, `image.edit`, `image.variation`." + }), + Schema.Null + ]) + ), + "size": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=size`, this field provides the image size of the grouped usage result." + }), + Schema.Null + ]) + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=model`, this field provides the model name of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated images usage details of the specific time bucket." }) +export type UsageModerationsResult = { + readonly "object": "organization.usage.moderations.result" + readonly "input_tokens": number + readonly "num_model_requests": number + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "model"?: string | null +} +export const UsageModerationsResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.moderations.result"), + "input_tokens": Schema.Number.annotate({ "description": "The aggregated number of input tokens used." }).check( + Schema.isInt() + ), + "num_model_requests": Schema.Number.annotate({ "description": "The count of requests made to the model." }).check( + Schema.isInt() + ), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=model`, this field provides the model name of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated moderations usage details of the specific time bucket." }) +export type UsageVectorStoresResult = { + readonly "object": "organization.usage.vector_stores.result" + readonly "usage_bytes": number + readonly "project_id"?: string | null +} +export const UsageVectorStoresResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.vector_stores.result"), + "usage_bytes": Schema.Number.annotate({ "description": "The vector stores usage in bytes." }).check(Schema.isInt()), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated vector stores usage details of the specific time bucket." }) +export type UsageWebSearchCallsResult = { + readonly "object": "organization.usage.web_searches.result" + readonly "num_model_requests": number + readonly "num_requests": number + readonly "project_id"?: string | null + readonly "user_id"?: string | null + readonly "api_key_id"?: string | null + readonly "model"?: string | null + readonly "context_level"?: string | null +} +export const UsageWebSearchCallsResult = Schema.Struct({ + "object": Schema.Literal("organization.usage.web_searches.result"), + "num_model_requests": Schema.Number.annotate({ "description": "The count of model requests." }).check(Schema.isInt()), + "num_requests": Schema.Number.annotate({ "description": "The count of web search calls." }).check(Schema.isInt()), + "project_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=project_id`, this field provides the project ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=user_id`, this field provides the user ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "api_key_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result." + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "When `group_by=model`, this field provides the model name of the grouped usage result." + }), + Schema.Null + ]) + ), + "context_level": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "When `group_by=context_level`, this field provides the search context size of the grouped usage result." + }), + Schema.Null + ]) + ) +}).annotate({ "description": "The aggregated web search calls usage details of the specific time bucket." }) +export type User = { + readonly "object": "organization.user" + readonly "id": string + readonly "name"?: string | null + readonly "email"?: string | null + readonly "role"?: string | null + readonly "added_at": number + readonly "is_default"?: boolean + readonly "created"?: number + readonly "user"?: { + readonly "object": "user" + readonly "id": string + readonly "email"?: string | null + readonly "name"?: string | null + readonly "picture"?: string | null + readonly "enabled"?: boolean | null + readonly "banned"?: boolean | null + readonly "banned_at"?: number | null + } + readonly "is_service_account"?: boolean + readonly "is_scale_tier_authorized_purchaser"?: boolean | null + readonly "is_scim_managed"?: boolean + readonly "api_key_last_used_at"?: number | null + readonly "technical_level"?: string | null + readonly "developer_persona"?: string | null + readonly "projects"?: { + readonly "object": "list" + readonly "data": ReadonlyArray< + { readonly "id"?: string | null; readonly "name"?: string | null; readonly "role"?: string | null } + > + } | null +} +export const User = Schema.Struct({ + "object": Schema.Literal("organization.user").annotate({ + "description": "The object type, which is always `organization.user`" + }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the user" }) + ), + "email": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The email address of the user" }) + ), + "role": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "`owner` or `reader`" }) + ), + "added_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the user was added.", + "format": "unixtime" + }).check(Schema.isInt()), + "is_default": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this is the organization's default user." }) + ), + "created": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the user was created.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "user": Schema.optionalKey( + Schema.Struct({ + "object": Schema.Literal("user"), + "id": Schema.String, + "email": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "picture": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "banned": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "banned_at": Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), Schema.Null]) + ) + }).annotate({ "description": "Nested user details." }) + ), + "is_service_account": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether the user is a service account." }) + ), + "is_scale_tier_authorized_purchaser": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the user is an authorized purchaser for Scale Tier." + }) + ), + "is_scim_managed": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether the user is managed through SCIM." }) + ), + "api_key_last_used_at": Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), Schema.Null]).annotate({ + "description": "The Unix timestamp (in seconds) of the user's last API key usage." + }) + ), + "technical_level": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The technical level metadata for the user." }) + ), + "developer_persona": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The developer persona metadata for the user." + }) + ), + "projects": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array( + Schema.Struct({ + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "role": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }) + ) + }), + Schema.Null + ]).annotate({ "description": "Projects associated with the user, if included." }) + ) +}).annotate({ "description": "Represents an individual `user` within an organization." }) +export type UserDeleteResponse = { + readonly "object": "organization.user.deleted" + readonly "id": string + readonly "deleted": boolean +} +export const UserDeleteResponse = Schema.Struct({ + "object": Schema.Literal("organization.user.deleted"), + "id": Schema.String, + "deleted": Schema.Boolean +}) +export type UserRoleUpdateRequest = { + readonly "role"?: string | null + readonly "role_id"?: string | null + readonly "technical_level"?: string | null + readonly "developer_persona"?: string | null +} +export const UserRoleUpdateRequest = Schema.Struct({ + "role": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "`owner` or `reader`" }) + ), + "role_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Role ID to assign to the user." }) + ), + "technical_level": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Technical level metadata." }) + ), + "developer_persona": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Developer persona metadata." }) + ) +}) +export type VadConfig = { + readonly "type": "server_vad" + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + readonly "threshold"?: number +} +export const VadConfig = Schema.Struct({ + "type": Schema.Literal("server_vad").annotate({ + "description": "Must be set to `server_vad` to enable manual chunking using server side VAD." + }), + "prefix_padding_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Amount of audio to include before the VAD detected speech (in \nmilliseconds).\n" + }).check(Schema.isInt()) + ), + "silence_duration_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Duration of silence to detect speech stop (in milliseconds).\nWith shorter values the model will respond more quickly, \nbut may jump in on short pauses from the user.\n" + }).check(Schema.isInt()) + ), + "threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Sensitivity threshold (0.0 to 1.0) for voice activity detection. A \nhigher threshold will require louder audio to activate the model, and \nthus might perform better in noisy environments.\n" + }).check(Schema.isFinite()) + ) +}) +export type VectorStoreExpirationAfter = { readonly "anchor": "last_active_at"; readonly "days": number } +export const VectorStoreExpirationAfter = Schema.Struct({ + "anchor": Schema.Literal("last_active_at").annotate({ + "description": "Anchor timestamp after which the expiration policy applies. Supported anchors: `last_active_at`." + }), + "days": Schema.Number.annotate({ + "description": "The number of days after the anchor time that the vector store will expire." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(365)) +}).annotate({ "title": "Vector store expiration policy", "description": "The expiration policy for a vector store." }) +export type VectorStoreFileAttributes = {} | null +export const VectorStoreFileAttributes = Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard. Keys are strings\nwith a maximum length of 64 characters. Values are strings with a maximum\nlength of 512 characters, booleans, or numbers.\n" + }).check(Schema.isMaxProperties(16)).check(Schema.isPropertyNames(Schema.String.check(Schema.isMaxLength(64)))), + Schema.Null +]) +export type VectorStoreFileBatchObject = { + readonly "id": string + readonly "object": "vector_store.files_batch" + readonly "created_at": number + readonly "vector_store_id": string + readonly "status": "in_progress" | "completed" | "cancelled" | "failed" + readonly "file_counts": { + readonly "in_progress": number + readonly "completed": number + readonly "failed": number + readonly "cancelled": number + readonly "total": number + } +} +export const VectorStoreFileBatchObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "object": Schema.Literal("vector_store.files_batch").annotate({ + "description": "The object type, which is always `vector_store.file_batch`." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the vector store files batch was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "vector_store_id": Schema.String.annotate({ + "description": + "The ID of the [vector store](/docs/api-reference/vector-stores/object) that the [File](/docs/api-reference/files) is attached to." + }), + "status": Schema.Literals(["in_progress", "completed", "cancelled", "failed"]).annotate({ + "description": + "The status of the vector store files batch, which can be either `in_progress`, `completed`, `cancelled` or `failed`." + }), + "file_counts": Schema.Struct({ + "in_progress": Schema.Number.annotate({ "description": "The number of files that are currently being processed." }) + .check(Schema.isInt()), + "completed": Schema.Number.annotate({ "description": "The number of files that have been processed." }).check( + Schema.isInt() + ), + "failed": Schema.Number.annotate({ "description": "The number of files that have failed to process." }).check( + Schema.isInt() + ), + "cancelled": Schema.Number.annotate({ "description": "The number of files that where cancelled." }).check( + Schema.isInt() + ), + "total": Schema.Number.annotate({ "description": "The total number of files." }).check(Schema.isInt()) + }) +}).annotate({ "title": "Vector store file batch", "description": "A batch of files attached to a vector store." }) +export type VectorStoreFileContentResponse = { + readonly "object": "vector_store.file_content.page" + readonly "data": ReadonlyArray<{ readonly "type"?: string; readonly "text"?: string }> + readonly "has_more": boolean + readonly "next_page": string | null +} +export const VectorStoreFileContentResponse = Schema.Struct({ + "object": Schema.Literal("vector_store.file_content.page").annotate({ + "description": "The object type, which is always `vector_store.file_content.page`" + }), + "data": Schema.Array( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.String.annotate({ "description": "The content type (currently only `\"text\"`)" }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The text content" })) + }) + ).annotate({ "description": "Parsed content of the file." }), + "has_more": Schema.Boolean.annotate({ "description": "Indicates if there are more content pages to fetch." }), + "next_page": Schema.Union([ + Schema.String.annotate({ "description": "The token for the next page, if any." }), + Schema.Null + ]) +}).annotate({ "description": "Represents the parsed content of a vector store file." }) +export type VectorStoreSearchResultContentObject = { readonly "type": "text"; readonly "text": string } +export const VectorStoreSearchResultContentObject = Schema.Struct({ + "type": Schema.Literal("text").annotate({ "description": "The type of content." }), + "text": Schema.String.annotate({ "description": "The text content returned from search." }) +}) +export type Verbosity = "low" | "medium" | "high" | null +export const Verbosity = Schema.Union([ + Schema.Literals(["low", "medium", "high"]).annotate({ + "description": + "Constrains the verbosity of the model's response. Lower values will result in\nmore concise responses, while higher values will result in more verbose responses.\nCurrently supported values are `low`, `medium`, and `high`.\n" + }), + Schema.Null +]) +export type VoiceConsentDeletedResource = { + readonly "id": string + readonly "object": "audio.voice_consent" + readonly "deleted": boolean +} +export const VoiceConsentDeletedResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The consent recording identifier." }), + "object": Schema.Literal("audio.voice_consent"), + "deleted": Schema.Boolean +}) +export type VoiceConsentResource = { + readonly "object": "audio.voice_consent" + readonly "id": string + readonly "name": string + readonly "language": string + readonly "created_at": number +} +export const VoiceConsentResource = Schema.Struct({ + "object": Schema.Literal("audio.voice_consent").annotate({ + "description": "The object type, which is always `audio.voice_consent`." + }), + "id": Schema.String.annotate({ "description": "The consent recording identifier." }), + "name": Schema.String.annotate({ "description": "The label provided when the consent recording was uploaded." }), + "language": Schema.String.annotate({ + "description": "The BCP 47 language tag for the consent phrase (for example, `en-US`)." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the consent recording was created.", + "format": "unixtime" + }).check(Schema.isInt()) +}).annotate({ + "title": "Voice consent", + "description": "A consent recording used to authorize creation of a custom voice." +}) +export type VoiceIdsShared = + | string + | "alloy" + | "ash" + | "ballad" + | "coral" + | "echo" + | "sage" + | "shimmer" + | "verse" + | "marin" + | "cedar" +export const VoiceIdsShared = Schema.Union([ + Schema.String, + Schema.Literals(["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]) +]) +export type VoiceResource = { + readonly "object": "audio.voice" + readonly "id": string + readonly "name": string + readonly "created_at": number +} +export const VoiceResource = Schema.Struct({ + "object": Schema.Literal("audio.voice").annotate({ + "description": "The object type, which is always `audio.voice`." + }), + "id": Schema.String.annotate({ "description": "The voice identifier, which can be referenced in API endpoints." }), + "name": Schema.String.annotate({ "description": "The name of the voice." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the voice was created.", + "format": "unixtime" + }).check(Schema.isInt()) +}).annotate({ "title": "Voice", "description": "A custom voice that can be used for audio output." }) +export type WebSearchApproximateLocation = { + readonly "type"?: "approximate" + readonly "country"?: string | null + readonly "region"?: string | null + readonly "city"?: string | null + readonly "timezone"?: string | null +} | null +export const WebSearchApproximateLocation = Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("approximate").annotate({ + "description": "The type of location approximation. Always `approximate`." + }) + ), + "country": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of the user, e.g. `US`." + }), + Schema.Null + ]) + ), + "region": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Free text input for the region of the user, e.g. `California`." }), + Schema.Null + ]) + ), + "city": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Free text input for the city of the user, e.g. `San Francisco`." }), + Schema.Null + ]) + ), + "timezone": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the user, e.g. `America/Los_Angeles`." + }), + Schema.Null + ]) + ) + }).annotate({ "title": "Web search approximate location", "description": "The approximate location of the user.\n" }), + Schema.Null +]) +export type WebSearchContextSize = "low" | "medium" | "high" +export const WebSearchContextSize = Schema.Literals(["low", "medium", "high"]).annotate({ + "description": + "High level guidance for the amount of context window space to use for the \nsearch. One of `low`, `medium`, or `high`. `medium` is the default.\n" +}) +export type WebSearchLocation = { + readonly "country"?: string + readonly "region"?: string + readonly "city"?: string + readonly "timezone"?: string +} +export const WebSearchLocation = Schema.Struct({ + "country": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The two-letter \n[ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of the user,\ne.g. `US`.\n" + }) + ), + "region": Schema.optionalKey( + Schema.String.annotate({ "description": "Free text input for the region of the user, e.g. `California`.\n" }) + ), + "city": Schema.optionalKey( + Schema.String.annotate({ "description": "Free text input for the city of the user, e.g. `San Francisco`.\n" }) + ), + "timezone": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The [IANA timezone](https://timeapi.io/documentation/iana-timezones) \nof the user, e.g. `America/Los_Angeles`.\n" + }) + ) +}).annotate({ "title": "Web search location", "description": "Approximate location parameters for the search." }) +export type WebSearchToolCall = { + readonly "id": string + readonly "type": "web_search_call" + readonly "status": "in_progress" | "searching" | "completed" | "failed" + readonly "action": + | { + readonly "type": "search" + readonly "query"?: string + readonly "queries"?: ReadonlyArray + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "type": "find_in_page"; readonly "url": string; readonly "pattern": string } +} +export const WebSearchToolCall = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the web search tool call.\n" }), + "type": Schema.Literal("web_search_call").annotate({ + "description": "The type of the web search tool call. Always `web_search_call`.\n" + }), + "status": Schema.Literals(["in_progress", "searching", "completed", "failed"]).annotate({ + "description": "The status of the web search tool call.\n" + }), + "action": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("search").annotate({ "description": "The action type.\n" }), + "query": Schema.optionalKey(Schema.String.annotate({ "description": "[DEPRECATED] The search query.\n" })), + "queries": Schema.optionalKey( + Schema.Array(Schema.String.annotate({ "description": "A search query.\n" })).annotate({ + "title": "Search queries", + "description": "The search queries.\n" + }) + ), + "sources": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "type": Schema.Literal("url").annotate({ "description": "The type of source. Always `url`.\n" }), + "url": Schema.String.annotate({ "description": "The URL of the source.\n", "format": "uri" }) + }).annotate({ "title": "Web search source", "description": "A source used in the search.\n" }) + ).annotate({ "title": "Web search sources", "description": "The sources used in the search.\n" }) + ) + }).annotate({ + "title": "Search action", + "description": + "An object describing the specific action taken in this web search call.\nIncludes details on how the model used the web (search, open_page, find_in_page).\n" + }), + Schema.Struct({ + "type": Schema.Literal("open_page").annotate({ "description": "The action type. Always `open_page`.\n" }), + "url": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "format": "uri" }), Schema.Null]).annotate({ + "description": "The URL opened by the model.\n" + }) + ) + }).annotate({ + "title": "Open page action", + "description": + "An object describing the specific action taken in this web search call.\nIncludes details on how the model used the web (search, open_page, find_in_page).\n" + }), + Schema.Struct({ + "type": Schema.Literal("find_in_page").annotate({ "description": "The action type.\n" }), + "url": Schema.String.annotate({ + "description": "The URL of the page searched for the pattern.\n", + "format": "uri" + }), + "pattern": Schema.String.annotate({ "description": "The pattern or text to search for within the page.\n" }) + }).annotate({ + "title": "Find action", + "description": + "An object describing the specific action taken in this web search call.\nIncludes details on how the model used the web (search, open_page, find_in_page).\n" + }) + ], { mode: "oneOf" }) +}).annotate({ + "title": "Web search tool call", + "description": + "The results of a web search tool call. See the\n[web search guide](/docs/guides/tools-web-search) for more information.\n" +}) +export type SkillReferenceParam = { + readonly "type": "skill_reference" + readonly "skill_id": string + readonly "version"?: string +} +export const SkillReferenceParam = Schema.Struct({ + "type": Schema.Literal("skill_reference").annotate({ + "description": "References a skill created with the /v1/skills endpoint." + }), + "skill_id": Schema.String.annotate({ "description": "The ID of the referenced skill." }).check(Schema.isMinLength(1)) + .check(Schema.isMaxLength(64)), + "version": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional skill version. Use a positive integer or 'latest'. Omit for default." + }) + ) +}) +export type InlineSkillParam = { + readonly "type": "inline" + readonly "name": string + readonly "description": string + readonly "source": { readonly "type": "base64"; readonly "media_type": "application/zip"; readonly "data": string } +} +export const InlineSkillParam = Schema.Struct({ + "type": Schema.Literal("inline").annotate({ "description": "Defines an inline skill for this request." }), + "name": Schema.String.annotate({ "description": "The name of the skill." }), + "description": Schema.String.annotate({ "description": "The description of the skill." }), + "source": Schema.Struct({ + "type": Schema.Literal("base64").annotate({ + "description": "The type of the inline skill source. Must be `base64`." + }), + "media_type": Schema.Literal("application/zip").annotate({ + "description": "The media type of the inline skill payload. Must be `application/zip`." + }), + "data": Schema.String.annotate({ "description": "Base64-encoded skill zip bundle." }).check(Schema.isMinLength(1)) + .check(Schema.isMaxLength(70254592)) + }).annotate({ "description": "Inline skill payload" }) +}) +export type ContainerNetworkPolicyDisabledParam = { readonly "type": "disabled" } +export const ContainerNetworkPolicyDisabledParam = Schema.Struct({ + "type": Schema.Literal("disabled").annotate({ "description": "Disable outbound network access. Always `disabled`." }) +}) +export type ContainerNetworkPolicyDomainSecretParam = { + readonly "domain": string + readonly "name": string + readonly "value": string +} +export const ContainerNetworkPolicyDomainSecretParam = Schema.Struct({ + "domain": Schema.String.annotate({ "description": "The domain associated with the secret." }).check( + Schema.isMinLength(1) + ), + "name": Schema.String.annotate({ "description": "The name of the secret to inject for the domain." }).check( + Schema.isMinLength(1) + ), + "value": Schema.String.annotate({ "description": "The secret value to inject for the domain." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(10485760)) +}) +export type IncludeEnum = + | "file_search_call.results" + | "web_search_call.results" + | "web_search_call.action.sources" + | "message.input_image.image_url" + | "computer_call_output.output.image_url" + | "code_interpreter_call.outputs" + | "reasoning.encrypted_content" + | "message.output_text.logprobs" +export const IncludeEnum = Schema.Literals([ + "file_search_call.results", + "web_search_call.results", + "web_search_call.action.sources", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "code_interpreter_call.outputs", + "reasoning.encrypted_content", + "message.output_text.logprobs" +]).annotate({ + "description": + "Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.results`: Include the search results of the web search tool call.\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program)." +}) +export type InputTextContent = { readonly "type": "input_text"; readonly "text": string } +export const InputTextContent = Schema.Struct({ + "type": Schema.Literal("input_text").annotate({ "description": "The type of the input item. Always `input_text`." }), + "text": Schema.String.annotate({ "description": "The text input to the model." }) +}).annotate({ "title": "Input text", "description": "A text input to the model." }) +export type FileCitationBody = { + readonly "type": "file_citation" + readonly "file_id": string + readonly "index": number + readonly "filename": string +} +export const FileCitationBody = Schema.Struct({ + "type": Schema.Literal("file_citation").annotate({ + "description": "The type of the file citation. Always `file_citation`." + }), + "file_id": Schema.String.annotate({ "description": "The ID of the file." }), + "index": Schema.Number.annotate({ "description": "The index of the file in the list of files." }).check( + Schema.isInt() + ), + "filename": Schema.String.annotate({ "description": "The filename of the file cited." }) +}).annotate({ "title": "File citation", "description": "A citation to a file." }) +export type UrlCitationBody = { + readonly "type": "url_citation" + readonly "url": string + readonly "start_index": number + readonly "end_index": number + readonly "title": string +} +export const UrlCitationBody = Schema.Struct({ + "type": Schema.Literal("url_citation").annotate({ + "description": "The type of the URL citation. Always `url_citation`." + }), + "url": Schema.String.annotate({ "description": "The URL of the web resource.", "format": "uri" }), + "start_index": Schema.Number.annotate({ + "description": "The index of the first character of the URL citation in the message." + }).check(Schema.isInt()), + "end_index": Schema.Number.annotate({ + "description": "The index of the last character of the URL citation in the message." + }).check(Schema.isInt()), + "title": Schema.String.annotate({ "description": "The title of the web resource." }) +}).annotate({ + "title": "URL citation", + "description": "A citation for a web resource used to generate a model response." +}) +export type ContainerFileCitationBody = { + readonly "type": "container_file_citation" + readonly "container_id": string + readonly "file_id": string + readonly "start_index": number + readonly "end_index": number + readonly "filename": string +} +export const ContainerFileCitationBody = Schema.Struct({ + "type": Schema.Literal("container_file_citation").annotate({ + "description": "The type of the container file citation. Always `container_file_citation`." + }), + "container_id": Schema.String.annotate({ "description": "The ID of the container file." }), + "file_id": Schema.String.annotate({ "description": "The ID of the file." }), + "start_index": Schema.Number.annotate({ + "description": "The index of the first character of the container file citation in the message." + }).check(Schema.isInt()), + "end_index": Schema.Number.annotate({ + "description": "The index of the last character of the container file citation in the message." + }).check(Schema.isInt()), + "filename": Schema.String.annotate({ "description": "The filename of the container file cited." }) +}).annotate({ + "title": "Container file citation", + "description": "A citation for a container file used to generate a model response." +}) +export type TopLogProb = { + readonly "token": string + readonly "logprob": number + readonly "bytes": ReadonlyArray +} +export const TopLogProb = Schema.Struct({ + "token": Schema.String, + "logprob": Schema.Number.check(Schema.isFinite()), + "bytes": Schema.Array(Schema.Number.check(Schema.isInt())) +}).annotate({ "title": "Top log probability", "description": "The top log probability of a token." }) +export type TextContent = { readonly "type": "text"; readonly "text": string } +export const TextContent = Schema.Struct({ "type": Schema.Literal("text"), "text": Schema.String }).annotate({ + "title": "Text Content", + "description": "A text content." +}) +export type SummaryTextContent = { readonly "type": "summary_text"; readonly "text": string } +export const SummaryTextContent = Schema.Struct({ + "type": Schema.Literal("summary_text").annotate({ "description": "The type of the object. Always `summary_text`." }), + "text": Schema.String.annotate({ "description": "A summary of the reasoning output from the model so far." }) +}).annotate({ "title": "Summary text", "description": "A summary text from the model." }) +export type ReasoningTextContent = { readonly "type": "reasoning_text"; readonly "text": string } +export const ReasoningTextContent = Schema.Struct({ + "type": Schema.Literal("reasoning_text").annotate({ + "description": "The type of the reasoning text. Always `reasoning_text`." + }), + "text": Schema.String.annotate({ "description": "The reasoning text from the model." }) +}).annotate({ "title": "Reasoning text", "description": "Reasoning text from the model." }) +export type RefusalContent = { readonly "type": "refusal"; readonly "refusal": string } +export const RefusalContent = Schema.Struct({ + "type": Schema.Literal("refusal").annotate({ "description": "The type of the refusal. Always `refusal`." }), + "refusal": Schema.String.annotate({ "description": "The refusal explanation from the model." }) +}).annotate({ "title": "Refusal", "description": "A refusal from the model." }) +export type InputImageContent = { + readonly "type": "input_image" + readonly "image_url"?: string | null + readonly "file_id"?: string | null + readonly "detail": "low" | "high" | "auto" | "original" +} +export const InputImageContent = Schema.Struct({ + "type": Schema.Literal("input_image").annotate({ + "description": "The type of the input item. Always `input_image`." + }), + "image_url": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The URL of the image to be sent to the model. A fully qualified URL or base64 encoded image in a data URL.", + "format": "uri" + }), + Schema.Null + ]) + ), + "file_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The ID of the file to be sent to the model." }), + Schema.Null + ]) + ), + "detail": Schema.Literals(["low", "high", "auto", "original"]).annotate({ + "description": + "The detail level of the image to be sent to the model. One of `high`, `low`, `auto`, or `original`. Defaults to `auto`." + }) +}).annotate({ + "title": "Input image", + "description": "An image input to the model. Learn about [image inputs](/docs/guides/vision)." +}) +export type ComputerScreenshotContent = { + readonly "type": "computer_screenshot" + readonly "image_url": string | null + readonly "file_id": string | null + readonly "detail": "low" | "high" | "auto" | "original" +} +export const ComputerScreenshotContent = Schema.Struct({ + "type": Schema.Literal("computer_screenshot").annotate({ + "description": + "Specifies the event type. For a computer screenshot, this property is always set to `computer_screenshot`." + }), + "image_url": Schema.Union([ + Schema.String.annotate({ "description": "The URL of the screenshot image.", "format": "uri" }), + Schema.Null + ]), + "file_id": Schema.Union([ + Schema.String.annotate({ "description": "The identifier of an uploaded file that contains the screenshot." }), + Schema.Null + ]), + "detail": Schema.Literals(["low", "high", "auto", "original"]).annotate({ + "description": + "The detail level of the screenshot image to be sent to the model. One of `high`, `low`, `auto`, or `original`. Defaults to `auto`." + }) +}).annotate({ "title": "Computer screenshot", "description": "A screenshot of a computer." }) +export type InputFileContent = { + readonly "type": "input_file" + readonly "file_id"?: string | null + readonly "filename"?: string + readonly "file_data"?: string + readonly "file_url"?: string + readonly "detail"?: "low" | "high" +} +export const InputFileContent = Schema.Struct({ + "type": Schema.Literal("input_file").annotate({ "description": "The type of the input item. Always `input_file`." }), + "file_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The ID of the file to be sent to the model." }), + Schema.Null + ]) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The name of the file to be sent to the model." }) + ), + "file_data": Schema.optionalKey( + Schema.String.annotate({ "description": "The content of the file to be sent to the model.\n" }) + ), + "file_url": Schema.optionalKey( + Schema.String.annotate({ "description": "The URL of the file to be sent to the model.", "format": "uri" }) + ), + "detail": Schema.optionalKey( + Schema.Literals(["low", "high"]).annotate({ + "description": + "The detail level of the file to be sent to the model. Use `low` for the default rendering behavior, or `high` to render the file at higher quality. Defaults to `low`." + }) + ) +}).annotate({ "title": "Input file", "description": "A file input to the model." }) +export type ClickParam = { + readonly "type": "click" + readonly "button": "left" | "right" | "wheel" | "back" | "forward" + readonly "x": number + readonly "y": number + readonly "keys"?: ReadonlyArray | null +} +export const ClickParam = Schema.Struct({ + "type": Schema.Literal("click").annotate({ + "description": "Specifies the event type. For a click action, this property is always `click`." + }), + "button": Schema.Literals(["left", "right", "wheel", "back", "forward"]).annotate({ + "description": + "Indicates which mouse button was pressed during the click. One of `left`, `right`, `wheel`, `back`, or `forward`." + }), + "x": Schema.Number.annotate({ "description": "The x-coordinate where the click occurred." }).check(Schema.isInt()), + "y": Schema.Number.annotate({ "description": "The y-coordinate where the click occurred." }).check(Schema.isInt()), + "keys": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ "description": "The keys being held while clicking." }), + Schema.Null + ]) + ) +}).annotate({ "title": "Click", "description": "A click action." }) +export type DoubleClickAction = { + readonly "type": "double_click" + readonly "x": number + readonly "y": number + readonly "keys": ReadonlyArray | null +} +export const DoubleClickAction = Schema.Struct({ + "type": Schema.Literal("double_click").annotate({ + "description": "Specifies the event type. For a double click action, this property is always set to `double_click`." + }), + "x": Schema.Number.annotate({ "description": "The x-coordinate where the double click occurred." }).check( + Schema.isInt() + ), + "y": Schema.Number.annotate({ "description": "The y-coordinate where the double click occurred." }).check( + Schema.isInt() + ), + "keys": Schema.Union([ + Schema.Array(Schema.String).annotate({ "description": "The keys being held while double-clicking." }), + Schema.Null + ]) +}).annotate({ "title": "DoubleClick", "description": "A double click action." }) +export type CoordParam = { readonly "x": number; readonly "y": number } +export const CoordParam = Schema.Struct({ + "x": Schema.Number.annotate({ "description": "The x-coordinate." }).check(Schema.isInt()), + "y": Schema.Number.annotate({ "description": "The y-coordinate." }).check(Schema.isInt()) +}).annotate({ "title": "Coordinate", "description": "An x/y coordinate pair, e.g. `{ x: 100, y: 200 }`." }) +export type KeyPressAction = { readonly "type": "keypress"; readonly "keys": ReadonlyArray } +export const KeyPressAction = Schema.Struct({ + "type": Schema.Literal("keypress").annotate({ + "description": "Specifies the event type. For a keypress action, this property is always set to `keypress`." + }), + "keys": Schema.Array( + Schema.String.annotate({ "description": "One of the keys the model is requesting to be pressed." }) + ).annotate({ + "description": + "The combination of keys the model is requesting to be pressed. This is an array of strings, each representing a key." + }) +}).annotate({ "title": "KeyPress", "description": "A collection of keypresses the model would like to perform." }) +export type MoveParam = { + readonly "type": "move" + readonly "x": number + readonly "y": number + readonly "keys"?: ReadonlyArray | null +} +export const MoveParam = Schema.Struct({ + "type": Schema.Literal("move").annotate({ + "description": "Specifies the event type. For a move action, this property is always set to `move`." + }), + "x": Schema.Number.annotate({ "description": "The x-coordinate to move to." }).check(Schema.isInt()), + "y": Schema.Number.annotate({ "description": "The y-coordinate to move to." }).check(Schema.isInt()), + "keys": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ "description": "The keys being held while moving the mouse." }), + Schema.Null + ]) + ) +}).annotate({ "title": "Move", "description": "A mouse move action." }) +export type ScreenshotParam = { readonly "type": "screenshot" } +export const ScreenshotParam = Schema.Struct({ + "type": Schema.Literal("screenshot").annotate({ + "description": "Specifies the event type. For a screenshot action, this property is always set to `screenshot`." + }) +}).annotate({ "title": "Screenshot", "description": "A screenshot action." }) +export type ScrollParam = { + readonly "type": "scroll" + readonly "x": number + readonly "y": number + readonly "scroll_x": number + readonly "scroll_y": number + readonly "keys"?: ReadonlyArray | null +} +export const ScrollParam = Schema.Struct({ + "type": Schema.Literal("scroll").annotate({ + "description": "Specifies the event type. For a scroll action, this property is always set to `scroll`." + }), + "x": Schema.Number.annotate({ "description": "The x-coordinate where the scroll occurred." }).check(Schema.isInt()), + "y": Schema.Number.annotate({ "description": "The y-coordinate where the scroll occurred." }).check(Schema.isInt()), + "scroll_x": Schema.Number.annotate({ "description": "The horizontal scroll distance." }).check(Schema.isInt()), + "scroll_y": Schema.Number.annotate({ "description": "The vertical scroll distance." }).check(Schema.isInt()), + "keys": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ "description": "The keys being held while scrolling." }), + Schema.Null + ]) + ) +}).annotate({ "title": "Scroll", "description": "A scroll action." }) +export type TypeParam = { readonly "type": "type"; readonly "text": string } +export const TypeParam = Schema.Struct({ + "type": Schema.Literal("type").annotate({ + "description": "Specifies the event type. For a type action, this property is always set to `type`." + }), + "text": Schema.String.annotate({ "description": "The text to type." }) +}).annotate({ "title": "Type", "description": "An action to type in text." }) +export type WaitParam = { readonly "type": "wait" } +export const WaitParam = Schema.Struct({ + "type": Schema.Literal("wait").annotate({ + "description": "Specifies the event type. For a wait action, this property is always set to `wait`." + }) +}).annotate({ "title": "Wait", "description": "A wait action." }) +export type ComputerCallSafetyCheckParam = { + readonly "id": string + readonly "code"?: string | null + readonly "message"?: string | null +} +export const ComputerCallSafetyCheckParam = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The ID of the pending safety check." }), + "code": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The type of the pending safety check." }), Schema.Null]) + ), + "message": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Details about the pending safety check." }), Schema.Null]) + ) +}).annotate({ "description": "A pending safety check for the computer call." }) +export type ToolSearchCall = { + readonly "type": "tool_search_call" + readonly "id": string + readonly "call_id": string | null + readonly "execution": "server" | "client" + readonly "arguments": unknown + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "created_by"?: string +} +export const ToolSearchCall = Schema.Struct({ + "type": Schema.Literal("tool_search_call").annotate({ + "description": "The type of the item. Always `tool_search_call`." + }), + "id": Schema.String.annotate({ "description": "The unique ID of the tool search call item." }), + "call_id": Schema.Union([ + Schema.String.annotate({ "description": "The unique ID of the tool search call generated by the model." }), + Schema.Null + ]), + "execution": Schema.Literals(["server", "client"]).annotate({ + "description": "Whether tool search was executed by the server or by the client." + }), + "arguments": Schema.Unknown.annotate({ "description": "Arguments used for the tool search call." }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the tool search call item that was recorded." + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item." }) + ) +}) +export type FunctionTool = { + readonly "type": "function" + readonly "name": string + readonly "description"?: string | null + readonly "parameters": {} | null + readonly "strict": boolean | null + readonly "defer_loading"?: boolean +} +export const FunctionTool = Schema.Struct({ + "type": Schema.Literal("function").annotate({ "description": "The type of the function tool. Always `function`." }), + "name": Schema.String.annotate({ "description": "The name of the function to call." }), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "A description of the function. Used by the model to determine whether or not to call the function." + }), + Schema.Null + ]) + ), + "parameters": Schema.Union([ + Schema.Struct({}).annotate({ "description": "A JSON schema object describing the parameters of the function." }), + Schema.Null + ]), + "strict": Schema.Union([ + Schema.Boolean.annotate({ "description": "Whether to enforce strict parameter validation. Default `true`." }), + Schema.Null + ]), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this function is deferred and loaded via tool search." }) + ) +}).annotate({ + "title": "Function", + "description": + "Defines a function in your own code the model can choose to call. Learn more about [function calling](https://platform.openai.com/docs/guides/function-calling)." +}) +export type ComputerTool = { readonly "type": "computer" } +export const ComputerTool = Schema.Struct({ + "type": Schema.Literal("computer").annotate({ "description": "The type of the computer tool. Always `computer`." }) +}).annotate({ + "title": "Computer", + "description": + "A tool that controls a virtual computer. Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use)." +}) +export type ComputerUsePreviewTool = { + readonly "type": "computer_use_preview" + readonly "environment": "windows" | "mac" | "linux" | "ubuntu" | "browser" + readonly "display_width": number + readonly "display_height": number +} +export const ComputerUsePreviewTool = Schema.Struct({ + "type": Schema.Literal("computer_use_preview").annotate({ + "description": "The type of the computer use tool. Always `computer_use_preview`." + }), + "environment": Schema.Literals(["windows", "mac", "linux", "ubuntu", "browser"]).annotate({ + "description": "The type of computer environment to control." + }), + "display_width": Schema.Number.annotate({ "description": "The width of the computer display." }).check( + Schema.isInt() + ), + "display_height": Schema.Number.annotate({ "description": "The height of the computer display." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "Computer use preview", + "description": + "A tool that controls a virtual computer. Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use)." +}) +export type InputFidelity = "high" | "low" +export const InputFidelity = Schema.Literals(["high", "low"]).annotate({ + "description": + "Control how much effort the model will exert to match the style and features, especially facial features, of input images. This parameter is only supported for `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`." +}) +export type LocalShellToolParam = { readonly "type": "local_shell" } +export const LocalShellToolParam = Schema.Struct({ + "type": Schema.Literal("local_shell").annotate({ + "description": "The type of the local shell tool. Always `local_shell`." + }) +}).annotate({ + "title": "Local shell tool", + "description": "A tool that allows the model to execute shell commands in a local environment." +}) +export type LocalSkillParam = { readonly "name": string; readonly "description": string; readonly "path": string } +export const LocalSkillParam = Schema.Struct({ + "name": Schema.String.annotate({ "description": "The name of the skill." }), + "description": Schema.String.annotate({ "description": "The description of the skill." }), + "path": Schema.String.annotate({ "description": "The path to the directory containing the skill." }) +}) +export type ContainerReferenceParam = { readonly "type": "container_reference"; readonly "container_id": string } +export const ContainerReferenceParam = Schema.Struct({ + "type": Schema.Literal("container_reference").annotate({ + "description": "References a container created with the /v1/containers endpoint" + }), + "container_id": Schema.String.annotate({ "description": "The ID of the referenced container." }) +}) +export type CustomTextFormatParam = { readonly "type": "text" } +export const CustomTextFormatParam = Schema.Struct({ + "type": Schema.Literal("text").annotate({ "description": "Unconstrained text format. Always `text`." }) +}).annotate({ "title": "Text format", "description": "Unconstrained free-form text." }) +export type CustomGrammarFormatParam = { + readonly "type": "grammar" + readonly "syntax": "lark" | "regex" + readonly "definition": string +} +export const CustomGrammarFormatParam = Schema.Struct({ + "type": Schema.Literal("grammar").annotate({ "description": "Grammar format. Always `grammar`." }), + "syntax": Schema.Literals(["lark", "regex"]).annotate({ + "description": "The syntax of the grammar definition. One of `lark` or `regex`." + }), + "definition": Schema.String.annotate({ "description": "The grammar definition." }) +}).annotate({ "title": "Grammar format", "description": "A grammar defined by the user." }) +export type EmptyModelParam = {} +export const EmptyModelParam = Schema.Struct({}) +export type ToolSearchToolParam = { + readonly "type": "tool_search" + readonly "execution"?: "server" | "client" + readonly "description"?: string | null + readonly "parameters"?: {} | null +} +export const ToolSearchToolParam = Schema.Struct({ + "type": Schema.Literal("tool_search").annotate({ "description": "The type of the tool. Always `tool_search`." }), + "execution": Schema.optionalKey( + Schema.Literals(["server", "client"]).annotate({ + "description": "Whether tool search is executed by the server or by the client." + }) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "Description shown to the model for a client-executed tool search tool." + }), + Schema.Null + ]) + ), + "parameters": Schema.optionalKey( + Schema.Union([ + Schema.Struct({}).annotate({ "description": "Parameter schema for a client-executed tool search tool." }), + Schema.Null + ]) + ) +}).annotate({ + "title": "Tool search tool", + "description": "Hosted or BYOT tool search configuration for deferred tools." +}) +export type SearchContentType = "text" | "image" +export const SearchContentType = Schema.Literals(["text", "image"]) +export type ApplyPatchToolParam = { readonly "type": "apply_patch" } +export const ApplyPatchToolParam = Schema.Struct({ + "type": Schema.Literal("apply_patch").annotate({ "description": "The type of the tool. Always `apply_patch`." }) +}).annotate({ + "title": "Apply patch tool", + "description": "Allows the assistant to create, delete, or update files using unified diffs." +}) +export type CompactionBody = { + readonly "type": "compaction" + readonly "id": string + readonly "encrypted_content": string + readonly "created_by"?: string +} +export const CompactionBody = Schema.Struct({ + "type": Schema.Literal("compaction").annotate({ "description": "The type of the item. Always `compaction`." }), + "id": Schema.String.annotate({ "description": "The unique ID of the compaction item." }), + "encrypted_content": Schema.String.annotate({ + "description": "The encrypted content that was produced by compaction." + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item." }) + ) +}).annotate({ + "title": "Compaction item", + "description": + "A compaction item generated by the [`v1/responses/compact` API](/docs/api-reference/responses/compact)." +}) +export type CodeInterpreterOutputLogs = { readonly "type": "logs"; readonly "logs": string } +export const CodeInterpreterOutputLogs = Schema.Struct({ + "type": Schema.Literal("logs").annotate({ "description": "The type of the output. Always `logs`." }), + "logs": Schema.String.annotate({ "description": "The logs output from the code interpreter." }) +}).annotate({ "title": "Code interpreter output logs", "description": "The logs output from the code interpreter." }) +export type CodeInterpreterOutputImage = { readonly "type": "image"; readonly "url": string } +export const CodeInterpreterOutputImage = Schema.Struct({ + "type": Schema.Literal("image").annotate({ "description": "The type of the output. Always `image`." }), + "url": Schema.String.annotate({ + "description": "The URL of the image output from the code interpreter.", + "format": "uri" + }) +}).annotate({ "title": "Code interpreter output image", "description": "The image output from the code interpreter." }) +export type LocalShellExecAction = { + readonly "type": "exec" + readonly "command": ReadonlyArray + readonly "timeout_ms"?: number | null + readonly "working_directory"?: string | null + readonly "env": {} + readonly "user"?: string | null +} +export const LocalShellExecAction = Schema.Struct({ + "type": Schema.Literal("exec").annotate({ "description": "The type of the local shell action. Always `exec`." }), + "command": Schema.Array(Schema.String).annotate({ "description": "The command to run." }), + "timeout_ms": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "Optional timeout in milliseconds for the command." }).check( + Schema.isInt() + ), + Schema.Null + ]) + ), + "working_directory": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Optional working directory to run the command in." }), + Schema.Null + ]) + ), + "env": Schema.Struct({}).annotate({ "description": "Environment variables to set for the command." }), + "user": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Optional user to run the command as." }), Schema.Null]) + ) +}).annotate({ "title": "Local shell exec action", "description": "Execute a shell command on the server." }) +export type LocalEnvironmentResource = { readonly "type": "local" } +export const LocalEnvironmentResource = Schema.Struct({ + "type": Schema.Literal("local").annotate({ "description": "The environment type. Always `local`." }) +}).annotate({ + "title": "Local Environment", + "description": "Represents the use of a local environment to perform shell actions." +}) +export type ContainerReferenceResource = { readonly "type": "container_reference"; readonly "container_id": string } +export const ContainerReferenceResource = Schema.Struct({ + "type": Schema.Literal("container_reference").annotate({ + "description": "The environment type. Always `container_reference`." + }), + "container_id": Schema.String +}).annotate({ "title": "Container Reference", "description": "Represents a container created with /v1/containers." }) +export type FunctionShellCallOutputTimeoutOutcome = { readonly "type": "timeout" } +export const FunctionShellCallOutputTimeoutOutcome = Schema.Struct({ + "type": Schema.Literal("timeout").annotate({ "description": "The outcome type. Always `timeout`." }) +}).annotate({ + "title": "Shell call timeout outcome", + "description": "Indicates that the shell call exceeded its configured time limit." +}) +export type FunctionShellCallOutputExitOutcome = { readonly "type": "exit"; readonly "exit_code": number } +export const FunctionShellCallOutputExitOutcome = Schema.Struct({ + "type": Schema.Literal("exit").annotate({ "description": "The outcome type. Always `exit`." }), + "exit_code": Schema.Number.annotate({ "description": "Exit code from the shell process." }).check(Schema.isInt()) +}).annotate({ + "title": "Shell call exit outcome", + "description": "Indicates that the shell commands finished and returned an exit code." +}) +export type ApplyPatchCreateFileOperation = { + readonly "type": "create_file" + readonly "path": string + readonly "diff": string +} +export const ApplyPatchCreateFileOperation = Schema.Struct({ + "type": Schema.Literal("create_file").annotate({ "description": "Create a new file with the provided diff." }), + "path": Schema.String.annotate({ "description": "Path of the file to create." }), + "diff": Schema.String.annotate({ "description": "Diff to apply." }) +}).annotate({ + "title": "Apply patch create file operation", + "description": "Instruction describing how to create a file via the apply_patch tool." +}) +export type ApplyPatchDeleteFileOperation = { readonly "type": "delete_file"; readonly "path": string } +export const ApplyPatchDeleteFileOperation = Schema.Struct({ + "type": Schema.Literal("delete_file").annotate({ "description": "Delete the specified file." }), + "path": Schema.String.annotate({ "description": "Path of the file to delete." }) +}).annotate({ + "title": "Apply patch delete file operation", + "description": "Instruction describing how to delete a file via the apply_patch tool." +}) +export type ApplyPatchUpdateFileOperation = { + readonly "type": "update_file" + readonly "path": string + readonly "diff": string +} +export const ApplyPatchUpdateFileOperation = Schema.Struct({ + "type": Schema.Literal("update_file").annotate({ "description": "Update an existing file with the provided diff." }), + "path": Schema.String.annotate({ "description": "Path of the file to update." }), + "diff": Schema.String.annotate({ "description": "Diff to apply." }) +}).annotate({ + "title": "Apply patch update file operation", + "description": "Instruction describing how to update a file via the apply_patch tool." +}) +export type ApplyPatchToolCallOutput = { + readonly "type": "apply_patch_call_output" + readonly "id": string + readonly "call_id": string + readonly "status": "completed" | "failed" + readonly "output"?: string | null + readonly "created_by"?: string +} +export const ApplyPatchToolCallOutput = Schema.Struct({ + "type": Schema.Literal("apply_patch_call_output").annotate({ + "description": "The type of the item. Always `apply_patch_call_output`." + }), + "id": Schema.String.annotate({ + "description": "The unique ID of the apply patch tool call output. Populated when this item is returned via API." + }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the apply patch tool call generated by the model." + }), + "status": Schema.Literals(["completed", "failed"]).annotate({ + "description": "The status of the apply patch tool call output. One of `completed` or `failed`." + }), + "output": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Optional textual output returned by the apply patch tool." }), + Schema.Null + ]) + ), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the entity that created this tool call output." }) + ) +}).annotate({ + "title": "Apply patch tool call output", + "description": "The output emitted by an apply patch tool call." +}) +export type InputTextContentParam = { readonly "type": "input_text"; readonly "text": string } +export const InputTextContentParam = Schema.Struct({ + "type": Schema.Literal("input_text").annotate({ "description": "The type of the input item. Always `input_text`." }), + "text": Schema.String.annotate({ "description": "The text input to the model." }).check(Schema.isMaxLength(10485760)) +}).annotate({ "title": "Input text", "description": "A text input to the model." }) +export type InputImageContentParamAutoParam = { + readonly "type": "input_image" + readonly "image_url"?: string | null + readonly "file_id"?: string | null + readonly "detail"?: "low" | "high" | "auto" | "original" | null +} +export const InputImageContentParamAutoParam = Schema.Struct({ + "type": Schema.Literal("input_image").annotate({ + "description": "The type of the input item. Always `input_image`." + }), + "image_url": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The URL of the image to be sent to the model. A fully qualified URL or base64 encoded image in a data URL.", + "format": "uri" + }).check(Schema.isMaxLength(20971520)), + Schema.Null + ]) + ), + "file_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The ID of the file to be sent to the model." }), + Schema.Null + ]) + ), + "detail": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["low", "high", "auto", "original"]).annotate({ + "description": + "The detail level of the image to be sent to the model. One of `high`, `low`, `auto`, or `original`. Defaults to `auto`." + }), + Schema.Null + ]) + ) +}).annotate({ + "title": "Input image", + "description": "An image input to the model. Learn about [image inputs](/docs/guides/vision)" +}) +export type InputFileContentParam = { + readonly "type": "input_file" + readonly "file_id"?: string | null + readonly "filename"?: string | null + readonly "file_data"?: string | null + readonly "file_url"?: string | null + readonly "detail"?: "low" | "high" +} +export const InputFileContentParam = Schema.Struct({ + "type": Schema.Literal("input_file").annotate({ "description": "The type of the input item. Always `input_file`." }), + "file_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The ID of the file to be sent to the model." }), + Schema.Null + ]) + ), + "filename": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The name of the file to be sent to the model." }), + Schema.Null + ]) + ), + "file_data": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The base64-encoded data of the file to be sent to the model." }).check( + Schema.isMaxLength(73400320) + ), + Schema.Null + ]) + ), + "file_url": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The URL of the file to be sent to the model.", "format": "uri" }), + Schema.Null + ]) + ), + "detail": Schema.optionalKey( + Schema.Literals(["low", "high"]).annotate({ + "description": + "The detail level of the file to be sent to the model. Use `low` for the default rendering behavior, or `high` to render the file at higher quality. Defaults to `low`." + }) + ) +}).annotate({ "title": "Input file", "description": "A file input to the model." }) +export type FunctionShellCallOutputTimeoutOutcomeParam = { readonly "type": "timeout" } +export const FunctionShellCallOutputTimeoutOutcomeParam = Schema.Struct({ + "type": Schema.Literal("timeout").annotate({ "description": "The outcome type. Always `timeout`." }) +}).annotate({ + "title": "Shell call timeout outcome", + "description": "Indicates that the shell call exceeded its configured time limit." +}) +export type FunctionShellCallOutputExitOutcomeParam = { readonly "type": "exit"; readonly "exit_code": number } +export const FunctionShellCallOutputExitOutcomeParam = Schema.Struct({ + "type": Schema.Literal("exit").annotate({ "description": "The outcome type. Always `exit`." }), + "exit_code": Schema.Number.annotate({ "description": "The exit code returned by the shell process." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "Shell call exit outcome", + "description": "Indicates that the shell commands finished and returned an exit code." +}) +export type ApplyPatchCreateFileOperationParam = { + readonly "type": "create_file" + readonly "path": string + readonly "diff": string +} +export const ApplyPatchCreateFileOperationParam = Schema.Struct({ + "type": Schema.Literal("create_file").annotate({ "description": "The operation type. Always `create_file`." }), + "path": Schema.String.annotate({ "description": "Path of the file to create relative to the workspace root." }).check( + Schema.isMinLength(1) + ), + "diff": Schema.String.annotate({ "description": "Unified diff content to apply when creating the file." }).check( + Schema.isMaxLength(10485760) + ) +}).annotate({ + "title": "Apply patch create file operation", + "description": "Instruction for creating a new file via the apply_patch tool." +}) +export type ApplyPatchDeleteFileOperationParam = { readonly "type": "delete_file"; readonly "path": string } +export const ApplyPatchDeleteFileOperationParam = Schema.Struct({ + "type": Schema.Literal("delete_file").annotate({ "description": "The operation type. Always `delete_file`." }), + "path": Schema.String.annotate({ "description": "Path of the file to delete relative to the workspace root." }).check( + Schema.isMinLength(1) + ) +}).annotate({ + "title": "Apply patch delete file operation", + "description": "Instruction for deleting an existing file via the apply_patch tool." +}) +export type ApplyPatchUpdateFileOperationParam = { + readonly "type": "update_file" + readonly "path": string + readonly "diff": string +} +export const ApplyPatchUpdateFileOperationParam = Schema.Struct({ + "type": Schema.Literal("update_file").annotate({ "description": "The operation type. Always `update_file`." }), + "path": Schema.String.annotate({ "description": "Path of the file to update relative to the workspace root." }).check( + Schema.isMinLength(1) + ), + "diff": Schema.String.annotate({ "description": "Unified diff content to apply to the existing file." }).check( + Schema.isMaxLength(10485760) + ) +}).annotate({ + "title": "Apply patch update file operation", + "description": "Instruction for updating an existing file via the apply_patch tool." +}) +export type CompactionTriggerItemParam = { readonly "type": "compaction_trigger" } +export const CompactionTriggerItemParam = Schema.Struct({ + "type": Schema.Literal("compaction_trigger").annotate({ + "description": "The type of the item. Always `compaction_trigger`." + }) +}).annotate({ + "title": "Compaction trigger", + "description": "Compacts the current context. Must be the final input item." +}) +export type ItemReferenceParam = { readonly "type"?: "item_reference" | null; readonly "id": string } +export const ItemReferenceParam = Schema.Struct({ + "type": Schema.optionalKey( + Schema.Union([ + Schema.Literal("item_reference").annotate({ + "description": "The type of item to reference. Always `item_reference`." + }), + Schema.Null + ]) + ), + "id": Schema.String.annotate({ "description": "The ID of the item to reference." }) +}).annotate({ "title": "Item reference", "description": "An internal identifier for an item to reference." }) +export type ConversationResource = { + readonly "id": string + readonly "object": "conversation" + readonly "metadata": unknown + readonly "created_at": number +} +export const ConversationResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the conversation." }), + "object": Schema.Literal("conversation").annotate({ + "description": "The object type, which is always `conversation`." + }), + "metadata": Schema.Unknown.annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format, and querying for objects via API or the dashboard.\n Keys are strings with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters." + }), + "created_at": Schema.Number.annotate({ + "description": "The time at which the conversation was created, measured in seconds since the Unix epoch.", + "format": "unixtime" + }).check(Schema.isInt()) +}) +export type ImageGenOutputTokensDetails = { readonly "image_tokens": number; readonly "text_tokens": number } +export const ImageGenOutputTokensDetails = Schema.Struct({ + "image_tokens": Schema.Number.annotate({ "description": "The number of image output tokens generated by the model." }) + .check(Schema.isInt()), + "text_tokens": Schema.Number.annotate({ "description": "The number of text output tokens generated by the model." }) + .check(Schema.isInt()) +}).annotate({ + "title": "Image generation output token details", + "description": "The output token details for the image generation." +}) +export type ImageGenInputUsageDetails = { readonly "text_tokens": number; readonly "image_tokens": number } +export const ImageGenInputUsageDetails = Schema.Struct({ + "text_tokens": Schema.Number.annotate({ "description": "The number of text tokens in the input prompt." }).check( + Schema.isInt() + ), + "image_tokens": Schema.Number.annotate({ "description": "The number of image tokens in the input prompt." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "Input usage details", + "description": "The input tokens detailed information for the image generation." +}) +export type SpecificApplyPatchParam = { readonly "type": "apply_patch" } +export const SpecificApplyPatchParam = Schema.Struct({ + "type": Schema.Literal("apply_patch").annotate({ "description": "The tool to call. Always `apply_patch`." }) +}).annotate({ + "title": "Specific apply patch tool choice", + "description": "Forces the model to call the apply_patch tool when executing a tool call." +}) +export type SpecificFunctionShellParam = { readonly "type": "shell" } +export const SpecificFunctionShellParam = Schema.Struct({ + "type": Schema.Literal("shell").annotate({ "description": "The tool to call. Always `shell`." }) +}).annotate({ + "title": "Specific shell tool choice", + "description": "Forces the model to call the shell tool when a tool call is required." +}) +export type ConversationParam_2 = { readonly "id": string } +export const ConversationParam_2 = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the conversation." }) +}).annotate({ "title": "Conversation object", "description": "The conversation that this response belongs to." }) +export type ContextManagementParam = { readonly "type": string; readonly "compact_threshold"?: number | null } +export const ContextManagementParam = Schema.Struct({ + "type": Schema.String.annotate({ + "description": "The context management entry type. Currently only 'compaction' is supported." + }), + "compact_threshold": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "Token threshold at which compaction should be triggered for this entry." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1000)), + Schema.Null + ]) + ) +}) +export type Conversation_2 = { readonly "id": string } +export const Conversation_2 = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The unique ID of the conversation that this response was associated with." + }) +}).annotate({ + "title": "Conversation", + "description": + "The conversation that this response belonged to. Input items and output items from this response were automatically added to this conversation." +}) +export type UpdateConversationBody = { readonly "metadata": {} | null } +export const UpdateConversationBody = Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format, and querying for objects via API or the dashboard.\n Keys are strings with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters." + }) +}) +export type DeletedConversationResource = { + readonly "object": "conversation.deleted" + readonly "deleted": boolean + readonly "id": string +} +export const DeletedConversationResource = Schema.Struct({ + "object": Schema.Literal("conversation.deleted"), + "deleted": Schema.Boolean, + "id": Schema.String +}) +export type OrderEnum = "asc" | "desc" +export const OrderEnum = Schema.Literals(["asc", "desc"]) +export type VideoResource = { + readonly "id": string + readonly "object": "video" + readonly "model": + | string + | "sora-2" + | "sora-2-pro" + | "sora-2-2025-10-06" + | "sora-2-pro-2025-10-06" + | "sora-2-2025-12-08" + readonly "status": "queued" | "in_progress" | "completed" | "failed" + readonly "progress": number + readonly "created_at": number + readonly "completed_at": number | null + readonly "expires_at": number | null + readonly "prompt": string | null + readonly "size": "720x1280" | "1280x720" | "1024x1792" | "1792x1024" + readonly "seconds": string + readonly "remixed_from_video_id": string | null + readonly "error": { readonly "code": string; readonly "message": string } | null +} +export const VideoResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Unique identifier for the video job." }), + "object": Schema.Literal("video").annotate({ "description": "The object type, which is always `video`." }), + "model": Schema.Union([ + Schema.String, + Schema.Literals(["sora-2", "sora-2-pro", "sora-2-2025-10-06", "sora-2-pro-2025-10-06", "sora-2-2025-12-08"]) + ]).annotate({ "description": "The video generation model that produced the job." }), + "status": Schema.Literals(["queued", "in_progress", "completed", "failed"]).annotate({ + "description": "Current lifecycle status of the video job." + }), + "progress": Schema.Number.annotate({ "description": "Approximate completion percentage for the generation task." }) + .check(Schema.isInt()), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (seconds) for when the job was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "completed_at": Schema.Union([ + Schema.Number.annotate({ + "description": "Unix timestamp (seconds) for when the job completed, if finished.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "expires_at": Schema.Union([ + Schema.Number.annotate({ + "description": "Unix timestamp (seconds) for when the downloadable assets expire, if set.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "prompt": Schema.Union([ + Schema.String.annotate({ "description": "The prompt that was used to generate the video." }), + Schema.Null + ]), + "size": Schema.Literals(["720x1280", "1280x720", "1024x1792", "1792x1024"]).annotate({ + "description": "The resolution of the generated video." + }), + "seconds": Schema.String.annotate({ + "description": "Duration of the generated clip in seconds. For extensions, this is the stitched total duration." + }), + "remixed_from_video_id": Schema.Union([ + Schema.String.annotate({ "description": "Identifier of the source video if this video is a remix." }), + Schema.Null + ]), + "error": Schema.Union([ + Schema.Struct({ + "code": Schema.String.annotate({ "description": "A machine-readable error code that was returned." }), + "message": Schema.String.annotate({ + "description": "A human-readable description of the error that was returned." + }) + }).annotate({ + "title": "Error", + "description": "Error payload that explains why generation failed, if applicable." + }), + Schema.Null + ]) +}).annotate({ "title": "Video job", "description": "Structured information describing a generated video job." }) +export type ImageRefParam_2 = { readonly "image_url"?: string; readonly "file_id"?: string } +export const ImageRefParam_2 = Schema.Struct({ + "image_url": Schema.optionalKey( + Schema.String.annotate({ "description": "A fully qualified URL or base64-encoded data URL.", "format": "uri" }) + .check(Schema.isMaxLength(20971520)) + ), + "file_id": Schema.optionalKey(Schema.String) +}) +export type CreateVideoJsonBody = { + readonly "model"?: + | string + | "sora-2" + | "sora-2-pro" + | "sora-2-2025-10-06" + | "sora-2-pro-2025-10-06" + | "sora-2-2025-12-08" + readonly "prompt": string + readonly "input_reference"?: { readonly "image_url"?: string; readonly "file_id"?: string } + readonly "seconds"?: "4" | "8" | "12" + readonly "size"?: "720x1280" | "1280x720" | "1024x1792" | "1792x1024" +} +export const CreateVideoJsonBody = Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["sora-2", "sora-2-pro", "sora-2-2025-10-06", "sora-2-pro-2025-10-06", "sora-2-2025-12-08"]) + ]).annotate({ + "description": "The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`." + }) + ), + "prompt": Schema.String.annotate({ "description": "Text prompt that describes the video to generate." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(32000)), + "input_reference": Schema.optionalKey( + Schema.Struct({ + "image_url": Schema.optionalKey( + Schema.String.annotate({ "description": "A fully qualified URL or base64-encoded data URL.", "format": "uri" }) + .check(Schema.isMaxLength(20971520)) + ), + "file_id": Schema.optionalKey(Schema.String) + }).annotate({ + "description": + "Optional reference object that guides generation. Provide exactly one of `image_url` or `file_id`." + }) + ), + "seconds": Schema.optionalKey( + Schema.Literals(["4", "8", "12"]).annotate({ + "description": "Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds." + }) + ), + "size": Schema.optionalKey( + Schema.Literals(["720x1280", "1280x720", "1024x1792", "1792x1024"]).annotate({ + "description": + "Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280." + }) + ) +}).annotate({ + "title": "Create video JSON request", + "description": "JSON parameters for creating a new video generation job." +}) +export type CreateVideoCharacterBody = { readonly "video": string; readonly "name": string } +export const CreateVideoCharacterBody = Schema.Struct({ + "video": Schema.String.annotate({ "description": "Video file used to create a character.", "format": "binary" }), + "name": Schema.String.annotate({ "description": "Display name for this API character." }).check(Schema.isMinLength(1)) + .check(Schema.isMaxLength(80)) +}).annotate({ + "title": "Create character request", + "description": "Parameters for creating a character from an uploaded video." +}) +export type VideoCharacterResource = { + readonly "id": string | null + readonly "name": string | null + readonly "created_at": number +} +export const VideoCharacterResource = Schema.Struct({ + "id": Schema.Union([ + Schema.String.annotate({ "description": "Identifier for the character creation cameo." }), + Schema.Null + ]), + "name": Schema.Union([Schema.String.annotate({ "description": "Display name for the character." }), Schema.Null]), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the character was created.", + "format": "unixtime" + }).check(Schema.isInt()) +}) +export type VideoReferenceInputParam = { readonly "id": string } +export const VideoReferenceInputParam = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier of the completed video." }) +}).annotate({ "description": "Reference to the completed video." }) +export type CreateVideoEditJsonBody = { readonly "video": { readonly "id": string }; readonly "prompt": string } +export const CreateVideoEditJsonBody = Schema.Struct({ + "video": Schema.Struct({ "id": Schema.String.annotate({ "description": "The identifier of the completed video." }) }) + .annotate({ "description": "Reference to the completed video to edit." }), + "prompt": Schema.String.annotate({ "description": "Text prompt that describes how to edit the source video." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(32000)) +}).annotate({ + "title": "Create video edit JSON request", + "description": "JSON parameters for editing an existing generated video." +}) +export type CreateVideoExtendJsonBody = { + readonly "video": { readonly "id": string } + readonly "prompt": string + readonly "seconds": "4" | "8" | "12" +} +export const CreateVideoExtendJsonBody = Schema.Struct({ + "video": Schema.Struct({ "id": Schema.String.annotate({ "description": "The identifier of the completed video." }) }) + .annotate({ "description": "Reference to the completed video to extend." }), + "prompt": Schema.String.annotate({ "description": "Updated text prompt that directs the extension generation." }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(32000)), + "seconds": Schema.Literals(["4", "8", "12"]).annotate({ + "description": "Length of the newly generated extension segment in seconds (allowed values: 4, 8, 12, 16, 20)." + }) +}).annotate({ + "title": "Create video extension JSON request", + "description": "JSON parameters for extending an existing generated video." +}) +export type DeletedVideoResource = { + readonly "object": "video.deleted" + readonly "deleted": boolean + readonly "id": string +} +export const DeletedVideoResource = Schema.Struct({ + "object": Schema.Literal("video.deleted").annotate({ + "description": "The object type that signals the deletion response." + }), + "deleted": Schema.Boolean.annotate({ "description": "Indicates that the video resource was deleted." }), + "id": Schema.String.annotate({ "description": "Identifier of the deleted video." }) +}).annotate({ + "title": "Deleted video response", + "description": "Confirmation payload returned after deleting a video." +}) +export type VideoContentVariant = "video" | "thumbnail" | "spritesheet" +export const VideoContentVariant = Schema.Literals(["video", "thumbnail", "spritesheet"]) +export type CreateVideoRemixBody = { readonly "prompt": string } +export const CreateVideoRemixBody = Schema.Struct({ + "prompt": Schema.String.annotate({ "description": "Updated text prompt that directs the remix generation." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(32000)) +}).annotate({ + "title": "Create video remix request", + "description": "Parameters for remixing an existing generated video." +}) +export type TokenCountsResource = { readonly "object": "response.input_tokens"; readonly "input_tokens": number } +export const TokenCountsResource = Schema.Struct({ + "object": Schema.Literal("response.input_tokens"), + "input_tokens": Schema.Number.check(Schema.isInt()) +}).annotate({ "title": "Token counts" }) +export type SkillResource = { + readonly "id": string + readonly "object": "skill" + readonly "name": string + readonly "description": string + readonly "created_at": number + readonly "default_version": string + readonly "latest_version": string +} +export const SkillResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Unique identifier for the skill." }), + "object": Schema.Literal("skill").annotate({ "description": "The object type, which is `skill`." }), + "name": Schema.String.annotate({ "description": "Name of the skill." }), + "description": Schema.String.annotate({ "description": "Description of the skill." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (seconds) for when the skill was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "default_version": Schema.String.annotate({ "description": "Default version for the skill." }), + "latest_version": Schema.String.annotate({ "description": "Latest version for the skill." }) +}) +export type CreateSkillBody = { readonly "files": ReadonlyArray | string } +export const CreateSkillBody = Schema.Struct({ + "files": Schema.Union([ + Schema.Array(Schema.String.annotate({ "format": "binary" })).annotate({ + "description": "Skill files to upload (directory upload) or a single zip file." + }).check(Schema.isMaxLength(500)), + Schema.String.annotate({ "description": "Skill zip file to upload.", "format": "binary" }) + ], { mode: "oneOf" }) +}).annotate({ + "title": "Create skill request", + "description": "Uploads a skill either as a directory (multipart `files[]`) or as a single zip file." +}) +export type SetDefaultSkillVersionBody = { readonly "default_version": string } +export const SetDefaultSkillVersionBody = Schema.Struct({ + "default_version": Schema.String.annotate({ "description": "The skill version number to set as default." }) +}).annotate({ "title": "Update skill request", "description": "Updates the default version pointer for a skill." }) +export type DeletedSkillResource = { + readonly "object": "skill.deleted" + readonly "deleted": boolean + readonly "id": string +} +export const DeletedSkillResource = Schema.Struct({ + "object": Schema.Literal("skill.deleted"), + "deleted": Schema.Boolean, + "id": Schema.String +}) +export type SkillVersionResource = { + readonly "object": "skill.version" + readonly "id": string + readonly "skill_id": string + readonly "version": string + readonly "created_at": number + readonly "name": string + readonly "description": string +} +export const SkillVersionResource = Schema.Struct({ + "object": Schema.Literal("skill.version").annotate({ "description": "The object type, which is `skill.version`." }), + "id": Schema.String.annotate({ "description": "Unique identifier for the skill version." }), + "skill_id": Schema.String.annotate({ "description": "Identifier of the skill for this version." }), + "version": Schema.String.annotate({ "description": "Version number for this skill." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (seconds) for when the version was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "name": Schema.String.annotate({ "description": "Name of the skill version." }), + "description": Schema.String.annotate({ "description": "Description of the skill version." }) +}) +export type CreateSkillVersionBody = { readonly "files": ReadonlyArray | string; readonly "default"?: boolean } +export const CreateSkillVersionBody = Schema.Struct({ + "files": Schema.Union([ + Schema.Array(Schema.String.annotate({ "format": "binary" })).annotate({ + "description": "Skill files to upload (directory upload) or a single zip file." + }).check(Schema.isMaxLength(500)), + Schema.String.annotate({ "description": "Skill zip file to upload.", "format": "binary" }) + ], { mode: "oneOf" }), + "default": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to set this version as the default." }) + ) +}).annotate({ "title": "Create skill version request", "description": "Uploads a new immutable version of a skill." }) +export type DeletedSkillVersionResource = { + readonly "object": "skill.version.deleted" + readonly "deleted": boolean + readonly "id": string + readonly "version": string +} +export const DeletedSkillVersionResource = Schema.Struct({ + "object": Schema.Literal("skill.version.deleted"), + "deleted": Schema.Boolean, + "id": Schema.String, + "version": Schema.String.annotate({ "description": "The deleted skill version." }) +}) +export type ChatSessionResource = { + readonly "id": string + readonly "object": "chatkit.session" + readonly "expires_at": number + readonly "client_secret": string + readonly "workflow": { + readonly "id": string + readonly "version": string | null + readonly "state_variables": {} | null + readonly "tracing": { readonly "enabled": boolean } + } + readonly "user": string + readonly "rate_limits": { readonly "max_requests_per_1_minute": number } + readonly "max_requests_per_1_minute": number + readonly "status": "active" | "expired" | "cancelled" + readonly "chatkit_configuration": { + readonly "automatic_thread_titling": { readonly "enabled": boolean } + readonly "file_upload": { + readonly "enabled": boolean + readonly "max_file_size": number | null + readonly "max_files": number | null + } + readonly "history": { readonly "enabled": boolean; readonly "recent_threads": number | null } + } +} +export const ChatSessionResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier for the ChatKit session." }), + "object": Schema.Literal("chatkit.session").annotate({ + "description": "Type discriminator that is always `chatkit.session`." + }), + "expires_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the session expires.", + "format": "unixtime" + }).check(Schema.isInt()), + "client_secret": Schema.String.annotate({ + "description": "Ephemeral client secret that authenticates session requests." + }), + "workflow": Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the workflow backing the session." }), + "version": Schema.Union([ + Schema.String.annotate({ + "description": + "Specific workflow version used for the session. Defaults to null when using the latest deployment." + }), + Schema.Null + ]), + "state_variables": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "State variable key-value pairs applied when invoking the workflow. Defaults to null when no overrides were provided." + }), + Schema.Null + ]), + "tracing": Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "description": "Indicates whether tracing is enabled." }) + }).annotate({ "title": "Tracing Configuration", "description": "Tracing settings applied to the workflow." }) + }).annotate({ "title": "Workflow", "description": "Workflow metadata for the session." }), + "user": Schema.String.annotate({ "description": "User identifier associated with the session." }), + "rate_limits": Schema.Struct({ + "max_requests_per_1_minute": Schema.Number.annotate({ + "description": "Maximum allowed requests per one-minute window." + }).check(Schema.isInt()) + }).annotate({ "title": "Rate limits", "description": "Resolved rate limit values." }), + "max_requests_per_1_minute": Schema.Number.annotate({ + "description": "Convenience copy of the per-minute request limit." + }).check(Schema.isInt()), + "status": Schema.Literals(["active", "expired", "cancelled"]).annotate({ + "description": "Current lifecycle state of the session." + }), + "chatkit_configuration": Schema.Struct({ + "automatic_thread_titling": Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "description": "Whether automatic thread titling is enabled." }) + }).annotate({ "title": "Automatic thread titling", "description": "Automatic thread titling preferences." }), + "file_upload": Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "description": "Indicates if uploads are enabled for the session." }), + "max_file_size": Schema.Union([ + Schema.Number.annotate({ "description": "Maximum upload size in megabytes." }).check(Schema.isInt()), + Schema.Null + ]), + "max_files": Schema.Union([ + Schema.Number.annotate({ "description": "Maximum number of uploads allowed during the session." }).check( + Schema.isInt() + ), + Schema.Null + ]) + }).annotate({ "title": "File upload settings", "description": "Upload settings for the session." }), + "history": Schema.Struct({ + "enabled": Schema.Boolean.annotate({ "description": "Indicates if chat history is persisted for the session." }), + "recent_threads": Schema.Union([ + Schema.Number.annotate({ + "description": + "Number of prior threads surfaced in history views. Defaults to null when all history is retained." + }).check(Schema.isInt()), + Schema.Null + ]) + }).annotate({ "title": "History settings", "description": "History retention configuration." }) + }).annotate({ + "title": "ChatKit configuration", + "description": "Resolved ChatKit feature configuration for the session." + }) +}).annotate({ + "title": "The chat session object", + "description": "Represents a ChatKit session and its resolved configuration." +}) +export type CreateChatSessionBody = { + readonly "workflow": { + readonly "id": string + readonly "version"?: string + readonly "state_variables"?: {} + readonly "tracing"?: { readonly "enabled"?: boolean } + } + readonly "user": string + readonly "expires_after"?: { readonly "anchor": "created_at"; readonly "seconds": number } + readonly "rate_limits"?: { readonly "max_requests_per_1_minute"?: number } + readonly "chatkit_configuration"?: { + readonly "automatic_thread_titling"?: { readonly "enabled"?: boolean } + readonly "file_upload"?: { + readonly "enabled"?: boolean + readonly "max_file_size"?: number + readonly "max_files"?: number + } + readonly "history"?: { readonly "enabled"?: boolean; readonly "recent_threads"?: number } + } +} +export const CreateChatSessionBody = Schema.Struct({ + "workflow": Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier for the workflow invoked by the session." }), + "version": Schema.optionalKey( + Schema.String.annotate({ + "description": "Specific workflow version to run. Defaults to the latest deployed version." + }) + ), + "state_variables": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "State variables forwarded to the workflow. Keys may be up to 64 characters, values must be primitive types, and the map defaults to an empty object." + }).check(Schema.isMaxProperties(64)) + ), + "tracing": Schema.optionalKey( + Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether tracing is enabled during the session. Defaults to true." }) + ) + }).annotate({ + "title": "Tracing Configuration", + "description": + "Optional tracing overrides for the workflow invocation. When omitted, tracing is enabled by default." + }) + ) + }).annotate({ "title": "Workflow settings", "description": "Workflow that powers the session." }), + "user": Schema.String.annotate({ + "description": + "A free-form string that identifies your end user; ensures this Session can access other objects that have the same `user` scope." + }).check(Schema.isMinLength(1)), + "expires_after": Schema.optionalKey( + Schema.Struct({ + "anchor": Schema.Literal("created_at").annotate({ + "description": "Base timestamp used to calculate expiration. Currently fixed to `created_at`." + }), + "seconds": Schema.Number.annotate({ + "description": "Number of seconds after the anchor when the session expires.", + "format": "int64" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(600)) + }).annotate({ + "title": "Expiration overrides", + "description": "Optional override for session expiration timing in seconds from creation. Defaults to 10 minutes." + }) + ), + "rate_limits": Schema.optionalKey( + Schema.Struct({ + "max_requests_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of requests allowed per minute for the session. Defaults to 10." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)) + ) + }).annotate({ + "title": "Rate limit overrides", + "description": "Optional override for per-minute request limits. When omitted, defaults to 10." + }) + ), + "chatkit_configuration": Schema.optionalKey( + Schema.Struct({ + "automatic_thread_titling": Schema.optionalKey( + Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Enable automatic thread title generation. Defaults to true." }) + ) + }).annotate({ + "title": "Automatic thread titling configuration", + "description": + "Configuration for automatic thread titling. When omitted, automatic thread titling is enabled by default." + }) + ), + "file_upload": Schema.optionalKey( + Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Enable uploads for this session. Defaults to false." }) + ), + "max_file_size": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum size in megabytes for each uploaded file. Defaults to 512 MB, which is the maximum allowable size." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(512)) + ), + "max_files": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of files that can be uploaded to the session. Defaults to 10." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)) + ) + }).annotate({ + "title": "File upload configuration", + "description": + "Configuration for upload enablement and limits. When omitted, uploads are disabled by default (max_files 10, max_file_size 512 MB)." + }) + ), + "history": Schema.optionalKey( + Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Enables chat users to access previous ChatKit threads. Defaults to true." + }) + ), + "recent_threads": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Number of recent ChatKit threads users have access to. Defaults to unlimited when unset." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)) + ) + }).annotate({ + "title": "Chat history configuration", + "description": + "Configuration for chat history retention. When omitted, history is enabled by default with no limit on recent_threads (null)." + }) + ) + }).annotate({ + "title": "ChatKit configuration overrides", + "description": "Optional overrides for ChatKit runtime configuration features" + }) + ) +}).annotate({ + "title": "Create chat session request", + "description": "Parameters for provisioning a new ChatKit session." +}) +export type UserMessageInputText = { readonly "type": "input_text"; readonly "text": string } +export const UserMessageInputText = Schema.Struct({ + "type": Schema.Literal("input_text").annotate({ "description": "Type discriminator that is always `input_text`." }), + "text": Schema.String.annotate({ "description": "Plain-text content supplied by the user." }) +}).annotate({ "title": "User message input", "description": "Text block that a user contributed to the thread." }) +export type UserMessageQuotedText = { readonly "type": "quoted_text"; readonly "text": string } +export const UserMessageQuotedText = Schema.Struct({ + "type": Schema.Literal("quoted_text").annotate({ "description": "Type discriminator that is always `quoted_text`." }), + "text": Schema.String.annotate({ "description": "Quoted text content." }) +}).annotate({ + "title": "User message quoted text", + "description": "Quoted snippet that the user referenced in their message." +}) +export type Attachment = { + readonly "type": "image" | "file" + readonly "id": string + readonly "name": string + readonly "mime_type": string + readonly "preview_url": string | null +} +export const Attachment = Schema.Struct({ + "type": Schema.Literals(["image", "file"]).annotate({ "description": "Attachment discriminator." }), + "id": Schema.String.annotate({ "description": "Identifier for the attachment." }), + "name": Schema.String.annotate({ "description": "Original display name for the attachment." }), + "mime_type": Schema.String.annotate({ "description": "MIME type of the attachment." }), + "preview_url": Schema.Union([ + Schema.String.annotate({ "description": "Preview URL for rendering the attachment inline.", "format": "uri" }), + Schema.Null + ]) +}).annotate({ "title": "Attachment", "description": "Attachment metadata included on thread items." }) +export type FileAnnotation = { + readonly "type": "file" + readonly "source": { readonly "type": "file"; readonly "filename": string } +} +export const FileAnnotation = Schema.Struct({ + "type": Schema.Literal("file").annotate({ + "description": "Type discriminator that is always `file` for this annotation." + }), + "source": Schema.Struct({ + "type": Schema.Literal("file").annotate({ "description": "Type discriminator that is always `file`." }), + "filename": Schema.String.annotate({ "description": "Filename referenced by the annotation." }) + }).annotate({ "title": "File annotation source", "description": "File attachment referenced by the annotation." }) +}).annotate({ "title": "File annotation", "description": "Annotation that references an uploaded file." }) +export type UrlAnnotation = { + readonly "type": "url" + readonly "source": { readonly "type": "url"; readonly "url": string } +} +export const UrlAnnotation = Schema.Struct({ + "type": Schema.Literal("url").annotate({ + "description": "Type discriminator that is always `url` for this annotation." + }), + "source": Schema.Struct({ + "type": Schema.Literal("url").annotate({ "description": "Type discriminator that is always `url`." }), + "url": Schema.String.annotate({ "description": "URL referenced by the annotation.", "format": "uri" }) + }).annotate({ "title": "URL annotation source", "description": "URL referenced by the annotation." }) +}).annotate({ "title": "URL annotation", "description": "Annotation that references a URL." }) +export type WidgetMessageItem = { + readonly "id": string + readonly "object": "chatkit.thread_item" + readonly "created_at": number + readonly "thread_id": string + readonly "type": "chatkit.widget" + readonly "widget": string +} +export const WidgetMessageItem = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the thread item." }), + "object": Schema.Literal("chatkit.thread_item").annotate({ + "description": "Type discriminator that is always `chatkit.thread_item`." + }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the item was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ "description": "Identifier of the parent thread." }), + "type": Schema.Literal("chatkit.widget").annotate({ + "description": "Type discriminator that is always `chatkit.widget`." + }), + "widget": Schema.String.annotate({ "description": "Serialized widget payload rendered in the UI." }) +}).annotate({ "title": "Widget message", "description": "Thread item that renders a widget payload." }) +export type ClientToolCallItem = { + readonly "id": string + readonly "object": "chatkit.thread_item" + readonly "created_at": number + readonly "thread_id": string + readonly "type": "chatkit.client_tool_call" + readonly "status": "in_progress" | "completed" + readonly "call_id": string + readonly "name": string + readonly "arguments": string + readonly "output": string | null +} +export const ClientToolCallItem = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the thread item." }), + "object": Schema.Literal("chatkit.thread_item").annotate({ + "description": "Type discriminator that is always `chatkit.thread_item`." + }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the item was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ "description": "Identifier of the parent thread." }), + "type": Schema.Literal("chatkit.client_tool_call").annotate({ + "description": "Type discriminator that is always `chatkit.client_tool_call`." + }), + "status": Schema.Literals(["in_progress", "completed"]).annotate({ + "description": "Execution status for the tool call." + }), + "call_id": Schema.String.annotate({ "description": "Identifier for the client tool call." }), + "name": Schema.String.annotate({ "description": "Tool name that was invoked." }), + "arguments": Schema.String.annotate({ "description": "JSON-encoded arguments that were sent to the tool." }), + "output": Schema.Union([ + Schema.String.annotate({ + "description": "JSON-encoded output captured from the tool. Defaults to null while execution is in progress." + }), + Schema.Null + ]) +}).annotate({ + "title": "Client tool call", + "description": "Record of a client side tool invocation initiated by the assistant." +}) +export type TaskItem = { + readonly "id": string + readonly "object": "chatkit.thread_item" + readonly "created_at": number + readonly "thread_id": string + readonly "type": "chatkit.task" + readonly "task_type": "custom" | "thought" + readonly "heading": string | null + readonly "summary": string | null +} +export const TaskItem = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the thread item." }), + "object": Schema.Literal("chatkit.thread_item").annotate({ + "description": "Type discriminator that is always `chatkit.thread_item`." + }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the item was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ "description": "Identifier of the parent thread." }), + "type": Schema.Literal("chatkit.task").annotate({ + "description": "Type discriminator that is always `chatkit.task`." + }), + "task_type": Schema.Literals(["custom", "thought"]).annotate({ "description": "Subtype for the task." }), + "heading": Schema.Union([ + Schema.String.annotate({ "description": "Optional heading for the task. Defaults to null when not provided." }), + Schema.Null + ]), + "summary": Schema.Union([ + Schema.String.annotate({ + "description": "Optional summary that describes the task. Defaults to null when omitted." + }), + Schema.Null + ]) +}).annotate({ + "title": "Task item", + "description": "Task emitted by the workflow to show progress and status updates." +}) +export type TaskGroupTask = { + readonly "type": "custom" | "thought" + readonly "heading": string | null + readonly "summary": string | null +} +export const TaskGroupTask = Schema.Struct({ + "type": Schema.Literals(["custom", "thought"]).annotate({ "description": "Subtype for the grouped task." }), + "heading": Schema.Union([ + Schema.String.annotate({ + "description": "Optional heading for the grouped task. Defaults to null when not provided." + }), + Schema.Null + ]), + "summary": Schema.Union([ + Schema.String.annotate({ + "description": "Optional summary that describes the grouped task. Defaults to null when omitted." + }), + Schema.Null + ]) +}).annotate({ "title": "Task group task", "description": "Task entry that appears within a TaskGroup." }) +export type ActiveStatus = { readonly "type": "active" } +export const ActiveStatus = Schema.Struct({ + "type": Schema.Literal("active").annotate({ "description": "Status discriminator that is always `active`." }) +}).annotate({ "title": "Active thread status", "description": "Indicates that a thread is active." }) +export type LockedStatus = { readonly "type": "locked"; readonly "reason": string | null } +export const LockedStatus = Schema.Struct({ + "type": Schema.Literal("locked").annotate({ "description": "Status discriminator that is always `locked`." }), + "reason": Schema.Union([ + Schema.String.annotate({ + "description": "Reason that the thread was locked. Defaults to null when no reason is recorded." + }), + Schema.Null + ]) +}).annotate({ + "title": "Locked thread status", + "description": "Indicates that a thread is locked and cannot accept new input." +}) +export type ClosedStatus = { readonly "type": "closed"; readonly "reason": string | null } +export const ClosedStatus = Schema.Struct({ + "type": Schema.Literal("closed").annotate({ "description": "Status discriminator that is always `closed`." }), + "reason": Schema.Union([ + Schema.String.annotate({ + "description": "Reason that the thread was closed. Defaults to null when no reason is recorded." + }), + Schema.Null + ]) +}).annotate({ "title": "Closed thread status", "description": "Indicates that a thread has been closed." }) +export type DeletedThreadResource = { + readonly "id": string + readonly "object": "chatkit.thread.deleted" + readonly "deleted": boolean +} +export const DeletedThreadResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the deleted thread." }), + "object": Schema.Literal("chatkit.thread.deleted").annotate({ + "description": "Type discriminator that is always `chatkit.thread.deleted`." + }), + "deleted": Schema.Boolean.annotate({ "description": "Indicates that the thread has been deleted." }) +}).annotate({ "title": "Deleted thread", "description": "Confirmation payload returned after deleting a thread." }) +export type ResponseKeepAliveEvent = { readonly "type": "keepalive"; readonly "sequence_number": number } +export const ResponseKeepAliveEvent = Schema.Struct({ + "type": Schema.Literal("keepalive").annotate({ + "description": "The type of the keepalive event. Always `keepalive`." + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this keepalive event." }).check( + Schema.isInt() + ) +}).annotate({ "title": "Keep alive", "description": "A keepalive event emitted during long-running response streams." }) +export type ResponseApplyPatchCallOperationDiffDeltaEvent = { + readonly "type": "response.apply_patch_call_operation_diff.delta" + readonly "sequence_number": number + readonly "output_index": number + readonly "item_id": string + readonly "delta": string +} +export const ResponseApplyPatchCallOperationDiffDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.apply_patch_call_operation_diff.delta").annotate({ + "description": "The event type identifier." + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "output_index": Schema.Number.annotate({ "description": "The index of the output this delta applies to." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ + "description": "Unique identifier for the API item associated with this event." + }), + "delta": Schema.String.annotate({ "description": "The incremental diff data for the apply_patch tool call." }) +}).annotate({ + "title": "ResponseApplyPatchCallOperationDiffDelta", + "description": "Event representing a delta for an apply_patch tool call operation diff." +}) +export type ResponseApplyPatchCallOperationDiffDoneEvent = { + readonly "type": "response.apply_patch_call_operation_diff.done" + readonly "sequence_number": number + readonly "output_index": number + readonly "item_id": string + readonly "delta"?: string +} +export const ResponseApplyPatchCallOperationDiffDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.apply_patch_call_operation_diff.done").annotate({ + "description": "The event type identifier." + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "output_index": Schema.Number.annotate({ "description": "The index of the output this event applies to." }).check( + Schema.isInt() + ), + "item_id": Schema.String.annotate({ + "description": "Unique identifier for the API item associated with this event." + }), + "delta": Schema.optionalKey( + Schema.String.annotate({ "description": "The final diff data for the apply_patch tool call." }) + ) +}).annotate({ + "title": "ResponseApplyPatchCallOperationDiffDone", + "description": "Event indicating that the operation diff for an apply_patch tool call is complete." +}) +export type ApiKeyList = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "first_id"?: string | null + readonly "last_id"?: string | null +} +export const ApiKeyList = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(AdminApiKey), + "has_more": Schema.Boolean, + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}) +export type RoleListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next": string | null +} +export const RoleListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "Always `list`." }), + "data": Schema.Array(AssignedRoleDetails).annotate({ + "description": "Role assignments returned in the current page." + }), + "has_more": Schema.Boolean.annotate({ + "description": "Whether additional assignments are available when paginating." + }), + "next": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Cursor to fetch the next page of results, or `null` when there are no more assignments." + }) +}).annotate({ "description": "Paginated list of roles assigned to a principal." }) +export type AuditLogActorApiKey = { + readonly "id"?: string + readonly "type"?: "user" | "service_account" + readonly "user"?: AuditLogActorUser + readonly "service_account"?: AuditLogActorServiceAccount +} +export const AuditLogActorApiKey = Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The tracking id of the API key." })), + "type": Schema.optionalKey( + Schema.Literals(["user", "service_account"]).annotate({ + "description": "The type of API key. Can be either `user` or `service_account`." + }) + ), + "user": Schema.optionalKey(AuditLogActorUser), + "service_account": Schema.optionalKey(AuditLogActorServiceAccount) +}).annotate({ "description": "The API Key used to perform the audit logged action." }) +export type AuditLogActorSession = { readonly "user"?: AuditLogActorUser; readonly "ip_address"?: string } +export const AuditLogActorSession = Schema.Struct({ + "user": Schema.optionalKey(AuditLogActorUser), + "ip_address": Schema.optionalKey( + Schema.String.annotate({ "description": "The IP address from which the action was performed." }) + ) +}).annotate({ "description": "The session in which the audit logged action was performed." }) +export type ChatCompletionAllowedToolsChoice = { + readonly "type": "allowed_tools" + readonly "allowed_tools": ChatCompletionAllowedTools +} +export const ChatCompletionAllowedToolsChoice = Schema.Struct({ + "type": Schema.Literal("allowed_tools").annotate({ + "description": "Allowed tool configuration type. Always `allowed_tools`." + }), + "allowed_tools": ChatCompletionAllowedTools +}).annotate({ + "title": "Allowed tools", + "description": "Constrains the tools available to the model to a pre-defined set.\n" +}) +export type ChatCompletionMessageToolCalls = ReadonlyArray< + ChatCompletionMessageToolCall | ChatCompletionMessageCustomToolCall +> +export const ChatCompletionMessageToolCalls = Schema.Array( + Schema.Union([ChatCompletionMessageToolCall, ChatCompletionMessageCustomToolCall], { mode: "oneOf" }) +).annotate({ "description": "The tool calls generated by the model, such as function calls." }) +export type ChatCompletionStreamResponseDelta = { + readonly "content"?: string | null + readonly "function_call"?: { readonly "arguments"?: string; readonly "name"?: string } + readonly "tool_calls"?: ReadonlyArray + readonly "role"?: "developer" | "system" | "user" | "assistant" | "tool" + readonly "refusal"?: string | null +} +export const ChatCompletionStreamResponseDelta = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The contents of the chunk message." }), Schema.Null]) + ), + "function_call": Schema.optionalKey( + Schema.Struct({ + "arguments": Schema.optionalKey(Schema.String.annotate({ + "description": + "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function." + })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the function to call." })) + }).annotate({ + "description": + "Deprecated and replaced by `tool_calls`. The name and arguments of a function that should be called, as generated by the model." + }) + ), + "tool_calls": Schema.optionalKey(Schema.Array(ChatCompletionMessageToolCallChunk)), + "role": Schema.optionalKey( + Schema.Literals(["developer", "system", "user", "assistant", "tool"]).annotate({ + "description": "The role of the author of this message." + }) + ), + "refusal": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The refusal message generated by the model." }), + Schema.Null + ]) + ) +}).annotate({ "description": "A chat completion delta generated by streamed model responses." }) +export type ChatCompletionRequestAssistantMessageContentPart = + | ChatCompletionRequestMessageContentPartText + | ChatCompletionRequestMessageContentPartRefusal +export const ChatCompletionRequestAssistantMessageContentPart = Schema.Union([ + ChatCompletionRequestMessageContentPartText, + ChatCompletionRequestMessageContentPartRefusal +], { mode: "oneOf" }) +export type ChatCompletionRequestDeveloperMessage = { + readonly "content": string | ReadonlyArray + readonly "role": "developer" + readonly "name"?: string +} +export const ChatCompletionRequestDeveloperMessage = Schema.Struct({ + "content": Schema.Union([ + Schema.String.annotate({ "title": "Text content", "description": "The contents of the developer message." }), + Schema.Array(ChatCompletionRequestMessageContentPartText).annotate({ + "title": "Array of content parts", + "description": + "An array of content parts with a defined type. For developer messages, only type `text` is supported." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }).annotate({ "description": "The contents of the developer message." }), + "role": Schema.Literal("developer").annotate({ + "description": "The role of the messages author, in this case `developer`." + }), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An optional name for the participant. Provides the model information to differentiate between participants of the same role." + }) + ) +}).annotate({ + "title": "Developer message", + "description": + "Developer-provided instructions that the model should follow, regardless of\nmessages sent by the user. With o1 models and newer, `developer` messages\nreplace the previous `system` messages.\n" +}) +export type ChatCompletionRequestSystemMessageContentPart = ChatCompletionRequestMessageContentPartText +export const ChatCompletionRequestSystemMessageContentPart = Schema.Union( + [ChatCompletionRequestMessageContentPartText], + { mode: "oneOf" } +) +export type ChatCompletionRequestToolMessageContentPart = ChatCompletionRequestMessageContentPartText +export const ChatCompletionRequestToolMessageContentPart = Schema.Union([ChatCompletionRequestMessageContentPartText], { + mode: "oneOf" +}) +export type ChatCompletionRequestUserMessageContentPart = + | ChatCompletionRequestMessageContentPartText + | ChatCompletionRequestMessageContentPartImage + | ChatCompletionRequestMessageContentPartAudio + | ChatCompletionRequestMessageContentPartFile +export const ChatCompletionRequestUserMessageContentPart = Schema.Union([ + ChatCompletionRequestMessageContentPartText, + ChatCompletionRequestMessageContentPartImage, + ChatCompletionRequestMessageContentPartAudio, + ChatCompletionRequestMessageContentPartFile +], { mode: "oneOf" }) +export type PredictionContent = { + readonly "type": "content" + readonly "content": string | ReadonlyArray +} +export const PredictionContent = Schema.Struct({ + "type": Schema.Literal("content").annotate({ + "description": "The type of the predicted content you want to provide. This type is\ncurrently always `content`.\n" + }), + "content": Schema.Union([ + Schema.String.annotate({ + "title": "Text content", + "description": + "The content used for a Predicted Output. This is often the\ntext of a file you are regenerating with minor changes.\n" + }), + Schema.Array(ChatCompletionRequestMessageContentPartText).annotate({ + "title": "Array of content parts", + "description": + "An array of content parts with a defined type. Supported options differ based on the [model](/docs/models) being used to generate the response. Can contain text inputs." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }).annotate({ + "description": + "The content that should be matched when generating a model response.\nIf generated tokens would match this content, the entire model response\ncan be returned much more quickly.\n" + }) +}).annotate({ + "title": "Static Content", + "description": "Static predicted output content, such as the content of a text file that is\nbeing regenerated.\n" +}) +export type CompoundFilter = { + readonly "type": "and" | "or" + readonly "filters": ReadonlyArray +} +export const CompoundFilter = Schema.Struct({ + "type": Schema.Literals(["and", "or"]).annotate({ "description": "Type of operation: `and` or `or`." }), + "filters": Schema.Array(Schema.Union([ComparisonFilter, Schema.Unknown], { mode: "oneOf" })).annotate({ + "description": "Array of filters to combine. Items can be `ComparisonFilter` or `CompoundFilter`." + }) +}).annotate({ "title": "Compound Filter", "description": "Combine multiple filters using `and` or `or`." }) +export type CreateCompletionResponse = { + readonly "id": string + readonly "choices": ReadonlyArray< + { + readonly "finish_reason": "stop" | "length" | "content_filter" + readonly "index": number + readonly "logprobs": { + readonly "text_offset"?: ReadonlyArray + readonly "token_logprobs"?: ReadonlyArray + readonly "tokens"?: ReadonlyArray + readonly "top_logprobs"?: ReadonlyArray<{}> + } | null + readonly "text": string + } + > + readonly "created": number + readonly "model": string + readonly "system_fingerprint"?: string + readonly "object": "text_completion" + readonly "usage"?: CompletionUsage +} +export const CreateCompletionResponse = Schema.Struct({ + "id": Schema.String.annotate({ "description": "A unique identifier for the completion." }), + "choices": Schema.Array(Schema.Struct({ + "finish_reason": Schema.Literals(["stop", "length", "content_filter"]).annotate({ + "description": + "The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence,\n`length` if the maximum number of tokens specified in the request was reached,\nor `content_filter` if content was omitted due to a flag from our content filters.\n" + }), + "index": Schema.Number.check(Schema.isInt()), + "logprobs": Schema.Union([ + Schema.Struct({ + "text_offset": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt()))), + "token_logprobs": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isFinite()))), + "tokens": Schema.optionalKey(Schema.Array(Schema.String)), + "top_logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({}))) + }), + Schema.Null + ]), + "text": Schema.String + })).annotate({ "description": "The list of completion choices the model generated for the input prompt." }), + "created": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the completion was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "model": Schema.String.annotate({ "description": "The model used for completion." }), + "system_fingerprint": Schema.optionalKey( + Schema.String.annotate({ + "description": + "This fingerprint represents the backend configuration that the model runs with.\n\nCan be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism.\n" + }) + ), + "object": Schema.Literal("text_completion").annotate({ + "description": "The object type, which is always \"text_completion\"" + }), + "usage": Schema.optionalKey(CompletionUsage) +}).annotate({ + "description": + "Represents a completion response from the API. Note: both the streamed and non-streamed response objects share the same shape (unlike the chat endpoint).\n" +}) +export type ContainerFileListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const ContainerFileListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be 'list'." }), + "data": Schema.Array(ContainerFileResource).annotate({ "description": "A list of container files." }), + "first_id": Schema.String.annotate({ "description": "The ID of the first file in the list." }), + "last_id": Schema.String.annotate({ "description": "The ID of the last file in the list." }), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more files available." }) +}) +export type ContainerListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const ContainerListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be 'list'." }), + "data": Schema.Array(ContainerResource).annotate({ "description": "A list of containers." }), + "first_id": Schema.String.annotate({ "description": "The ID of the first container in the list." }), + "last_id": Schema.String.annotate({ "description": "The ID of the last container in the list." }), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more containers available." }) +}) +export type CreateEmbeddingResponse = { + readonly "data": ReadonlyArray + readonly "model": string + readonly "object": "list" + readonly "usage": { readonly "prompt_tokens": number; readonly "total_tokens": number } +} +export const CreateEmbeddingResponse = Schema.Struct({ + "data": Schema.Array(Embedding).annotate({ "description": "The list of embeddings generated by the model." }), + "model": Schema.String.annotate({ "description": "The name of the model used to generate the embedding." }), + "object": Schema.Literal("list").annotate({ "description": "The object type, which is always \"list\"." }), + "usage": Schema.Struct({ + "prompt_tokens": Schema.Number.annotate({ "description": "The number of tokens used by the prompt." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ "description": "The total number of tokens used by the request." }).check( + Schema.isInt() + ) + }).annotate({ "description": "The usage information for the request." }) +}) +export type ErrorEvent = { readonly "event": "error"; readonly "data": Error } +export const ErrorEvent = Schema.Struct({ "event": Schema.Literal("error"), "data": Error }).annotate({ + "description": + "Occurs when an [error](/docs/guides/error-codes#api-errors) occurs. This can happen due to an internal server error or a timeout." +}) +export type ErrorResponse = { readonly "error": Error } +export const ErrorResponse = Schema.Struct({ "error": Error }) +export type EvalRunOutputItem = { + readonly "object": "eval.run.output_item" + readonly "id": string + readonly "run_id": string + readonly "eval_id": string + readonly "created_at": number + readonly "status": string + readonly "datasource_item_id": number + readonly "datasource_item": {} + readonly "results": ReadonlyArray + readonly "sample": { + readonly "input": ReadonlyArray<{ readonly "role": string; readonly "content": string }> + readonly "output": ReadonlyArray<{ readonly "role"?: string; readonly "content"?: string }> + readonly "finish_reason": string + readonly "model": string + readonly "usage": { + readonly "total_tokens": number + readonly "completion_tokens": number + readonly "prompt_tokens": number + readonly "cached_tokens": number + } + readonly "error": EvalApiError + readonly "temperature": number + readonly "max_completion_tokens": number + readonly "top_p": number + readonly "seed": number + } +} +export const EvalRunOutputItem = Schema.Struct({ + "object": Schema.Literal("eval.run.output_item").annotate({ + "description": "The type of the object. Always \"eval.run.output_item\"." + }), + "id": Schema.String.annotate({ "description": "Unique identifier for the evaluation run output item." }), + "run_id": Schema.String.annotate({ + "description": "The identifier of the evaluation run associated with this output item." + }), + "eval_id": Schema.String.annotate({ "description": "The identifier of the evaluation group." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the evaluation run was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "status": Schema.String.annotate({ "description": "The status of the evaluation run." }), + "datasource_item_id": Schema.Number.annotate({ "description": "The identifier for the data source item." }).check( + Schema.isInt() + ), + "datasource_item": Schema.Struct({}).annotate({ "description": "Details of the input data source item." }), + "results": Schema.Array(EvalRunOutputItemResult).annotate({ + "description": "A list of grader results for this output item." + }), + "sample": Schema.Struct({ + "input": Schema.Array( + Schema.Struct({ + "role": Schema.String.annotate({ + "description": "The role of the message sender (e.g., system, user, developer)." + }), + "content": Schema.String.annotate({ "description": "The content of the message." }) + }).annotate({ "description": "An input message." }) + ).annotate({ "description": "An array of input messages." }), + "output": Schema.Array( + Schema.Struct({ + "role": Schema.optionalKey( + Schema.String.annotate({ + "description": "The role of the message (e.g. \"system\", \"assistant\", \"user\")." + }) + ), + "content": Schema.optionalKey(Schema.String.annotate({ "description": "The content of the message." })) + }) + ).annotate({ "description": "An array of output messages." }), + "finish_reason": Schema.String.annotate({ "description": "The reason why the sample generation was finished." }), + "model": Schema.String.annotate({ "description": "The model used for generating the sample." }), + "usage": Schema.Struct({ + "total_tokens": Schema.Number.annotate({ "description": "The total number of tokens used." }).check( + Schema.isInt() + ), + "completion_tokens": Schema.Number.annotate({ "description": "The number of completion tokens generated." }) + .check(Schema.isInt()), + "prompt_tokens": Schema.Number.annotate({ "description": "The number of prompt tokens used." }).check( + Schema.isInt() + ), + "cached_tokens": Schema.Number.annotate({ "description": "The number of tokens retrieved from cache." }).check( + Schema.isInt() + ) + }).annotate({ "description": "Token usage details for the sample." }), + "error": EvalApiError, + "temperature": Schema.Number.annotate({ "description": "The sampling temperature used." }).check(Schema.isFinite()), + "max_completion_tokens": Schema.Number.annotate({ + "description": "The maximum number of tokens allowed for completion." + }).check(Schema.isInt()), + "top_p": Schema.Number.annotate({ "description": "The top_p value used for sampling." }).check(Schema.isFinite()), + "seed": Schema.Number.annotate({ "description": "The seed used for generating the sample." }).check(Schema.isInt()) + }).annotate({ "description": "A sample containing the input and output of the evaluation run." }) +}).annotate({ "title": "EvalRunOutputItem", "description": "A schema representing an evaluation run output item.\n" }) +export type CreateFileRequest = { + readonly "file": string + readonly "purpose": "assistants" | "batch" | "fine-tune" | "vision" | "user_data" | "evals" + readonly "expires_after"?: FileExpirationAfter +} +export const CreateFileRequest = Schema.Struct({ + "file": Schema.String.annotate({ + "description": "The File object (not file name) to be uploaded.\n", + "format": "binary" + }), + "purpose": Schema.Literals(["assistants", "batch", "fine-tune", "vision", "user_data", "evals"]).annotate({ + "description": + "The intended purpose of the uploaded file. One of:\n- `assistants`: Used in the Assistants API\n- `batch`: Used in the Batch API\n- `fine-tune`: Used for fine-tuning\n- `vision`: Images used for vision fine-tuning\n- `user_data`: Flexible file type for any purpose\n- `evals`: Used for eval data sets\n" + }), + "expires_after": Schema.optionalKey(FileExpirationAfter) +}) +export type CreateUploadRequest = { + readonly "filename": string + readonly "purpose": "assistants" | "batch" | "fine-tune" | "vision" + readonly "bytes": number + readonly "mime_type": string + readonly "expires_after"?: FileExpirationAfter +} +export const CreateUploadRequest = Schema.Struct({ + "filename": Schema.String.annotate({ "description": "The name of the file to upload.\n" }), + "purpose": Schema.Literals(["assistants", "batch", "fine-tune", "vision"]).annotate({ + "description": + "The intended purpose of the uploaded file.\n\nSee the [documentation on File\npurposes](/docs/api-reference/files/create#files-create-purpose).\n" + }), + "bytes": Schema.Number.annotate({ "description": "The number of bytes in the file you are uploading.\n" }).check( + Schema.isInt() + ), + "mime_type": Schema.String.annotate({ + "description": + "The MIME type of the file.\n\n\nThis must fall within the supported MIME types for your file purpose. See\nthe supported MIME types for assistants and vision.\n" + }), + "expires_after": Schema.optionalKey(FileExpirationAfter) +}) +export type FileSearchRankingOptions = { readonly "ranker"?: FileSearchRanker; readonly "score_threshold": number } +export const FileSearchRankingOptions = Schema.Struct({ + "ranker": Schema.optionalKey(FileSearchRanker), + "score_threshold": Schema.Number.annotate({ + "description": + "The score threshold for the file search. All values must be a floating point number between 0 and 1." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) +}).annotate({ + "title": "File search tool call ranking options", + "description": + "The ranking options for the file search. If not specified, the file search tool will use the `auto` ranker and a score_threshold of 0.\n\nSee the [file search tool documentation](/docs/assistants/tools/file-search#customizing-file-search-settings) for more information.\n" +}) +export type RunStepDetailsToolCallsFileSearchRankingOptionsObject = { + readonly "ranker": FileSearchRanker + readonly "score_threshold": number +} +export const RunStepDetailsToolCallsFileSearchRankingOptionsObject = Schema.Struct({ + "ranker": FileSearchRanker, + "score_threshold": Schema.Number.annotate({ + "description": + "The score threshold for the file search. All values must be a floating point number between 0 and 1." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) +}).annotate({ + "title": "File search tool call ranking options", + "description": "The ranking options for the file search." +}) +export type FineTuneDPOMethod = { readonly "hyperparameters"?: FineTuneDPOHyperparameters } +export const FineTuneDPOMethod = Schema.Struct({ "hyperparameters": Schema.optionalKey(FineTuneDPOHyperparameters) }) + .annotate({ "description": "Configuration for the DPO fine-tuning method." }) +export type FineTuneSupervisedMethod = { readonly "hyperparameters"?: FineTuneSupervisedHyperparameters } +export const FineTuneSupervisedMethod = Schema.Struct({ + "hyperparameters": Schema.optionalKey(FineTuneSupervisedHyperparameters) +}).annotate({ "description": "Configuration for the supervised fine-tuning method." }) +export type ListFineTuningCheckpointPermissionResponse = { + readonly "data": ReadonlyArray + readonly "object": "list" + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ListFineTuningCheckpointPermissionResponse = Schema.Struct({ + "data": Schema.Array(FineTuningCheckpointPermission), + "object": Schema.Literal("list"), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type ListFineTuningJobCheckpointsResponse = { + readonly "data": ReadonlyArray + readonly "object": "list" + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ListFineTuningJobCheckpointsResponse = Schema.Struct({ + "data": Schema.Array(FineTuningJobCheckpoint), + "object": Schema.Literal("list"), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type ListFineTuningJobEventsResponse = { + readonly "data": ReadonlyArray + readonly "object": "list" + readonly "has_more": boolean +} +export const ListFineTuningJobEventsResponse = Schema.Struct({ + "data": Schema.Array(FineTuningJobEvent), + "object": Schema.Literal("list"), + "has_more": Schema.Boolean +}) +export type ChatCompletionFunctions = { + readonly "description"?: string + readonly "name": string + readonly "parameters"?: FunctionParameters +} +export const ChatCompletionFunctions = Schema.Struct({ + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A description of what the function does, used by the model to choose when and how to call the function." + }) + ), + "name": Schema.String.annotate({ + "description": + "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64." + }), + "parameters": Schema.optionalKey(FunctionParameters) +}) +export type FunctionObject = { + readonly "description"?: string + readonly "name": string + readonly "parameters"?: FunctionParameters + readonly "strict"?: boolean | null +} +export const FunctionObject = Schema.Struct({ + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A description of what the function does, used by the model to choose when and how to call the function." + }) + ), + "name": Schema.String.annotate({ + "description": + "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64." + }), + "parameters": Schema.optionalKey(FunctionParameters), + "strict": Schema.optionalKey(Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to enable strict schema adherence when generating the function call. If set to true, the model will follow the exact schema defined in the `parameters` field. Only a subset of JSON Schema is supported when `strict` is `true`. Learn more about Structured Outputs in the [function calling guide](/docs/guides/function-calling)." + }), + Schema.Null + ])) +}) +export type GroupListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next": string | null +} +export const GroupListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "Always `list`." }), + "data": Schema.Array(GroupResponse).annotate({ "description": "Groups returned in the current page." }), + "has_more": Schema.Boolean.annotate({ "description": "Whether additional groups are available when paginating." }), + "next": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Cursor to fetch the next page of results, or `null` if there are no more results." + }) +}).annotate({ "description": "Paginated list of organization groups." }) +export type UserListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next": string | null +} +export const UserListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "Always `list`." }), + "data": Schema.Array(GroupUser).annotate({ "description": "Users in the current page." }), + "has_more": Schema.Boolean.annotate({ "description": "Whether more users are available when paginating." }), + "next": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Cursor to fetch the next page of results, or `null` when no further users are available." + }) +}).annotate({ "description": "Paginated list of user objects returned when inspecting group membership." }) +export type ProjectHostedToolPermissions = { + readonly "file_search": HostedToolPermission + readonly "web_search": HostedToolPermission + readonly "image_generation": HostedToolPermission + readonly "mcp": HostedToolPermission + readonly "code_interpreter": HostedToolPermission +} +export const ProjectHostedToolPermissions = Schema.Struct({ + "file_search": HostedToolPermission, + "web_search": HostedToolPermission, + "image_generation": HostedToolPermission, + "mcp": HostedToolPermission, + "code_interpreter": HostedToolPermission +}).annotate({ "description": "Represents hosted tool permissions for a project." }) +export type ProjectHostedToolPermissionsUpdateRequest = { + readonly "file_search"?: HostedToolPermissionUpdate | null + readonly "web_search"?: HostedToolPermissionUpdate | null + readonly "image_generation"?: HostedToolPermissionUpdate | null + readonly "mcp"?: HostedToolPermissionUpdate | null + readonly "code_interpreter"?: HostedToolPermissionUpdate | null +} +export const ProjectHostedToolPermissionsUpdateRequest = Schema.Struct({ + "file_search": Schema.optionalKey( + Schema.Union([HostedToolPermissionUpdate, Schema.Null]).annotate({ + "description": "The file search permission update." + }) + ), + "web_search": Schema.optionalKey( + Schema.Union([HostedToolPermissionUpdate, Schema.Null]).annotate({ + "description": "The web search permission update." + }) + ), + "image_generation": Schema.optionalKey( + Schema.Union([HostedToolPermissionUpdate, Schema.Null]).annotate({ + "description": "The image generation permission update." + }) + ), + "mcp": Schema.optionalKey( + Schema.Union([HostedToolPermissionUpdate, Schema.Null]).annotate({ "description": "The MCP permission update." }) + ), + "code_interpreter": Schema.optionalKey( + Schema.Union([HostedToolPermissionUpdate, Schema.Null]).annotate({ + "description": "The code interpreter permission update." + }) + ) +}) +export type ImageEditCompletedEvent = { + readonly "type": "image_edit.completed" + readonly "b64_json": string + readonly "created_at": number + readonly "size": "1024x1024" | "1024x1536" | "1536x1024" | "auto" + readonly "quality": "low" | "medium" | "high" | "auto" + readonly "background": "transparent" | "opaque" | "auto" + readonly "output_format": "png" | "webp" | "jpeg" + readonly "usage": ImagesUsage +} +export const ImageEditCompletedEvent = Schema.Struct({ + "type": Schema.Literal("image_edit.completed").annotate({ + "description": "The type of the event. Always `image_edit.completed`.\n" + }), + "b64_json": Schema.String.annotate({ + "description": "Base64-encoded final edited image data, suitable for rendering as an image.\n" + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp when the event was created.\n", + "format": "unixtime" + }).check(Schema.isInt()), + "size": Schema.Literals(["1024x1024", "1024x1536", "1536x1024", "auto"]).annotate({ + "description": "The size of the edited image.\n" + }), + "quality": Schema.Literals(["low", "medium", "high", "auto"]).annotate({ + "description": "The quality setting for the edited image.\n" + }), + "background": Schema.Literals(["transparent", "opaque", "auto"]).annotate({ + "description": "The background setting for the edited image.\n" + }), + "output_format": Schema.Literals(["png", "webp", "jpeg"]).annotate({ + "description": "The output format for the edited image.\n" + }), + "usage": ImagesUsage +}).annotate({ "description": "Emitted when image editing has completed and the final image is available.\n" }) +export type ImageGenCompletedEvent = { + readonly "type": "image_generation.completed" + readonly "b64_json": string + readonly "created_at": number + readonly "size": "1024x1024" | "1024x1536" | "1536x1024" | "auto" + readonly "quality": "low" | "medium" | "high" | "auto" + readonly "background": "transparent" | "opaque" | "auto" + readonly "output_format": "png" | "webp" | "jpeg" + readonly "usage": ImagesUsage +} +export const ImageGenCompletedEvent = Schema.Struct({ + "type": Schema.Literal("image_generation.completed").annotate({ + "description": "The type of the event. Always `image_generation.completed`.\n" + }), + "b64_json": Schema.String.annotate({ + "description": "Base64-encoded image data, suitable for rendering as an image.\n" + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp when the event was created.\n", + "format": "unixtime" + }).check(Schema.isInt()), + "size": Schema.Literals(["1024x1024", "1024x1536", "1536x1024", "auto"]).annotate({ + "description": "The size of the generated image.\n" + }), + "quality": Schema.Literals(["low", "medium", "high", "auto"]).annotate({ + "description": "The quality setting for the generated image.\n" + }), + "background": Schema.Literals(["transparent", "opaque", "auto"]).annotate({ + "description": "The background setting for the generated image.\n" + }), + "output_format": Schema.Literals(["png", "webp", "jpeg"]).annotate({ + "description": "The output format for the generated image.\n" + }), + "usage": ImagesUsage +}).annotate({ "description": "Emitted when image generation has completed and the final image is available.\n" }) +export type InviteListResponse = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const InviteListResponse = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The object type, which is always `list`" }), + "data": Schema.Array(Invite), + "first_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The first `invite_id` in the retrieved `list`" + }) + ), + "last_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The last `invite_id` in the retrieved `list`" + }) + ), + "has_more": Schema.Boolean.annotate({ + "description": "The `has_more` property is used for pagination to indicate there are additional results." + }) +}) +export type RealtimeServerEventConversationItemInputAudioTranscriptionCompleted = { + readonly "event_id": string + readonly "type": "conversation.item.input_audio_transcription.completed" + readonly "item_id": string + readonly "content_index": number + readonly "transcript": string + readonly "logprobs"?: ReadonlyArray | null + readonly "usage": { + readonly "type": "tokens" + readonly "input_tokens": number + readonly "input_token_details"?: { readonly "text_tokens"?: number; readonly "audio_tokens"?: number } + readonly "output_tokens": number + readonly "total_tokens": number + } | { readonly "type": "duration"; readonly "seconds": number } +} +export const RealtimeServerEventConversationItemInputAudioTranscriptionCompleted = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.input_audio_transcription.completed").annotate({ + "description": "The event type, must be\n`conversation.item.input_audio_transcription.completed`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the item containing the audio that is being transcribed." + }), + "content_index": Schema.Number.annotate({ "description": "The index of the content part containing the audio." }) + .check(Schema.isInt()), + "transcript": Schema.String.annotate({ "description": "The transcribed text." }), + "logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Array(LogProbProperties).annotate({ "description": "The log probabilities of the transcription." }), + Schema.Null + ]) + ), + "usage": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("tokens").annotate({ + "description": "The type of the usage object. Always `tokens` for this variant." + }), + "input_tokens": Schema.Number.annotate({ "description": "Number of input tokens billed for this request." }) + .check(Schema.isInt()), + "input_token_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of text tokens billed for this request." }).check( + Schema.isInt() + ) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of audio tokens billed for this request." }).check( + Schema.isInt() + ) + ) + }).annotate({ "description": "Details about the input tokens billed for this request." }) + ), + "output_tokens": Schema.Number.annotate({ "description": "Number of output tokens generated." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used (input + output)." }).check( + Schema.isInt() + ) + }).annotate({ + "title": "Token Usage", + "description": + "Usage statistics for the transcription, this is billed according to the ASR model's pricing rather than the realtime model's pricing." + }), + Schema.Struct({ + "type": Schema.Literal("duration").annotate({ + "description": "The type of the usage object. Always `duration` for this variant." + }), + "seconds": Schema.Number.annotate({ + "description": "Duration of the input audio in seconds.", + "format": "double" + }).check(Schema.isFinite()) + }).annotate({ + "title": "Duration Usage", + "description": + "Usage statistics for the transcription, this is billed according to the ASR model's pricing rather than the realtime model's pricing." + }) + ], { mode: "oneOf" }) +}).annotate({ + "description": + "This event is the output of audio transcription for user audio written to the\nuser audio buffer. Transcription begins when the input audio buffer is\ncommitted by the client or server (when VAD is enabled). Transcription runs\nasynchronously with Response creation, so this event may come before or after\nthe Response events.\n\nRealtime API models accept audio natively, and thus input transcription is a\nseparate process run on a separate ASR (Automatic Speech Recognition) model.\nThe transcript may diverge somewhat from the model's interpretation, and\nshould be treated as a rough guide.\n" +}) +export type RealtimeServerEventConversationItemInputAudioTranscriptionDelta = { + readonly "event_id": string + readonly "type": "conversation.item.input_audio_transcription.delta" + readonly "item_id": string + readonly "content_index"?: number + readonly "delta"?: string + readonly "logprobs"?: ReadonlyArray | null +} +export const RealtimeServerEventConversationItemInputAudioTranscriptionDelta = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.input_audio_transcription.delta").annotate({ + "description": "The event type, must be `conversation.item.input_audio_transcription.delta`." + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the item containing the audio that is being transcribed." + }), + "content_index": Schema.optionalKey( + Schema.Number.annotate({ "description": "The index of the content part in the item's content array." }).check( + Schema.isInt() + ) + ), + "delta": Schema.optionalKey(Schema.String.annotate({ "description": "The text delta." })), + "logprobs": Schema.optionalKey(Schema.Union([ + Schema.Array(LogProbProperties).annotate({ + "description": + "The log probabilities of the transcription. These can be enabled by configurating the session with `\"include\": [\"item.input_audio_transcription.logprobs\"]`. Each entry in the array corresponds a log probability of which token would be selected for this chunk of transcription. This can help to identify if it was possible there were multiple valid options for a given chunk of transcription." + }), + Schema.Null + ])) +}).annotate({ + "description": + "Returned when the text value of an input audio transcription content part is updated with incremental transcription results.\n" +}) +export type MCPListTools = { + readonly "type": "mcp_list_tools" + readonly "id": string + readonly "server_label": string + readonly "tools": ReadonlyArray + readonly "error"?: string | null +} +export const MCPListTools = Schema.Struct({ + "type": Schema.Literal("mcp_list_tools").annotate({ + "description": "The type of the item. Always `mcp_list_tools`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the list.\n" }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server.\n" }), + "tools": Schema.Array(MCPListToolsTool).annotate({ "description": "The tools available on the server.\n" }), + "error": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Error message if the server could not list tools.\n" }), + Schema.Null + ]) + ) +}).annotate({ "title": "MCP list tools", "description": "A list of tools available on an MCP server.\n" }) +export type RealtimeMCPListTools = { + readonly "type": "mcp_list_tools" + readonly "id"?: string + readonly "server_label": string + readonly "tools": ReadonlyArray +} +export const RealtimeMCPListTools = Schema.Struct({ + "type": Schema.Literal("mcp_list_tools").annotate({ + "description": "The type of the item. Always `mcp_list_tools`." + }), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The unique ID of the list." })), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server." }), + "tools": Schema.Array(MCPListToolsTool).annotate({ "description": "The tools available on the server." }) +}).annotate({ + "title": "Realtime MCP list tools", + "description": "A Realtime item listing tools available on an MCP server.\n" +}) +export type MCPTool = { + readonly "type": "mcp" + readonly "server_label": string + readonly "server_url"?: string + readonly "connector_id"?: + | "connector_dropbox" + | "connector_gmail" + | "connector_googlecalendar" + | "connector_googledrive" + | "connector_microsoftteams" + | "connector_outlookcalendar" + | "connector_outlookemail" + | "connector_sharepoint" + readonly "authorization"?: string + readonly "server_description"?: string + readonly "headers"?: {} | null + readonly "allowed_tools"?: ReadonlyArray | MCPToolFilter | null + readonly "require_approval"?: + | { readonly "always"?: MCPToolFilter; readonly "never"?: MCPToolFilter } + | "always" + | "never" + | null + readonly "defer_loading"?: boolean +} +export const MCPTool = Schema.Struct({ + "type": Schema.Literal("mcp").annotate({ "description": "The type of the MCP tool. Always `mcp`." }), + "server_label": Schema.String.annotate({ + "description": "A label for this MCP server, used to identify it in tool calls.\n" + }), + "server_url": Schema.optionalKey( + Schema.String.annotate({ + "description": "The URL for the MCP server. One of `server_url` or `connector_id` must be\nprovided.\n", + "format": "uri" + }) + ), + "connector_id": Schema.optionalKey( + Schema.Literals([ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint" + ]).annotate({ + "description": + "Identifier for service connectors, like those available in ChatGPT. One of\n`server_url` or `connector_id` must be provided. Learn more about service\nconnectors [here](/docs/guides/tools-remote-mcp#connectors).\n\nCurrently supported `connector_id` values are:\n\n- Dropbox: `connector_dropbox`\n- Gmail: `connector_gmail`\n- Google Calendar: `connector_googlecalendar`\n- Google Drive: `connector_googledrive`\n- Microsoft Teams: `connector_microsoftteams`\n- Outlook Calendar: `connector_outlookcalendar`\n- Outlook Email: `connector_outlookemail`\n- SharePoint: `connector_sharepoint`\n" + }) + ), + "authorization": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An OAuth access token that can be used with a remote MCP server, either\nwith a custom MCP server URL or a service connector. Your application\nmust handle the OAuth authorization flow and provide the token here.\n" + }) + ), + "server_description": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional description of the MCP server, used to provide more context.\n" }) + ), + "headers": Schema.optionalKey( + Schema.Union([ + Schema.Struct({}).annotate({ + "description": "Optional HTTP headers to send to the MCP server. Use for authentication\nor other purposes.\n" + }), + Schema.Null + ]) + ), + "allowed_tools": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Array(Schema.String).annotate({ + "title": "MCP allowed tools", + "description": "A string array of allowed tool names" + }), + MCPToolFilter + ], { mode: "oneOf" }).annotate({ "description": "List of allowed tool names or a filter object.\n" }), + Schema.Null + ]) + ), + "require_approval": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Struct({ "always": Schema.optionalKey(MCPToolFilter), "never": Schema.optionalKey(MCPToolFilter) }) + .annotate({ + "title": "MCP tool approval filter", + "description": + "Specify which of the MCP server's tools require approval. Can be\n`always`, `never`, or a filter object associated with tools\nthat require approval.\n" + }), + Schema.Literals(["always", "never"]).annotate({ + "title": "MCP tool approval setting", + "description": + "Specify a single approval policy for all tools. One of `always` or\n`never`. When set to `always`, all tools will require approval. When\nset to `never`, all tools will not require approval.\n" + }) + ], { mode: "oneOf" }).annotate({ "description": "Specify which of the MCP server's tools require approval." }), + Schema.Null + ]) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this MCP tool is deferred and discovered via tool search.\n" }) + ) +}).annotate({ + "title": "MCP tool", + "description": + "Give the model access to additional tools via remote Model Context Protocol\n(MCP) servers. [Learn more about MCP](/docs/guides/tools-remote-mcp).\n" +}) +export type MessageContentTextObject = { + readonly "type": "text" + readonly "text": { + readonly "value": string + readonly "annotations": ReadonlyArray< + MessageContentTextAnnotationsFileCitationObject | MessageContentTextAnnotationsFilePathObject + > + } +} +export const MessageContentTextObject = Schema.Struct({ + "type": Schema.Literal("text").annotate({ "description": "Always `text`." }), + "text": Schema.Struct({ + "value": Schema.String.annotate({ "description": "The data that makes up the text." }), + "annotations": Schema.Array( + Schema.Union([MessageContentTextAnnotationsFileCitationObject, MessageContentTextAnnotationsFilePathObject], { + mode: "oneOf" + }) + ) + }) +}).annotate({ "title": "Text", "description": "The text content that is part of a message." }) +export type MessageDeltaContentTextObject = { + readonly "index": number + readonly "type": "text" + readonly "text"?: { + readonly "value"?: string + readonly "annotations"?: ReadonlyArray< + MessageDeltaContentTextAnnotationsFileCitationObject | MessageDeltaContentTextAnnotationsFilePathObject + > + } +} +export const MessageDeltaContentTextObject = Schema.Struct({ + "index": Schema.Number.annotate({ "description": "The index of the content part in the message." }).check( + Schema.isInt() + ), + "type": Schema.Literal("text").annotate({ "description": "Always `text`." }), + "text": Schema.optionalKey(Schema.Struct({ + "value": Schema.optionalKey(Schema.String.annotate({ "description": "The data that makes up the text." })), + "annotations": Schema.optionalKey( + Schema.Array( + Schema.Union([ + MessageDeltaContentTextAnnotationsFileCitationObject, + MessageDeltaContentTextAnnotationsFilePathObject + ], { mode: "oneOf" }) + ) + ) + })) +}).annotate({ "title": "Text", "description": "The text content that is part of a message." }) +export type Batch = { + readonly "id": string + readonly "object": "batch" + readonly "endpoint": string + readonly "model"?: string + readonly "errors"?: { + readonly "object"?: string + readonly "data"?: ReadonlyArray< + { + readonly "code"?: string + readonly "message"?: string + readonly "param"?: string | null + readonly "line"?: number | null + } + > + } + readonly "input_file_id": string + readonly "completion_window": string + readonly "status": + | "validating" + | "failed" + | "in_progress" + | "finalizing" + | "completed" + | "expired" + | "cancelling" + | "cancelled" + readonly "output_file_id"?: string + readonly "error_file_id"?: string + readonly "created_at": number + readonly "in_progress_at"?: number + readonly "expires_at"?: number + readonly "finalizing_at"?: number + readonly "completed_at"?: number + readonly "failed_at"?: number + readonly "expired_at"?: number + readonly "cancelling_at"?: number + readonly "cancelled_at"?: number + readonly "request_counts"?: { readonly "total": number; readonly "completed": number; readonly "failed": number } + 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 "metadata"?: Metadata +} +export const Batch = Schema.Struct({ + "id": Schema.String, + "object": Schema.Literal("batch").annotate({ "description": "The object type, which is always `batch`." }), + "endpoint": Schema.String.annotate({ "description": "The OpenAI API endpoint used by the batch." }), + "model": Schema.optionalKey(Schema.String.annotate({ + "description": + "Model ID used to process the batch, like `gpt-5-2025-08-07`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model\nguide](/docs/models) to browse and compare available models.\n" + })), + "errors": Schema.optionalKey(Schema.Struct({ + "object": Schema.optionalKey(Schema.String.annotate({ "description": "The object type, which is always `list`." })), + "data": Schema.optionalKey(Schema.Array(Schema.Struct({ + "code": Schema.optionalKey( + Schema.String.annotate({ "description": "An error code identifying the error type." }) + ), + "message": Schema.optionalKey( + Schema.String.annotate({ "description": "A human-readable message providing more details about the error." }) + ), + "param": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The name of the parameter that caused the error, if applicable." }), + Schema.Null + ]) + ), + "line": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The line number of the input file where the error occurred, if applicable." + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }))) + })), + "input_file_id": Schema.String.annotate({ "description": "The ID of the input file for the batch." }), + "completion_window": Schema.String.annotate({ + "description": "The time frame within which the batch should be processed." + }), + "status": Schema.Literals([ + "validating", + "failed", + "in_progress", + "finalizing", + "completed", + "expired", + "cancelling", + "cancelled" + ]).annotate({ "description": "The current status of the batch." }), + "output_file_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The ID of the file containing the outputs of successfully executed requests." + }) + ), + "error_file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the file containing the outputs of requests with errors." }) + ), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "in_progress_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch started processing.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch will expire.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "finalizing_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch started finalizing.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "completed_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch was completed.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "failed_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch failed.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "expired_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch expired.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "cancelling_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch started cancelling.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "cancelled_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the batch was cancelled.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "request_counts": Schema.optionalKey( + Schema.Struct({ + "total": Schema.Number.annotate({ "description": "Total number of requests in the batch." }).check( + Schema.isInt() + ), + "completed": Schema.Number.annotate({ + "description": "Number of requests that have been completed successfully." + }).check(Schema.isInt()), + "failed": Schema.Number.annotate({ "description": "Number of requests that have failed." }).check(Schema.isInt()) + }).annotate({ "description": "The request counts for different statuses within the batch." }) + ), + "usage": Schema.optionalKey( + Schema.Struct({ + "input_tokens": Schema.Number.annotate({ "description": "The number of input tokens." }).check(Schema.isInt()), + "input_tokens_details": Schema.Struct({ + "cached_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that were retrieved from the cache. [More on\nprompt caching](/docs/guides/prompt-caching).\n" + }).check(Schema.isInt()) + }).annotate({ "description": "A detailed breakdown of the input tokens." }), + "output_tokens": Schema.Number.annotate({ "description": "The number of output tokens." }).check(Schema.isInt()), + "output_tokens_details": Schema.Struct({ + "reasoning_tokens": Schema.Number.annotate({ "description": "The number of reasoning tokens." }).check( + Schema.isInt() + ) + }).annotate({ "description": "A detailed breakdown of the output tokens." }), + "total_tokens": Schema.Number.annotate({ "description": "The total number of tokens used." }).check( + Schema.isInt() + ) + }).annotate({ + "description": + "Represents token usage details including input tokens, output tokens, a\nbreakdown of output tokens, and the total tokens used. Only populated on\nbatches created after September 7, 2025.\n" + }) + ), + "metadata": Schema.optionalKey(Metadata) +}) +export type CreateMessageRequest = { + readonly "role": "user" | "assistant" + readonly "content": + | string + | ReadonlyArray + readonly "attachments"?: + | ReadonlyArray< + { + readonly "file_id"?: string + readonly "tools"?: ReadonlyArray + } + > + | null + readonly "metadata"?: Metadata +} +export const CreateMessageRequest = Schema.Struct({ + "role": Schema.Literals(["user", "assistant"]).annotate({ + "description": + "The role of the entity that is creating the message. Allowed values include:\n- `user`: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.\n- `assistant`: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.\n" + }), + "content": Schema.Union([ + Schema.String.annotate({ "title": "Text content", "description": "The text contents of the message." }), + Schema.Array( + Schema.Union([MessageContentImageFileObject, MessageContentImageUrlObject, MessageRequestContentTextObject], { + mode: "oneOf" + }) + ).annotate({ + "title": "Array of content parts", + "description": + "An array of content parts with a defined type, each can be of type `text` or images can be passed with `image_url` or `image_file`. Image types are only supported on [Vision-compatible models](/docs/models)." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }), + "attachments": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Struct({ + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the file to attach to the message." }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([AssistantToolsCode, AssistantToolsFileSearchTypeOnly], { mode: "oneOf" })).annotate({ + "description": "The tools to add this file to." + }) + ) + })).annotate({ "description": "A list of files attached to the message, and the tools they should be added to." }), + Schema.Null + ])), + "metadata": Schema.optionalKey(Metadata) +}) +export type EvalStoredCompletionsSource = { + readonly "type": "stored_completions" + readonly "metadata"?: Metadata + readonly "model"?: string | null + readonly "created_after"?: number | null + readonly "created_before"?: number | null + readonly "limit"?: number | null +} +export const EvalStoredCompletionsSource = Schema.Struct({ + "type": Schema.Literal("stored_completions").annotate({ + "description": "The type of source. Always `stored_completions`." + }), + "metadata": Schema.optionalKey(Metadata), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "An optional model to filter by (e.g., 'gpt-4o')." }), + Schema.Null + ]) + ), + "created_after": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "An optional Unix timestamp to filter items created after this time." }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "created_before": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "An optional Unix timestamp to filter items created before this time." }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "limit": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "An optional maximum number of items to return." }).check(Schema.isInt()), + Schema.Null + ]) + ) +}).annotate({ + "title": "StoredCompletionsRunDataSource", + "description": "A StoredCompletionsRunDataSource configuration describing a set of filters\n" +}) +export type ModifyMessageRequest = { readonly "metadata"?: Metadata } +export const ModifyMessageRequest = Schema.Struct({ "metadata": Schema.optionalKey(Metadata) }) +export type ModifyRunRequest = { readonly "metadata"?: Metadata } +export const ModifyRunRequest = Schema.Struct({ "metadata": Schema.optionalKey(Metadata) }) +export type ModifyThreadRequest = { + readonly "tool_resources"?: { + readonly "code_interpreter"?: { readonly "file_ids"?: ReadonlyArray } + readonly "file_search"?: { readonly "vector_store_ids"?: ReadonlyArray } + } | null + readonly "metadata"?: Metadata +} +export const ModifyThreadRequest = Schema.Struct({ + "tool_resources": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "code_interpreter": Schema.optionalKey(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files associated with the tool.\n" + }).check(Schema.isMaxLength(20)) + ) + })), + "file_search": Schema.optionalKey(Schema.Struct({ + "vector_store_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "The [vector store](/docs/api-reference/vector-stores/object) attached to this thread. There can be a maximum of 1 vector store attached to the thread.\n" + }).check(Schema.isMaxLength(1)) + ) + })) + }).annotate({ + "description": + "A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n" + }), + Schema.Null + ])), + "metadata": Schema.optionalKey(Metadata) +}) +export type ThreadObject = { + readonly "id": string + readonly "object": "thread" + readonly "created_at": number + readonly "tool_resources": { + readonly "code_interpreter"?: { readonly "file_ids"?: ReadonlyArray } + readonly "file_search"?: { readonly "vector_store_ids"?: ReadonlyArray } + } | null + readonly "metadata": Metadata +} +export const ThreadObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "object": Schema.Literal("thread").annotate({ "description": "The object type, which is always `thread`." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the thread was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "tool_resources": Schema.Union([ + Schema.Struct({ + "code_interpreter": Schema.optionalKey(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files associated with the tool.\n" + }).check(Schema.isMaxLength(20)) + ) + })), + "file_search": Schema.optionalKey(Schema.Struct({ + "vector_store_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "The [vector store](/docs/api-reference/vector-stores/object) attached to this thread. There can be a maximum of 1 vector store attached to the thread.\n" + }).check(Schema.isMaxLength(1)) + ) + })) + }).annotate({ + "description": + "A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n" + }), + Schema.Null + ]), + "metadata": Metadata +}).annotate({ + "title": "Thread", + "description": "Represents a thread that contains [messages](/docs/api-reference/messages)." +}) +export type UpdateVectorStoreRequest = { + readonly "name"?: string | null + readonly "expires_after"?: { readonly "anchor": "last_active_at"; readonly "days": number } + readonly "metadata"?: Metadata +} +export const UpdateVectorStoreRequest = Schema.Struct({ + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The name of the vector store." }) + ), + "expires_after": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "anchor": Schema.Literal("last_active_at").annotate({ + "description": + "Anchor timestamp after which the expiration policy applies. Supported anchors: `last_active_at`." + }), + "days": Schema.Number.annotate({ + "description": "The number of days after the anchor time that the vector store will expire." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(365)) + }).annotate({ + "title": "Vector store expiration policy", + "description": "The expiration policy for a vector store." + }) + ])), + "metadata": Schema.optionalKey(Metadata) +}) +export type ListModelsResponse = { readonly "object": "list"; readonly "data": ReadonlyArray } +export const ListModelsResponse = Schema.Struct({ "object": Schema.Literal("list"), "data": Schema.Array(Model) }) +export type ModelIdsResponses = + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" +export const ModelIdsResponses = Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) +]) +export type RealtimeTranscriptionSessionCreateRequest = { + readonly "turn_detection"?: { + readonly "type"?: "server_vad" + readonly "threshold"?: number + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + } + readonly "input_audio_noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "input_audio_format"?: "pcm16" | "g711_ulaw" | "g711_alaw" + readonly "input_audio_transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + readonly "delay"?: "minimal" | "low" | "medium" | "high" | "xhigh" + } + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> +} +export const RealtimeTranscriptionSessionCreateRequest = Schema.Struct({ + "turn_detection": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("server_vad").annotate({ + "description": + "Type of turn detection. Only `server_vad` is currently supported for transcription sessions.\n" + }) + ), + "threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A\nhigher threshold will require louder audio to activate the model, and\nthus might perform better in noisy environments.\n" + }).check(Schema.isFinite()) + ), + "prefix_padding_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Amount of audio to include before the VAD detected speech (in\nmilliseconds). Defaults to 300ms.\n" + }).check(Schema.isInt()) + ), + "silence_duration_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Duration of silence to detect speech stop (in milliseconds). Defaults\nto 500ms. With shorter values the model will respond more quickly,\nbut may jump in on short pauses from the user.\n" + }).check(Schema.isInt()) + ) + }).annotate({ + "description": + "Configuration for turn detection. Can be set to `null` to turn off. Server VAD means that the model will detect the start and end of speech based on audio volume and respond at the end of user speech.\n" + }) + ), + "input_audio_noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "input_audio_format": Schema.optionalKey( + Schema.Literals(["pcm16", "g711_ulaw", "g711_alaw"]).annotate({ + "description": + "The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\nFor `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate,\nsingle channel (mono), and little-endian byte order.\n" + }) + ), + "input_audio_transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model to use for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use `gpt-4o-transcribe-diarize` when you need diarization with speaker labels.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The language of the input audio. Supplying the input language in\n[ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format\nwill improve accuracy and latency.\n" + }) + ), + "prompt": Schema.optionalKey(Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio\nsegment.\nFor `whisper-1`, the [prompt is a list of keywords](/docs/guides/speech-to-text#prompting).\nFor `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the prompt is a free text string, for example \"expect words related to technology\".\nPrompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + })), + "delay": Schema.optionalKey( + Schema.Literals(["minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Controls how long the model waits before emitting transcription text.\nHigher values can improve transcription accuracy at the cost of latency.\nOnly supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "The set of items to include in the transcription. Current available items are:\n`item.input_audio_transcription.logprobs`\n" + }) + ) +}).annotate({ + "title": "Realtime transcription session configuration", + "description": "Realtime transcription session object configuration." +}) +export type RealtimeTranslationClientEventSessionUpdate = { + readonly "event_id"?: string + readonly "type": "session.update" + readonly "session": { + readonly "audio"?: { + readonly "input"?: { + readonly "transcription"?: { readonly "model": string } | null + readonly "noise_reduction"?: { readonly "type": NoiseReductionType } | null + } + readonly "output"?: { readonly "language"?: string } + } + } +} +export const RealtimeTranslationClientEventSessionUpdate = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("session.update").annotate({ "description": "The event type, must be `session.update`." }), + "session": Schema.Struct({ + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "transcription": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "model": Schema.String.annotate({ + "description": "The transcription model to use for source transcript deltas." + }) + }).annotate({ + "description": + "Optional source-language transcription. When configured, the server emits\n`session.input_transcript.delta` events. Translation itself still runs from\nthe input audio stream.\n" + }), + Schema.Null + ]) + ), + "noise_reduction": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": NoiseReductionType }).annotate({ + "description": "Optional input noise reduction. Set to `null` to disable it.\n" + }), + Schema.Null + ]) + ) + })), + "output": Schema.optionalKey( + Schema.Struct({ + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Target language for translated output audio and transcript deltas.\n" + }) + ) + }) + ) + }).annotate({ "description": "Configuration for translation input and output audio.\n" }) + ) + }).annotate({ + "title": "Realtime translation session update", + "description": + "Translation session fields to update. The session `type` and `model` are set\nat creation and cannot be changed with `session.update`.\n" + }) +}).annotate({ + "description": + "Send this event to update the translation session configuration. Translation\nsessions support updates to `audio.output.language`, `audio.input.transcription`,\nand `audio.input.noise_reduction`.\n" +}) +export type RealtimeTranslationServerEventSessionCreated = { + readonly "event_id": string + readonly "type": "session.created" + readonly "session": { + readonly "id": string + readonly "type": "translation" + readonly "expires_at": number + readonly "model": string + readonly "audio": { + readonly "input"?: { + readonly "transcription"?: { readonly "model": string } | null + readonly "noise_reduction"?: { readonly "type": NoiseReductionType } | null + } + readonly "output"?: { readonly "language"?: string } + } + } +} +export const RealtimeTranslationServerEventSessionCreated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.created").annotate({ "description": "The event type, must be `session.created`." }), + "session": Schema.Struct({ + "id": Schema.String.annotate({ + "description": "Unique identifier for the session that looks like `sess_1234567890abcdef`.\n" + }), + "type": Schema.Literal("translation").annotate({ + "description": "The session type. Always `translation` for Realtime translation sessions.\n" + }), + "expires_at": Schema.Number.annotate({ + "description": "Expiration timestamp for the session, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()), + "model": Schema.String.annotate({ + "description": + "The Realtime translation model used for this session. This field is set at\nsession creation and cannot be changed with `session.update`.\n" + }), + "audio": Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "transcription": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "model": Schema.String.annotate({ + "description": "The transcription model used for source transcript deltas." + }) + }).annotate({ + "description": + "Optional source-language transcription. When configured, the server emits\n`session.input_transcript.delta` events. Translation itself still runs from\nthe input audio stream.\n" + }), + Schema.Null + ]) + ), + "noise_reduction": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": NoiseReductionType }).annotate({ + "description": "Optional input noise reduction.\n" + }), + Schema.Null + ]) + ) + })), + "output": Schema.optionalKey( + Schema.Struct({ + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Target language for translated output audio and transcript deltas.\n" + }) + ) + }) + ) + }).annotate({ "description": "Configuration for translation input and output audio.\n" }) + }).annotate({ "title": "Realtime translation session", "description": "The translation session configuration." }) +}).annotate({ + "description": + "Returned when a translation session is created. Emitted automatically when a\nnew connection is established as the first server event. This event contains\nthe default translation session configuration.\n" +}) +export type RealtimeTranslationServerEventSessionUpdated = { + readonly "event_id": string + readonly "type": "session.updated" + readonly "session": { + readonly "id": string + readonly "type": "translation" + readonly "expires_at": number + readonly "model": string + readonly "audio": { + readonly "input"?: { + readonly "transcription"?: { readonly "model": string } | null + readonly "noise_reduction"?: { readonly "type": NoiseReductionType } | null + } + readonly "output"?: { readonly "language"?: string } + } + } +} +export const RealtimeTranslationServerEventSessionUpdated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.updated").annotate({ "description": "The event type, must be `session.updated`." }), + "session": Schema.Struct({ + "id": Schema.String.annotate({ + "description": "Unique identifier for the session that looks like `sess_1234567890abcdef`.\n" + }), + "type": Schema.Literal("translation").annotate({ + "description": "The session type. Always `translation` for Realtime translation sessions.\n" + }), + "expires_at": Schema.Number.annotate({ + "description": "Expiration timestamp for the session, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()), + "model": Schema.String.annotate({ + "description": + "The Realtime translation model used for this session. This field is set at\nsession creation and cannot be changed with `session.update`.\n" + }), + "audio": Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "transcription": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "model": Schema.String.annotate({ + "description": "The transcription model used for source transcript deltas." + }) + }).annotate({ + "description": + "Optional source-language transcription. When configured, the server emits\n`session.input_transcript.delta` events. Translation itself still runs from\nthe input audio stream.\n" + }), + Schema.Null + ]) + ), + "noise_reduction": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": NoiseReductionType }).annotate({ + "description": "Optional input noise reduction.\n" + }), + Schema.Null + ]) + ) + })), + "output": Schema.optionalKey( + Schema.Struct({ + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Target language for translated output audio and transcript deltas.\n" + }) + ) + }) + ) + }).annotate({ "description": "Configuration for translation input and output audio.\n" }) + }).annotate({ "title": "Realtime translation session", "description": "The translation session configuration." }) +}).annotate({ + "description": + "Returned when a translation session is updated with a `session.update` event,\nunless there is an error.\n" +}) +export type RealtimeTranslationSession = { + readonly "id": string + readonly "type": "translation" + readonly "expires_at": number + readonly "model": string + readonly "audio": { + readonly "input"?: { + readonly "transcription"?: { readonly "model": string } | null + readonly "noise_reduction"?: { readonly "type": NoiseReductionType } | null + } + readonly "output"?: { readonly "language"?: string } + } +} +export const RealtimeTranslationSession = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "Unique identifier for the session that looks like `sess_1234567890abcdef`.\n" + }), + "type": Schema.Literal("translation").annotate({ + "description": "The session type. Always `translation` for Realtime translation sessions.\n" + }), + "expires_at": Schema.Number.annotate({ + "description": "Expiration timestamp for the session, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()), + "model": Schema.String.annotate({ + "description": + "The Realtime translation model used for this session. This field is set at\nsession creation and cannot be changed with `session.update`.\n" + }), + "audio": Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "transcription": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "model": Schema.String.annotate({ + "description": "The transcription model used for source transcript deltas." + }) + }).annotate({ + "description": + "Optional source-language transcription. When configured, the server emits\n`session.input_transcript.delta` events. Translation itself still runs from\nthe input audio stream.\n" + }), + Schema.Null + ]) + ), + "noise_reduction": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": NoiseReductionType }).annotate({ + "description": "Optional input noise reduction.\n" + }), + Schema.Null + ]) + ) + })), + "output": Schema.optionalKey( + Schema.Struct({ + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Target language for translated output audio and transcript deltas.\n" + }) + ) + }) + ) + }).annotate({ "description": "Configuration for translation input and output audio.\n" }) +}).annotate({ + "title": "Realtime translation session", + "description": + "A Realtime translation session. Translation sessions continuously translate input\naudio into the configured output language.\n" +}) +export type RealtimeTranslationSessionCreateRequest = { + readonly "model": string + readonly "audio"?: { + readonly "input"?: { + readonly "transcription"?: { readonly "model": string } | null + readonly "noise_reduction"?: { readonly "type": NoiseReductionType } | null + } + readonly "output"?: { readonly "language"?: string } + } +} +export const RealtimeTranslationSessionCreateRequest = Schema.Struct({ + "model": Schema.String.annotate({ "description": "The Realtime translation model used for this session.\n" }), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "transcription": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "model": Schema.String.annotate({ + "description": "The transcription model to use for source transcript deltas." + }) + }).annotate({ + "description": + "Optional source-language transcription. When configured, the server emits\n`session.input_transcript.delta` events. Translation itself still runs from\nthe input audio stream.\n" + }), + Schema.Null + ]) + ), + "noise_reduction": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": NoiseReductionType }).annotate({ + "description": "Optional input noise reduction. Set to `null` to disable it.\n" + }), + Schema.Null + ]) + ) + })), + "output": Schema.optionalKey( + Schema.Struct({ + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Target language for translated output audio and transcript deltas.\n" + }) + ) + }) + ) + }).annotate({ "description": "Configuration for translation input and output audio.\n" }) + ) +}).annotate({ + "title": "Realtime translation session configuration", + "description": + "Realtime translation session configuration. Translation sessions stream source\naudio in and translated audio plus transcript deltas out continuously.\n" +}) +export type ListFilesResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const ListFilesResponse = Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(OpenAIFile), + "first_id": Schema.String, + "last_id": Schema.String, + "has_more": Schema.Boolean +}) +export type ListCertificatesResponse = { + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "last_id": string | null + readonly "has_more": boolean + readonly "object": "list" +} +export const ListCertificatesResponse = Schema.Struct({ + "data": Schema.Array(OrganizationCertificate), + "first_id": Schema.Union([Schema.String, Schema.Null]), + "last_id": Schema.Union([Schema.String, Schema.Null]), + "has_more": Schema.Boolean, + "object": Schema.Literal("list") +}) +export type OrganizationCertificateActivationResponse = { + readonly "object": "organization.certificate.activation" + readonly "data": ReadonlyArray +} +export const OrganizationCertificateActivationResponse = Schema.Struct({ + "object": Schema.Literal("organization.certificate.activation").annotate({ + "description": "The organization certificate activation result type." + }), + "data": Schema.Array(OrganizationCertificate) +}) +export type OrganizationCertificateDeactivationResponse = { + readonly "object": "organization.certificate.deactivation" + readonly "data": ReadonlyArray +} +export const OrganizationCertificateDeactivationResponse = Schema.Struct({ + "object": Schema.Literal("organization.certificate.deactivation").annotate({ + "description": "The organization certificate deactivation result type." + }), + "data": Schema.Array(OrganizationCertificate) +}) +export type ListProjectCertificatesResponse = { + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "last_id": string | null + readonly "has_more": boolean + readonly "object": "list" +} +export const ListProjectCertificatesResponse = Schema.Struct({ + "data": Schema.Array(OrganizationProjectCertificate), + "first_id": Schema.Union([Schema.String, Schema.Null]), + "last_id": Schema.Union([Schema.String, Schema.Null]), + "has_more": Schema.Boolean, + "object": Schema.Literal("list") +}) +export type OrganizationProjectCertificateActivationResponse = { + readonly "object": "organization.project.certificate.activation" + readonly "data": ReadonlyArray +} +export const OrganizationProjectCertificateActivationResponse = Schema.Struct({ + "object": Schema.Literal("organization.project.certificate.activation").annotate({ + "description": "The project certificate activation result type." + }), + "data": Schema.Array(OrganizationProjectCertificate) +}) +export type OrganizationProjectCertificateDeactivationResponse = { + readonly "object": "organization.project.certificate.deactivation" + readonly "data": ReadonlyArray +} +export const OrganizationProjectCertificateDeactivationResponse = Schema.Struct({ + "object": Schema.Literal("organization.project.certificate.deactivation").annotate({ + "description": "The project certificate deactivation result type." + }), + "data": Schema.Array(OrganizationProjectCertificate) +}) +export type CreateImageRequest = { + readonly "prompt": string + readonly "model"?: string | "gpt-image-1.5" | "dall-e-2" | "dall-e-3" | "gpt-image-1" | "gpt-image-1-mini" | null + readonly "n"?: number + readonly "quality"?: "standard" | "hd" | "low" | "medium" | "high" | "auto" | null + readonly "response_format"?: "url" | "b64_json" | null + readonly "output_format"?: "png" | "jpeg" | "webp" | null + readonly "output_compression"?: number | null + readonly "stream"?: boolean | null + readonly "partial_images"?: PartialImages + readonly "size"?: + | string + | "auto" + | "1024x1024" + | "1536x1024" + | "1024x1536" + | "256x256" + | "512x512" + | "1792x1024" + | "1024x1792" + | null + readonly "moderation"?: "low" | "auto" | null + readonly "background"?: "transparent" | "opaque" | "auto" | null + readonly "style"?: "vivid" | "natural" | null + readonly "user"?: string +} +export const CreateImageRequest = Schema.Struct({ + "prompt": Schema.String.annotate({ + "description": + "A text description of the desired image(s). The maximum length is 32000 characters for the GPT image models, 1000 characters for `dall-e-2` and 4000 characters for `dall-e-3`." + }), + "model": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String, + Schema.Literals(["gpt-image-1.5", "dall-e-2", "dall-e-3", "gpt-image-1", "gpt-image-1-mini"]) + ]).annotate({ + "description": + "The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or a GPT image model (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`). Defaults to `dall-e-2` unless a parameter specific to the GPT image models is used." + }), + Schema.Null + ]) + ), + "n": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(10)], { + "description": + "The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only `n=1` is supported." + }) + ) + ]) + ), + "quality": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["standard", "hd", "low", "medium", "high", "auto"]).annotate({ + "description": + "The quality of the image that will be generated.\n\n- `auto` (default value) will automatically select the best quality for the given model.\n- `high`, `medium` and `low` are supported for the GPT image models.\n- `hd` and `standard` are supported for `dall-e-3`.\n- `standard` is the only option for `dall-e-2`.\n" + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The quality of the image that will be generated.\n\n- `auto` (default value) will automatically select the best quality for the given model.\n- `high`, `medium` and `low` are supported for the GPT image models.\n- `hd` and `standard` are supported for `dall-e-3`.\n- `standard` is the only option for `dall-e-2`.\n" + }) + ]) + ), + "response_format": Schema.optionalKey(Schema.Union([ + Schema.Literals(["url", "b64_json"]).annotate({ + "description": + "The format in which generated images with `dall-e-2` and `dall-e-3` are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated. This parameter isn't supported for the GPT image models, which always return base64-encoded images." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The format in which generated images with `dall-e-2` and `dall-e-3` are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated. This parameter isn't supported for the GPT image models, which always return base64-encoded images." + }) + ])), + "output_format": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["png", "jpeg", "webp"]).annotate({ + "description": + "The format in which the generated images are returned. This parameter is only supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The format in which the generated images are returned. This parameter is only supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`." + }) + ]) + ), + "output_compression": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "description": + "The compression level (0-100%) for the generated images. This parameter is only supported for the GPT image models with the `webp` or `jpeg` output formats, and defaults to 100." + }) + ), + "stream": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Generate the image in streaming mode. Defaults to `false`. See the\n[Image generation guide](/docs/guides/image-generation) for more information.\nThis parameter is only supported for the GPT image models.\n" + }) + ), + "partial_images": Schema.optionalKey(PartialImages), + "size": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String, + Schema.Literals(["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792"]) + ]).annotate({ + "description": + "The size of the generated images. For `gpt-image-2` and `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` strings, for example `1536x864`. Width and height must both be divisible by 16 and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above `2560x1440` are experimental, and the maximum supported resolution is `3840x2160`. The requested size must also satisfy the model's current pixel and edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are supported by the GPT image models; `auto` is supported for models that allow automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or `1024x1792`." + }), + Schema.Null + ]) + ), + "moderation": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["low", "auto"]).annotate({ + "description": + "Control the content-moderation level for images generated by the GPT image models. Must be either `low` for less restrictive filtering or `auto` (default value)." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "Control the content-moderation level for images generated by the GPT image models. Must be either `low` for less restrictive filtering or `auto` (default value)." + }) + ]) + ), + "background": Schema.optionalKey(Schema.Union([ + Schema.Literals(["transparent", "opaque", "auto"]).annotate({ + "description": + "Allows to set transparency for the background of the generated image(s).\nThis parameter is only supported for the GPT image models. Must be one of\n`transparent`, `opaque` or `auto` (default value). When `auto` is used, the\nmodel will automatically determine the best background for the image.\n\nIf `transparent`, the output format needs to support transparency, so it\nshould be set to either `png` (default value) or `webp`.\n" + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "Allows to set transparency for the background of the generated image(s).\nThis parameter is only supported for the GPT image models. Must be one of\n`transparent`, `opaque` or `auto` (default value). When `auto` is used, the\nmodel will automatically determine the best background for the image.\n\nIf `transparent`, the output format needs to support transparency, so it\nshould be set to either `png` (default value) or `webp`.\n" + }) + ])), + "style": Schema.optionalKey(Schema.Union([ + Schema.Literals(["vivid", "natural"]).annotate({ + "description": + "The style of the generated images. This parameter is only supported for `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean towards generating hyper-real and dramatic images. Natural causes the model to produce more natural, less hyper-real looking images." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The style of the generated images. This parameter is only supported for `dall-e-3`. Must be one of `vivid` or `natural`. Vivid causes the model to lean towards generating hyper-real and dramatic images. Natural causes the model to produce more natural, less hyper-real looking images." + }) + ])), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n" + }) + ) +}) +export type EditImageBodyJsonParam = { + readonly "model"?: string | "gpt-image-1.5" | "gpt-image-1" | "gpt-image-1-mini" | "chatgpt-image-latest" | null + readonly "images": ReadonlyArray + readonly "mask"?: ImageRefParam + readonly "prompt": string + readonly "n"?: number | null + readonly "quality"?: "low" | "medium" | "high" | "auto" | null + readonly "input_fidelity"?: "high" | "low" | null + readonly "size"?: "auto" | "1024x1024" | "1536x1024" | "1024x1536" | null + readonly "user"?: string + readonly "output_format"?: "png" | "jpeg" | "webp" | null + readonly "output_compression"?: number | null + readonly "moderation"?: "low" | "auto" | null + readonly "background"?: "transparent" | "opaque" | "auto" | null + readonly "stream"?: boolean | null + readonly "partial_images"?: PartialImages +} +export const EditImageBodyJsonParam = Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["gpt-image-1.5", "gpt-image-1", "gpt-image-1-mini", "chatgpt-image-latest"]), + Schema.Null + ]).annotate({ "description": "The model to use for image editing." }) + ), + "images": Schema.Array(ImageRefParam).annotate({ + "description": "Input image references to edit.\nFor GPT image models, you can provide up to 16 images.\n" + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(16)), + "mask": Schema.optionalKey(ImageRefParam), + "prompt": Schema.String.annotate({ "description": "A text description of the desired image edit." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(32000)), + "n": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(10)), + Schema.Null + ]).annotate({ "description": "The number of edited images to generate." }) + ), + "quality": Schema.optionalKey( + Schema.Union([Schema.Literals(["low", "medium", "high", "auto"]), Schema.Null]).annotate({ + "description": "Output quality for GPT image models.\n" + }) + ), + "input_fidelity": Schema.optionalKey( + Schema.Union([Schema.Literals(["high", "low"]), Schema.Null]).annotate({ + "description": "Controls fidelity to the original input image(s)." + }) + ), + "size": Schema.optionalKey( + Schema.Union([Schema.Literals(["auto", "1024x1024", "1536x1024", "1024x1536"]), Schema.Null]).annotate({ + "description": "Requested output image size." + }) + ), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which can help OpenAI\nmonitor and detect abuse.\n" + }) + ), + "output_format": Schema.optionalKey( + Schema.Union([Schema.Literals(["png", "jpeg", "webp"]), Schema.Null]).annotate({ + "description": "Output image format. Supported for GPT image models." + }) + ), + "output_compression": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check( + Schema.isLessThanOrEqualTo(100) + ), + Schema.Null + ]).annotate({ "description": "Compression level for `jpeg` or `webp` output." }) + ), + "moderation": Schema.optionalKey( + Schema.Union([Schema.Literals(["low", "auto"]), Schema.Null]).annotate({ + "description": "Moderation level for GPT image models." + }) + ), + "background": Schema.optionalKey( + Schema.Union([Schema.Literals(["transparent", "opaque", "auto"]), Schema.Null]).annotate({ + "description": "Background behavior for generated image output." + }) + ), + "stream": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "description": "Stream partial image results as events." }) + ), + "partial_images": Schema.optionalKey(PartialImages) +}).annotate({ + "description": + "JSON request body for image edits.\n\nUse `images` (array of `ImageRefParam`) instead of multipart `image` uploads.\nYou can reference images via external URLs, data URLs, or uploaded file IDs.\nJSON edits support GPT image models only; DALL-E edits require multipart (`dall-e-2` only).\n" +}) +export type ProjectListResponse = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ProjectListResponse = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(Project), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type ProjectApiKey = { + readonly "object": "organization.project.api_key" + readonly "redacted_value": string + readonly "name": string + readonly "created_at": number + readonly "last_used_at": number | null + readonly "id": string + readonly "owner": { + readonly "type"?: "user" | "service_account" + readonly "user"?: ProjectApiKeyOwnerUser + readonly "service_account"?: ProjectApiKeyOwnerServiceAccount + } +} +export const ProjectApiKey = Schema.Struct({ + "object": Schema.Literal("organization.project.api_key").annotate({ + "description": "The object type, which is always `organization.project.api_key`" + }), + "redacted_value": Schema.String.annotate({ "description": "The redacted value of the API key" }), + "name": Schema.String.annotate({ "description": "The name of the API key" }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the API key was created", + "format": "unixtime" + }).check(Schema.isInt()), + "last_used_at": Schema.Union([Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), Schema.Null]) + .annotate({ "description": "The Unix timestamp (in seconds) of when the API key was last used." }), + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints" }), + "owner": Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["user", "service_account"]).annotate({ "description": "`user` or `service_account`" }) + ), + "user": Schema.optionalKey(ProjectApiKeyOwnerUser), + "service_account": Schema.optionalKey(ProjectApiKeyOwnerServiceAccount) + }) +}).annotate({ "description": "Represents an individual API key in a project." }) +export type ProjectGroupListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next": string | null +} +export const ProjectGroupListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "Always `list`." }), + "data": Schema.Array(ProjectGroup).annotate({ + "description": "Project group memberships returned in the current page." + }), + "has_more": Schema.Boolean.annotate({ "description": "Whether additional project group memberships are available." }), + "next": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Cursor to fetch the next page of results, or `null` when there are no more results." + }) +}).annotate({ "description": "Paginated list of groups that have access to a project." }) +export type ProjectRateLimitListResponse = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ProjectRateLimitListResponse = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(ProjectRateLimit), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type ProjectServiceAccountListResponse = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ProjectServiceAccountListResponse = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(ProjectServiceAccount), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type ProjectServiceAccountCreateResponse = { + readonly "object": "organization.project.service_account" + readonly "id": string + readonly "name": string + readonly "role": "member" + readonly "created_at": number + readonly "api_key": ProjectServiceAccountApiKey | null +} +export const ProjectServiceAccountCreateResponse = Schema.Struct({ + "object": Schema.Literal("organization.project.service_account"), + "id": Schema.String, + "name": Schema.String, + "role": Schema.Literal("member").annotate({ + "description": "Service accounts can only have one role of type `member`" + }), + "created_at": Schema.Number.annotate({ "format": "unixtime" }).check(Schema.isInt()), + "api_key": Schema.Union([ProjectServiceAccountApiKey, Schema.Null]) +}) +export type ProjectUserListResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ProjectUserListResponse = Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(ProjectUser), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type RealtimeTranscriptionSessionCreateResponseGA = { + readonly "type": "transcription" + readonly "id": string + readonly "object": string + readonly "expires_at"?: number + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: RealtimeAudioFormats + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: { + readonly "type"?: string + readonly "threshold"?: number + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + } | null + } + } +} +export const RealtimeTranscriptionSessionCreateResponseGA = Schema.Struct({ + "type": Schema.Literal("transcription").annotate({ + "description": "The type of session. Always `transcription` for transcription sessions.\n" + }), + "id": Schema.String.annotate({ + "description": "Unique identifier for the session that looks like `sess_1234567890abcdef`.\n" + }), + "object": Schema.String.annotate({ "description": "The object type. Always `realtime.transcription_session`." }), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Expiration timestamp for the session, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n- `item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey(RealtimeAudioFormats), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model used for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ "description": "The language of the input audio.\n" }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt configured for input audio transcription, when present.\n" + }) + ) + }).annotate({ "description": "Configuration of the transcription model.\n" }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": "Configuration for input audio noise reduction.\n" + }) + ), + "turn_detection": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.String.annotate({ + "description": "Type of turn detection, only `server_vad` is currently supported.\n" + }) + ), + "threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A\nhigher threshold will require louder audio to activate the model, and\nthus might perform better in noisy environments.\n" + }).check(Schema.isFinite()) + ), + "prefix_padding_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Amount of audio to include before the VAD detected speech (in\nmilliseconds). Defaults to 300ms.\n" + }).check(Schema.isInt()) + ), + "silence_duration_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Duration of silence to detect speech stop (in milliseconds). Defaults\nto 500ms. With shorter values the model will respond more quickly,\nbut may jump in on short pauses from the user.\n" + }).check(Schema.isInt()) + ) + }).annotate({ + "description": + "Configuration for turn detection. Can be set to `null` to turn off. Server\nVAD means that the model will detect the start and end of speech based on\naudio volume and respond at the end of user speech. For `gpt-realtime-whisper`, this must be `null`; VAD is not supported.\n" + }), + Schema.Null + ]).annotate({ + "description": + "Configuration for turn detection. For `gpt-realtime-whisper`, this must be `null`; VAD is not supported.\n" + }) + ) + })) + }).annotate({ "description": "Configuration for input audio for the session.\n" }) + ) +}).annotate({ + "title": "Realtime transcription session configuration object", + "description": "A Realtime transcription session configuration object.\n" +}) +export type RealtimeMCPToolCall = { + readonly "type": "mcp_call" + readonly "id": string + readonly "server_label": string + readonly "name": string + readonly "arguments": string + readonly "approval_request_id"?: string | null + readonly "output"?: string | null + readonly "error"?: RealtimeMCPProtocolError | RealtimeMCPToolExecutionError | RealtimeMCPHTTPError | null +} +export const RealtimeMCPToolCall = Schema.Struct({ + "type": Schema.Literal("mcp_call").annotate({ "description": "The type of the item. Always `mcp_call`." }), + "id": Schema.String.annotate({ "description": "The unique ID of the tool call." }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server running the tool." }), + "name": Schema.String.annotate({ "description": "The name of the tool that was run." }), + "arguments": Schema.String.annotate({ "description": "A JSON string of the arguments passed to the tool." }), + "approval_request_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The ID of an associated approval request, if any." }), + Schema.Null + ]) + ), + "output": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The output from the tool call." }), Schema.Null]) + ), + "error": Schema.optionalKey( + Schema.Union([ + Schema.Union([RealtimeMCPProtocolError, RealtimeMCPToolExecutionError, RealtimeMCPHTTPError], { mode: "oneOf" }) + .annotate({ "description": "The error from the tool call, if any." }), + Schema.Null + ]) + ) +}).annotate({ + "title": "Realtime MCP tool call", + "description": "A Realtime item representing an invocation of a tool on an MCP server.\n" +}) +export type RealtimeReasoning = { readonly "effort"?: RealtimeReasoningEffort } +export const RealtimeReasoning = Schema.Struct({ "effort": Schema.optionalKey(RealtimeReasoningEffort) }).annotate({ + "title": "Realtime reasoning configuration", + "description": "Configuration for reasoning-capable Realtime models such as `gpt-realtime-2`.\n" +}) +export type RealtimeTranscriptionSessionCreateRequestGA = { + readonly "type": "transcription" + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: RealtimeAudioFormats + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + readonly "delay"?: "minimal" | "low" | "medium" | "high" | "xhigh" + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: RealtimeTurnDetection + } + } + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> +} +export const RealtimeTranscriptionSessionCreateRequestGA = Schema.Struct({ + "type": Schema.Literal("transcription").annotate({ + "description": "The type of session to create. Always `transcription` for transcription sessions.\n" + }), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey(RealtimeAudioFormats), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model to use for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use `gpt-4o-transcribe-diarize` when you need diarization with speaker labels.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The language of the input audio. Supplying the input language in\n[ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format\nwill improve accuracy and latency.\n" + }) + ), + "prompt": Schema.optionalKey(Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio\nsegment.\nFor `whisper-1`, the [prompt is a list of keywords](/docs/guides/speech-to-text#prompting).\nFor `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the prompt is a free text string, for example \"expect words related to technology\".\nPrompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + })), + "delay": Schema.optionalKey( + Schema.Literals(["minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Controls how long the model waits before emitting transcription text.\nHigher values can improve transcription accuracy at the cost of latency.\nOnly supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be set to `null` to turn off once on. Input audio transcription is not native to the model, since the model consumes audio directly. Transcription runs asynchronously through [the /audio/transcriptions endpoint](/docs/api-reference/audio/createTranscription) and should be treated as guidance of input audio content rather than precisely what the model heard. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "turn_detection": Schema.optionalKey(RealtimeTurnDetection) + })) + }).annotate({ "description": "Configuration for input and output audio.\n" }) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n\n`item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ) +}).annotate({ + "title": "Realtime transcription session configuration", + "description": "Realtime transcription session object configuration." +}) +export type Reasoning = { + readonly "effort"?: ReasoningEffort + readonly "summary"?: "auto" | "concise" | "detailed" | null + readonly "generate_summary"?: "auto" | "concise" | "detailed" | null +} +export const Reasoning = Schema.Struct({ + "effort": Schema.optionalKey(ReasoningEffort), + "summary": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "concise", "detailed"]).annotate({ + "description": + "A summary of the reasoning performed by the model. This can be\nuseful for debugging and understanding the model's reasoning process.\nOne of `auto`, `concise`, or `detailed`.\n\n`concise` is supported for `computer-use-preview` models and all reasoning models after `gpt-5`.\n" + }), + Schema.Null + ])), + "generate_summary": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["auto", "concise", "detailed"]).annotate({ + "description": + "**Deprecated:** use `summary` instead.\n\nA summary of the reasoning performed by the model. This can be\nuseful for debugging and understanding the model's reasoning process.\nOne of `auto`, `concise`, or `detailed`.\n" + }), + Schema.Null + ]) + ) +}).annotate({ + "title": "Reasoning", + "description": + "**gpt-5 and o-series models only**\n\nConfiguration options for\n[reasoning models](https://platform.openai.com/docs/guides/reasoning).\n" +}) +export type ResponseError = { readonly "code": ResponseErrorCode; readonly "message": string } | null +export const ResponseError = Schema.Union([ + Schema.Struct({ + "code": ResponseErrorCode, + "message": Schema.String.annotate({ "description": "A human-readable description of the error.\n" }) + }).annotate({ "description": "An error object returned when the model fails to generate a Response.\n" }), + Schema.Null +]) +export type ResponseFormatJsonSchema = { + readonly "type": "json_schema" + readonly "json_schema": { + readonly "description"?: string + readonly "name": string + readonly "schema"?: ResponseFormatJsonSchemaSchema + readonly "strict"?: boolean | null + } +} +export const ResponseFormatJsonSchema = Schema.Struct({ + "type": Schema.Literal("json_schema").annotate({ + "description": "The type of response format being defined. Always `json_schema`." + }), + "json_schema": Schema.Struct({ + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A description of what the response format is for, used by the model to\ndetermine how to respond in the format.\n" + }) + ), + "name": Schema.String.annotate({ + "description": + "The name of the response format. Must be a-z, A-Z, 0-9, or contain\nunderscores and dashes, with a maximum length of 64.\n" + }), + "schema": Schema.optionalKey(ResponseFormatJsonSchemaSchema), + "strict": Schema.optionalKey(Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to enable strict schema adherence when generating the output.\nIf set to true, the model will always follow the exact schema defined\nin the `schema` field. Only a subset of JSON Schema is supported when\n`strict` is `true`. To learn more, read the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n" + }), + Schema.Null + ])) + }).annotate({ + "title": "JSON schema", + "description": "Structured Outputs configuration options, including a JSON Schema.\n" + }) +}).annotate({ + "title": "JSON schema", + "description": + "JSON Schema response format. Used to generate structured JSON responses.\nLearn more about [Structured Outputs](/docs/guides/structured-outputs).\n" +}) +export type TextResponseFormatJsonSchema = { + readonly "type": "json_schema" + readonly "description"?: string + readonly "name": string + readonly "schema": ResponseFormatJsonSchemaSchema + readonly "strict"?: boolean | null +} +export const TextResponseFormatJsonSchema = Schema.Struct({ + "type": Schema.Literal("json_schema").annotate({ + "description": "The type of response format being defined. Always `json_schema`." + }), + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A description of what the response format is for, used by the model to\ndetermine how to respond in the format.\n" + }) + ), + "name": Schema.String.annotate({ + "description": + "The name of the response format. Must be a-z, A-Z, 0-9, or contain\nunderscores and dashes, with a maximum length of 64.\n" + }), + "schema": ResponseFormatJsonSchemaSchema, + "strict": Schema.optionalKey(Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to enable strict schema adherence when generating the output.\nIf set to true, the model will always follow the exact schema defined\nin the `schema` field. Only a subset of JSON Schema is supported when\n`strict` is `true`. To learn more, read the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n" + }), + Schema.Null + ])) +}).annotate({ + "title": "JSON schema", + "description": + "JSON Schema response format. Used to generate structured JSON responses.\nLearn more about [Structured Outputs](/docs/guides/structured-outputs).\n" +}) +export type ResponseTextDeltaEvent = { + readonly "type": "response.output_text.delta" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "delta": string + readonly "sequence_number": number + readonly "logprobs": ReadonlyArray +} +export const ResponseTextDeltaEvent = Schema.Struct({ + "type": Schema.Literal("response.output_text.delta").annotate({ + "description": "The type of the event. Always `response.output_text.delta`.\n" + }), + "item_id": Schema.String.annotate({ "description": "The ID of the output item that the text delta was added to.\n" }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the text delta was added to.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part that the text delta was added to.\n" + }).check(Schema.isInt()), + "delta": Schema.String.annotate({ "description": "The text delta that was added.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number for this event." }).check( + Schema.isInt() + ), + "logprobs": Schema.Array(ResponseLogProb).annotate({ + "description": "The log probabilities of the tokens in the delta.\n" + }) +}).annotate({ "description": "Emitted when there is an additional text delta." }) +export type ResponseTextDoneEvent = { + readonly "type": "response.output_text.done" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "text": string + readonly "sequence_number": number + readonly "logprobs": ReadonlyArray +} +export const ResponseTextDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.output_text.done").annotate({ + "description": "The type of the event. Always `response.output_text.done`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the text content is finalized.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the text content is finalized.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ + "description": "The index of the content part that the text content is finalized.\n" + }).check(Schema.isInt()), + "text": Schema.String.annotate({ "description": "The text content that is finalized.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number for this event." }).check( + Schema.isInt() + ), + "logprobs": Schema.Array(ResponseLogProb).annotate({ + "description": "The log probabilities of the tokens in the delta.\n" + }) +}).annotate({ "description": "Emitted when text content is finalized." }) +export type Prompt = { + readonly "id": string + readonly "version"?: string | null + readonly "variables"?: ResponsePromptVariables +} | null +export const Prompt = Schema.Union([ + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique identifier of the prompt template to use." }), + "version": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Optional version of the prompt template." }), Schema.Null]) + ), + "variables": Schema.optionalKey(ResponsePromptVariables) + }).annotate({ + "description": + "Reference to a prompt template and its variables.\n[Learn more](/docs/guides/text?api-mode=responses#reusable-prompts).\n" + }), + Schema.Null +]) +export type GroupRoleAssignment = { readonly "object": "group.role"; readonly "group": Group; readonly "role": Role } +export const GroupRoleAssignment = Schema.Struct({ + "object": Schema.Literal("group.role").annotate({ "description": "Always `group.role`." }), + "group": Group, + "role": Role +}).annotate({ "description": "Role assignment linking a group to a role." }) +export type PublicRoleListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next": string | null +} +export const PublicRoleListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "Always `list`." }), + "data": Schema.Array(Role).annotate({ "description": "Roles returned in the current page." }), + "has_more": Schema.Boolean.annotate({ "description": "Whether more roles are available when paginating." }), + "next": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Cursor to fetch the next page of results, or `null` when there are no additional roles." + }) +}).annotate({ "description": "Paginated list of roles available on an organization or project." }) +export type RunStepDeltaObject = { + readonly "id": string + readonly "object": "thread.run.step.delta" + readonly "delta": { + readonly "step_details"?: { + readonly "type": "message_creation" + readonly "message_creation"?: { readonly "message_id"?: string } + } | { + readonly "type": "tool_calls" + readonly "tool_calls"?: ReadonlyArray< + | RunStepDeltaStepDetailsToolCallsCodeObject + | RunStepDeltaStepDetailsToolCallsFileSearchObject + | RunStepDeltaStepDetailsToolCallsFunctionObject + > + } + } +} +export const RunStepDeltaObject = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The identifier of the run step, which can be referenced in API endpoints." + }), + "object": Schema.Literal("thread.run.step.delta").annotate({ + "description": "The object type, which is always `thread.run.step.delta`." + }), + "delta": Schema.Struct({ + "step_details": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("message_creation").annotate({ "description": "Always `message_creation`." }), + "message_creation": Schema.optionalKey( + Schema.Struct({ + "message_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the message that was created by this run step." }) + ) + }) + ) + }).annotate({ "title": "Message creation", "description": "The details of the run step." }), + Schema.Struct({ + "type": Schema.Literal("tool_calls").annotate({ "description": "Always `tool_calls`." }), + "tool_calls": Schema.optionalKey( + Schema.Array( + Schema.Union([ + RunStepDeltaStepDetailsToolCallsCodeObject, + RunStepDeltaStepDetailsToolCallsFileSearchObject, + RunStepDeltaStepDetailsToolCallsFunctionObject + ], { mode: "oneOf" }) + ).annotate({ + "description": + "An array of tool calls the run step was involved in. These can be associated with one of three types of tools: `code_interpreter`, `file_search`, or `function`.\n" + }) + ) + }).annotate({ "title": "Tool calls", "description": "The details of the run step." }) + ], { mode: "oneOf" })) + }).annotate({ "description": "The delta containing the fields that have changed on the run step." }) +}).annotate({ + "title": "Run step delta object", + "description": "Represents a run step delta i.e. any changed fields on a run step during streaming.\n" +}) +export type CreateSpeechResponseStreamEvent = SpeechAudioDeltaEvent | SpeechAudioDoneEvent +export const CreateSpeechResponseStreamEvent = Schema.Union([SpeechAudioDeltaEvent, SpeechAudioDoneEvent]) +export type ChunkingStrategyRequestParam = { readonly "type": "auto" } | { + readonly "type": "static" + readonly "static": StaticChunkingStrategy +} +export const ChunkingStrategyRequestParam = Schema.Union([ + Schema.Struct({ "type": Schema.Literal("auto").annotate({ "description": "Always `auto`." }) }).annotate({ + "title": "Auto Chunking Strategy", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }), + Schema.Struct({ + "type": Schema.Literal("static").annotate({ "description": "Always `static`." }), + "static": StaticChunkingStrategy + }).annotate({ + "title": "Static Chunking Strategy", + "description": "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }) +], { mode: "oneOf" }) +export type CreateCompletionRequest = { + readonly "model": string | "gpt-3.5-turbo-instruct" | "davinci-002" | "babbage-002" + readonly "prompt": + | string + | ReadonlyArray + | ReadonlyArray + | ReadonlyArray> + | null + readonly "best_of"?: number + readonly "echo"?: boolean | null + readonly "frequency_penalty"?: number + readonly "logit_bias"?: {} + readonly "logprobs"?: number + readonly "max_tokens"?: number + readonly "n"?: number + readonly "presence_penalty"?: number + readonly "seed"?: never + readonly "stop"?: StopConfiguration + readonly "stream"?: boolean | null + readonly "stream_options"?: ChatCompletionStreamOptions + readonly "suffix"?: string | null + readonly "temperature"?: number + readonly "top_p"?: number + readonly "user"?: string +} +export const CreateCompletionRequest = Schema.Struct({ + "model": Schema.Union([Schema.String, Schema.Literals(["gpt-3.5-turbo-instruct", "davinci-002", "babbage-002"])]) + .annotate({ + "description": + "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n" + }), + "prompt": Schema.Union([ + Schema.Union([ + Schema.String, + Schema.Array(Schema.String), + Schema.Array(Schema.Number.check(Schema.isInt())).check(Schema.isMinLength(1)), + Schema.Array(Schema.Array(Schema.Number.check(Schema.isInt())).check(Schema.isMinLength(1))).check( + Schema.isMinLength(1) + ) + ], { mode: "oneOf" }).annotate({ + "description": + "The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays.\n\nNote that <|endoftext|> is the document separator that the model sees during training, so if a prompt is not specified the model will generate as if from the beginning of a new document.\n" + }), + Schema.Null + ]), + "best_of": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(20)], { + "description": + "Generates `best_of` completions server-side and returns the \"best\" (the one with the highest log probability per token). Results cannot be streamed.\n\nWhen used with `n`, `best_of` controls the number of candidate completions and `n` specifies how many to return – `best_of` must be greater than `n`.\n\n**Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`.\n" + }) + ) + ]) + ), + "echo": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Echo back the prompt in addition to the completion\n" + }) + ), + "frequency_penalty": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(-2), Schema.isLessThanOrEqualTo(2)], { + "description": + "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation)\n" + }) + ) + ]) + ), + "logit_bias": Schema.optionalKey(Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the GPT tokenizer) to an associated bias value from -100 to 100. You can use this [tokenizer tool](/tokenizer?view=bpe) to convert text to token IDs. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token.\n\nAs an example, you can pass `{\"50256\": -100}` to prevent the <|endoftext|> token from being generated.\n" + }) + ])), + "logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(5)], { + "description": + "Include the log probabilities on the `logprobs` most likely output tokens, as well the chosen tokens. For example, if `logprobs` is 5, the API will return a list of the 5 most likely tokens. The API will always return the `logprob` of the sampled token, so there may be up to `logprobs+1` elements in the response.\n\nThe maximum value for `logprobs` is 5.\n" + }) + ) + ]) + ), + "max_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(0)], { + "description": + "The maximum number of [tokens](/tokenizer) that can be generated in the completion.\n\nThe token count of your prompt plus `max_tokens` cannot exceed the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens.\n" + }) + ) + ]) + ), + "n": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(128)], { + "description": + "How many completions to generate for each prompt.\n\n**Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`.\n" + }) + ) + ]) + ), + "presence_penalty": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(-2), Schema.isLessThanOrEqualTo(2)], { + "description": + "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.\n\n[See more information about frequency and presence penalties.](/docs/guides/text-generation)\n" + }) + ) + ]) + ), + "seed": Schema.optionalKey(Schema.Never), + "stop": Schema.optionalKey(StopConfiguration), + "stream": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to stream back partial progress. If set, tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).\n" + }) + ), + "stream_options": Schema.optionalKey(ChatCompletionStreamOptions), + "suffix": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The suffix that comes after a completion of inserted text.\n\nThis parameter is only supported for `gpt-3.5-turbo-instruct`.\n" + }) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(2)], { + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n\nWe generally recommend altering this or `top_p` but not both.\n" + }) + ) + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1)], { + "description": + "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }) + ) + ]) + ), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n" + }) + ) +}) +export type TranscriptTextDoneEvent = { + readonly "type": "transcript.text.done" + readonly "text": string + readonly "logprobs"?: ReadonlyArray< + { readonly "token"?: string; readonly "logprob"?: number; readonly "bytes"?: ReadonlyArray } + > + readonly "usage"?: TranscriptTextUsageTokens +} +export const TranscriptTextDoneEvent = Schema.Struct({ + "type": Schema.Literal("transcript.text.done").annotate({ + "description": "The type of the event. Always `transcript.text.done`.\n" + }), + "text": Schema.String.annotate({ "description": "The text that was transcribed.\n" }), + "logprobs": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "token": Schema.optionalKey( + Schema.String.annotate({ "description": "The token that was used to generate the log probability.\n" }) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "description": "The log probability of the token.\n" }).check(Schema.isFinite()) + ), + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": "The bytes that were used to generate the log probability.\n" + }) + ) + })).annotate({ + "description": + "The log probabilities of the individual tokens in the transcription. Only included if you [create a transcription](/docs/api-reference/audio/create-transcription) with the `include[]` parameter set to `logprobs`.\n" + }) + ), + "usage": Schema.optionalKey(TranscriptTextUsageTokens) +}).annotate({ + "description": + "Emitted when the transcription is complete. Contains the complete transcription text. Only emitted when you [create a transcription](/docs/api-reference/audio/create-transcription) with the `Stream` parameter set to `true`." +}) +export type CreateTranscriptionResponseDiarizedJson = { + readonly "task": "transcribe" + readonly "duration": number + readonly "text": string + readonly "segments": ReadonlyArray + readonly "usage"?: { + readonly "type": "tokens" + readonly "input_tokens": number + readonly "input_token_details"?: { readonly "text_tokens"?: number; readonly "audio_tokens"?: number } + readonly "output_tokens": number + readonly "total_tokens": number + } | { readonly "type": "duration"; readonly "seconds": number } +} +export const CreateTranscriptionResponseDiarizedJson = Schema.Struct({ + "task": Schema.Literal("transcribe").annotate({ + "description": "The type of task that was run. Always `transcribe`." + }), + "duration": Schema.Number.annotate({ "description": "Duration of the input audio in seconds.", "format": "double" }) + .check(Schema.isFinite()), + "text": Schema.String.annotate({ "description": "The concatenated transcript text for the entire audio input." }), + "segments": Schema.Array(TranscriptionDiarizedSegment).annotate({ + "description": "Segments of the transcript annotated with timestamps and speaker labels." + }), + "usage": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("tokens").annotate({ + "description": "The type of the usage object. Always `tokens` for this variant." + }), + "input_tokens": Schema.Number.annotate({ "description": "Number of input tokens billed for this request." }) + .check(Schema.isInt()), + "input_token_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of text tokens billed for this request." }).check( + Schema.isInt() + ) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of audio tokens billed for this request." }).check( + Schema.isInt() + ) + ) + }).annotate({ "description": "Details about the input tokens billed for this request." }) + ), + "output_tokens": Schema.Number.annotate({ "description": "Number of output tokens generated." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used (input + output)." }).check( + Schema.isInt() + ) + }).annotate({ "title": "Token Usage", "description": "Token or duration usage statistics for the request." }), + Schema.Struct({ + "type": Schema.Literal("duration").annotate({ + "description": "The type of the usage object. Always `duration` for this variant." + }), + "seconds": Schema.Number.annotate({ + "description": "Duration of the input audio in seconds.", + "format": "double" + }).check(Schema.isFinite()) + }).annotate({ "title": "Duration Usage", "description": "Token or duration usage statistics for the request." }) + ], { mode: "oneOf" })) +}).annotate({ + "description": + "Represents a diarized transcription response returned by the model, including the combined transcript and speaker-segment annotations.\n" +}) +export type CreateTranslationResponseVerboseJson = { + readonly "language": string + readonly "duration": number + readonly "text": string + readonly "segments"?: ReadonlyArray +} +export const CreateTranslationResponseVerboseJson = Schema.Struct({ + "language": Schema.String.annotate({ "description": "The language of the output translation (always `english`)." }), + "duration": Schema.Number.annotate({ "description": "The duration of the input audio.", "format": "double" }).check( + Schema.isFinite() + ), + "text": Schema.String.annotate({ "description": "The translated text." }), + "segments": Schema.optionalKey( + Schema.Array(TranscriptionSegment).annotate({ + "description": "Segments of the translated text and their corresponding details." + }) + ) +}) +export type CreateTranscriptionResponseVerboseJson = { + readonly "language": string + readonly "duration": number + readonly "text": string + readonly "words"?: ReadonlyArray + readonly "segments"?: ReadonlyArray + readonly "usage"?: TranscriptTextUsageDuration +} +export const CreateTranscriptionResponseVerboseJson = Schema.Struct({ + "language": Schema.String.annotate({ "description": "The language of the input audio." }), + "duration": Schema.Number.annotate({ "description": "The duration of the input audio.", "format": "double" }).check( + Schema.isFinite() + ), + "text": Schema.String.annotate({ "description": "The transcribed text." }), + "words": Schema.optionalKey( + Schema.Array(TranscriptionWord).annotate({ "description": "Extracted words and their corresponding timestamps." }) + ), + "segments": Schema.optionalKey( + Schema.Array(TranscriptionSegment).annotate({ + "description": "Segments of the transcribed text and their corresponding details." + }) + ), + "usage": Schema.optionalKey(TranscriptTextUsageDuration) +}).annotate({ + "description": "Represents a verbose json transcription response returned by model, based on the provided input." +}) +export type UsageTimeBucket = { + readonly "object": "bucket" + readonly "start_time": number + readonly "end_time": number + readonly "results": ReadonlyArray< + | UsageCompletionsResult + | UsageEmbeddingsResult + | UsageModerationsResult + | UsageImagesResult + | UsageAudioSpeechesResult + | UsageAudioTranscriptionsResult + | UsageVectorStoresResult + | UsageCodeInterpreterSessionsResult + | UsageFileSearchCallsResult + | UsageWebSearchCallsResult + | CostsResult + > +} +export const UsageTimeBucket = Schema.Struct({ + "object": Schema.Literal("bucket"), + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.Number.check(Schema.isInt()), + "results": Schema.Array( + Schema.Union([ + UsageCompletionsResult, + UsageEmbeddingsResult, + UsageModerationsResult, + UsageImagesResult, + UsageAudioSpeechesResult, + UsageAudioTranscriptionsResult, + UsageVectorStoresResult, + UsageCodeInterpreterSessionsResult, + UsageFileSearchCallsResult, + UsageWebSearchCallsResult, + CostsResult + ], { mode: "oneOf" }) + ) +}) +export type UserListResponse = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const UserListResponse = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(User), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type UserRoleAssignment = { readonly "object": "user.role"; readonly "user": User; readonly "role": Role } +export const UserRoleAssignment = Schema.Struct({ + "object": Schema.Literal("user.role").annotate({ "description": "Always `user.role`." }), + "user": User, + "role": Role +}).annotate({ "description": "Role assignment linking a user to a role." }) +export type CreateTranscriptionRequest = { + readonly "file": string + readonly "model": + | string + | "whisper-1" + | "gpt-4o-transcribe" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe-diarize" + readonly "language"?: string + readonly "prompt"?: string + readonly "response_format"?: AudioResponseFormat + readonly "temperature"?: number + readonly "include"?: ReadonlyArray + readonly "timestamp_granularities"?: ReadonlyArray<"word" | "segment"> + readonly "stream"?: boolean | null + readonly "chunking_strategy"?: "auto" | VadConfig | null + readonly "known_speaker_names"?: ReadonlyArray + readonly "known_speaker_references"?: ReadonlyArray +} +export const CreateTranscriptionRequest = Schema.Struct({ + "file": Schema.String.annotate({ + "description": + "The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.\n", + "format": "binary" + }), + "model": Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-transcribe", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe-diarize" + ]) + ]).annotate({ + "description": + "ID of the model to use. The options are `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `whisper-1` (which is powered by our open source Whisper V2 model), and `gpt-4o-transcribe-diarize`.\n" + }), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The language of the input audio. Supplying the input language in [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format will improve accuracy and latency.\n" + }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text#prompting) should match the audio language. This field is not supported when using `gpt-4o-transcribe-diarize`.\n" + }) + ), + "response_format": Schema.optionalKey(AudioResponseFormat), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit.\n" + }).check(Schema.isFinite()) + ), + "include": Schema.optionalKey( + Schema.Array(TranscriptionInclude).annotate({ + "description": + "Additional information to include in the transcription response.\n`logprobs` will return the log probabilities of the tokens in the\nresponse to understand the model's confidence in the transcription.\n`logprobs` only works with response_format set to `json` and only with\nthe models `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, and `gpt-4o-mini-transcribe-2025-12-15`. This field is not supported when using `gpt-4o-transcribe-diarize`.\n" + }) + ), + "timestamp_granularities": Schema.optionalKey( + Schema.Array(Schema.Literals(["word", "segment"])).annotate({ + "description": + "The timestamp granularities to populate for this transcription. `response_format` must be set `verbose_json` to use timestamp granularities. Either or both of these options are supported: `word`, or `segment`. Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency.\nThis option is not available for `gpt-4o-transcribe-diarize`.\n" + }) + ), + "stream": Schema.optionalKey(Schema.Union([ + Schema.Boolean.annotate({ + "description": + "If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section of the Speech-to-Text guide](/docs/guides/speech-to-text?lang=curl#streaming-transcriptions)\nfor more information.\n\nNote: Streaming is not supported for the `whisper-1` model and will be ignored.\n" + }), + Schema.Null + ])), + "chunking_strategy": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literal("auto").annotate({ + "description": "Automatically set chunking parameters based on the audio. Must be set to `\"auto\"`.\n" + }), + VadConfig + ]).annotate({ + "description": + "Controls how the audio is cut into chunks. When set to `\"auto\"`, the server first normalizes loudness and then uses voice activity detection (VAD) to choose boundaries. `server_vad` object can be provided to tweak VAD detection parameters manually. If unset, the audio is transcribed as a single block. Required when using `gpt-4o-transcribe-diarize` for inputs longer than 30 seconds. " + }), + Schema.Null + ]) + ), + "known_speaker_names": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Optional list of speaker names that correspond to the audio samples provided in `known_speaker_references[]`. Each entry should be a short identifier (for example `customer` or `agent`). Up to 4 speakers are supported.\n" + }).check(Schema.isMaxLength(4)) + ), + "known_speaker_references": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Optional list of audio samples (as [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)) that contain known speaker references matching `known_speaker_names[]`. Each sample must be between 2 and 10 seconds, and can use any of the same input audio formats supported by `file`.\n" + }).check(Schema.isMaxLength(4)) + ) +}) +export type CreateVectorStoreRequest = { + readonly "file_ids"?: ReadonlyArray + readonly "name"?: string + readonly "description"?: string + readonly "expires_after"?: VectorStoreExpirationAfter + readonly "chunking_strategy"?: { readonly "type": "auto" } | { + readonly "type": "static" + readonly "static": StaticChunkingStrategy + } + readonly "metadata"?: Metadata +} +export const CreateVectorStoreRequest = Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files." + }).check(Schema.isMaxLength(500)) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the vector store." })), + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": "A description for the vector store. Can be used to describe the vector store's purpose." + }) + ), + "expires_after": Schema.optionalKey(VectorStoreExpirationAfter), + "chunking_strategy": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("auto").annotate({ "description": "Always `auto`." }) }).annotate({ + "title": "Auto Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty." + }), + Schema.Struct({ + "type": Schema.Literal("static").annotate({ "description": "Always `static`." }), + "static": StaticChunkingStrategy + }).annotate({ + "title": "Static Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy. Only applicable if `file_ids` is non-empty." + }) + ], { mode: "oneOf" }) + ), + "metadata": Schema.optionalKey(Metadata) +}) +export type VectorStoreObject = { + readonly "id": string + readonly "object": "vector_store" + readonly "created_at": number + readonly "name": string + readonly "usage_bytes": number + readonly "file_counts": { + readonly "in_progress": number + readonly "completed": number + readonly "failed": number + readonly "cancelled": number + readonly "total": number + } + readonly "status": "expired" | "in_progress" | "completed" + readonly "expires_after"?: VectorStoreExpirationAfter + readonly "expires_at"?: number | null + readonly "last_active_at": number | null + readonly "metadata": Metadata +} +export const VectorStoreObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "object": Schema.Literal("vector_store").annotate({ + "description": "The object type, which is always `vector_store`." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the vector store was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "name": Schema.String.annotate({ "description": "The name of the vector store." }), + "usage_bytes": Schema.Number.annotate({ + "description": "The total number of bytes used by the files in the vector store." + }).check(Schema.isInt()), + "file_counts": Schema.Struct({ + "in_progress": Schema.Number.annotate({ "description": "The number of files that are currently being processed." }) + .check(Schema.isInt()), + "completed": Schema.Number.annotate({ "description": "The number of files that have been successfully processed." }) + .check(Schema.isInt()), + "failed": Schema.Number.annotate({ "description": "The number of files that have failed to process." }).check( + Schema.isInt() + ), + "cancelled": Schema.Number.annotate({ "description": "The number of files that were cancelled." }).check( + Schema.isInt() + ), + "total": Schema.Number.annotate({ "description": "The total number of files." }).check(Schema.isInt()) + }), + "status": Schema.Literals(["expired", "in_progress", "completed"]).annotate({ + "description": + "The status of the vector store, which can be either `expired`, `in_progress`, or `completed`. A status of `completed` indicates that the vector store is ready for use." + }), + "expires_after": Schema.optionalKey(VectorStoreExpirationAfter), + "expires_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the vector store will expire.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "last_active_at": Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the vector store was last active.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "metadata": Metadata +}).annotate({ + "title": "Vector store", + "description": "A vector store is a collection of processed files can be used by the `file_search` tool." +}) +export type FileSearchToolCall = { + readonly "id": string + readonly "type": "file_search_call" + readonly "status": "in_progress" | "searching" | "completed" | "incomplete" | "failed" + readonly "queries": ReadonlyArray + readonly "results"?: + | ReadonlyArray< + { + readonly "file_id"?: string + readonly "text"?: string + readonly "filename"?: string + readonly "attributes"?: VectorStoreFileAttributes + readonly "score"?: number + } + > + | null +} +export const FileSearchToolCall = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the file search tool call.\n" }), + "type": Schema.Literal("file_search_call").annotate({ + "description": "The type of the file search tool call. Always `file_search_call`.\n" + }), + "status": Schema.Literals(["in_progress", "searching", "completed", "incomplete", "failed"]).annotate({ + "description": + "The status of the file search tool call. One of `in_progress`,\n`searching`, `incomplete` or `failed`,\n" + }), + "queries": Schema.Array(Schema.String).annotate({ "description": "The queries used to search for files.\n" }), + "results": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Struct({ + "file_id": Schema.optionalKey(Schema.String.annotate({ "description": "The unique ID of the file.\n" })), + "text": Schema.optionalKey( + Schema.String.annotate({ "description": "The text that was retrieved from the file.\n" }) + ), + "filename": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the file.\n" })), + "attributes": Schema.optionalKey(VectorStoreFileAttributes), + "score": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The relevance score of the file - a value between 0 and 1.\n", + "format": "float" + }).check(Schema.isFinite()) + ) + })).annotate({ "description": "The results of the file search tool call.\n" }), + Schema.Null + ])) +}).annotate({ + "title": "File search tool call", + "description": + "The results of a file search tool call. See the\n[file search guide](/docs/guides/tools-file-search) for more information.\n" +}) +export type UpdateVectorStoreFileAttributesRequest = { readonly "attributes": VectorStoreFileAttributes } +export const UpdateVectorStoreFileAttributesRequest = Schema.Struct({ "attributes": VectorStoreFileAttributes }) +export type VectorStoreFileObject = { + readonly "id": string + readonly "object": "vector_store.file" + readonly "usage_bytes": number + readonly "created_at": number + readonly "vector_store_id": string + readonly "status": "in_progress" | "completed" | "cancelled" | "failed" + readonly "last_error": { + readonly "code": "server_error" | "unsupported_file" | "invalid_file" + readonly "message": string + } | null + readonly "chunking_strategy"?: { readonly "type": "static"; readonly "static": StaticChunkingStrategy } | { + readonly "type": "other" + } + readonly "attributes"?: VectorStoreFileAttributes +} +export const VectorStoreFileObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "object": Schema.Literal("vector_store.file").annotate({ + "description": "The object type, which is always `vector_store.file`." + }), + "usage_bytes": Schema.Number.annotate({ + "description": "The total vector store usage in bytes. Note that this may be different from the original file size." + }).check(Schema.isInt()), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the vector store file was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "vector_store_id": Schema.String.annotate({ + "description": + "The ID of the [vector store](/docs/api-reference/vector-stores/object) that the [File](/docs/api-reference/files) is attached to." + }), + "status": Schema.Literals(["in_progress", "completed", "cancelled", "failed"]).annotate({ + "description": + "The status of the vector store file, which can be either `in_progress`, `completed`, `cancelled`, or `failed`. The status `completed` indicates that the vector store file is ready for use." + }), + "last_error": Schema.Union([ + Schema.Struct({ + "code": Schema.Literals(["server_error", "unsupported_file", "invalid_file"]).annotate({ + "description": "One of `server_error`, `unsupported_file`, or `invalid_file`." + }), + "message": Schema.String.annotate({ "description": "A human-readable description of the error." }) + }).annotate({ + "description": "The last error associated with this vector store file. Will be `null` if there are no errors." + }), + Schema.Null + ]), + "chunking_strategy": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("static").annotate({ "description": "Always `static`." }), + "static": StaticChunkingStrategy + }).annotate({ "title": "Static Chunking Strategy", "description": "The strategy used to chunk the file." }), + Schema.Struct({ "type": Schema.Literal("other").annotate({ "description": "Always `other`." }) }).annotate({ + "title": "Other Chunking Strategy", + "description": "The strategy used to chunk the file." + }) + ], { mode: "oneOf" }) + ), + "attributes": Schema.optionalKey(VectorStoreFileAttributes) +}).annotate({ "title": "Vector store files", "description": "A list of files attached to a vector store." }) +export type VectorStoreSearchResultItem = { + readonly "file_id": string + readonly "filename": string + readonly "score": number + readonly "attributes": VectorStoreFileAttributes + readonly "content": ReadonlyArray +} +export const VectorStoreSearchResultItem = Schema.Struct({ + "file_id": Schema.String.annotate({ "description": "The ID of the vector store file." }), + "filename": Schema.String.annotate({ "description": "The name of the vector store file." }), + "score": Schema.Number.annotate({ "description": "The similarity score for the result." }).check(Schema.isFinite()) + .check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + "attributes": VectorStoreFileAttributes, + "content": Schema.Array(VectorStoreSearchResultContentObject).annotate({ + "description": "Content chunks from the file." + }) +}) +export type VoiceConsentListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const VoiceConsentListResource = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(VoiceConsentResource), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type CreateSpeechRequest = { + readonly "model": string | "tts-1" | "tts-1-hd" | "gpt-4o-mini-tts" | "gpt-4o-mini-tts-2025-12-15" + readonly "input": string + readonly "instructions"?: string + readonly "voice": VoiceIdsShared | { readonly "id": string } + readonly "response_format"?: "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm" + readonly "speed"?: number + readonly "stream_format"?: "sse" | "audio" +} +export const CreateSpeechRequest = Schema.Struct({ + "model": Schema.Union([ + Schema.String, + Schema.Literals(["tts-1", "tts-1-hd", "gpt-4o-mini-tts", "gpt-4o-mini-tts-2025-12-15"]) + ]).annotate({ + "description": + "One of the available [TTS models](/docs/models#tts): `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, or `gpt-4o-mini-tts-2025-12-15`.\n" + }), + "input": Schema.String.annotate({ + "description": "The text to generate audio for. The maximum length is 4096 characters." + }).check(Schema.isMaxLength(4096)), + "instructions": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Control the voice of your generated audio with additional instructions. Does not work with `tts-1` or `tts-1-hd`." + }).check(Schema.isMaxLength(4096)) + ), + "voice": Schema.Union([ + VoiceIdsShared, + Schema.Struct({ "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) }) + .annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice to use when generating the audio. Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ \"id\": \"voice_1234\" }`. Previews of the voices are available in the [Text to speech guide](/docs/guides/text-to-speech#voice-options)." + }), + "response_format": Schema.optionalKey( + Schema.Literals(["mp3", "opus", "aac", "flac", "wav", "pcm"]).annotate({ + "description": "The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`." + }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default." + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0.25)).check(Schema.isLessThanOrEqualTo(4)) + ), + "stream_format": Schema.optionalKey( + Schema.Literals(["sse", "audio"]).annotate({ + "description": + "The format to stream the audio in. Supported formats are `sse` and `audio`. `sse` is not supported for `tts-1` or `tts-1-hd`." + }) + ) +}) +export type RealtimeSessionCreateResponse = { + readonly "id"?: string + readonly "object"?: string + readonly "expires_at"?: number + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> + readonly "model"?: string + readonly "output_modalities"?: ReadonlyArray<"text" | "audio"> + readonly "instructions"?: string + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: RealtimeAudioFormats + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: { + readonly "type"?: string + readonly "threshold"?: number + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + } + } + readonly "output"?: { + readonly "format"?: RealtimeAudioFormats + readonly "voice"?: VoiceIdsShared + readonly "speed"?: number + } + } + readonly "tracing"?: "auto" | { + readonly "workflow_name"?: string + readonly "group_id"?: string + readonly "metadata"?: {} + } + readonly "turn_detection"?: { + readonly "type"?: string + readonly "threshold"?: number + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + } + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: string + readonly "max_output_tokens"?: number | "inf" +} +export const RealtimeSessionCreateResponse = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Unique identifier for the session that looks like `sess_1234567890abcdef`.\n" + }) + ), + "object": Schema.optionalKey( + Schema.String.annotate({ "description": "The object type. Always `realtime.session`." }) + ), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Expiration timestamp for the session, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n- `item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ), + "model": Schema.optionalKey(Schema.String.annotate({ "description": "The Realtime model used for this session." })), + "output_modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": "The set of modalities the model can respond with. To disable audio,\nset this to [\"text\"].\n" + }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model\ncalls. This field allows the client to guide the model on desired\nresponses. The model can be instructed on response content and format,\n(e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good\nresponses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion\ninto your voice\", \"laugh frequently\"). The instructions are not guaranteed\nto be followed by the model, but they provide guidance to the model on the\ndesired behavior.\n\nNote that the server sets default instructions which will be used if this\nfield is not set and are visible in the `session.created` event at the\nstart of the session.\n" + })), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey(RealtimeAudioFormats), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model used for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ "description": "The language of the input audio.\n" }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt configured for input audio transcription, when present.\n" + }) + ) + }).annotate({ "description": "Configuration for input audio transcription.\n" }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": "Configuration for input audio noise reduction.\n" + }) + ), + "turn_detection": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.String.annotate({ + "description": "Type of turn detection, only `server_vad` is currently supported.\n" + }) + ), + "threshold": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), + "prefix_padding_ms": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "silence_duration_ms": Schema.optionalKey(Schema.Number.check(Schema.isInt())) + }).annotate({ "description": "Configuration for turn detection.\n" }) + ) + })), + "output": Schema.optionalKey( + Schema.Struct({ + "format": Schema.optionalKey(RealtimeAudioFormats), + "voice": Schema.optionalKey(VoiceIdsShared), + "speed": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) + }) + ) + }).annotate({ "description": "Configuration for input and output audio for the session.\n" }) + ), + "tracing": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto").annotate({ "description": "Default tracing mode for the session.\n" }), + Schema.Struct({ + "workflow_name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The name of the workflow to attach to this trace. This is used to\nname the trace in the traces dashboard.\n" + }) + ), + "group_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The group id to attach to this trace to enable filtering and\ngrouping in the traces dashboard.\n" + }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The arbitrary metadata to attach to this trace to enable\nfiltering in the traces dashboard.\n" + }) + ) + }).annotate({ "title": "Tracing Configuration", "description": "Granular configuration for tracing.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Tracing Configuration", + "description": + "Configuration options for tracing. Set to null to disable tracing. Once\ntracing is enabled for a session, the configuration cannot be modified.\n\n`auto` will create a trace for the session with default values for the\nworkflow name, group id, and metadata.\n" + }) + ), + "turn_detection": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.String.annotate({ "description": "Type of turn detection, only `server_vad` is currently supported.\n" }) + ), + "threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A\nhigher threshold will require louder audio to activate the model, and\nthus might perform better in noisy environments.\n" + }).check(Schema.isFinite()) + ), + "prefix_padding_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Amount of audio to include before the VAD detected speech (in\nmilliseconds). Defaults to 300ms.\n" + }).check(Schema.isInt()) + ), + "silence_duration_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Duration of silence to detect speech stop (in milliseconds). Defaults\nto 500ms. With shorter values the model will respond more quickly,\nbut may jump in on short pauses from the user.\n" + }).check(Schema.isInt()) + ) + }).annotate({ + "description": + "Configuration for turn detection. Can be set to `null` to turn off. Server\nVAD means that the model will detect the start and end of speech based on\naudio volume and respond at the end of user speech.\n" + }) + ), + "tools": Schema.optionalKey( + Schema.Array(RealtimeFunctionTool).annotate({ "description": "Tools (functions) available to the model." }) + ), + "tool_choice": Schema.optionalKey( + Schema.String.annotate({ + "description": "How the model chooses tools. Options are `auto`, `none`, `required`, or\nspecify a function.\n" + }) + ), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ) +}).annotate({ + "title": "Realtime session configuration object", + "description": "A Realtime session configuration object.\n" +}) +export type WebSearchTool = { + readonly "type": "web_search" | "web_search_2025_08_26" + readonly "filters"?: { readonly "allowed_domains"?: ReadonlyArray | null } | null + readonly "user_location"?: WebSearchApproximateLocation + readonly "search_context_size"?: "low" | "medium" | "high" +} +export const WebSearchTool = Schema.Struct({ + "type": Schema.Literals(["web_search", "web_search_2025_08_26"]).annotate({ + "description": "The type of the web search tool. One of `web_search` or `web_search_2025_08_26`." + }), + "filters": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "allowed_domains": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String.annotate({ "description": "Allowed domain for the search." })).annotate({ + "title": "Allowed domains for the search.", + "description": + "Allowed domains for the search. If not provided, all domains are allowed.\nSubdomains of the provided domains are allowed as well.\n\nExample: `[\"pubmed.ncbi.nlm.nih.gov\"]`\n" + }), + Schema.Null + ]) + ) + }).annotate({ "description": "Filters for the search.\n" }), + Schema.Null + ])), + "user_location": Schema.optionalKey(WebSearchApproximateLocation), + "search_context_size": Schema.optionalKey( + Schema.Literals(["low", "medium", "high"]).annotate({ + "description": + "High level guidance for the amount of context window space to use for the search. One of `low`, `medium`, or `high`. `medium` is the default." + }) + ) +}).annotate({ + "title": "Web search", + "description": + "Search the Internet for sources related to the prompt. Learn more about the\n[web search tool](/docs/guides/tools-web-search).\n" +}) +export type ContainerNetworkPolicyAllowlistParam = { + readonly "type": "allowlist" + readonly "allowed_domains": ReadonlyArray + readonly "domain_secrets"?: ReadonlyArray +} +export const ContainerNetworkPolicyAllowlistParam = Schema.Struct({ + "type": Schema.Literal("allowlist").annotate({ + "description": "Allow outbound network access only to specified domains. Always `allowlist`." + }), + "allowed_domains": Schema.Array(Schema.String).annotate({ + "description": "A list of allowed domains when type is `allowlist`." + }).check(Schema.isMinLength(1)), + "domain_secrets": Schema.optionalKey( + Schema.Array(ContainerNetworkPolicyDomainSecretParam).annotate({ + "description": "Optional domain-scoped secrets for allowlisted domains." + }).check(Schema.isMinLength(1)) + ) +}) +export type EvalItemContentItem = + | EvalItemContentText + | InputTextContent + | EvalItemContentOutputText + | EvalItemInputImage + | InputAudio +export const EvalItemContentItem = Schema.Union([ + EvalItemContentText, + InputTextContent, + EvalItemContentOutputText, + EvalItemInputImage, + InputAudio +], { mode: "oneOf" }).annotate({ + "title": "Eval content item", + "description": "A single content item: input text, output text, input image, or input audio.\n" +}) +export type Annotation = FileCitationBody | UrlCitationBody | ContainerFileCitationBody | FilePath +export const Annotation = Schema.Union([FileCitationBody, UrlCitationBody, ContainerFileCitationBody, FilePath], { + mode: "oneOf" +}).annotate({ "description": "An annotation that applies to a span of output text." }) +export type LogProb = { + readonly "token": string + readonly "logprob": number + readonly "bytes": ReadonlyArray + readonly "top_logprobs": ReadonlyArray +} +export const LogProb = Schema.Struct({ + "token": Schema.String, + "logprob": Schema.Number.check(Schema.isFinite()), + "bytes": Schema.Array(Schema.Number.check(Schema.isInt())), + "top_logprobs": Schema.Array(TopLogProb) +}).annotate({ "title": "Log probability", "description": "The log probability of a token." }) +export type ReasoningItem = { + readonly "type": "reasoning" + readonly "id": string + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + readonly "content"?: ReadonlyArray + readonly "status"?: "in_progress" | "completed" | "incomplete" +} +export const ReasoningItem = Schema.Struct({ + "type": Schema.Literal("reasoning").annotate({ "description": "The type of the object. Always `reasoning`.\n" }), + "id": Schema.String.annotate({ "description": "The unique identifier of the reasoning content.\n" }), + "encrypted_content": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The encrypted content of the reasoning item - populated when a response is\ngenerated with `reasoning.encrypted_content` in the `include` parameter.\n" + }), + Schema.Null + ]) + ), + "summary": Schema.Array(SummaryTextContent).annotate({ "description": "Reasoning summary content.\n" }), + "content": Schema.optionalKey( + Schema.Array(ReasoningTextContent).annotate({ "description": "Reasoning text content.\n" }) + ), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ) +}).annotate({ + "title": "Reasoning", + "description": + "A description of the chain of thought used by a reasoning model while generating\na response. Be sure to include these items in your `input` to the Responses API\nfor subsequent turns of a conversation if you are manually\n[managing context](/docs/guides/conversation-state).\n" +}) +export type FunctionAndCustomToolCallOutput = InputTextContent | InputImageContent | InputFileContent +export const FunctionAndCustomToolCallOutput = Schema.Union([InputTextContent, InputImageContent, InputFileContent], { + mode: "oneOf" +}) +export type InputContent = InputTextContent | InputImageContent | InputFileContent +export const InputContent = Schema.Union([InputTextContent, InputImageContent, InputFileContent], { mode: "oneOf" }) +export type DragParam = { + readonly "type": "drag" + readonly "path": ReadonlyArray + readonly "keys"?: ReadonlyArray | null +} +export const DragParam = Schema.Struct({ + "type": Schema.Literal("drag").annotate({ + "description": "Specifies the event type. For a drag action, this property is always set to `drag`." + }), + "path": Schema.Array(CoordParam).annotate({ + "description": + "An array of coordinates representing the path of the drag action. Coordinates will appear as an array of objects, eg\n```\n[\n { x: 100, y: 200 },\n { x: 200, y: 300 }\n]\n```" + }), + "keys": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ "description": "The keys being held while dragging the mouse." }), + Schema.Null + ]) + ) +}).annotate({ "title": "Drag", "description": "A drag action." }) +export type ComputerToolCallOutputResource = { + readonly "type": "computer_call_output" + readonly "id": string + readonly "call_id": string + readonly "acknowledged_safety_checks"?: ReadonlyArray + readonly "output": ComputerScreenshotImage + readonly "status": "completed" | "incomplete" + readonly "created_by"?: string +} +export const ComputerToolCallOutputResource = Schema.Struct({ + "type": Schema.Literal("computer_call_output").annotate({ + "description": "The type of the computer tool call output. Always `computer_call_output`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the computer call tool output.\n" }), + "call_id": Schema.String.annotate({ "description": "The ID of the computer tool call that produced the output.\n" }), + "acknowledged_safety_checks": Schema.optionalKey( + Schema.Array(ComputerCallSafetyCheckParam).annotate({ + "description": "The safety checks reported by the API that have been acknowledged by the\ndeveloper.\n" + }) + ), + "output": ComputerScreenshotImage, + "status": Schema.Union([ + Schema.Literal("completed").annotate({ + "description": + "The status of the message input. One of `in_progress`, `completed`, or\n`incomplete`. Populated when input items are returned via API.\n" + }), + Schema.Literal("incomplete").annotate({ + "description": + "The status of the message input. One of `in_progress`, `completed`, or\n`incomplete`. Populated when input items are returned via API.\n" + }) + ]).annotate({ + "description": + "The status of the message input. One of `in_progress`, `completed`, or\n`incomplete`. Populated when input items are returned via API.\n" + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item.\n" }) + ) +}).annotate({ "title": "Computer tool call output", "description": "The output of a computer tool call.\n" }) +export type CreateImageEditRequest = { + readonly "image": string | ReadonlyArray + readonly "prompt": string + readonly "mask"?: string + readonly "background"?: "transparent" | "opaque" | "auto" | null + readonly "model"?: + | string + | "gpt-image-1.5" + | "dall-e-2" + | "gpt-image-1" + | "gpt-image-1-mini" + | "chatgpt-image-latest" + | null + readonly "n"?: number + readonly "size"?: string | "256x256" | "512x512" | "1024x1024" | "1536x1024" | "1024x1536" | "auto" | null + readonly "response_format"?: "url" | "b64_json" | null + readonly "output_format"?: "png" | "jpeg" | "webp" | null + readonly "output_compression"?: number | null + readonly "user"?: string + readonly "input_fidelity"?: InputFidelity | null + readonly "stream"?: boolean | null + readonly "partial_images"?: PartialImages + readonly "quality"?: "standard" | "low" | "medium" | "high" | "auto" | null +} +export const CreateImageEditRequest = Schema.Struct({ + "image": Schema.Union([ + Schema.String.annotate({ "format": "binary" }), + Schema.Array(Schema.String.annotate({ "format": "binary" })).check(Schema.isMaxLength(16)) + ]).annotate({ + "description": + "The image(s) to edit. Must be a supported image file or an array of images.\n\nFor the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, and `gpt-image-1.5`), each image should be a `png`, `webp`, or `jpg`\nfile less than 50MB. You can provide up to 16 images.\n`chatgpt-image-latest` follows the same input constraints as GPT image models.\n\nFor `dall-e-2`, you can only provide one image, and it should be a square\n`png` file less than 4MB.\n" + }), + "prompt": Schema.String.annotate({ + "description": + "A text description of the desired image(s). The maximum length is 1000 characters for `dall-e-2`, and 32000 characters for the GPT image models." + }), + "mask": Schema.optionalKey(Schema.String.annotate({ + "description": + "An additional image whose fully transparent areas (e.g. where alpha is zero) indicate where `image` should be edited. If there are multiple images provided, the mask will be applied on the first image. Must be a valid PNG file, less than 4MB, and have the same dimensions as `image`.", + "format": "binary" + })), + "background": Schema.optionalKey(Schema.Union([ + Schema.Literals(["transparent", "opaque", "auto"]).annotate({ + "description": + "Allows to set transparency for the background of the generated image(s).\nThis parameter is only supported for the GPT image models. Must be one of\n`transparent`, `opaque` or `auto` (default value). When `auto` is used, the\nmodel will automatically determine the best background for the image.\n\nIf `transparent`, the output format needs to support transparency, so it\nshould be set to either `png` (default value) or `webp`.\n" + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "Allows to set transparency for the background of the generated image(s).\nThis parameter is only supported for the GPT image models. Must be one of\n`transparent`, `opaque` or `auto` (default value). When `auto` is used, the\nmodel will automatically determine the best background for the image.\n\nIf `transparent`, the output format needs to support transparency, so it\nshould be set to either `png` (default value) or `webp`.\n" + }) + ])), + "model": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String, + Schema.Literals(["gpt-image-1.5", "dall-e-2", "gpt-image-1", "gpt-image-1-mini", "chatgpt-image-latest"]) + ]).annotate({ "description": "The model to use for image generation. Defaults to `gpt-image-1.5`." }), + Schema.Null + ]) + ), + "n": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(10)], { + "description": "The number of images to generate. Must be between 1 and 10." + }) + ) + ]) + ), + "size": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String, + Schema.Literals(["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "auto"]) + ]).annotate({ + "description": + "The size of the generated images. For `gpt-image-2` and `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` strings, for example `1536x864`. Width and height must both be divisible by 16 and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above `2560x1440` are experimental, and the maximum supported resolution is `3840x2160`. The requested size must also satisfy the model's current pixel and edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are supported by the GPT image models; `auto` is supported for models that allow automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or `1024x1792`." + }), + Schema.Null + ]) + ), + "response_format": Schema.optionalKey(Schema.Union([ + Schema.Literals(["url", "b64_json"]).annotate({ + "description": + "The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated. This parameter is only supported for `dall-e-2` (default is `url` for `dall-e-2`), as GPT image models always return base64-encoded images." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated. This parameter is only supported for `dall-e-2` (default is `url` for `dall-e-2`), as GPT image models always return base64-encoded images." + }) + ])), + "output_format": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["png", "jpeg", "webp"]).annotate({ + "description": + "The format in which the generated images are returned. This parameter is\nonly supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`.\nThe default value is `png`.\n" + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The format in which the generated images are returned. This parameter is\nonly supported for the GPT image models. Must be one of `png`, `jpeg`, or `webp`.\nThe default value is `png`.\n" + }) + ]) + ), + "output_compression": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "description": + "The compression level (0-100%) for the generated images. This parameter\nis only supported for the GPT image models with the `webp` or `jpeg` output\nformats, and defaults to 100.\n" + }) + ), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices#end-user-ids).\n" + }) + ), + "input_fidelity": Schema.optionalKey(Schema.Union([InputFidelity, Schema.Null])), + "stream": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Edit the image in streaming mode. Defaults to `false`. See the\n[Image generation guide](/docs/guides/image-generation) for more information.\n" + }) + ), + "partial_images": Schema.optionalKey(PartialImages), + "quality": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["standard", "low", "medium", "high", "auto"]).annotate({ + "description": "The quality of the image that will be generated for GPT image models. Defaults to `auto`.\n" + }), + Schema.Union([Schema.Null]).annotate({ + "description": "The quality of the image that will be generated for GPT image models. Defaults to `auto`.\n" + }) + ]) + ) +}) +export type ImageGenTool = { + readonly "type": "image_generation" + readonly "model"?: string | "gpt-image-1" | "gpt-image-1-mini" | "gpt-image-1.5" + readonly "quality"?: "low" | "medium" | "high" | "auto" + readonly "size"?: string | "1024x1024" | "1024x1536" | "1536x1024" | "auto" + readonly "output_format"?: "png" | "webp" | "jpeg" + readonly "output_compression"?: number + readonly "moderation"?: "auto" | "low" + readonly "background"?: "transparent" | "opaque" | "auto" + readonly "input_fidelity"?: InputFidelity | null + readonly "input_image_mask"?: { readonly "image_url"?: string; readonly "file_id"?: string } + readonly "partial_images"?: number + readonly "action"?: "generate" | "edit" | "auto" +} +export const ImageGenTool = Schema.Struct({ + "type": Schema.Literal("image_generation").annotate({ + "description": "The type of the image generation tool. Always `image_generation`.\n" + }), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["gpt-image-1", "gpt-image-1-mini", "gpt-image-1.5"]).annotate({ + "description": "The image generation model to use. Default: `gpt-image-1`.\n" + }) + ]) + ), + "quality": Schema.optionalKey( + Schema.Literals(["low", "medium", "high", "auto"]).annotate({ + "description": + "The quality of the generated image. One of `low`, `medium`, `high`,\nor `auto`. Default: `auto`.\n" + }) + ), + "size": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Literals(["1024x1024", "1024x1536", "1536x1024", "auto"])]).annotate({ + "description": + "The size of the generated images. For `gpt-image-2` and `gpt-image-2-2026-04-21`, arbitrary resolutions are supported as `WIDTHxHEIGHT` strings, for example `1536x864`. Width and height must both be divisible by 16 and the requested aspect ratio must be between 1:3 and 3:1. Resolutions above `2560x1440` are experimental, and the maximum supported resolution is `3840x2160`. The requested size must also satisfy the model's current pixel and edge limits. The standard sizes `1024x1024`, `1536x1024`, and `1024x1536` are supported by the GPT image models; `auto` is supported for models that allow automatic sizing. For `dall-e-2`, use one of `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of `1024x1024`, `1792x1024`, or `1024x1792`." + }) + ), + "output_format": Schema.optionalKey( + Schema.Literals(["png", "webp", "jpeg"]).annotate({ + "description": "The output format of the generated image. One of `png`, `webp`, or\n`jpeg`. Default: `png`.\n" + }) + ), + "output_compression": Schema.optionalKey( + Schema.Number.annotate({ "description": "Compression level for the output image. Default: 100.\n" }).check( + Schema.isInt() + ).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(100)) + ), + "moderation": Schema.optionalKey( + Schema.Literals(["auto", "low"]).annotate({ + "description": "Moderation level for the generated image. Default: `auto`.\n" + }) + ), + "background": Schema.optionalKey( + Schema.Literals(["transparent", "opaque", "auto"]).annotate({ + "description": + "Background type for the generated image. One of `transparent`,\n`opaque`, or `auto`. Default: `auto`.\n" + }) + ), + "input_fidelity": Schema.optionalKey(Schema.Union([InputFidelity, Schema.Null])), + "input_image_mask": Schema.optionalKey( + Schema.Struct({ + "image_url": Schema.optionalKey(Schema.String.annotate({ "description": "Base64-encoded mask image.\n" })), + "file_id": Schema.optionalKey(Schema.String.annotate({ "description": "File ID for the mask image.\n" })) + }).annotate({ + "description": + "Optional mask for inpainting. Contains `image_url`\n(string, optional) and `file_id` (string, optional).\n" + }) + ), + "partial_images": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Number of partial images to generate in streaming mode, from 0 (default value) to 3.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(3)) + ), + "action": Schema.optionalKey( + Schema.Literals(["generate", "edit", "auto"]).annotate({ + "description": "Whether to generate a new image or edit an existing image. Default: `auto`.\n" + }) + ) +}).annotate({ + "title": "Image generation tool", + "description": "A tool that generates images using the GPT image models.\n" +}) +export type LocalEnvironmentParam = { readonly "type": "local"; readonly "skills"?: ReadonlyArray } +export const LocalEnvironmentParam = Schema.Struct({ + "type": Schema.Literal("local").annotate({ "description": "Use a local computer environment." }), + "skills": Schema.optionalKey( + Schema.Array(LocalSkillParam).annotate({ "description": "An optional list of skills." }).check( + Schema.isMaxLength(200) + ) + ) +}) +export type CustomToolParam = { + readonly "type": "custom" + readonly "name": string + readonly "description"?: string + readonly "format"?: CustomTextFormatParam | CustomGrammarFormatParam + readonly "defer_loading"?: boolean +} +export const CustomToolParam = Schema.Struct({ + "type": Schema.Literal("custom").annotate({ "description": "The type of the custom tool. Always `custom`." }), + "name": Schema.String.annotate({ "description": "The name of the custom tool, used to identify it in tool calls." }), + "description": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional description of the custom tool, used to provide more context." }) + ), + "format": Schema.optionalKey( + Schema.Union([CustomTextFormatParam, CustomGrammarFormatParam], { mode: "oneOf" }).annotate({ + "description": "The input format for the custom tool. Default is unconstrained text." + }) + ), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this tool should be deferred and discovered via tool search." }) + ) +}).annotate({ + "title": "Custom tool", + "description": + "A custom tool that processes input using a specified format. Learn more about [custom tools](/docs/guides/function-calling#custom-tools)" +}) +export type FunctionToolParam = { + readonly "name": string + readonly "description"?: string | null + readonly "parameters"?: EmptyModelParam | null + readonly "strict"?: boolean | null + readonly "type": "function" + readonly "defer_loading"?: boolean +} +export const FunctionToolParam = Schema.Struct({ + "name": Schema.String.check(Schema.isMinLength(1)).check(Schema.isMaxLength(128)).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9_-]+$")) + ), + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "parameters": Schema.optionalKey(Schema.Union([EmptyModelParam, Schema.Null])), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function"), + "defer_loading": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Whether this function should be deferred and discovered via tool search." + }) + ) +}) +export type WebSearchPreviewTool = { + readonly "type": "web_search_preview" | "web_search_preview_2025_03_11" + readonly "user_location"?: { + readonly "type": "approximate" + readonly "country"?: string | null + readonly "region"?: string | null + readonly "city"?: string | null + readonly "timezone"?: string | null + } | null + readonly "search_context_size"?: "low" | "medium" | "high" + readonly "search_content_types"?: ReadonlyArray +} +export const WebSearchPreviewTool = Schema.Struct({ + "type": Schema.Literals(["web_search_preview", "web_search_preview_2025_03_11"]).annotate({ + "description": "The type of the web search tool. One of `web_search_preview` or `web_search_preview_2025_03_11`." + }), + "user_location": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("approximate").annotate({ + "description": "The type of location approximation. Always `approximate`." + }), + "country": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of the user, e.g. `US`." + }), + Schema.Null + ]) + ), + "region": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Free text input for the region of the user, e.g. `California`." }), + Schema.Null + ]) + ), + "city": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Free text input for the city of the user, e.g. `San Francisco`." }), + Schema.Null + ]) + ), + "timezone": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the user, e.g. `America/Los_Angeles`." + }), + Schema.Null + ]) + ) + }).annotate({ "description": "The user's location." }), + Schema.Null + ])), + "search_context_size": Schema.optionalKey( + Schema.Literals(["low", "medium", "high"]).annotate({ + "description": + "High level guidance for the amount of context window space to use for the search. One of `low`, `medium`, or `high`. `medium` is the default." + }) + ), + "search_content_types": Schema.optionalKey(Schema.Array(SearchContentType)) +}).annotate({ + "title": "Web search preview", + "description": + "This tool searches the web for relevant results to use in a response. Learn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search)." +}) +export type CodeInterpreterToolCall = { + readonly "type": "code_interpreter_call" + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" | "interpreting" | "failed" + readonly "container_id": string + readonly "code": string | null + readonly "outputs": ReadonlyArray | null +} +export const CodeInterpreterToolCall = Schema.Struct({ + "type": Schema.Literal("code_interpreter_call").annotate({ + "description": "The type of the code interpreter tool call. Always `code_interpreter_call`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the code interpreter tool call.\n" }), + "status": Schema.Literals(["in_progress", "completed", "incomplete", "interpreting", "failed"]).annotate({ + "description": + "The status of the code interpreter tool call. Valid values are `in_progress`, `completed`, `incomplete`, `interpreting`, and `failed`.\n" + }), + "container_id": Schema.String.annotate({ "description": "The ID of the container used to run the code.\n" }), + "code": Schema.Union([ + Schema.String.annotate({ "description": "The code to run, or null if not available.\n" }), + Schema.Null + ]), + "outputs": Schema.Union([ + Schema.Array(Schema.Union([CodeInterpreterOutputLogs, CodeInterpreterOutputImage], { mode: "oneOf" })).annotate({ + "description": + "The outputs generated by the code interpreter, such as logs or images.\nCan be null if no outputs are available.\n" + }), + Schema.Null + ]) +}).annotate({ "title": "Code interpreter tool call", "description": "A tool call to run code.\n" }) +export type LocalShellToolCall = { + readonly "type": "local_shell_call" + readonly "id": string + readonly "call_id": string + readonly "action": LocalShellExecAction + readonly "status": "in_progress" | "completed" | "incomplete" +} +export const LocalShellToolCall = Schema.Struct({ + "type": Schema.Literal("local_shell_call").annotate({ + "description": "The type of the local shell call. Always `local_shell_call`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the local shell call.\n" }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the local shell tool call generated by the model.\n" + }), + "action": LocalShellExecAction, + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the local shell call.\n" + }) +}).annotate({ "title": "Local shell call", "description": "A tool call to run a command on the local shell.\n" }) +export type FunctionShellCall = { + readonly "type": "shell_call" + readonly "id": string + readonly "call_id": string + readonly "action": { + readonly "commands": ReadonlyArray + readonly "timeout_ms": number | null + readonly "max_output_length": number | null + } + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "environment": LocalEnvironmentResource | ContainerReferenceResource | null + readonly "created_by"?: string +} +export const FunctionShellCall = Schema.Struct({ + "type": Schema.Literal("shell_call").annotate({ "description": "The type of the item. Always `shell_call`." }), + "id": Schema.String.annotate({ + "description": "The unique ID of the shell tool call. Populated when this item is returned via API." + }), + "call_id": Schema.String.annotate({ "description": "The unique ID of the shell tool call generated by the model." }), + "action": Schema.Struct({ + "commands": Schema.Array(Schema.String.annotate({ "description": "A list of commands to run." })), + "timeout_ms": Schema.Union([ + Schema.Number.annotate({ "description": "Optional timeout in milliseconds for the commands." }).check( + Schema.isInt() + ), + Schema.Null + ]), + "max_output_length": Schema.Union([ + Schema.Number.annotate({ "description": "Optional maximum number of characters to return from each command." }) + .check(Schema.isInt()), + Schema.Null + ]) + }).annotate({ + "title": "Shell exec action", + "description": "The shell commands and limits that describe how to run the tool call." + }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the shell call. One of `in_progress`, `completed`, or `incomplete`." + }), + "environment": Schema.Union([ + Schema.Union([LocalEnvironmentResource, ContainerReferenceResource], { mode: "oneOf" }), + Schema.Null + ]), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the entity that created this tool call." }) + ) +}).annotate({ + "title": "Shell tool call", + "description": "A tool call that executes one or more shell commands in a managed environment." +}) +export type FunctionShellCallOutputContent = { + readonly "stdout": string + readonly "stderr": string + readonly "outcome": FunctionShellCallOutputTimeoutOutcome | FunctionShellCallOutputExitOutcome + readonly "created_by"?: string +} +export const FunctionShellCallOutputContent = Schema.Struct({ + "stdout": Schema.String.annotate({ "description": "The standard output that was captured." }), + "stderr": Schema.String.annotate({ "description": "The standard error output that was captured." }), + "outcome": Schema.Union([FunctionShellCallOutputTimeoutOutcome, FunctionShellCallOutputExitOutcome], { + mode: "oneOf" + }).annotate({ + "title": "Shell call outcome", + "description": + "Represents either an exit outcome (with an exit code) or a timeout outcome for a shell call output chunk." + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item." }) + ) +}).annotate({ + "title": "Shell call output content", + "description": "The content of a shell tool call output that was emitted." +}) +export type ApplyPatchToolCall = { + readonly "type": "apply_patch_call" + readonly "id": string + readonly "call_id": string + readonly "status": "in_progress" | "completed" + readonly "operation": ApplyPatchCreateFileOperation | ApplyPatchDeleteFileOperation | ApplyPatchUpdateFileOperation + readonly "created_by"?: string +} +export const ApplyPatchToolCall = Schema.Struct({ + "type": Schema.Literal("apply_patch_call").annotate({ + "description": "The type of the item. Always `apply_patch_call`." + }), + "id": Schema.String.annotate({ + "description": "The unique ID of the apply patch tool call. Populated when this item is returned via API." + }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the apply patch tool call generated by the model." + }), + "status": Schema.Literals(["in_progress", "completed"]).annotate({ + "description": "The status of the apply patch tool call. One of `in_progress` or `completed`." + }), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchDeleteFileOperation, + ApplyPatchUpdateFileOperation + ], { mode: "oneOf" }).annotate({ + "title": "Apply patch operation", + "description": "One of the create_file, delete_file, or update_file operations applied via apply_patch." + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the entity that created this tool call." }) + ) +}).annotate({ + "title": "Apply patch tool call", + "description": "A tool call that applies file diffs by creating, deleting, or updating files." +}) +export type FunctionShellCallOutputContentParam = { + readonly "stdout": string + readonly "stderr": string + readonly "outcome": FunctionShellCallOutputTimeoutOutcomeParam | FunctionShellCallOutputExitOutcomeParam +} +export const FunctionShellCallOutputContentParam = Schema.Struct({ + "stdout": Schema.String.annotate({ "description": "Captured stdout output for the shell call." }).check( + Schema.isMaxLength(10485760) + ), + "stderr": Schema.String.annotate({ "description": "Captured stderr output for the shell call." }).check( + Schema.isMaxLength(10485760) + ), + "outcome": Schema.Union([FunctionShellCallOutputTimeoutOutcomeParam, FunctionShellCallOutputExitOutcomeParam], { + mode: "oneOf" + }).annotate({ + "title": "Shell call outcome", + "description": "The exit or timeout outcome associated with this shell call." + }) +}).annotate({ + "title": "Shell output content", + "description": "Captured stdout and stderr for a portion of a shell tool call output." +}) +export type ImageGenUsage = { + readonly "input_tokens": number + readonly "total_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details"?: ImageGenOutputTokensDetails + readonly "input_tokens_details": ImageGenInputUsageDetails +} +export const ImageGenUsage = Schema.Struct({ + "input_tokens": Schema.Number.annotate({ + "description": "The number of tokens (images and text) in the input prompt." + }).check(Schema.isInt()), + "total_tokens": Schema.Number.annotate({ + "description": "The total number of tokens (images and text) used for the image generation." + }).check(Schema.isInt()), + "output_tokens": Schema.Number.annotate({ "description": "The number of output tokens generated by the model." }) + .check(Schema.isInt()), + "output_tokens_details": Schema.optionalKey(ImageGenOutputTokensDetails), + "input_tokens_details": ImageGenInputUsageDetails +}).annotate({ + "title": "Image generation usage", + "description": "For `gpt-image-1` only, the token usage information for the image generation." +}) +export type ToolChoiceParam = + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam +export const ToolChoiceParam = Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam +], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" +}) +export type ConversationParam = string | ConversationParam_2 +export const ConversationParam = Schema.Union([ + Schema.String.annotate({ "title": "Conversation ID", "description": "The unique ID of the conversation.\n" }), + ConversationParam_2 +], { mode: "oneOf" }).annotate({ + "description": + "The conversation that this response belongs to. Items from this conversation are prepended to `input_items` for this response request.\nInput items and output items from this response are automatically added to this conversation after this response completes.\n" +}) +export type VideoListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "last_id": string | null + readonly "has_more": boolean +} +export const VideoListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be `list`." }), + "data": Schema.Array(VideoResource).annotate({ "description": "A list of items" }), + "first_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the first item in the list." }), + Schema.Null + ]), + "last_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the last item in the list." }), + Schema.Null + ]), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more items available." }) +}) +export type CreateVideoMultipartBody = { + readonly "model"?: + | string + | "sora-2" + | "sora-2-pro" + | "sora-2-2025-10-06" + | "sora-2-pro-2025-10-06" + | "sora-2-2025-12-08" + readonly "prompt": string + readonly "input_reference"?: string | ImageRefParam_2 + readonly "seconds"?: "4" | "8" | "12" + readonly "size"?: "720x1280" | "1280x720" | "1024x1792" | "1792x1024" +} +export const CreateVideoMultipartBody = Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["sora-2", "sora-2-pro", "sora-2-2025-10-06", "sora-2-pro-2025-10-06", "sora-2-2025-12-08"]) + ]).annotate({ + "description": "The video generation model to use (allowed values: sora-2, sora-2-pro). Defaults to `sora-2`." + }) + ), + "prompt": Schema.String.annotate({ "description": "Text prompt that describes the video to generate." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(32000)), + "input_reference": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "Optional reference asset upload or reference object that guides generation.", + "format": "binary" + }), + ImageRefParam_2 + ], { mode: "oneOf" }) + ), + "seconds": Schema.optionalKey( + Schema.Literals(["4", "8", "12"]).annotate({ + "description": "Clip duration in seconds (allowed values: 4, 8, 12). Defaults to 4 seconds." + }) + ), + "size": Schema.optionalKey( + Schema.Literals(["720x1280", "1280x720", "1024x1792", "1792x1024"]).annotate({ + "description": + "Output resolution formatted as width x height (allowed values: 720x1280, 1280x720, 1024x1792, 1792x1024). Defaults to 720x1280." + }) + ) +}).annotate({ + "title": "Create video multipart request", + "description": "Multipart parameters for creating a new video generation job." +}) +export type CreateVideoEditMultipartBody = { + readonly "video": string | VideoReferenceInputParam + readonly "prompt": string +} +export const CreateVideoEditMultipartBody = Schema.Struct({ + "video": Schema.Union([ + Schema.String.annotate({ "description": "Reference to the completed video to edit.", "format": "binary" }), + VideoReferenceInputParam + ], { mode: "oneOf" }), + "prompt": Schema.String.annotate({ "description": "Text prompt that describes how to edit the source video." }).check( + Schema.isMinLength(1) + ).check(Schema.isMaxLength(32000)) +}).annotate({ + "title": "Create video edit multipart request", + "description": "Parameters for editing an existing generated video." +}) +export type CreateVideoExtendMultipartBody = { + readonly "video": VideoReferenceInputParam | string + readonly "prompt": string + readonly "seconds": "4" | "8" | "12" +} +export const CreateVideoExtendMultipartBody = Schema.Struct({ + "video": Schema.Union([ + VideoReferenceInputParam, + Schema.String.annotate({ "description": "Reference to the completed video to extend.", "format": "binary" }) + ], { mode: "oneOf" }), + "prompt": Schema.String.annotate({ "description": "Updated text prompt that directs the extension generation." }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(32000)), + "seconds": Schema.Literals(["4", "8", "12"]).annotate({ + "description": "Length of the newly generated extension segment in seconds (allowed values: 4, 8, 12, 16, 20)." + }) +}).annotate({ + "title": "Create video extension multipart request", + "description": "Multipart parameters for extending an existing generated video." +}) +export type SkillListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "last_id": string | null + readonly "has_more": boolean +} +export const SkillListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be `list`." }), + "data": Schema.Array(SkillResource).annotate({ "description": "A list of items" }), + "first_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the first item in the list." }), + Schema.Null + ]), + "last_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the last item in the list." }), + Schema.Null + ]), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more items available." }) +}) +export type SkillVersionListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "last_id": string | null + readonly "has_more": boolean +} +export const SkillVersionListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be `list`." }), + "data": Schema.Array(SkillVersionResource).annotate({ "description": "A list of items" }), + "first_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the first item in the list." }), + Schema.Null + ]), + "last_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the last item in the list." }), + Schema.Null + ]), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more items available." }) +}) +export type UserMessageItem = { + readonly "id": string + readonly "object": "chatkit.thread_item" + readonly "created_at": number + readonly "thread_id": string + readonly "type": "chatkit.user_message" + readonly "content": ReadonlyArray + readonly "attachments": ReadonlyArray + readonly "inference_options": { + readonly "tool_choice": { readonly "id": string } | null + readonly "model": string | null + } | null +} +export const UserMessageItem = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the thread item." }), + "object": Schema.Literal("chatkit.thread_item").annotate({ + "description": "Type discriminator that is always `chatkit.thread_item`." + }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the item was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ "description": "Identifier of the parent thread." }), + "type": Schema.Literal("chatkit.user_message"), + "content": Schema.Array( + Schema.Union([UserMessageInputText, UserMessageQuotedText], { mode: "oneOf" }).annotate({ + "description": "Content blocks that comprise a user message." + }) + ).annotate({ "description": "Ordered content elements supplied by the user." }), + "attachments": Schema.Array(Attachment).annotate({ + "description": "Attachments associated with the user message. Defaults to an empty list." + }), + "inference_options": Schema.Union([ + Schema.Struct({ + "tool_choice": Schema.Union([ + Schema.Struct({ "id": Schema.String.annotate({ "description": "Identifier of the requested tool." }) }) + .annotate({ + "title": "Tool choice", + "description": "Preferred tool to invoke. Defaults to null when ChatKit should auto-select." + }), + Schema.Null + ]), + "model": Schema.Union([ + Schema.String.annotate({ + "description": "Model name that generated the response. Defaults to null when using the session default." + }), + Schema.Null + ]) + }).annotate({ + "title": "Inference options", + "description": "Inference overrides applied to the message. Defaults to null when unset." + }), + Schema.Null + ]) +}).annotate({ "title": "User Message Item", "description": "User-authored messages within a thread." }) +export type ResponseOutputText = { + readonly "type": "output_text" + readonly "text": string + readonly "annotations": ReadonlyArray +} +export const ResponseOutputText = Schema.Struct({ + "type": Schema.Literal("output_text").annotate({ "description": "Type discriminator that is always `output_text`." }), + "text": Schema.String.annotate({ "description": "Assistant generated text." }), + "annotations": Schema.Array( + Schema.Union([FileAnnotation, UrlAnnotation], { mode: "oneOf" }).annotate({ + "description": "Annotation object describing a cited source." + }) + ).annotate({ "description": "Ordered list of annotations attached to the response text." }) +}).annotate({ + "title": "Assistant message content", + "description": "Assistant response text accompanied by optional annotations." +}) +export type TaskGroupItem = { + readonly "id": string + readonly "object": "chatkit.thread_item" + readonly "created_at": number + readonly "thread_id": string + readonly "type": "chatkit.task_group" + readonly "tasks": ReadonlyArray +} +export const TaskGroupItem = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the thread item." }), + "object": Schema.Literal("chatkit.thread_item").annotate({ + "description": "Type discriminator that is always `chatkit.thread_item`." + }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the item was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ "description": "Identifier of the parent thread." }), + "type": Schema.Literal("chatkit.task_group").annotate({ + "description": "Type discriminator that is always `chatkit.task_group`." + }), + "tasks": Schema.Array(TaskGroupTask).annotate({ "description": "Tasks included in the group." }) +}).annotate({ "title": "Task group", "description": "Collection of workflow tasks grouped together in the thread." }) +export type ThreadResource = { + readonly "id": string + readonly "object": "chatkit.thread" + readonly "created_at": number + readonly "title": string | null + readonly "status": ActiveStatus | LockedStatus | ClosedStatus + readonly "user": string +} +export const ThreadResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the thread." }), + "object": Schema.Literal("chatkit.thread").annotate({ + "description": "Type discriminator that is always `chatkit.thread`." + }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the thread was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "title": Schema.Union([ + Schema.String.annotate({ + "description": "Optional human-readable title for the thread. Defaults to null when no title has been generated." + }), + Schema.Null + ]), + "status": Schema.Union([ActiveStatus, LockedStatus, ClosedStatus], { mode: "oneOf" }).annotate({ + "description": "Current status for the thread. Defaults to `active` for newly created threads." + }), + "user": Schema.String.annotate({ + "description": "Free-form string that identifies your end user who owns the thread." + }) +}).annotate({ "title": "The thread object", "description": "Represents a ChatKit thread and its current status." }) +export type AuditLogActor = { + readonly "type"?: "session" | "api_key" + readonly "session"?: AuditLogActorSession + readonly "api_key"?: AuditLogActorApiKey +} +export const AuditLogActor = Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["session", "api_key"]).annotate({ + "description": "The type of actor. Is either `session` or `api_key`." + }) + ), + "session": Schema.optionalKey(AuditLogActorSession), + "api_key": Schema.optionalKey(AuditLogActorApiKey) +}).annotate({ "description": "The actor who performed the audit logged action." }) +export type ChatCompletionToolChoiceOption = + | "none" + | "auto" + | "required" + | ChatCompletionAllowedToolsChoice + | ChatCompletionNamedToolChoice + | ChatCompletionNamedToolChoiceCustom +export const ChatCompletionToolChoiceOption = Schema.Union([ + Schema.Literals(["none", "auto", "required"]).annotate({ + "title": "Tool choice mode", + "description": + "`none` means the model will not call any tool and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools.\n" + }), + ChatCompletionAllowedToolsChoice, + ChatCompletionNamedToolChoice, + ChatCompletionNamedToolChoiceCustom +], { mode: "oneOf" }).annotate({ + "description": + "Controls which (if any) tool is called by the model.\n`none` means the model will not call any tool and instead generates a message.\n`auto` means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools.\nSpecifying a particular tool via `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n\n`none` is the default when no tools are present. `auto` is the default if tools are present.\n" +}) +export type ChatCompletionMessageList = { + readonly "object": "list" + readonly "data": ReadonlyArray< + { + readonly "content": string | null + readonly "refusal": string | null + readonly "tool_calls"?: ChatCompletionMessageToolCalls + readonly "annotations"?: ReadonlyArray< + { + readonly "type": "url_citation" + readonly "url_citation": { + readonly "end_index": number + readonly "start_index": number + readonly "url": string + readonly "title": string + } + } + > + readonly "role": "assistant" + readonly "function_call"?: { readonly "arguments": string; readonly "name": string } + readonly "audio"?: { + readonly "id": string + readonly "expires_at": number + readonly "data": string + readonly "transcript": string + } | null + readonly "id": string + readonly "content_parts"?: + | ReadonlyArray + | null + } + > + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const ChatCompletionMessageList = Schema.Struct({ + "object": Schema.Literal("list").annotate({ + "description": "The type of this object. It is always set to \"list\".\n" + }), + "data": Schema.Array( + Schema.Struct({ + "content": Schema.Union([Schema.String.annotate({ "description": "The contents of the message." }), Schema.Null]), + "refusal": Schema.Union([ + Schema.String.annotate({ "description": "The refusal message generated by the model." }), + Schema.Null + ]), + "tool_calls": Schema.optionalKey(ChatCompletionMessageToolCalls), + "annotations": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "type": Schema.Literal("url_citation").annotate({ + "description": "The type of the URL citation. Always `url_citation`." + }), + "url_citation": Schema.Struct({ + "end_index": Schema.Number.annotate({ + "description": "The index of the last character of the URL citation in the message." + }).check(Schema.isInt()), + "start_index": Schema.Number.annotate({ + "description": "The index of the first character of the URL citation in the message." + }).check(Schema.isInt()), + "url": Schema.String.annotate({ "description": "The URL of the web resource.", "format": "uri" }), + "title": Schema.String.annotate({ "description": "The title of the web resource." }) + }).annotate({ "description": "A URL citation when using web search." }) + }).annotate({ "description": "A URL citation when using web search.\n" }) + ).annotate({ + "description": + "Annotations for the message, when applicable, as when using the\n[web search tool](/docs/guides/tools-web-search?api-mode=chat).\n" + }) + ), + "role": Schema.Literal("assistant").annotate({ "description": "The role of the author of this message." }), + "function_call": Schema.optionalKey( + Schema.Struct({ + "arguments": Schema.String.annotate({ + "description": + "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function." + }), + "name": Schema.String.annotate({ "description": "The name of the function to call." }) + }).annotate({ + "description": + "Deprecated and replaced by `tool_calls`. The name and arguments of a function that should be called, as generated by the model." + }) + ), + "audio": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "id": Schema.String.annotate({ "description": "Unique identifier for this audio response." }), + "expires_at": Schema.Number.annotate({ + "description": + "The Unix timestamp (in seconds) for when this audio response will\nno longer be accessible on the server for use in multi-turn\nconversations.\n", + "format": "unixtime" + }).check(Schema.isInt()), + "data": Schema.String.annotate({ + "description": + "Base64 encoded audio bytes generated by the model, in the format\nspecified in the request.\n" + }), + "transcript": Schema.String.annotate({ "description": "Transcript of the audio generated by the model." }) + }).annotate({ + "description": + "If the audio output modality is requested, this object contains data\nabout the audio response from the model. [Learn more](/docs/guides/audio).\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "The identifier of the chat message." }), + "content_parts": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ChatCompletionRequestMessageContentPartText, ChatCompletionRequestMessageContentPartImage], { + mode: "oneOf" + }) + ).annotate({ + "description": + "If a content parts array was provided, this is an array of `text` and `image_url` parts.\nOtherwise, null.\n" + }), + Schema.Null + ]) + ) + }).annotate({ "description": "A chat completion message generated by the model." }) + ).annotate({ "description": "An array of chat completion message objects.\n" }), + "first_id": Schema.String.annotate({ "description": "The identifier of the first chat message in the data array." }), + "last_id": Schema.String.annotate({ "description": "The identifier of the last chat message in the data array." }), + "has_more": Schema.Boolean.annotate({ "description": "Indicates whether there are more chat messages available." }) +}).annotate({ + "title": "ChatCompletionMessageList", + "description": "An object representing a list of chat completion messages.\n" +}) +export type ChatCompletionResponseMessage = { + readonly "content": string | null + readonly "refusal": string | null + readonly "tool_calls"?: ChatCompletionMessageToolCalls + readonly "annotations"?: ReadonlyArray< + { + readonly "type": "url_citation" + readonly "url_citation": { + readonly "end_index": number + readonly "start_index": number + readonly "url": string + readonly "title": string + } + } + > + readonly "role": "assistant" + readonly "function_call"?: { readonly "arguments": string; readonly "name": string } + readonly "audio"?: { + readonly "id": string + readonly "expires_at": number + readonly "data": string + readonly "transcript": string + } | null +} +export const ChatCompletionResponseMessage = Schema.Struct({ + "content": Schema.Union([Schema.String.annotate({ "description": "The contents of the message." }), Schema.Null]), + "refusal": Schema.Union([ + Schema.String.annotate({ "description": "The refusal message generated by the model." }), + Schema.Null + ]), + "tool_calls": Schema.optionalKey(ChatCompletionMessageToolCalls), + "annotations": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "type": Schema.Literal("url_citation").annotate({ + "description": "The type of the URL citation. Always `url_citation`." + }), + "url_citation": Schema.Struct({ + "end_index": Schema.Number.annotate({ + "description": "The index of the last character of the URL citation in the message." + }).check(Schema.isInt()), + "start_index": Schema.Number.annotate({ + "description": "The index of the first character of the URL citation in the message." + }).check(Schema.isInt()), + "url": Schema.String.annotate({ "description": "The URL of the web resource.", "format": "uri" }), + "title": Schema.String.annotate({ "description": "The title of the web resource." }) + }).annotate({ "description": "A URL citation when using web search." }) + }).annotate({ "description": "A URL citation when using web search.\n" }) + ).annotate({ + "description": + "Annotations for the message, when applicable, as when using the\n[web search tool](/docs/guides/tools-web-search?api-mode=chat).\n" + }) + ), + "role": Schema.Literal("assistant").annotate({ "description": "The role of the author of this message." }), + "function_call": Schema.optionalKey( + Schema.Struct({ + "arguments": Schema.String.annotate({ + "description": + "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function." + }), + "name": Schema.String.annotate({ "description": "The name of the function to call." }) + }).annotate({ + "description": + "Deprecated and replaced by `tool_calls`. The name and arguments of a function that should be called, as generated by the model." + }) + ), + "audio": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "id": Schema.String.annotate({ "description": "Unique identifier for this audio response." }), + "expires_at": Schema.Number.annotate({ + "description": + "The Unix timestamp (in seconds) for when this audio response will\nno longer be accessible on the server for use in multi-turn\nconversations.\n", + "format": "unixtime" + }).check(Schema.isInt()), + "data": Schema.String.annotate({ + "description": "Base64 encoded audio bytes generated by the model, in the format\nspecified in the request.\n" + }), + "transcript": Schema.String.annotate({ "description": "Transcript of the audio generated by the model." }) + }).annotate({ + "description": + "If the audio output modality is requested, this object contains data\nabout the audio response from the model. [Learn more](/docs/guides/audio).\n" + }), + Schema.Null + ])) +}).annotate({ "description": "A chat completion message generated by the model." }) +export type CreateChatCompletionStreamResponse = { + readonly "id": string + readonly "choices": ReadonlyArray< + { + readonly "delta": ChatCompletionStreamResponseDelta + readonly "logprobs"?: { + readonly "content": ReadonlyArray< + { + readonly "token": string + readonly "logprob": number + readonly "bytes": ReadonlyArray | null + readonly "top_logprobs": ReadonlyArray< + { readonly "token": string; readonly "logprob": number; readonly "bytes": ReadonlyArray | null } + > + } + > + readonly "refusal": ReadonlyArray< + { + readonly "token": string + readonly "logprob": number + readonly "bytes": ReadonlyArray | null + readonly "top_logprobs": ReadonlyArray< + { readonly "token": string; readonly "logprob": number; readonly "bytes": ReadonlyArray | null } + > + } + > + } + readonly "finish_reason": "stop" | "length" | "tool_calls" | "content_filter" | "function_call" | null + readonly "index": number + } + > + readonly "created": number + readonly "model": string + readonly "service_tier"?: ServiceTier + readonly "system_fingerprint"?: string + readonly "object": "chat.completion.chunk" + readonly "usage"?: { + readonly "completion_tokens": number + readonly "prompt_tokens": number + readonly "total_tokens": number + readonly "completion_tokens_details"?: { + readonly "accepted_prediction_tokens"?: number + readonly "audio_tokens"?: number + readonly "reasoning_tokens"?: number + readonly "rejected_prediction_tokens"?: number + } + readonly "prompt_tokens_details"?: { readonly "audio_tokens"?: number; readonly "cached_tokens"?: number } + } | null +} +export const CreateChatCompletionStreamResponse = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "A unique identifier for the chat completion. Each chunk has the same ID." + }), + "choices": Schema.Array(Schema.Struct({ + "delta": ChatCompletionStreamResponseDelta, + "logprobs": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "content": Schema.Union([ + Schema.Array(Schema.Struct({ + "token": Schema.String.annotate({ "description": "The token." }), + "logprob": Schema.Number.annotate({ + "description": + "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely." + }).check(Schema.isFinite()), + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": + "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token." + }), + Schema.Null + ]), + "top_logprobs": Schema.Array(Schema.Struct({ + "token": Schema.String.annotate({ "description": "The token." }), + "logprob": Schema.Number.annotate({ + "description": + "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely." + }).check(Schema.isFinite()), + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": + "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token." + }), + Schema.Null + ]) + })).annotate({ + "description": + "List of the most likely tokens and their log probability, at this token position. The number of entries may be fewer than the requested `top_logprobs`." + }) + })).annotate({ "description": "A list of message content tokens with log probability information." }) + ]), + "refusal": Schema.Union([ + Schema.Array(Schema.Struct({ + "token": Schema.String.annotate({ "description": "The token." }), + "logprob": Schema.Number.annotate({ + "description": + "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely." + }).check(Schema.isFinite()), + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": + "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token." + }), + Schema.Null + ]), + "top_logprobs": Schema.Array(Schema.Struct({ + "token": Schema.String.annotate({ "description": "The token." }), + "logprob": Schema.Number.annotate({ + "description": + "The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely." + }).check(Schema.isFinite()), + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt())).annotate({ + "description": + "A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token." + }), + Schema.Null + ]) + })).annotate({ + "description": + "List of the most likely tokens and their log probability, at this token position. The number of entries may be fewer than the requested `top_logprobs`." + }) + })).annotate({ "description": "A list of message refusal tokens with log probability information." }) + ]) + }).annotate({ "description": "Log probability information for the choice." }) + ])), + "finish_reason": Schema.Union([ + Schema.Literals(["stop", "length", "tool_calls", "content_filter", "function_call"]).annotate({ + "description": + "The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence,\n`length` if the maximum number of tokens specified in the request was reached,\n`content_filter` if content was omitted due to a flag from our content filters,\n`tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function.\n" + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence,\n`length` if the maximum number of tokens specified in the request was reached,\n`content_filter` if content was omitted due to a flag from our content filters,\n`tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function.\n" + }) + ]), + "index": Schema.Number.annotate({ "description": "The index of the choice in the list of choices." }).check( + Schema.isInt() + ) + })).annotate({ + "description": + "A list of chat completion choices. Can contain more than one elements if `n` is greater than 1. Can also be empty for the\nlast chunk if you set `stream_options: {\"include_usage\": true}`.\n" + }), + "created": Schema.Number.annotate({ + "description": + "The Unix timestamp (in seconds) of when the chat completion was created. Each chunk has the same timestamp.", + "format": "unixtime" + }).check(Schema.isInt()), + "model": Schema.String.annotate({ "description": "The model to generate the completion." }), + "service_tier": Schema.optionalKey(ServiceTier), + "system_fingerprint": Schema.optionalKey( + Schema.String.annotate({ + "description": + "This fingerprint represents the backend configuration that the model runs with.\nCan be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism.\n" + }) + ), + "object": Schema.Literal("chat.completion.chunk").annotate({ + "description": "The object type, which is always `chat.completion.chunk`." + }), + "usage": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ "description": "Number of tokens in the generated completion." }) + .check(Schema.isInt()), + "prompt_tokens": Schema.Number.annotate({ "description": "Number of tokens in the prompt." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ + "description": "Total number of tokens used in the request (prompt + completion)." + }).check(Schema.isInt()), + "completion_tokens_details": Schema.optionalKey( + Schema.Struct({ + "accepted_prediction_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "When using Predicted Outputs, the number of tokens in the\nprediction that appeared in the completion.\n" + }).check(Schema.isInt()) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio input tokens generated by the model." }).check( + Schema.isInt() + ) + ), + "reasoning_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Tokens generated by the model for reasoning." }).check( + Schema.isInt() + ) + ), + "rejected_prediction_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "When using Predicted Outputs, the number of tokens in the\nprediction that did not appear in the completion. However, like\nreasoning tokens, these tokens are still counted in the total\ncompletion tokens for purposes of billing, output, and context window\nlimits.\n" + }).check(Schema.isInt()) + ) + }).annotate({ "description": "Breakdown of tokens used in a completion." }) + ), + "prompt_tokens_details": Schema.optionalKey( + Schema.Struct({ + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio input tokens present in the prompt." }).check(Schema.isInt()) + ), + "cached_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cached tokens present in the prompt." }).check(Schema.isInt()) + ) + }).annotate({ "description": "Breakdown of tokens used in the prompt." }) + ) + }).annotate({ + "description": + "An optional field that will only be present when you set\n`stream_options: {\"include_usage\": true}` in your request. When present, it\ncontains a null value **except for the last chunk** which contains the\ntoken usage statistics for the entire request.\n\n**NOTE:** If the stream is interrupted or cancelled, you may not\nreceive the final usage chunk which contains the total token usage for\nthe request.\n" + }), + Schema.Null + ])) +}).annotate({ + "description": + "Represents a streamed chunk of a chat completion response returned\nby the model, based on the provided input. \n[Learn more](/docs/guides/streaming-responses).\n" +}) +export type ChatCompletionRequestAssistantMessage = { + readonly "content"?: string | ReadonlyArray | null + readonly "refusal"?: string | null + readonly "role": "assistant" + readonly "name"?: string + readonly "audio"?: { readonly "id": string } | null + readonly "tool_calls"?: ChatCompletionMessageToolCalls + readonly "function_call"?: { readonly "arguments": string; readonly "name": string } | null +} +export const ChatCompletionRequestAssistantMessage = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String.annotate({ "title": "Text content", "description": "The contents of the assistant message." }), + Schema.Array(ChatCompletionRequestAssistantMessageContentPart).annotate({ + "title": "Array of content parts", + "description": + "An array of content parts with a defined type. Can be one or more of type `text`, or exactly one of type `refusal`." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }).annotate({ + "description": + "The contents of the assistant message. Required unless `tool_calls` or `function_call` is specified.\n" + }), + Schema.Null + ]) + ), + "refusal": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The refusal message by the assistant." }), Schema.Null]) + ), + "role": Schema.Literal("assistant").annotate({ + "description": "The role of the messages author, in this case `assistant`." + }), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An optional name for the participant. Provides the model information to differentiate between participants of the same role." + }) + ), + "audio": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "id": Schema.String.annotate({ + "description": "Unique identifier for a previous audio response from the model.\n" + }) + }).annotate({ + "description": "Data about a previous audio response from the model.\n[Learn more](/docs/guides/audio).\n" + }), + Schema.Null + ]) + ), + "tool_calls": Schema.optionalKey(ChatCompletionMessageToolCalls), + "function_call": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "arguments": Schema.String.annotate({ + "description": + "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function." + }), + "name": Schema.String.annotate({ "description": "The name of the function to call." }) + }).annotate({ + "description": + "Deprecated and replaced by `tool_calls`. The name and arguments of a function that should be called, as generated by the model." + }), + Schema.Null + ])) +}).annotate({ + "title": "Assistant message", + "description": "Messages sent by the model in response to user messages.\n" +}) +export type ChatCompletionRequestSystemMessage = { + readonly "content": string | ReadonlyArray + readonly "role": "system" + readonly "name"?: string +} +export const ChatCompletionRequestSystemMessage = Schema.Struct({ + "content": Schema.Union([ + Schema.String.annotate({ "title": "Text content", "description": "The contents of the system message." }), + Schema.Array(ChatCompletionRequestSystemMessageContentPart).annotate({ + "title": "Array of content parts", + "description": + "An array of content parts with a defined type. For system messages, only type `text` is supported." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }).annotate({ "description": "The contents of the system message." }), + "role": Schema.Literal("system").annotate({ + "description": "The role of the messages author, in this case `system`." + }), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An optional name for the participant. Provides the model information to differentiate between participants of the same role." + }) + ) +}).annotate({ + "title": "System message", + "description": + "Developer-provided instructions that the model should follow, regardless of\nmessages sent by the user. With o1 models and newer, use `developer` messages\nfor this purpose instead.\n" +}) +export type ChatCompletionRequestToolMessage = { + readonly "role": "tool" + readonly "content": string | ReadonlyArray + readonly "tool_call_id": string +} +export const ChatCompletionRequestToolMessage = Schema.Struct({ + "role": Schema.Literal("tool").annotate({ "description": "The role of the messages author, in this case `tool`." }), + "content": Schema.Union([ + Schema.String.annotate({ "title": "Text content", "description": "The contents of the tool message." }), + Schema.Array(ChatCompletionRequestToolMessageContentPart).annotate({ + "title": "Array of content parts", + "description": "An array of content parts with a defined type. For tool messages, only type `text` is supported." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }).annotate({ "description": "The contents of the tool message." }), + "tool_call_id": Schema.String.annotate({ "description": "Tool call that this message is responding to." }) +}).annotate({ "title": "Tool message" }) +export type ChatCompletionRequestUserMessage = { + readonly "content": string | ReadonlyArray + readonly "role": "user" + readonly "name"?: string +} +export const ChatCompletionRequestUserMessage = Schema.Struct({ + "content": Schema.Union([ + Schema.String.annotate({ "title": "Text content", "description": "The text contents of the message." }), + Schema.Array(ChatCompletionRequestUserMessageContentPart).annotate({ + "title": "Array of content parts", + "description": + "An array of content parts with a defined type. Supported options differ based on the [model](/docs/models) being used to generate the response. Can contain text, image, or audio inputs." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }).annotate({ "description": "The contents of the user message.\n" }), + "role": Schema.Literal("user").annotate({ "description": "The role of the messages author, in this case `user`." }), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An optional name for the participant. Provides the model information to differentiate between participants of the same role." + }) + ) +}).annotate({ + "title": "User message", + "description": "Messages sent by an end user, containing prompts or additional context\ninformation.\n" +}) +export type VectorStoreSearchRequest = { + readonly "query": string | ReadonlyArray + readonly "rewrite_query"?: boolean + readonly "max_num_results"?: number + readonly "filters"?: ComparisonFilter | CompoundFilter + readonly "ranking_options"?: { + readonly "ranker"?: "none" | "auto" | "default-2024-11-15" + readonly "score_threshold"?: number + } +} +export const VectorStoreSearchRequest = Schema.Struct({ + "query": Schema.Union([ + Schema.String, + Schema.Array(Schema.String.annotate({ "description": "A list of queries to search for." })) + ], { mode: "oneOf" }).annotate({ "description": "A query string for a search" }), + "rewrite_query": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to rewrite the natural language query for vector search." }) + ), + "max_num_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum number of results to return. This number should be between 1 and 50 inclusive." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(50)) + ), + "filters": Schema.optionalKey( + Schema.Union([ComparisonFilter, CompoundFilter], { mode: "oneOf" }).annotate({ + "description": "A filter to apply based on file attributes." + }) + ), + "ranking_options": Schema.optionalKey( + Schema.Struct({ + "ranker": Schema.optionalKey( + Schema.Literals(["none", "auto", "default-2024-11-15"]).annotate({ + "description": "Enable re-ranking; set to `none` to disable, which can help reduce latency." + }) + ), + "score_threshold": Schema.optionalKey( + Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check( + Schema.isLessThanOrEqualTo(1) + ) + ) + }).annotate({ "description": "Ranking options for search." }) + ) +}) +export type FileSearchTool = { + readonly "type": "file_search" + readonly "vector_store_ids": ReadonlyArray + readonly "max_num_results"?: number + readonly "ranking_options"?: { + readonly "ranker"?: "auto" | "default-2024-11-15" + readonly "score_threshold"?: number + readonly "hybrid_search"?: { readonly "embedding_weight": number; readonly "text_weight": number } + } + readonly "filters"?: ComparisonFilter | CompoundFilter | null +} +export const FileSearchTool = Schema.Struct({ + "type": Schema.Literal("file_search").annotate({ + "description": "The type of the file search tool. Always `file_search`." + }), + "vector_store_ids": Schema.Array(Schema.String).annotate({ + "description": "The IDs of the vector stores to search." + }), + "max_num_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum number of results to return. This number should be between 1 and 50 inclusive." + }).check(Schema.isInt()) + ), + "ranking_options": Schema.optionalKey( + Schema.Struct({ + "ranker": Schema.optionalKey( + Schema.Literals(["auto", "default-2024-11-15"]).annotate({ + "description": "The ranker to use for the file search." + }) + ), + "score_threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The score threshold for the file search, a number between 0 and 1. Numbers closer to 1 will attempt to return only the most relevant results, but may return fewer results." + }).check(Schema.isFinite()) + ), + "hybrid_search": Schema.optionalKey( + Schema.Struct({ + "embedding_weight": Schema.Number.annotate({ + "description": "The weight of the embedding in the reciprocal ranking fusion." + }).check(Schema.isFinite()), + "text_weight": Schema.Number.annotate({ + "description": "The weight of the text in the reciprocal ranking fusion." + }).check(Schema.isFinite()) + }).annotate({ + "description": + "Weights that control how reciprocal rank fusion balances semantic embedding matches versus sparse keyword matches when hybrid search is enabled." + }) + ) + }).annotate({ "description": "Ranking options for search." }) + ), + "filters": Schema.optionalKey( + Schema.Union([ + Schema.Union([ComparisonFilter, CompoundFilter]).annotate({ "description": "A filter to apply." }), + Schema.Null + ]) + ) +}).annotate({ + "title": "File search", + "description": + "A tool that searches for relevant content from uploaded files. Learn more about the [file search tool](https://platform.openai.com/docs/guides/tools-file-search)." +}) +export type EvalRunOutputItemList = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const EvalRunOutputItemList = Schema.Struct({ + "object": Schema.Literal("list").annotate({ + "description": "The type of this object. It is always set to \"list\".\n" + }), + "data": Schema.Array(EvalRunOutputItem).annotate({ "description": "An array of eval run output item objects.\n" }), + "first_id": Schema.String.annotate({ + "description": "The identifier of the first eval run output item in the data array." + }), + "last_id": Schema.String.annotate({ + "description": "The identifier of the last eval run output item in the data array." + }), + "has_more": Schema.Boolean.annotate({ + "description": "Indicates whether there are more eval run output items available." + }) +}).annotate({ + "title": "EvalRunOutputItemList", + "description": "An object representing a list of output items for an evaluation run.\n" +}) +export type AssistantToolsFileSearch = { + readonly "type": "file_search" + readonly "file_search"?: { + readonly "max_num_results"?: number + readonly "ranking_options"?: FileSearchRankingOptions + } +} +export const AssistantToolsFileSearch = Schema.Struct({ + "type": Schema.Literal("file_search").annotate({ "description": "The type of tool being defined: `file_search`" }), + "file_search": Schema.optionalKey( + Schema.Struct({ + "max_num_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The maximum number of results the file search tool should output. The default is 20 for `gpt-4*` models and 5 for `gpt-3.5-turbo`. This number should be between 1 and 50 inclusive.\n\nNote that the file search tool may output fewer than `max_num_results` results. See the [file search tool documentation](/docs/assistants/tools/file-search#customizing-file-search-settings) for more information.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check(Schema.isLessThanOrEqualTo(50)) + ), + "ranking_options": Schema.optionalKey(FileSearchRankingOptions) + }).annotate({ "description": "Overrides for the file search tool." }) + ) +}).annotate({ "title": "FileSearch tool" }) +export type RunStepDetailsToolCallsFileSearchObject = { + readonly "id": string + readonly "type": "file_search" + readonly "file_search": { + readonly "ranking_options"?: RunStepDetailsToolCallsFileSearchRankingOptionsObject + readonly "results"?: ReadonlyArray + } +} +export const RunStepDetailsToolCallsFileSearchObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The ID of the tool call object." }), + "type": Schema.Literal("file_search").annotate({ + "description": "The type of tool call. This is always going to be `file_search` for this type of tool call." + }), + "file_search": Schema.Struct({ + "ranking_options": Schema.optionalKey(RunStepDetailsToolCallsFileSearchRankingOptionsObject), + "results": Schema.optionalKey( + Schema.Array(RunStepDetailsToolCallsFileSearchResultObject).annotate({ + "description": "The results of the file search." + }) + ) + }).annotate({ "description": "For now, this is always going to be an empty object." }) +}).annotate({ "title": "File search tool call" }) +export type AssistantToolsFunction = { readonly "type": "function"; readonly "function": FunctionObject } +export const AssistantToolsFunction = Schema.Struct({ + "type": Schema.Literal("function").annotate({ "description": "The type of tool being defined: `function`" }), + "function": FunctionObject +}).annotate({ "title": "Function tool" }) +export type ChatCompletionTool = { readonly "type": "function"; readonly "function": FunctionObject } +export const ChatCompletionTool = Schema.Struct({ + "type": Schema.Literal("function").annotate({ + "description": "The type of the tool. Currently, only `function` is supported." + }), + "function": FunctionObject +}).annotate({ "title": "Function tool", "description": "A function tool that can be used to generate a response.\n" }) +export type ImageEditStreamEvent = ImageEditPartialImageEvent | ImageEditCompletedEvent +export const ImageEditStreamEvent = Schema.Union([ImageEditPartialImageEvent, ImageEditCompletedEvent]) +export type ImageGenStreamEvent = ImageGenPartialImageEvent | ImageGenCompletedEvent +export const ImageGenStreamEvent = Schema.Union([ImageGenPartialImageEvent, ImageGenCompletedEvent]) +export type MessageObject = { + readonly "id": string + readonly "object": "thread.message" + readonly "created_at": number + readonly "thread_id": string + readonly "status": "in_progress" | "incomplete" | "completed" + readonly "incomplete_details": { + readonly "reason": "content_filter" | "max_tokens" | "run_cancelled" | "run_expired" | "run_failed" + } | null + readonly "completed_at": number | null + readonly "incomplete_at": number | null + readonly "role": "user" | "assistant" + readonly "content": ReadonlyArray< + | MessageContentImageFileObject + | MessageContentImageUrlObject + | MessageContentTextObject + | MessageContentRefusalObject + > + readonly "assistant_id": string | null + readonly "run_id": string | null + readonly "attachments": + | ReadonlyArray< + { + readonly "file_id"?: string + readonly "tools"?: ReadonlyArray + } + > + | null + readonly "metadata": Metadata +} +export const MessageObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "object": Schema.Literal("thread.message").annotate({ + "description": "The object type, which is always `thread.message`." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the message was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ + "description": "The [thread](/docs/api-reference/threads) ID that this message belongs to." + }), + "status": Schema.Literals(["in_progress", "incomplete", "completed"]).annotate({ + "description": "The status of the message, which can be either `in_progress`, `incomplete`, or `completed`." + }), + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.Literals(["content_filter", "max_tokens", "run_cancelled", "run_expired", "run_failed"]) + .annotate({ "description": "The reason the message is incomplete." }) + }).annotate({ "description": "On an incomplete message, details about why the message is incomplete." }), + Schema.Null + ]), + "completed_at": Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the message was completed.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "incomplete_at": Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the message was marked as incomplete.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "role": Schema.Literals(["user", "assistant"]).annotate({ + "description": "The entity that produced the message. One of `user` or `assistant`." + }), + "content": Schema.Array( + Schema.Union([ + MessageContentImageFileObject, + MessageContentImageUrlObject, + MessageContentTextObject, + MessageContentRefusalObject + ], { mode: "oneOf" }) + ).annotate({ "description": "The content of the message in array of text and/or images." }), + "assistant_id": Schema.Union([ + Schema.String.annotate({ + "description": + "If applicable, the ID of the [assistant](/docs/api-reference/assistants) that authored this message." + }), + Schema.Null + ]), + "run_id": Schema.Union([ + Schema.String.annotate({ + "description": + "The ID of the [run](/docs/api-reference/runs) associated with the creation of this message. Value is `null` when messages are created manually using the create message or create thread endpoints." + }), + Schema.Null + ]), + "attachments": Schema.Union([ + Schema.Array(Schema.Struct({ + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the file to attach to the message." }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([AssistantToolsCode, AssistantToolsFileSearchTypeOnly], { mode: "oneOf" })).annotate({ + "description": "The tools to add this file to." + }) + ) + })).annotate({ "description": "A list of files attached to the message, and the tools they were added to." }), + Schema.Null + ]), + "metadata": Metadata +}).annotate({ + "title": "The message object", + "description": "Represents a message within a [thread](/docs/api-reference/threads)." +}) +export type MessageDeltaObject = { + readonly "id": string + readonly "object": "thread.message.delta" + readonly "delta": { + readonly "role"?: "user" | "assistant" + readonly "content"?: ReadonlyArray< + | MessageDeltaContentImageFileObject + | MessageDeltaContentTextObject + | MessageDeltaContentRefusalObject + | MessageDeltaContentImageUrlObject + > + } +} +export const MessageDeltaObject = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The identifier of the message, which can be referenced in API endpoints." + }), + "object": Schema.Literal("thread.message.delta").annotate({ + "description": "The object type, which is always `thread.message.delta`." + }), + "delta": Schema.Struct({ + "role": Schema.optionalKey( + Schema.Literals(["user", "assistant"]).annotate({ + "description": "The entity that produced the message. One of `user` or `assistant`." + }) + ), + "content": Schema.optionalKey( + Schema.Array( + Schema.Union([ + MessageDeltaContentImageFileObject, + MessageDeltaContentTextObject, + MessageDeltaContentRefusalObject, + MessageDeltaContentImageUrlObject + ], { mode: "oneOf" }) + ).annotate({ "description": "The content of the message in array of text and/or images." }) + ) + }).annotate({ "description": "The delta containing the fields that have changed on the Message." }) +}).annotate({ + "title": "Message delta object", + "description": "Represents a message delta i.e. any changed fields on a message during streaming.\n" +}) +export type ListBatchesResponse = { + readonly "data": ReadonlyArray + readonly "first_id"?: string + readonly "last_id"?: string + readonly "has_more": boolean + readonly "object": "list" +} +export const ListBatchesResponse = Schema.Struct({ + "data": Schema.Array(Batch), + "first_id": Schema.optionalKey(Schema.String), + "last_id": Schema.optionalKey(Schema.String), + "has_more": Schema.Boolean, + "object": Schema.Literal("list") +}) +export type CreateThreadRequest = { + readonly "messages"?: ReadonlyArray + readonly "tool_resources"?: { + readonly "code_interpreter"?: { readonly "file_ids"?: ReadonlyArray } + readonly "file_search"?: { + readonly "vector_store_ids": ReadonlyArray + readonly "vector_stores"?: ReadonlyArray< + { + readonly "file_ids"?: ReadonlyArray + readonly "chunking_strategy"?: { readonly "type": "auto" } | { + readonly "type": "static" + readonly "static": { readonly "max_chunk_size_tokens": number; readonly "chunk_overlap_tokens": number } + } + readonly "metadata"?: Metadata + } + > + } | { + readonly "vector_stores": ReadonlyArray< + { + readonly "file_ids"?: ReadonlyArray + readonly "chunking_strategy"?: { readonly "type": "auto" } | { + readonly "type": "static" + readonly "static": { readonly "max_chunk_size_tokens": number; readonly "chunk_overlap_tokens": number } + } + readonly "metadata"?: Metadata + } + > + readonly "vector_store_ids"?: ReadonlyArray + } + } | null + readonly "metadata"?: Metadata +} +export const CreateThreadRequest = Schema.Struct({ + "messages": Schema.optionalKey( + Schema.Array(CreateMessageRequest).annotate({ + "description": "A list of [messages](/docs/api-reference/messages) to start the thread with." + }) + ), + "tool_resources": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "code_interpreter": Schema.optionalKey(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files associated with the tool.\n" + }).check(Schema.isMaxLength(20)) + ) + })), + "file_search": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "vector_store_ids": Schema.Array(Schema.String).annotate({ + "description": + "The [vector store](/docs/api-reference/vector-stores/object) attached to this thread. There can be a maximum of 1 vector store attached to the thread.\n" + }).check(Schema.isMaxLength(1)), + "vector_stores": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs to add to the vector store. For vector stores created before Nov 2025, there can be a maximum of 10,000 files in a vector store. For vector stores created starting in Nov 2025, the limit is 100,000,000 files.\n" + }).check(Schema.isMaxLength(100000000)) + ), + "chunking_strategy": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("auto").annotate({ "description": "Always `auto`." }) }) + .annotate({ + "title": "Auto Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }), + Schema.Struct({ + "type": Schema.Literal("static").annotate({ "description": "Always `static`." }), + "static": Schema.Struct({ + "max_chunk_size_tokens": Schema.Number.annotate({ + "description": + "The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(100)).check( + Schema.isLessThanOrEqualTo(4096) + ), + "chunk_overlap_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that overlap between chunks. The default value is `400`.\n\nNote that the overlap must not exceed half of `max_chunk_size_tokens`.\n" + }).check(Schema.isInt()) + }) + }).annotate({ + "title": "Static Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }) + ], { mode: "oneOf" }) + ), + "metadata": Schema.optionalKey(Metadata) + })).annotate({ + "description": + "A helper to create a [vector store](/docs/api-reference/vector-stores/object) with file_ids and attach it to this thread. There can be a maximum of 1 vector store attached to the thread.\n" + }).check(Schema.isMaxLength(1)) + ) + }), + Schema.Struct({ + "vector_stores": Schema.Array(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs to add to the vector store. For vector stores created before Nov 2025, there can be a maximum of 10,000 files in a vector store. For vector stores created starting in Nov 2025, the limit is 100,000,000 files.\n" + }).check(Schema.isMaxLength(100000000)) + ), + "chunking_strategy": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("auto").annotate({ "description": "Always `auto`." }) }) + .annotate({ + "title": "Auto Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }), + Schema.Struct({ + "type": Schema.Literal("static").annotate({ "description": "Always `static`." }), + "static": Schema.Struct({ + "max_chunk_size_tokens": Schema.Number.annotate({ + "description": + "The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(100)).check( + Schema.isLessThanOrEqualTo(4096) + ), + "chunk_overlap_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that overlap between chunks. The default value is `400`.\n\nNote that the overlap must not exceed half of `max_chunk_size_tokens`.\n" + }).check(Schema.isInt()) + }) + }).annotate({ + "title": "Static Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }) + ], { mode: "oneOf" }) + ), + "metadata": Schema.optionalKey(Metadata) + })).annotate({ + "description": + "A helper to create a [vector store](/docs/api-reference/vector-stores/object) with file_ids and attach it to this thread. There can be a maximum of 1 vector store attached to the thread.\n" + }).check(Schema.isMaxLength(1)), + "vector_store_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "The [vector store](/docs/api-reference/vector-stores/object) attached to this thread. There can be a maximum of 1 vector store attached to the thread.\n" + }).check(Schema.isMaxLength(1)) + ) + }) + ], { mode: "oneOf" })) + }).annotate({ + "description": + "A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n" + }), + Schema.Null + ])), + "metadata": Schema.optionalKey(Metadata) +}).annotate({ + "description": + "Options to create a new thread. If no thread is provided when running a\nrequest, an empty thread will be created.\n" +}) +export type ThreadStreamEvent = { + readonly "enabled"?: boolean + readonly "event": "thread.created" + readonly "data": ThreadObject +} +export const ThreadStreamEvent = Schema.Union([ + Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to enable input audio transcription." }) + ), + "event": Schema.Literal("thread.created"), + "data": ThreadObject + }).annotate({ "description": "Occurs when a new [thread](/docs/api-reference/threads/object) is created." }) +], { mode: "oneOf" }) +export type ModelIdsCompaction = ModelIdsResponses | string | null +export const ModelIdsCompaction = Schema.Union([ModelIdsResponses, Schema.String, Schema.Null]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-5` or `o3`. OpenAI offers a wide range of models with different capabilities, performance characteristics, and price points. Refer to the [model guide](/docs/models) to browse and compare available models." +}) +export type RealtimeTranslationClientSecretCreateResponse = { + readonly "value": string + readonly "expires_at": number + readonly "session": RealtimeTranslationSession +} +export const RealtimeTranslationClientSecretCreateResponse = Schema.Struct({ + "value": Schema.String.annotate({ "description": "The generated client secret value." }), + "expires_at": Schema.Number.annotate({ + "description": "Expiration timestamp for the client secret, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()), + "session": RealtimeTranslationSession +}).annotate({ + "title": "Realtime translation session and client secret", + "description": "Response from creating a translation session and client secret for the Realtime API.\n" +}) +export type RealtimeTranslationClientSecretCreateRequest = { + readonly "expires_after"?: { readonly "anchor"?: "created_at"; readonly "seconds"?: number } + readonly "session": RealtimeTranslationSessionCreateRequest +} +export const RealtimeTranslationClientSecretCreateRequest = Schema.Struct({ + "expires_after": Schema.optionalKey( + Schema.Struct({ + "anchor": Schema.optionalKey( + Schema.Literal("created_at").annotate({ + "description": + "The anchor point for the client secret expiration, meaning that `seconds` will be added to the `created_at` time of the client secret to produce an expiration timestamp. Only `created_at` is currently supported.\n" + }) + ), + "seconds": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The number of seconds from the anchor point to the expiration. Select a value between `10` and `7200` (2 hours). This default to 600 seconds (10 minutes) if not specified.\n", + "format": "int64" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(10)).check(Schema.isLessThanOrEqualTo(7200)) + ) + }).annotate({ + "title": "Client secret expiration", + "description": + "Configuration for the client secret expiration. Expiration refers to the time after which\na client secret will no longer be valid for creating sessions. The session itself may\ncontinue after that time once started. A secret can be used to create multiple sessions\nuntil it expires.\n" + }) + ), + "session": RealtimeTranslationSessionCreateRequest +}).annotate({ + "title": "Realtime translation client secret creation request", + "description": "Create a translation session and client secret for the Realtime API.\n" +}) +export type ProjectApiKeyListResponse = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ProjectApiKeyListResponse = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(ProjectApiKey), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type RealtimeConversationItem = + | RealtimeConversationItemMessageSystem + | RealtimeConversationItemMessageUser + | RealtimeConversationItemMessageAssistant + | RealtimeConversationItemFunctionCall + | RealtimeConversationItemFunctionCallOutput + | RealtimeMCPApprovalResponse + | RealtimeMCPListTools + | RealtimeMCPToolCall + | RealtimeMCPApprovalRequest +export const RealtimeConversationItem = Schema.Union([ + RealtimeConversationItemMessageSystem, + RealtimeConversationItemMessageUser, + RealtimeConversationItemMessageAssistant, + RealtimeConversationItemFunctionCall, + RealtimeConversationItemFunctionCallOutput, + RealtimeMCPApprovalResponse, + RealtimeMCPListTools, + RealtimeMCPToolCall, + RealtimeMCPApprovalRequest +]).annotate({ "description": "A single item within a Realtime conversation." }) +export type AssistantsApiResponseFormatOption = + | "auto" + | ResponseFormatText + | ResponseFormatJsonObject + | ResponseFormatJsonSchema +export const AssistantsApiResponseFormatOption = Schema.Union([ + Schema.Literal("auto").annotate({ "description": "`auto` is the default value\n" }), + ResponseFormatText, + ResponseFormatJsonObject, + ResponseFormatJsonSchema +], { mode: "oneOf" }).annotate({ + "description": + "Specifies the format that the model must output. Compatible with [GPT-4o](/docs/models#gpt-4o), [GPT-4 Turbo](/docs/models#gpt-4-turbo-and-gpt-4), and all GPT-3.5 Turbo models since `gpt-3.5-turbo-1106`.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema. Learn more in the [Structured Outputs guide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length.\n" +}) +export type TextResponseFormatConfiguration = + | ResponseFormatText + | TextResponseFormatJsonSchema + | ResponseFormatJsonObject +export const TextResponseFormatConfiguration = Schema.Union([ + ResponseFormatText, + TextResponseFormatJsonSchema, + ResponseFormatJsonObject +], { mode: "oneOf" }).annotate({ + "description": + "An object specifying the format that the model must output.\n\nConfiguring `{ \"type\": \"json_schema\" }` enables Structured Outputs, \nwhich ensures the model will match your supplied JSON schema. Learn more in the \n[Structured Outputs guide](/docs/guides/structured-outputs).\n\nThe default format is `{ \"type\": \"text\" }` with no additional options.\n\n**Not recommended for gpt-4o and newer models:**\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n" +}) +export type RealtimeCallCreateRequest = { + readonly "sdp": string + readonly "session"?: { + readonly "type": "realtime" + readonly "output_modalities"?: ReadonlyArray<"text" | "audio"> + readonly "model"?: + | string + | "gpt-realtime" + | "gpt-realtime-1.5" + | "gpt-realtime-2" + | "gpt-realtime-2025-08-28" + | "gpt-4o-realtime-preview" + | "gpt-4o-realtime-preview-2024-10-01" + | "gpt-4o-realtime-preview-2024-12-17" + | "gpt-4o-realtime-preview-2025-06-03" + | "gpt-4o-mini-realtime-preview" + | "gpt-4o-mini-realtime-preview-2024-12-17" + | "gpt-realtime-mini" + | "gpt-realtime-mini-2025-10-06" + | "gpt-realtime-mini-2025-12-15" + | "gpt-audio-1.5" + | "gpt-audio-mini" + | "gpt-audio-mini-2025-10-06" + | "gpt-audio-mini-2025-12-15" + readonly "instructions"?: string + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + readonly "delay"?: "minimal" | "low" | "medium" | "high" | "xhigh" + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: RealtimeTurnDetection + } + readonly "output"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "voice"?: VoiceIdsShared | { readonly "id": string } + readonly "speed"?: number + } + } + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> + readonly "tracing"?: "auto" | { + readonly "workflow_name"?: string + readonly "group_id"?: string + readonly "metadata"?: {} + } | null + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: ToolChoiceOptions | ToolChoiceFunction | ToolChoiceMCP + readonly "parallel_tool_calls"?: boolean + readonly "reasoning"?: RealtimeReasoning + readonly "max_output_tokens"?: number | "inf" + readonly "truncation"?: RealtimeTruncation + readonly "prompt"?: Prompt + } +} +export const RealtimeCallCreateRequest = Schema.Struct({ + "sdp": Schema.String.annotate({ + "description": "WebRTC Session Description Protocol (SDP) offer generated by the caller." + }), + "session": Schema.optionalKey( + Schema.Struct({ + "type": Schema.Literal("realtime").annotate({ + "description": "The type of session to create. Always `realtime` for the Realtime API.\n" + }), + "output_modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "The set of modalities the model can respond with. It defaults to `[\"audio\"]`, indicating\nthat the model will respond with audio plus a transcript. `[\"text\"]` can be used to make\nthe model respond with text only. It is not possible to request both `text` and `audio` at the same time.\n" + }) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15" + ]) + ]).annotate({ "description": "The Realtime model used for this session.\n" }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model calls. This field allows the client to guide the model on desired responses. The model can be instructed on response content and format, (e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good responses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion into your voice\", \"laugh frequently\"). The instructions are not guaranteed to be followed by the model, but they provide guidance to the model on the desired behavior.\n\nNote that the server sets default instructions which will be used if this field is not set and are visible in the `session.created` event at the start of the session.\n" + })), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the input audio." }) + ), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model to use for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use `gpt-4o-transcribe-diarize` when you need diarization with speaker labels.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The language of the input audio. Supplying the input language in\n[ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format\nwill improve accuracy and latency.\n" + }) + ), + "prompt": Schema.optionalKey(Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio\nsegment.\nFor `whisper-1`, the [prompt is a list of keywords](/docs/guides/speech-to-text#prompting).\nFor `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the prompt is a free text string, for example \"expect words related to technology\".\nPrompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + })), + "delay": Schema.optionalKey( + Schema.Literals(["minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Controls how long the model waits before emitting transcription text.\nHigher values can improve transcription accuracy at the cost of latency.\nOnly supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be set to `null` to turn off once on. Input audio transcription is not native to the model, since the model consumes audio directly. Transcription runs asynchronously through [the /audio/transcriptions endpoint](/docs/api-reference/audio/createTranscription) and should be treated as guidance of input audio content rather than precisely what the model heard. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "turn_detection": Schema.optionalKey(RealtimeTurnDetection) + })), + "output": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the output audio." }) + ), + "voice": Schema.optionalKey( + Schema.Union([ + VoiceIdsShared, + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) + }).annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice the model uses to respond. Supported built-in voices are\n`alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`,\n`marin`, and `cedar`. You may also provide a custom voice object with\nan `id`, for example `{ \"id\": \"voice_1234\" }`. Voice cannot be changed\nduring the session once the model has responded with audio at least once.\nWe recommend `marin` and `cedar` for best quality.\n" + }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The speed of the model's spoken response as a multiple of the original speed.\n1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. This value can only be changed in between model turns, not while a response is in progress.\n\nThis parameter is a post-processing adjustment to the audio after it is generated, it's\nalso possible to prompt the model to speak faster or slower.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0.25)).check( + Schema.isLessThanOrEqualTo(1.5) + ) + ) + })) + }).annotate({ "description": "Configuration for input and output audio.\n" }) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n\n`item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ), + "tracing": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literal("auto").annotate({ + "title": "auto", + "description": + "Enables tracing and sets default values for tracing configuration options. Always `auto`.\n" + }), + Schema.Struct({ + "workflow_name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The name of the workflow to attach to this trace. This is used to\nname the trace in the Traces Dashboard.\n" + }) + ), + "group_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The group id to attach to this trace to enable filtering and\ngrouping in the Traces Dashboard.\n" + }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The arbitrary metadata to attach to this trace to enable\nfiltering in the Traces Dashboard.\n" + }) + ) + }).annotate({ "title": "Tracing Configuration", "description": "Granular configuration for tracing.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Tracing Configuration", + "description": + "Realtime API can write session traces to the [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to disable tracing. Once\ntracing is enabled for a session, the configuration cannot be modified.\n\n`auto` will create a trace for the session with default values for the\nworkflow name, group id, and metadata.\n" + }), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([RealtimeFunctionTool, MCPTool], { mode: "oneOf" })).annotate({ + "description": "Tools available to the model." + }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMCP], { mode: "oneOf" }).annotate({ + "description": + "How the model chooses tools. Provide one of the string modes or force a specific\nfunction/MCP tool.\n" + }) + ), + "parallel_tool_calls": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether the model may call multiple tools in parallel. Only supported by\nreasoning Realtime models such as `gpt-realtime-2`.\n" + }) + ), + "reasoning": Schema.optionalKey(RealtimeReasoning), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "truncation": Schema.optionalKey(RealtimeTruncation), + "prompt": Schema.optionalKey(Prompt) + }).annotate({ "title": "Realtime session configuration", "description": "Realtime session object configuration." }) + ) +}).annotate({ + "title": "Realtime call creation request", + "description": + "Parameters required to initiate a realtime call and receive the SDP answer\nneeded to complete a WebRTC peer connection. Provide an SDP offer generated\nby your client and optionally configure the session that will answer the call." +}) +export type RealtimeClientEventSessionUpdate = { + readonly "event_id"?: string + readonly "type": "session.update" + readonly "session": { + readonly "type": "realtime" + readonly "output_modalities"?: ReadonlyArray<"text" | "audio"> + readonly "model"?: + | string + | "gpt-realtime" + | "gpt-realtime-1.5" + | "gpt-realtime-2" + | "gpt-realtime-2025-08-28" + | "gpt-4o-realtime-preview" + | "gpt-4o-realtime-preview-2024-10-01" + | "gpt-4o-realtime-preview-2024-12-17" + | "gpt-4o-realtime-preview-2025-06-03" + | "gpt-4o-mini-realtime-preview" + | "gpt-4o-mini-realtime-preview-2024-12-17" + | "gpt-realtime-mini" + | "gpt-realtime-mini-2025-10-06" + | "gpt-realtime-mini-2025-12-15" + | "gpt-audio-1.5" + | "gpt-audio-mini" + | "gpt-audio-mini-2025-10-06" + | "gpt-audio-mini-2025-12-15" + readonly "instructions"?: string + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + readonly "delay"?: "minimal" | "low" | "medium" | "high" | "xhigh" + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: RealtimeTurnDetection + } + readonly "output"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "voice"?: VoiceIdsShared | { readonly "id": string } + readonly "speed"?: number + } + } + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> + readonly "tracing"?: "auto" | { + readonly "workflow_name"?: string + readonly "group_id"?: string + readonly "metadata"?: {} + } | null + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: ToolChoiceOptions | ToolChoiceFunction | ToolChoiceMCP + readonly "parallel_tool_calls"?: boolean + readonly "reasoning"?: RealtimeReasoning + readonly "max_output_tokens"?: number | "inf" + readonly "truncation"?: RealtimeTruncation + readonly "prompt"?: Prompt + } | { + readonly "type": "transcription" + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: RealtimeAudioFormats + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + readonly "delay"?: "minimal" | "low" | "medium" | "high" | "xhigh" + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: RealtimeTurnDetection + } + } + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> + } +} +export const RealtimeClientEventSessionUpdate = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Optional client-generated ID used to identify this event. This is an arbitrary string that a client may assign. It will be passed back if there is an error with the event, but the corresponding `session.updated` event will not include it." + }).check(Schema.isMaxLength(512)) + ), + "type": Schema.Literal("session.update").annotate({ "description": "The event type, must be `session.update`." }), + "session": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("realtime").annotate({ + "description": "The type of session to create. Always `realtime` for the Realtime API.\n" + }), + "output_modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "The set of modalities the model can respond with. It defaults to `[\"audio\"]`, indicating\nthat the model will respond with audio plus a transcript. `[\"text\"]` can be used to make\nthe model respond with text only. It is not possible to request both `text` and `audio` at the same time.\n" + }) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15" + ]) + ]).annotate({ "description": "The Realtime model used for this session.\n" }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model calls. This field allows the client to guide the model on desired responses. The model can be instructed on response content and format, (e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good responses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion into your voice\", \"laugh frequently\"). The instructions are not guaranteed to be followed by the model, but they provide guidance to the model on the desired behavior.\n\nNote that the server sets default instructions which will be used if this field is not set and are visible in the `session.created` event at the start of the session.\n" + })), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the input audio." }) + ), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model to use for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use `gpt-4o-transcribe-diarize` when you need diarization with speaker labels.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The language of the input audio. Supplying the input language in\n[ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format\nwill improve accuracy and latency.\n" + }) + ), + "prompt": Schema.optionalKey(Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio\nsegment.\nFor `whisper-1`, the [prompt is a list of keywords](/docs/guides/speech-to-text#prompting).\nFor `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the prompt is a free text string, for example \"expect words related to technology\".\nPrompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + })), + "delay": Schema.optionalKey( + Schema.Literals(["minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Controls how long the model waits before emitting transcription text.\nHigher values can improve transcription accuracy at the cost of latency.\nOnly supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be set to `null` to turn off once on. Input audio transcription is not native to the model, since the model consumes audio directly. Transcription runs asynchronously through [the /audio/transcriptions endpoint](/docs/api-reference/audio/createTranscription) and should be treated as guidance of input audio content rather than precisely what the model heard. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "turn_detection": Schema.optionalKey(RealtimeTurnDetection) + })), + "output": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the output audio." }) + ), + "voice": Schema.optionalKey( + Schema.Union([ + VoiceIdsShared, + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) + }).annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice the model uses to respond. Supported built-in voices are\n`alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`,\n`marin`, and `cedar`. You may also provide a custom voice object with\nan `id`, for example `{ \"id\": \"voice_1234\" }`. Voice cannot be changed\nduring the session once the model has responded with audio at least once.\nWe recommend `marin` and `cedar` for best quality.\n" + }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The speed of the model's spoken response as a multiple of the original speed.\n1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. This value can only be changed in between model turns, not while a response is in progress.\n\nThis parameter is a post-processing adjustment to the audio after it is generated, it's\nalso possible to prompt the model to speak faster or slower.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0.25)).check( + Schema.isLessThanOrEqualTo(1.5) + ) + ) + })) + }).annotate({ "description": "Configuration for input and output audio.\n" }) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n\n`item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ), + "tracing": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literal("auto").annotate({ + "title": "auto", + "description": + "Enables tracing and sets default values for tracing configuration options. Always `auto`.\n" + }), + Schema.Struct({ + "workflow_name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The name of the workflow to attach to this trace. This is used to\nname the trace in the Traces Dashboard.\n" + }) + ), + "group_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The group id to attach to this trace to enable filtering and\ngrouping in the Traces Dashboard.\n" + }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The arbitrary metadata to attach to this trace to enable\nfiltering in the Traces Dashboard.\n" + }) + ) + }).annotate({ "title": "Tracing Configuration", "description": "Granular configuration for tracing.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Tracing Configuration", + "description": + "Realtime API can write session traces to the [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to disable tracing. Once\ntracing is enabled for a session, the configuration cannot be modified.\n\n`auto` will create a trace for the session with default values for the\nworkflow name, group id, and metadata.\n" + }), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([RealtimeFunctionTool, MCPTool], { mode: "oneOf" })).annotate({ + "description": "Tools available to the model." + }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMCP], { mode: "oneOf" }).annotate({ + "description": + "How the model chooses tools. Provide one of the string modes or force a specific\nfunction/MCP tool.\n" + }) + ), + "parallel_tool_calls": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether the model may call multiple tools in parallel. Only supported by\nreasoning Realtime models such as `gpt-realtime-2`.\n" + }) + ), + "reasoning": Schema.optionalKey(RealtimeReasoning), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "truncation": Schema.optionalKey(RealtimeTruncation), + "prompt": Schema.optionalKey(Prompt) + }).annotate({ + "title": "Realtime session configuration", + "description": "Update the Realtime session. Choose either a realtime\nsession or a transcription session.\n" + }), + Schema.Struct({ + "type": Schema.Literal("transcription").annotate({ + "description": "The type of session to create. Always `transcription` for transcription sessions.\n" + }), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey(RealtimeAudioFormats), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model to use for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use `gpt-4o-transcribe-diarize` when you need diarization with speaker labels.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The language of the input audio. Supplying the input language in\n[ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format\nwill improve accuracy and latency.\n" + }) + ), + "prompt": Schema.optionalKey(Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio\nsegment.\nFor `whisper-1`, the [prompt is a list of keywords](/docs/guides/speech-to-text#prompting).\nFor `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the prompt is a free text string, for example \"expect words related to technology\".\nPrompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + })), + "delay": Schema.optionalKey( + Schema.Literals(["minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Controls how long the model waits before emitting transcription text.\nHigher values can improve transcription accuracy at the cost of latency.\nOnly supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be set to `null` to turn off once on. Input audio transcription is not native to the model, since the model consumes audio directly. Transcription runs asynchronously through [the /audio/transcriptions endpoint](/docs/api-reference/audio/createTranscription) and should be treated as guidance of input audio content rather than precisely what the model heard. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "turn_detection": Schema.optionalKey(RealtimeTurnDetection) + })) + }).annotate({ "description": "Configuration for input and output audio.\n" }) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n\n`item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ) + }).annotate({ + "title": "Realtime transcription session configuration", + "description": "Update the Realtime session. Choose either a realtime\nsession or a transcription session.\n" + }) + ], { mode: "oneOf" }) +}).annotate({ + "description": + "Send this event to update the session’s configuration.\nThe client may send this event at any time to update any field\nexcept for `voice` and `model`. `voice` can be updated only if there have been no other audio outputs yet.\n\nWhen the server receives a `session.update`, it will respond\nwith a `session.updated` event showing the full, effective configuration.\nOnly the fields that are present in the `session.update` are updated. To clear a field like\n`instructions`, pass an empty string. To clear a field like `tools`, pass an empty array.\nTo clear a field like `turn_detection`, pass `null`.\n" +}) +export type RealtimeSession = { + readonly "id"?: string + readonly "object"?: "realtime.session" + readonly "modalities"?: ReadonlyArray<"text" | "audio"> + readonly "model"?: + | string + | "gpt-realtime" + | "gpt-realtime-1.5" + | "gpt-realtime-2025-08-28" + | "gpt-4o-realtime-preview" + | "gpt-4o-realtime-preview-2024-10-01" + | "gpt-4o-realtime-preview-2024-12-17" + | "gpt-4o-realtime-preview-2025-06-03" + | "gpt-4o-mini-realtime-preview" + | "gpt-4o-mini-realtime-preview-2024-12-17" + | "gpt-realtime-mini" + | "gpt-realtime-mini-2025-10-06" + | "gpt-realtime-mini-2025-12-15" + | "gpt-audio-1.5" + | "gpt-audio-mini" + | "gpt-audio-mini-2025-10-06" + | "gpt-audio-mini-2025-12-15" + readonly "instructions"?: string + readonly "voice"?: + | string + | "alloy" + | "ash" + | "ballad" + | "coral" + | "echo" + | "sage" + | "shimmer" + | "verse" + | "marin" + | "cedar" + readonly "input_audio_format"?: "pcm16" | "g711_ulaw" | "g711_alaw" + readonly "output_audio_format"?: "pcm16" | "g711_ulaw" | "g711_alaw" + readonly "input_audio_transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + } | null + readonly "turn_detection"?: RealtimeTurnDetection + readonly "input_audio_noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "speed"?: number + readonly "tracing"?: "auto" | { + readonly "workflow_name"?: string + readonly "group_id"?: string + readonly "metadata"?: {} + } | null + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: string + readonly "temperature"?: number + readonly "max_response_output_tokens"?: number | "inf" + readonly "expires_at"?: number + readonly "prompt"?: Prompt | null + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> | null +} +export const RealtimeSession = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Unique identifier for the session that looks like `sess_1234567890abcdef`.\n" + }) + ), + "object": Schema.optionalKey( + Schema.Literal("realtime.session").annotate({ "description": "The object type. Always `realtime.session`." }) + ), + "modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": "The set of modalities the model can respond with. To disable audio,\nset this to [\"text\"].\n" + }) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15" + ]) + ]).annotate({ "description": "The Realtime model used for this session.\n" }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model\ncalls. This field allows the client to guide the model on desired\nresponses. The model can be instructed on response content and format,\n(e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good\nresponses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion\ninto your voice\", \"laugh frequently\"). The instructions are not\nguaranteed to be followed by the model, but they provide guidance to the\nmodel on the desired behavior.\n\n\nNote that the server sets default instructions which will be used if this\nfield is not set and are visible in the `session.created` event at the\nstart of the session.\n" + })), + "voice": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]) + ]).annotate({ + "description": + "The voice the model uses to respond. Voice cannot be changed during the\nsession once the model has responded with audio at least once. Current\nvoice options are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`,\n`shimmer`, and `verse`.\n" + }) + ), + "input_audio_format": Schema.optionalKey( + Schema.Literals(["pcm16", "g711_ulaw", "g711_alaw"]).annotate({ + "description": + "The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\nFor `pcm16`, input audio must be 16-bit PCM at a 24kHz sample rate,\nsingle channel (mono), and little-endian byte order.\n" + }) + ), + "output_audio_format": Schema.optionalKey( + Schema.Literals(["pcm16", "g711_ulaw", "g711_alaw"]).annotate({ + "description": + "The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\nFor `pcm16`, output audio is sampled at a rate of 24kHz.\n" + }) + ), + "input_audio_transcription": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model used for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`.\n" + }) + ), + "language": Schema.optionalKey(Schema.String.annotate({ "description": "The language of the input audio.\n" })), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt configured for input audio transcription, when present.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be set to `null` to turn off once on. Input audio transcription is not native to the model, since the model consumes audio directly. Transcription runs asynchronously through [the /audio/transcriptions endpoint](https://platform.openai.com/docs/api-reference/audio/createTranscription) and should be treated as guidance of input audio content rather than precisely what the model heard. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }), + Schema.Null + ])), + "turn_detection": Schema.optionalKey(RealtimeTurnDetection), + "input_audio_noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The speed of the model's spoken response. 1.0 is the default speed. 0.25 is\nthe minimum speed. 1.5 is the maximum speed. This value can only be changed\nin between model turns, not while a response is in progress.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0.25)).check(Schema.isLessThanOrEqualTo(1.5)) + ), + "tracing": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literal("auto").annotate({ "description": "Default tracing mode for the session.\n" }), + Schema.Struct({ + "workflow_name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The name of the workflow to attach to this trace. This is used to\nname the trace in the traces dashboard.\n" + }) + ), + "group_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The group id to attach to this trace to enable filtering and\ngrouping in the traces dashboard.\n" + }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The arbitrary metadata to attach to this trace to enable\nfiltering in the traces dashboard.\n" + }) + ) + }).annotate({ "title": "Tracing Configuration", "description": "Granular configuration for tracing.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Tracing Configuration", + "description": + "Configuration options for tracing. Set to null to disable tracing. Once\ntracing is enabled for a session, the configuration cannot be modified.\n\n`auto` will create a trace for the session with default values for the\nworkflow name, group id, and metadata.\n" + }), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Array(RealtimeFunctionTool).annotate({ "description": "Tools (functions) available to the model." }) + ), + "tool_choice": Schema.optionalKey( + Schema.String.annotate({ + "description": "How the model chooses tools. Options are `auto`, `none`, `required`, or\nspecify a function.\n" + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Sampling temperature for the model, limited to [0.6, 1.2]. For audio models a temperature of 0.8 is highly recommended for best performance.\n" + }).check(Schema.isFinite()) + ), + "max_response_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Expiration timestamp for the session, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "prompt": Schema.optionalKey(Schema.Union([Prompt, Schema.Null])), + "include": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n- `item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }), + Schema.Null + ]) + ) +}).annotate({ "description": "Realtime session object for the beta interface." }) +export type RealtimeSessionCreateRequest = { + readonly "client_secret": { readonly "value": string; readonly "expires_at": number } + readonly "modalities"?: ReadonlyArray<"text" | "audio"> + readonly "instructions"?: string + readonly "voice"?: VoiceIdsShared | { readonly "id": string } + readonly "input_audio_format"?: string + readonly "output_audio_format"?: string + readonly "input_audio_transcription"?: { readonly "model"?: string } + readonly "speed"?: number + readonly "tracing"?: "auto" | { + readonly "workflow_name"?: string + readonly "group_id"?: string + readonly "metadata"?: {} + } + readonly "turn_detection"?: { + readonly "type"?: string + readonly "threshold"?: number + readonly "prefix_padding_ms"?: number + readonly "silence_duration_ms"?: number + } + readonly "tools"?: ReadonlyArray< + { + readonly "type"?: "function" + readonly "name"?: string + readonly "description"?: string + readonly "parameters"?: {} + } + > + readonly "tool_choice"?: string + readonly "temperature"?: number + readonly "max_response_output_tokens"?: number | "inf" + readonly "truncation"?: RealtimeTruncation + readonly "prompt"?: Prompt +} +export const RealtimeSessionCreateRequest = Schema.Struct({ + "client_secret": Schema.Struct({ + "value": Schema.String.annotate({ + "description": + "Ephemeral key usable in client environments to authenticate connections\nto the Realtime API. Use this in client-side environments rather than\na standard API token, which should only be used server-side.\n" + }), + "expires_at": Schema.Number.annotate({ + "description": "Timestamp for when the token expires. Currently, all tokens expire\nafter one minute.\n", + "format": "unixtime" + }).check(Schema.isInt()) + }).annotate({ "description": "Ephemeral key returned by the API." }), + "modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": "The set of modalities the model can respond with. To disable audio,\nset this to [\"text\"].\n" + }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model calls. This field allows the client to guide the model on desired responses. The model can be instructed on response content and format, (e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good responses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion into your voice\", \"laugh frequently\"). The instructions are not guaranteed to be followed by the model, but they provide guidance to the model on the desired behavior.\nNote that the server sets default instructions which will be used if this field is not set and are visible in the `session.created` event at the start of the session.\n" + })), + "voice": Schema.optionalKey( + Schema.Union([ + VoiceIdsShared, + Schema.Struct({ "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) }) + .annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice the model uses to respond. Supported built-in voices are\n`alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`,\n`marin`, and `cedar`. You may also provide a custom voice object with an\n`id`, for example `{ \"id\": \"voice_1234\" }`. Voice cannot be changed during\nthe session once the model has responded with audio at least once.\n" + }) + ), + "input_audio_format": Schema.optionalKey( + Schema.String.annotate({ + "description": "The format of input audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\n" + }) + ), + "output_audio_format": Schema.optionalKey( + Schema.String.annotate({ + "description": "The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\n" + }) + ), + "input_audio_transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey(Schema.String.annotate({ "description": "The model to use for transcription.\n" })) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be\nset to `null` to turn off once on. Input audio transcription is not native\nto the model, since the model consumes audio directly. Transcription runs\nasynchronously and should be treated as rough guidance\nrather than the representation understood by the model.\n" + }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The speed of the model's spoken response. 1.0 is the default speed. 0.25 is\nthe minimum speed. 1.5 is the maximum speed. This value can only be changed\nin between model turns, not while a response is in progress.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0.25)).check(Schema.isLessThanOrEqualTo(1.5)) + ), + "tracing": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto").annotate({ "description": "Default tracing mode for the session.\n" }), + Schema.Struct({ + "workflow_name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The name of the workflow to attach to this trace. This is used to\nname the trace in the traces dashboard.\n" + }) + ), + "group_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The group id to attach to this trace to enable filtering and\ngrouping in the traces dashboard.\n" + }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The arbitrary metadata to attach to this trace to enable\nfiltering in the traces dashboard.\n" + }) + ) + }).annotate({ "title": "Tracing Configuration", "description": "Granular configuration for tracing.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Tracing Configuration", + "description": + "Configuration options for tracing. Set to null to disable tracing. Once\ntracing is enabled for a session, the configuration cannot be modified.\n\n`auto` will create a trace for the session with default values for the\nworkflow name, group id, and metadata.\n" + }) + ), + "turn_detection": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.String.annotate({ "description": "Type of turn detection, only `server_vad` is currently supported.\n" }) + ), + "threshold": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Activation threshold for VAD (0.0 to 1.0), this defaults to 0.5. A\nhigher threshold will require louder audio to activate the model, and\nthus might perform better in noisy environments.\n" + }).check(Schema.isFinite()) + ), + "prefix_padding_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Amount of audio to include before the VAD detected speech (in\nmilliseconds). Defaults to 300ms.\n" + }).check(Schema.isInt()) + ), + "silence_duration_ms": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Duration of silence to detect speech stop (in milliseconds). Defaults\nto 500ms. With shorter values the model will respond more quickly,\nbut may jump in on short pauses from the user.\n" + }).check(Schema.isInt()) + ) + }).annotate({ + "description": + "Configuration for turn detection. Can be set to `null` to turn off. Server\nVAD means that the model will detect the start and end of speech based on\naudio volume and respond at the end of user speech.\n" + }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("function").annotate({ "description": "The type of the tool, i.e. `function`." }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the function." })), + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The description of the function, including guidance on when and how\nto call it, and guidance about what to tell the user when calling\n(if anything).\n" + }) + ), + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Parameters of the function in JSON Schema." }) + ) + })).annotate({ "description": "Tools (functions) available to the model." }) + ), + "tool_choice": Schema.optionalKey( + Schema.String.annotate({ + "description": "How the model chooses tools. Options are `auto`, `none`, `required`, or\nspecify a function.\n" + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Sampling temperature for the model, limited to [0.6, 1.2]. Defaults to 0.8.\n" + }).check(Schema.isFinite()) + ), + "max_response_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "truncation": Schema.optionalKey(RealtimeTruncation), + "prompt": Schema.optionalKey(Prompt) +}).annotate({ + "description": "A new Realtime session configuration, with an ephemeral key. Default TTL\nfor keys is one minute.\n" +}) +export type RealtimeSessionCreateRequestGA = { + readonly "type": "realtime" + readonly "output_modalities"?: ReadonlyArray<"text" | "audio"> + readonly "model"?: + | string + | "gpt-realtime" + | "gpt-realtime-1.5" + | "gpt-realtime-2" + | "gpt-realtime-2025-08-28" + | "gpt-4o-realtime-preview" + | "gpt-4o-realtime-preview-2024-10-01" + | "gpt-4o-realtime-preview-2024-12-17" + | "gpt-4o-realtime-preview-2025-06-03" + | "gpt-4o-mini-realtime-preview" + | "gpt-4o-mini-realtime-preview-2024-12-17" + | "gpt-realtime-mini" + | "gpt-realtime-mini-2025-10-06" + | "gpt-realtime-mini-2025-12-15" + | "gpt-audio-1.5" + | "gpt-audio-mini" + | "gpt-audio-mini-2025-10-06" + | "gpt-audio-mini-2025-12-15" + readonly "instructions"?: string + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + readonly "delay"?: "minimal" | "low" | "medium" | "high" | "xhigh" + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: RealtimeTurnDetection + } + readonly "output"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "voice"?: VoiceIdsShared | { readonly "id": string } + readonly "speed"?: number + } + } + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> + readonly "tracing"?: "auto" | { + readonly "workflow_name"?: string + readonly "group_id"?: string + readonly "metadata"?: {} + } | null + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: ToolChoiceOptions | ToolChoiceFunction | ToolChoiceMCP + readonly "parallel_tool_calls"?: boolean + readonly "reasoning"?: RealtimeReasoning + readonly "max_output_tokens"?: number | "inf" + readonly "truncation"?: RealtimeTruncation + readonly "prompt"?: Prompt +} +export const RealtimeSessionCreateRequestGA = Schema.Struct({ + "type": Schema.Literal("realtime").annotate({ + "description": "The type of session to create. Always `realtime` for the Realtime API.\n" + }), + "output_modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "The set of modalities the model can respond with. It defaults to `[\"audio\"]`, indicating\nthat the model will respond with audio plus a transcript. `[\"text\"]` can be used to make\nthe model respond with text only. It is not possible to request both `text` and `audio` at the same time.\n" + }) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15" + ]) + ]).annotate({ "description": "The Realtime model used for this session.\n" }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model calls. This field allows the client to guide the model on desired responses. The model can be instructed on response content and format, (e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good responses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion into your voice\", \"laugh frequently\"). The instructions are not guaranteed to be followed by the model, but they provide guidance to the model on the desired behavior.\n\nNote that the server sets default instructions which will be used if this field is not set and are visible in the `session.created` event at the start of the session.\n" + })), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the input audio." }) + ), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model to use for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`. Use `gpt-4o-transcribe-diarize` when you need diarization with speaker labels.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The language of the input audio. Supplying the input language in\n[ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g. `en`) format\nwill improve accuracy and latency.\n" + }) + ), + "prompt": Schema.optionalKey(Schema.String.annotate({ + "description": + "An optional text to guide the model's style or continue a previous audio\nsegment.\nFor `whisper-1`, the [prompt is a list of keywords](/docs/guides/speech-to-text#prompting).\nFor `gpt-4o-transcribe` models (excluding `gpt-4o-transcribe-diarize`), the prompt is a free text string, for example \"expect words related to technology\".\nPrompt is not supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + })), + "delay": Schema.optionalKey( + Schema.Literals(["minimal", "low", "medium", "high", "xhigh"]).annotate({ + "description": + "Controls how long the model waits before emitting transcription text.\nHigher values can improve transcription accuracy at the cost of latency.\nOnly supported with `gpt-realtime-whisper` in GA Realtime sessions.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be set to `null` to turn off once on. Input audio transcription is not native to the model, since the model consumes audio directly. Transcription runs asynchronously through [the /audio/transcriptions endpoint](/docs/api-reference/audio/createTranscription) and should be treated as guidance of input audio content rather than precisely what the model heard. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "turn_detection": Schema.optionalKey(RealtimeTurnDetection) + })), + "output": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the output audio." }) + ), + "voice": Schema.optionalKey( + Schema.Union([ + VoiceIdsShared, + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) + }).annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice the model uses to respond. Supported built-in voices are\n`alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`,\n`marin`, and `cedar`. You may also provide a custom voice object with\nan `id`, for example `{ \"id\": \"voice_1234\" }`. Voice cannot be changed\nduring the session once the model has responded with audio at least once.\nWe recommend `marin` and `cedar` for best quality.\n" + }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The speed of the model's spoken response as a multiple of the original speed.\n1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. This value can only be changed in between model turns, not while a response is in progress.\n\nThis parameter is a post-processing adjustment to the audio after it is generated, it's\nalso possible to prompt the model to speak faster or slower.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0.25)).check(Schema.isLessThanOrEqualTo(1.5)) + ) + })) + }).annotate({ "description": "Configuration for input and output audio.\n" }) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n\n`item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ), + "tracing": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literal("auto").annotate({ + "title": "auto", + "description": "Enables tracing and sets default values for tracing configuration options. Always `auto`.\n" + }), + Schema.Struct({ + "workflow_name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The name of the workflow to attach to this trace. This is used to\nname the trace in the Traces Dashboard.\n" + }) + ), + "group_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The group id to attach to this trace to enable filtering and\ngrouping in the Traces Dashboard.\n" + }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The arbitrary metadata to attach to this trace to enable\nfiltering in the Traces Dashboard.\n" + }) + ) + }).annotate({ "title": "Tracing Configuration", "description": "Granular configuration for tracing.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Tracing Configuration", + "description": + "Realtime API can write session traces to the [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to disable tracing. Once\ntracing is enabled for a session, the configuration cannot be modified.\n\n`auto` will create a trace for the session with default values for the\nworkflow name, group id, and metadata.\n" + }), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([RealtimeFunctionTool, MCPTool], { mode: "oneOf" })).annotate({ + "description": "Tools available to the model." + }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMCP], { mode: "oneOf" }).annotate({ + "description": + "How the model chooses tools. Provide one of the string modes or force a specific\nfunction/MCP tool.\n" + }) + ), + "parallel_tool_calls": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether the model may call multiple tools in parallel. Only supported by\nreasoning Realtime models such as `gpt-realtime-2`.\n" + }) + ), + "reasoning": Schema.optionalKey(RealtimeReasoning), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "truncation": Schema.optionalKey(RealtimeTruncation), + "prompt": Schema.optionalKey(Prompt) +}).annotate({ "title": "Realtime session configuration", "description": "Realtime session object configuration." }) +export type RealtimeSessionCreateResponseGA = { + readonly "type": "realtime" + readonly "id": string + readonly "object": "realtime.session" + readonly "expires_at"?: number + readonly "output_modalities"?: ReadonlyArray<"text" | "audio"> + readonly "model"?: + | string + | "gpt-realtime" + | "gpt-realtime-1.5" + | "gpt-realtime-2" + | "gpt-realtime-2025-08-28" + | "gpt-4o-realtime-preview" + | "gpt-4o-realtime-preview-2024-10-01" + | "gpt-4o-realtime-preview-2024-12-17" + | "gpt-4o-realtime-preview-2025-06-03" + | "gpt-4o-mini-realtime-preview" + | "gpt-4o-mini-realtime-preview-2024-12-17" + | "gpt-realtime-mini" + | "gpt-realtime-mini-2025-10-06" + | "gpt-realtime-mini-2025-12-15" + | "gpt-audio-1.5" + | "gpt-audio-mini" + | "gpt-audio-mini-2025-10-06" + | "gpt-audio-mini-2025-12-15" + readonly "instructions"?: string + readonly "audio"?: { + readonly "input"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "transcription"?: { + readonly "model"?: + | string + | "whisper-1" + | "gpt-4o-mini-transcribe" + | "gpt-4o-mini-transcribe-2025-12-15" + | "gpt-4o-transcribe" + | "gpt-4o-transcribe-diarize" + | "gpt-realtime-whisper" + readonly "language"?: string + readonly "prompt"?: string + } + readonly "noise_reduction"?: { readonly "type"?: NoiseReductionType } + readonly "turn_detection"?: RealtimeTurnDetection + } + readonly "output"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "voice"?: + | string + | "alloy" + | "ash" + | "ballad" + | "coral" + | "echo" + | "sage" + | "shimmer" + | "verse" + | "marin" + | "cedar" + readonly "speed"?: number + } + } + readonly "include"?: ReadonlyArray<"item.input_audio_transcription.logprobs"> + readonly "tracing"?: "auto" | { + readonly "workflow_name"?: string + readonly "group_id"?: string + readonly "metadata"?: {} + } | null + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: ToolChoiceOptions | ToolChoiceFunction | ToolChoiceMCP + readonly "reasoning"?: RealtimeReasoning + readonly "max_output_tokens"?: number | "inf" + readonly "truncation"?: RealtimeTruncation + readonly "prompt"?: Prompt +} +export const RealtimeSessionCreateResponseGA = Schema.Struct({ + "type": Schema.Literal("realtime").annotate({ + "description": "The type of session to create. Always `realtime` for the Realtime API.\n" + }), + "id": Schema.String.annotate({ + "description": "Unique identifier for the session that looks like `sess_1234567890abcdef`.\n" + }), + "object": Schema.Literal("realtime.session").annotate({ + "description": "The object type. Always `realtime.session`." + }), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Expiration timestamp for the session, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()) + ), + "output_modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "The set of modalities the model can respond with. It defaults to `[\"audio\"]`, indicating\nthat the model will respond with audio plus a transcript. `[\"text\"]` can be used to make\nthe model respond with text only. It is not possible to request both `text` and `audio` at the same time.\n" + }) + ), + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-realtime", + "gpt-realtime-1.5", + "gpt-realtime-2", + "gpt-realtime-2025-08-28", + "gpt-4o-realtime-preview", + "gpt-4o-realtime-preview-2024-10-01", + "gpt-4o-realtime-preview-2024-12-17", + "gpt-4o-realtime-preview-2025-06-03", + "gpt-4o-mini-realtime-preview", + "gpt-4o-mini-realtime-preview-2024-12-17", + "gpt-realtime-mini", + "gpt-realtime-mini-2025-10-06", + "gpt-realtime-mini-2025-12-15", + "gpt-audio-1.5", + "gpt-audio-mini", + "gpt-audio-mini-2025-10-06", + "gpt-audio-mini-2025-12-15" + ]) + ]).annotate({ "description": "The Realtime model used for this session.\n" }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model calls. This field allows the client to guide the model on desired responses. The model can be instructed on response content and format, (e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good responses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion into your voice\", \"laugh frequently\"). The instructions are not guaranteed to be followed by the model, but they provide guidance to the model on the desired behavior.\n\nNote that the server sets default instructions which will be used if this field is not set and are visible in the `session.created` event at the start of the session.\n" + })), + "audio": Schema.optionalKey( + Schema.Struct({ + "input": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the input audio." }) + ), + "transcription": Schema.optionalKey( + Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals([ + "whisper-1", + "gpt-4o-mini-transcribe", + "gpt-4o-mini-transcribe-2025-12-15", + "gpt-4o-transcribe", + "gpt-4o-transcribe-diarize", + "gpt-realtime-whisper" + ]) + ]).annotate({ + "description": + "The model used for transcription. Current options are `whisper-1`, `gpt-4o-mini-transcribe`, `gpt-4o-mini-transcribe-2025-12-15`, `gpt-4o-transcribe`, `gpt-4o-transcribe-diarize`, and `gpt-realtime-whisper`.\n" + }) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ "description": "The language of the input audio.\n" }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt configured for input audio transcription, when present.\n" + }) + ) + }).annotate({ + "description": + "Configuration for input audio transcription, defaults to off and can be set to `null` to turn off once on. Input audio transcription is not native to the model, since the model consumes audio directly. Transcription runs asynchronously through [the /audio/transcriptions endpoint](/docs/api-reference/audio/createTranscription) and should be treated as guidance of input audio content rather than precisely what the model heard. The client can optionally set the language and prompt for transcription, these offer additional guidance to the transcription service.\n" + }) + ), + "noise_reduction": Schema.optionalKey( + Schema.Struct({ "type": Schema.optionalKey(NoiseReductionType) }).annotate({ + "description": + "Configuration for input audio noise reduction. This can be set to `null` to turn off.\nNoise reduction filters audio added to the input audio buffer before it is sent to VAD and the model.\nFiltering the audio can improve VAD and turn detection accuracy (reducing false positives) and model performance by improving perception of the input audio.\n" + }) + ), + "turn_detection": Schema.optionalKey(RealtimeTurnDetection) + })), + "output": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the output audio." }) + ), + "voice": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]) + ]).annotate({ + "description": + "The voice the model uses to respond. Voice cannot be changed during the\nsession once the model has responded with audio at least once. Current\nvoice options are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`,\n`shimmer`, `verse`, `marin`, and `cedar`. We recommend `marin` and `cedar` for\nbest quality.\n" + }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The speed of the model's spoken response as a multiple of the original speed.\n1.0 is the default speed. 0.25 is the minimum speed. 1.5 is the maximum speed. This value can only be changed in between model turns, not while a response is in progress.\n\nThis parameter is a post-processing adjustment to the audio after it is generated, it's\nalso possible to prompt the model to speak faster or slower.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0.25)).check(Schema.isLessThanOrEqualTo(1.5)) + ) + })) + }).annotate({ "description": "Configuration for input and output audio.\n" }) + ), + "include": Schema.optionalKey( + Schema.Array(Schema.Literal("item.input_audio_transcription.logprobs")).annotate({ + "description": + "Additional fields to include in server outputs.\n\n`item.input_audio_transcription.logprobs`: Include logprobs for input audio transcription.\n" + }) + ), + "tracing": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literal("auto").annotate({ + "title": "auto", + "description": "Enables tracing and sets default values for tracing configuration options. Always `auto`.\n" + }), + Schema.Struct({ + "workflow_name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The name of the workflow to attach to this trace. This is used to\nname the trace in the Traces Dashboard.\n" + }) + ), + "group_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The group id to attach to this trace to enable filtering and\ngrouping in the Traces Dashboard.\n" + }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The arbitrary metadata to attach to this trace to enable\nfiltering in the Traces Dashboard.\n" + }) + ) + }).annotate({ "title": "Tracing Configuration", "description": "Granular configuration for tracing.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Tracing Configuration", + "description": + "Realtime API can write session traces to the [Traces Dashboard](https://platform.openai.com/logs?api=traces). Set to null to disable tracing. Once\ntracing is enabled for a session, the configuration cannot be modified.\n\n`auto` will create a trace for the session with default values for the\nworkflow name, group id, and metadata.\n" + }), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([RealtimeFunctionTool, MCPTool], { mode: "oneOf" })).annotate({ + "description": "Tools available to the model." + }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMCP], { mode: "oneOf" }).annotate({ + "description": + "How the model chooses tools. Provide one of the string modes or force a specific\nfunction/MCP tool.\n" + }) + ), + "reasoning": Schema.optionalKey(RealtimeReasoning), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "truncation": Schema.optionalKey(RealtimeTruncation), + "prompt": Schema.optionalKey(Prompt) +}).annotate({ + "title": "Realtime session configuration object", + "description": "A Realtime session configuration object.\n" +}) +export type CreateVectorStoreFileRequest = { + readonly "file_id": string + readonly "chunking_strategy"?: ChunkingStrategyRequestParam + readonly "attributes"?: VectorStoreFileAttributes +} +export const CreateVectorStoreFileRequest = Schema.Struct({ + "file_id": Schema.String.annotate({ + "description": + "A [File](/docs/api-reference/files) ID that the vector store should use. Useful for tools like `file_search` that can access files. For multi-file ingestion, we recommend [`file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch) to minimize per-vector-store write requests." + }), + "chunking_strategy": Schema.optionalKey(ChunkingStrategyRequestParam), + "attributes": Schema.optionalKey(VectorStoreFileAttributes) +}) +export type CreateTranscriptionResponseStreamEvent = + | TranscriptTextSegmentEvent + | TranscriptTextDeltaEvent + | TranscriptTextDoneEvent +export const CreateTranscriptionResponseStreamEvent = Schema.Union([ + TranscriptTextSegmentEvent, + TranscriptTextDeltaEvent, + TranscriptTextDoneEvent +]) +export type UsageResponse = { + readonly "object": "page" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next_page": string | null +} +export const UsageResponse = Schema.Struct({ + "object": Schema.Literal("page"), + "data": Schema.Array(UsageTimeBucket), + "has_more": Schema.Boolean, + "next_page": Schema.Union([Schema.String, Schema.Null]) +}) +export type ListVectorStoresResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean + readonly [x: string]: unknown +} +export const ListVectorStoresResponse = Schema.StructWithRest( + Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(VectorStoreObject), + "first_id": Schema.String, + "last_id": Schema.String, + "has_more": Schema.Boolean + }), + [Schema.Record(Schema.String, Schema.Json)] +) +export type ListVectorStoreFilesResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean + readonly [x: string]: unknown +} +export const ListVectorStoreFilesResponse = Schema.StructWithRest( + Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(VectorStoreFileObject), + "first_id": Schema.String, + "last_id": Schema.String, + "has_more": Schema.Boolean + }), + [Schema.Record(Schema.String, Schema.Json)] +) +export type VectorStoreSearchResultsPage = { + readonly "object": "vector_store.search_results.page" + readonly "search_query": ReadonlyArray + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "next_page": string | null +} +export const VectorStoreSearchResultsPage = Schema.Struct({ + "object": Schema.Literal("vector_store.search_results.page").annotate({ + "description": "The object type, which is always `vector_store.search_results.page`" + }), + "search_query": Schema.Array(Schema.String.annotate({ "description": "The query used for this search." })), + "data": Schema.Array(VectorStoreSearchResultItem).annotate({ "description": "The list of search result items." }), + "has_more": Schema.Boolean.annotate({ "description": "Indicates if there are more results to fetch." }), + "next_page": Schema.Union([ + Schema.String.annotate({ "description": "The token for the next page, if any." }), + Schema.Null + ]) +}) +export type CreateContainerBody = { + readonly "name": string + readonly "file_ids"?: ReadonlyArray + readonly "expires_after"?: { readonly "anchor": "last_active_at"; readonly "minutes": number } + readonly "skills"?: ReadonlyArray + readonly "memory_limit"?: "1g" | "4g" | "16g" | "64g" + readonly "network_policy"?: ContainerNetworkPolicyDisabledParam | ContainerNetworkPolicyAllowlistParam +} +export const CreateContainerBody = Schema.Struct({ + "name": Schema.String.annotate({ "description": "Name of the container to create." }), + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "IDs of files to copy to the container." }) + ), + "expires_after": Schema.optionalKey( + Schema.Struct({ + "anchor": Schema.Literal("last_active_at").annotate({ + "description": "Time anchor for the expiration time. Currently only 'last_active_at' is supported." + }), + "minutes": Schema.Number.check(Schema.isInt()) + }).annotate({ "description": "Container expiration time in seconds relative to the 'anchor' time." }) + ), + "skills": Schema.optionalKey( + Schema.Array(Schema.Union([SkillReferenceParam, InlineSkillParam], { mode: "oneOf" })).annotate({ + "description": "An optional list of skills referenced by id or inline data." + }) + ), + "memory_limit": Schema.optionalKey( + Schema.Literals(["1g", "4g", "16g", "64g"]).annotate({ + "description": "Optional memory limit for the container. Defaults to \"1g\"." + }) + ), + "network_policy": Schema.optionalKey( + Schema.Union([ContainerNetworkPolicyDisabledParam, ContainerNetworkPolicyAllowlistParam], { mode: "oneOf" }) + .annotate({ "description": "Network access policy for the container." }) + ) +}) +export type AutoCodeInterpreterToolParam = { + readonly "type": "auto" + readonly "file_ids"?: ReadonlyArray + readonly "memory_limit"?: "1g" | "4g" | "16g" | "64g" | null + readonly "network_policy"?: ContainerNetworkPolicyDisabledParam | ContainerNetworkPolicyAllowlistParam +} +export const AutoCodeInterpreterToolParam = Schema.Struct({ + "type": Schema.Literal("auto").annotate({ "description": "Always `auto`." }), + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": "An optional list of uploaded files to make available to your code." + }).check(Schema.isMaxLength(50)) + ), + "memory_limit": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["1g", "4g", "16g", "64g"]).annotate({ + "description": "The memory limit for the code interpreter container." + }), + Schema.Null + ]) + ), + "network_policy": Schema.optionalKey( + Schema.Union([ContainerNetworkPolicyDisabledParam, ContainerNetworkPolicyAllowlistParam], { mode: "oneOf" }) + .annotate({ "description": "Network access policy for the container." }) + ) +}).annotate({ + "title": "CodeInterpreterToolAuto", + "description": + "Configuration for a code interpreter container. Optionally specify the IDs of the files to run the code on." +}) +export type ContainerAutoParam = { + readonly "type": "container_auto" + readonly "file_ids"?: ReadonlyArray + readonly "memory_limit"?: "1g" | "4g" | "16g" | "64g" | null + readonly "network_policy"?: ContainerNetworkPolicyDisabledParam | ContainerNetworkPolicyAllowlistParam + readonly "skills"?: ReadonlyArray +} +export const ContainerAutoParam = Schema.Struct({ + "type": Schema.Literal("container_auto").annotate({ + "description": "Automatically creates a container for this request" + }), + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": "An optional list of uploaded files to make available to your code." + }).check(Schema.isMaxLength(50)) + ), + "memory_limit": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["1g", "4g", "16g", "64g"]).annotate({ "description": "The memory limit for the container." }), + Schema.Null + ]) + ), + "network_policy": Schema.optionalKey( + Schema.Union([ContainerNetworkPolicyDisabledParam, ContainerNetworkPolicyAllowlistParam], { mode: "oneOf" }) + .annotate({ "description": "Network access policy for the container." }) + ), + "skills": Schema.optionalKey( + Schema.Array(Schema.Union([SkillReferenceParam, InlineSkillParam], { mode: "oneOf" })).annotate({ + "description": "An optional list of skills referenced by id or inline data." + }).check(Schema.isMaxLength(200)) + ) +}) +export type EvalItemContentArray = ReadonlyArray +export const EvalItemContentArray = Schema.Array(EvalItemContentItem).annotate({ + "title": "An array of Input text, Output text, Input image, and Input audio", + "description": + "A list of inputs, each of which may be either an input text, output text, input\nimage, or input audio object.\n" +}) +export type OutputTextContent = { + readonly "type": "output_text" + readonly "text": string + readonly "annotations": ReadonlyArray + readonly "logprobs": ReadonlyArray +} +export const OutputTextContent = Schema.Struct({ + "type": Schema.Literal("output_text").annotate({ + "description": "The type of the output text. Always `output_text`." + }), + "text": Schema.String.annotate({ "description": "The text output from the model." }), + "annotations": Schema.Array(Annotation).annotate({ "description": "The annotations of the text output." }), + "logprobs": Schema.Array(LogProb) +}).annotate({ "title": "Output text", "description": "A text output from the model." }) +export type CustomToolCallOutput = { + readonly "type": "custom_tool_call_output" + readonly "id"?: string + readonly "call_id": string + readonly "output": string | ReadonlyArray +} +export const CustomToolCallOutput = Schema.Struct({ + "type": Schema.Literal("custom_tool_call_output").annotate({ + "description": "The type of the custom tool call output. Always `custom_tool_call_output`.\n" + }), + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The unique ID of the custom tool call output in the OpenAI platform.\n" }) + ), + "call_id": Schema.String.annotate({ + "description": "The call ID, used to map this custom tool call output to a custom tool call.\n" + }), + "output": Schema.Union([ + Schema.String.annotate({ + "title": "string output", + "description": "A string of the output of the custom tool call.\n" + }), + Schema.Array(FunctionAndCustomToolCallOutput).annotate({ + "title": "output content list", + "description": "Text, image, or file output of the custom tool call.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "The output from the custom tool call generated by your code.\nCan be a string or an list of output content.\n" + }) +}).annotate({ + "title": "Custom tool call output", + "description": "The output of a custom tool call from your code, being sent back to the model.\n" +}) +export type CustomToolCallOutputResource = { + readonly "type": "custom_tool_call_output" + readonly "id": string + readonly "call_id": string + readonly "output": string | ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "created_by"?: string +} +export const CustomToolCallOutputResource = Schema.Struct({ + "type": Schema.Literal("custom_tool_call_output").annotate({ + "description": "The type of the custom tool call output. Always `custom_tool_call_output`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the custom tool call output item.\n" }), + "call_id": Schema.String.annotate({ + "description": "The call ID, used to map this custom tool call output to a custom tool call.\n" + }), + "output": Schema.Union([ + Schema.String.annotate({ + "title": "string output", + "description": "A string of the output of the custom tool call.\n" + }), + Schema.Array(FunctionAndCustomToolCallOutput).annotate({ + "title": "output content list", + "description": "Text, image, or file output of the custom tool call.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "The output from the custom tool call generated by your code.\nCan be a string or an list of output content.\n" + }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item.\n" }) + ) +}).annotate({ + "title": "Custom tool call output", + "description": "The output of a custom tool call from your code, being sent back to the model.\n" +}) +export type FunctionToolCallOutput = { + readonly "id"?: string + readonly "type": "function_call_output" + readonly "call_id": string + readonly "output": string | ReadonlyArray + readonly "status"?: "in_progress" | "completed" | "incomplete" +} +export const FunctionToolCallOutput = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The unique ID of the function tool call output. Populated when this item\nis returned via API.\n" + }) + ), + "type": Schema.Literal("function_call_output").annotate({ + "description": "The type of the function tool call output. Always `function_call_output`.\n" + }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the function tool call generated by the model.\n" + }), + "output": Schema.Union([ + Schema.String.annotate({ + "title": "string output", + "description": "A string of the output of the function call.\n" + }), + Schema.Array(FunctionAndCustomToolCallOutput).annotate({ + "title": "output content list", + "description": "Text, image, or file output of the function call.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "The output from the function call generated by your code.\nCan be a string or an list of output content.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ) +}).annotate({ "title": "Function tool call output", "description": "The output of a function tool call.\n" }) +export type FunctionToolCallOutputResource = { + readonly "id": string + readonly "type": "function_call_output" + readonly "call_id": string + readonly "output": string | ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "created_by"?: string +} +export const FunctionToolCallOutputResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the function call tool output.\n" }), + "type": Schema.Literal("function_call_output").annotate({ + "description": "The type of the function tool call output. Always `function_call_output`.\n" + }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the function tool call generated by the model.\n" + }), + "output": Schema.Union([ + Schema.String.annotate({ + "title": "string output", + "description": "A string of the output of the function call.\n" + }), + Schema.Array(FunctionAndCustomToolCallOutput).annotate({ + "title": "output content list", + "description": "Text, image, or file output of the function call.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "The output from the function call generated by your code.\nCan be a string or an list of output content.\n" + }), + "status": Schema.Union([ + Schema.Literal("in_progress").annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + Schema.Literal("completed").annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + Schema.Literal("incomplete").annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item.\n" }) + ) +}).annotate({ "title": "Function tool call output", "description": "The output of a function tool call.\n" }) +export type InputMessageContentList = ReadonlyArray +export const InputMessageContentList = Schema.Array(InputContent).annotate({ + "title": "Input item content list", + "description": "A list of one or many input items to the model, containing different content \ntypes.\n" +}) +export type ComputerAction = + | ClickParam + | DoubleClickAction + | DragParam + | KeyPressAction + | MoveParam + | ScreenshotParam + | ScrollParam + | TypeParam + | WaitParam +export const ComputerAction = Schema.Union([ + ClickParam, + DoubleClickAction, + DragParam, + KeyPressAction, + MoveParam, + ScreenshotParam, + ScrollParam, + TypeParam, + WaitParam +], { mode: "oneOf" }) +export type NamespaceToolParam = { + readonly "type": "namespace" + readonly "name": string + readonly "description": string + readonly "tools": ReadonlyArray +} +export const NamespaceToolParam = Schema.Struct({ + "type": Schema.Literal("namespace").annotate({ "description": "The type of the tool. Always `namespace`." }), + "name": Schema.String.annotate({ "description": "The namespace name used in tool calls (for example, `crm`)." }) + .check(Schema.isMinLength(1)), + "description": Schema.String.annotate({ "description": "A description of the namespace shown to the model." }).check( + Schema.isMinLength(1) + ), + "tools": Schema.Array( + Schema.Union([FunctionToolParam, CustomToolParam], { mode: "oneOf" }).annotate({ + "description": "A function or custom tool that belongs to a namespace." + }) + ).annotate({ "description": "The function/custom tools available inside this namespace." }).check( + Schema.isMinLength(1) + ) +}).annotate({ "title": "Namespace", "description": "Groups function/custom tools under a shared namespace." }) +export type FunctionShellCallOutput = { + readonly "type": "shell_call_output" + readonly "id": string + readonly "call_id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "output": ReadonlyArray + readonly "max_output_length": number | null + readonly "created_by"?: string +} +export const FunctionShellCallOutput = Schema.Struct({ + "type": Schema.Literal("shell_call_output").annotate({ + "description": "The type of the shell call output. Always `shell_call_output`." + }), + "id": Schema.String.annotate({ + "description": "The unique ID of the shell call output. Populated when this item is returned via API." + }), + "call_id": Schema.String.annotate({ "description": "The unique ID of the shell tool call generated by the model." }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the shell call output. One of `in_progress`, `completed`, or `incomplete`." + }), + "output": Schema.Array(FunctionShellCallOutputContent).annotate({ + "description": "An array of shell call output contents" + }), + "max_output_length": Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum length of the shell command output. This is generated by the model and should be passed back with the raw output." + }).check(Schema.isInt()), + Schema.Null + ]), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item." }) + ) +}).annotate({ "title": "Shell call output", "description": "The output of a shell tool call that was emitted." }) +export type ImagesResponse = { + readonly "created": number + readonly "data"?: ReadonlyArray + readonly "background"?: "transparent" | "opaque" + readonly "output_format"?: "png" | "webp" | "jpeg" + readonly "size"?: "1024x1024" | "1024x1536" | "1536x1024" + readonly "quality"?: "low" | "medium" | "high" + readonly "usage"?: ImageGenUsage +} +export const ImagesResponse = Schema.Struct({ + "created": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the image was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "data": Schema.optionalKey(Schema.Array(Image).annotate({ "description": "The list of generated images." })), + "background": Schema.optionalKey( + Schema.Literals(["transparent", "opaque"]).annotate({ + "description": "The background parameter used for the image generation. Either `transparent` or `opaque`." + }) + ), + "output_format": Schema.optionalKey( + Schema.Literals(["png", "webp", "jpeg"]).annotate({ + "description": "The output format of the image generation. Either `png`, `webp`, or `jpeg`." + }) + ), + "size": Schema.optionalKey( + Schema.Literals(["1024x1024", "1024x1536", "1536x1024"]).annotate({ + "description": "The size of the image generated. Either `1024x1024`, `1024x1536`, or `1536x1024`." + }) + ), + "quality": Schema.optionalKey( + Schema.Literals(["low", "medium", "high"]).annotate({ + "description": "The quality of the image generated. Either `low`, `medium`, or `high`." + }) + ), + "usage": Schema.optionalKey(ImageGenUsage) +}).annotate({ "title": "Image generation response", "description": "The response from the image generation endpoint." }) +export type AssistantMessageItem = { + readonly "id": string + readonly "object": "chatkit.thread_item" + readonly "created_at": number + readonly "thread_id": string + readonly "type": "chatkit.assistant_message" + readonly "content": ReadonlyArray +} +export const AssistantMessageItem = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Identifier of the thread item." }), + "object": Schema.Literal("chatkit.thread_item").annotate({ + "description": "Type discriminator that is always `chatkit.thread_item`." + }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) for when the item was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ "description": "Identifier of the parent thread." }), + "type": Schema.Literal("chatkit.assistant_message").annotate({ + "description": "Type discriminator that is always `chatkit.assistant_message`." + }), + "content": Schema.Array(ResponseOutputText).annotate({ "description": "Ordered assistant response segments." }) +}).annotate({ "title": "Assistant message", "description": "Assistant-authored message within a thread." }) +export type ThreadListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "last_id": string | null + readonly "has_more": boolean +} +export const ThreadListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be `list`." }), + "data": Schema.Array(ThreadResource).annotate({ "description": "A list of items" }), + "first_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the first item in the list." }), + Schema.Null + ]), + "last_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the last item in the list." }), + Schema.Null + ]), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more items available." }) +}).annotate({ "title": "Threads", "description": "A paginated list of ChatKit threads." }) +export type AuditLog = { + readonly "id": string + readonly "type": AuditLogEventType + readonly "effective_at": number + readonly "project"?: { readonly "id"?: string; readonly "name"?: string } + readonly "actor"?: AuditLogActor | null + readonly "api_key.created"?: { + readonly "id"?: string + readonly "data"?: { readonly "scopes"?: ReadonlyArray } + } + readonly "api_key.updated"?: { + readonly "id"?: string + readonly "changes_requested"?: { readonly "scopes"?: ReadonlyArray } + } + readonly "api_key.deleted"?: { readonly "id"?: string } + readonly "checkpoint.permission.created"?: { + readonly "id"?: string + readonly "data"?: { readonly "project_id"?: string; readonly "fine_tuned_model_checkpoint"?: string } + } + readonly "checkpoint.permission.deleted"?: { readonly "id"?: string } + readonly "external_key.registered"?: { readonly "id"?: string; readonly "data"?: {} } + readonly "external_key.removed"?: { readonly "id"?: string } + readonly "group.created"?: { readonly "id"?: string; readonly "data"?: { readonly "group_name"?: string } } + readonly "group.updated"?: { + readonly "id"?: string + readonly "changes_requested"?: { readonly "group_name"?: string } + } + readonly "group.deleted"?: { readonly "id"?: string } + readonly "scim.enabled"?: { readonly "id"?: string } + readonly "scim.disabled"?: { readonly "id"?: string } + readonly "invite.sent"?: { + readonly "id"?: string + readonly "data"?: { readonly "email"?: string; readonly "role"?: string } + } + readonly "invite.accepted"?: { readonly "id"?: string } + readonly "invite.deleted"?: { readonly "id"?: string } + readonly "ip_allowlist.created"?: { + readonly "id"?: string + readonly "name"?: string + readonly "allowed_ips"?: ReadonlyArray + } + readonly "ip_allowlist.updated"?: { readonly "id"?: string; readonly "allowed_ips"?: ReadonlyArray } + readonly "ip_allowlist.deleted"?: { + readonly "id"?: string + readonly "name"?: string + readonly "allowed_ips"?: ReadonlyArray + } + readonly "ip_allowlist.config.activated"?: { + readonly "configs"?: ReadonlyArray<{ readonly "id"?: string; readonly "name"?: string }> + } + readonly "ip_allowlist.config.deactivated"?: { + readonly "configs"?: ReadonlyArray<{ readonly "id"?: string; readonly "name"?: string }> + } + readonly "login.succeeded"?: {} + readonly "login.failed"?: { readonly "error_code"?: string; readonly "error_message"?: string } + readonly "logout.succeeded"?: {} + readonly "logout.failed"?: { readonly "error_code"?: string; readonly "error_message"?: string } + readonly "organization.updated"?: { + readonly "id"?: string + readonly "changes_requested"?: { + readonly "title"?: string + readonly "description"?: string + readonly "name"?: string + readonly "threads_ui_visibility"?: string + readonly "usage_dashboard_visibility"?: string + readonly "api_call_logging"?: string + readonly "api_call_logging_project_ids"?: string + } + } + readonly "project.created"?: { + readonly "id"?: string + readonly "data"?: { readonly "name"?: string; readonly "title"?: string } + } + readonly "project.updated"?: { readonly "id"?: string; readonly "changes_requested"?: { readonly "title"?: string } } + readonly "project.archived"?: { readonly "id"?: string } + readonly "project.deleted"?: { readonly "id"?: string } + readonly "rate_limit.updated"?: { + readonly "id"?: string + readonly "changes_requested"?: { + readonly "max_requests_per_1_minute"?: number + readonly "max_tokens_per_1_minute"?: number + readonly "max_images_per_1_minute"?: number + readonly "max_audio_megabytes_per_1_minute"?: number + readonly "max_requests_per_1_day"?: number + readonly "batch_1_day_max_input_tokens"?: number + } + } + readonly "rate_limit.deleted"?: { readonly "id"?: string } + readonly "role.created"?: { + readonly "id"?: string + readonly "role_name"?: string + readonly "permissions"?: ReadonlyArray + readonly "resource_type"?: string + readonly "resource_id"?: string + } + readonly "role.updated"?: { + readonly "id"?: string + readonly "changes_requested"?: { + readonly "role_name"?: string + readonly "resource_id"?: string + readonly "resource_type"?: string + readonly "permissions_added"?: ReadonlyArray + readonly "permissions_removed"?: ReadonlyArray + readonly "description"?: string + readonly "metadata"?: {} + } + } + readonly "role.deleted"?: { readonly "id"?: string } + readonly "role.assignment.created"?: { + readonly "id"?: string + readonly "principal_id"?: string + readonly "principal_type"?: string + readonly "resource_id"?: string + readonly "resource_type"?: string + } + readonly "role.assignment.deleted"?: { + readonly "id"?: string + readonly "principal_id"?: string + readonly "principal_type"?: string + readonly "resource_id"?: string + readonly "resource_type"?: string + } + readonly "service_account.created"?: { readonly "id"?: string; readonly "data"?: { readonly "role"?: string } } + readonly "service_account.updated"?: { + readonly "id"?: string + readonly "changes_requested"?: { readonly "role"?: string } + } + readonly "service_account.deleted"?: { readonly "id"?: string } + readonly "user.added"?: { readonly "id"?: string; readonly "data"?: { readonly "role"?: string } } + readonly "user.updated"?: { readonly "id"?: string; readonly "changes_requested"?: { readonly "role"?: string } } + readonly "user.deleted"?: { readonly "id"?: string } + readonly "certificate.created"?: { readonly "id"?: string; readonly "name"?: string } + readonly "certificate.updated"?: { readonly "id"?: string; readonly "name"?: string } + readonly "certificate.deleted"?: { readonly "id"?: string; readonly "name"?: string; readonly "certificate"?: string } + readonly "certificates.activated"?: { + readonly "certificates"?: ReadonlyArray<{ readonly "id"?: string; readonly "name"?: string }> + } + readonly "certificates.deactivated"?: { + readonly "certificates"?: ReadonlyArray<{ readonly "id"?: string; readonly "name"?: string }> + } +} +export const AuditLog = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The ID of this log." }), + "type": AuditLogEventType, + "effective_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of the event.", + "format": "unixtime" + }).check(Schema.isInt()), + "project": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The project ID." })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The project title." })) + }).annotate({ + "description": + "The project that the action was scoped to. Absent for actions not scoped to projects. Note that any admin actions taken via Admin API keys are associated with the default project." + }) + ), + "actor": Schema.optionalKey(Schema.Union([AuditLogActor, Schema.Null])), + "api_key.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The tracking ID of the API key." })), + "data": Schema.optionalKey( + Schema.Struct({ + "scopes": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": "A list of scopes allowed for the API key, e.g. `[\"api.model.request\"]`" + }) + ) + }).annotate({ "description": "The payload used to create the API key." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "api_key.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The tracking ID of the API key." })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "scopes": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": "A list of scopes allowed for the API key, e.g. `[\"api.model.request\"]`" + }) + ) + }).annotate({ "description": "The payload used to update the API key." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "api_key.deleted": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The tracking ID of the API key." })) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "checkpoint.permission.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the checkpoint permission." })), + "data": Schema.optionalKey( + Schema.Struct({ + "project_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The ID of the project that the checkpoint permission was created for." + }) + ), + "fine_tuned_model_checkpoint": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the fine-tuned model checkpoint." }) + ) + }).annotate({ "description": "The payload used to create the checkpoint permission." }) + ) + }).annotate({ + "description": "The project and fine-tuned model checkpoint that the checkpoint permission was created for." + }) + ), + "checkpoint.permission.deleted": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the checkpoint permission." })) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "external_key.registered": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the external key configuration." })), + "data": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "The configuration for the external key." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "external_key.removed": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the external key configuration." })) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "group.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the group." })), + "data": Schema.optionalKey( + Schema.Struct({ + "group_name": Schema.optionalKey(Schema.String.annotate({ "description": "The group name." })) + }).annotate({ "description": "Information about the created group." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "group.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the group." })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "group_name": Schema.optionalKey(Schema.String.annotate({ "description": "The updated group name." })) + }).annotate({ "description": "The payload used to update the group." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "group.deleted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the group." })) }) + .annotate({ "description": "The details for events with this `type`." }) + ), + "scim.enabled": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the SCIM was enabled for." })) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "scim.disabled": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the SCIM was disabled for." })) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "invite.sent": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the invite." })), + "data": Schema.optionalKey( + Schema.Struct({ + "email": Schema.optionalKey( + Schema.String.annotate({ "description": "The email invited to the organization." }) + ), + "role": Schema.optionalKey( + Schema.String.annotate({ + "description": "The role the email was invited to be. Is either `owner` or `member`." + }) + ) + }).annotate({ "description": "The payload used to create the invite." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "invite.accepted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the invite." })) }) + .annotate({ "description": "The details for events with this `type`." }) + ), + "invite.deleted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the invite." })) }) + .annotate({ "description": "The details for events with this `type`." }) + ), + "ip_allowlist.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the IP allowlist configuration." })), + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "The name of the IP allowlist configuration." }) + ), + "allowed_ips": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": "The IP addresses or CIDR ranges included in the configuration." + }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "ip_allowlist.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the IP allowlist configuration." })), + "allowed_ips": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": "The updated set of IP addresses or CIDR ranges in the configuration." + }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "ip_allowlist.deleted": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The ID of the IP allowlist configuration." })), + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "The name of the IP allowlist configuration." }) + ), + "allowed_ips": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": "The IP addresses or CIDR ranges that were in the configuration." + }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "ip_allowlist.config.activated": Schema.optionalKey( + Schema.Struct({ + "configs": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the IP allowlist configuration." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "The name of the IP allowlist configuration." }) + ) + }) + ).annotate({ "description": "The configurations that were activated." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "ip_allowlist.config.deactivated": Schema.optionalKey( + Schema.Struct({ + "configs": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the IP allowlist configuration." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "The name of the IP allowlist configuration." }) + ) + }) + ).annotate({ "description": "The configurations that were deactivated." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "login.succeeded": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": "This event has no additional fields beyond the standard audit log attributes." + }) + ), + "login.failed": Schema.optionalKey( + Schema.Struct({ + "error_code": Schema.optionalKey(Schema.String.annotate({ "description": "The error code of the failure." })), + "error_message": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message of the failure." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "logout.succeeded": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": "This event has no additional fields beyond the standard audit log attributes." + }) + ), + "logout.failed": Schema.optionalKey( + Schema.Struct({ + "error_code": Schema.optionalKey(Schema.String.annotate({ "description": "The error code of the failure." })), + "error_message": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message of the failure." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "organization.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The organization ID." })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "title": Schema.optionalKey(Schema.String.annotate({ "description": "The organization title." })), + "description": Schema.optionalKey(Schema.String.annotate({ "description": "The organization description." })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The organization name." })), + "threads_ui_visibility": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Visibility of the threads page which shows messages created with the Assistants API and Playground. One of `ANY_ROLE`, `OWNERS`, or `NONE`." + }) + ), + "usage_dashboard_visibility": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Visibility of the usage dashboard which shows activity and costs for your organization. One of `ANY_ROLE` or `OWNERS`." + }) + ), + "api_call_logging": Schema.optionalKey( + Schema.String.annotate({ + "description": + "How your organization logs data from supported API calls. One of `disabled`, `enabled_per_call`, `enabled_for_all_projects`, or `enabled_for_selected_projects`" + }) + ), + "api_call_logging_project_ids": Schema.optionalKey( + Schema.String.annotate({ + "description": "The list of project ids if api_call_logging is set to `enabled_for_selected_projects`" + }) + ) + }).annotate({ "description": "The payload used to update the organization settings." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "project.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The project ID." })), + "data": Schema.optionalKey( + Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The project name." })), + "title": Schema.optionalKey( + Schema.String.annotate({ "description": "The title of the project as seen on the dashboard." }) + ) + }).annotate({ "description": "The payload used to create the project." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "project.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The project ID." })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "title": Schema.optionalKey( + Schema.String.annotate({ "description": "The title of the project as seen on the dashboard." }) + ) + }).annotate({ "description": "The payload used to update the project." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "project.archived": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The project ID." })) }).annotate({ + "description": "The details for events with this `type`." + }) + ), + "project.deleted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The project ID." })) }).annotate({ + "description": "The details for events with this `type`." + }) + ), + "rate_limit.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The rate limit ID" })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "max_requests_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum requests per minute." }).check(Schema.isInt()) + ), + "max_tokens_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum tokens per minute." }).check(Schema.isInt()) + ), + "max_images_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum images per minute. Only relevant for certain models." + }).check(Schema.isInt()) + ), + "max_audio_megabytes_per_1_minute": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum audio megabytes per minute. Only relevant for certain models." + }).check(Schema.isInt()) + ), + "max_requests_per_1_day": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum requests per day. Only relevant for certain models." }) + .check(Schema.isInt()) + ), + "batch_1_day_max_input_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The maximum batch input tokens per day. Only relevant for certain models." + }).check(Schema.isInt()) + ) + }).annotate({ "description": "The payload used to update the rate limits." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "rate_limit.deleted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The rate limit ID" })) }) + .annotate({ "description": "The details for events with this `type`." }) + ), + "role.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The role ID." })), + "role_name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the role." })), + "permissions": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "The permissions granted by the role." }) + ), + "resource_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of resource the role belongs to." }) + ), + "resource_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The resource the role is scoped to." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "role.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The role ID." })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "role_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The updated role name, when provided." }) + ), + "resource_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The resource the role is scoped to." }) + ), + "resource_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of resource the role belongs to." }) + ), + "permissions_added": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "The permissions added to the role." }) + ), + "permissions_removed": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "The permissions removed from the role." }) + ), + "description": Schema.optionalKey( + Schema.String.annotate({ "description": "The updated role description, when provided." }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Additional metadata stored on the role." }) + ) + }).annotate({ "description": "The payload used to update the role." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "role.deleted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The role ID." })) }).annotate({ + "description": "The details for events with this `type`." + }) + ), + "role.assignment.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The identifier of the role assignment." })), + "principal_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The principal (user or group) that received the role." }) + ), + "principal_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of principal (user or group) that received the role." }) + ), + "resource_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The resource the role assignment is scoped to." }) + ), + "resource_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of resource the role assignment is scoped to." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "role.assignment.deleted": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The identifier of the role assignment." })), + "principal_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The principal (user or group) that had the role removed." }) + ), + "principal_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of principal (user or group) that had the role removed." }) + ), + "resource_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The resource the role assignment was scoped to." }) + ), + "resource_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of resource the role assignment was scoped to." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "service_account.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The service account ID." })), + "data": Schema.optionalKey( + Schema.Struct({ + "role": Schema.optionalKey( + Schema.String.annotate({ "description": "The role of the service account. Is either `owner` or `member`." }) + ) + }).annotate({ "description": "The payload used to create the service account." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "service_account.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The service account ID." })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "role": Schema.optionalKey( + Schema.String.annotate({ "description": "The role of the service account. Is either `owner` or `member`." }) + ) + }).annotate({ "description": "The payload used to updated the service account." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "service_account.deleted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The service account ID." })) }) + .annotate({ "description": "The details for events with this `type`." }) + ), + "user.added": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The user ID." })), + "data": Schema.optionalKey( + Schema.Struct({ + "role": Schema.optionalKey( + Schema.String.annotate({ "description": "The role of the user. Is either `owner` or `member`." }) + ) + }).annotate({ "description": "The payload used to add the user to the project." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "user.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The project ID." })), + "changes_requested": Schema.optionalKey( + Schema.Struct({ + "role": Schema.optionalKey( + Schema.String.annotate({ "description": "The role of the user. Is either `owner` or `member`." }) + ) + }).annotate({ "description": "The payload used to update the user." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "user.deleted": Schema.optionalKey( + Schema.Struct({ "id": Schema.optionalKey(Schema.String.annotate({ "description": "The user ID." })) }).annotate({ + "description": "The details for events with this `type`." + }) + ), + "certificate.created": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The certificate ID." })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the certificate." })) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "certificate.updated": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The certificate ID." })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the certificate." })) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "certificate.deleted": Schema.optionalKey( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The certificate ID." })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the certificate." })), + "certificate": Schema.optionalKey( + Schema.String.annotate({ "description": "The certificate content in PEM format." }) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "certificates.activated": Schema.optionalKey( + Schema.Struct({ + "certificates": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The certificate ID." })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the certificate." })) + }) + ) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ), + "certificates.deactivated": Schema.optionalKey( + Schema.Struct({ + "certificates": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The certificate ID." })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the certificate." })) + }) + ) + ) + }).annotate({ "description": "The details for events with this `type`." }) + ) +}).annotate({ "description": "A log of a user action or configuration change within this organization." }) +export type CreateChatCompletionResponse = { + readonly "id": string + readonly "choices": ReadonlyArray< + { + readonly "finish_reason": "stop" | "length" | "tool_calls" | "content_filter" | "function_call" + readonly "index": number + readonly "message": ChatCompletionResponseMessage + readonly "logprobs": { + readonly "content": ReadonlyArray | null + readonly "refusal": ReadonlyArray | null + } | null + } + > + readonly "created": number + readonly "model": string + readonly "service_tier"?: ServiceTier + readonly "system_fingerprint"?: string + readonly "object": "chat.completion" + readonly "usage"?: CompletionUsage +} +export const CreateChatCompletionResponse = Schema.Struct({ + "id": Schema.String.annotate({ "description": "A unique identifier for the chat completion." }), + "choices": Schema.Array(Schema.Struct({ + "finish_reason": Schema.Literals(["stop", "length", "tool_calls", "content_filter", "function_call"]).annotate({ + "description": + "The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence,\n`length` if the maximum number of tokens specified in the request was reached,\n`content_filter` if content was omitted due to a flag from our content filters,\n`tool_calls` if the model called a tool, or `function_call` (deprecated) if the model called a function.\n" + }), + "index": Schema.Number.annotate({ "description": "The index of the choice in the list of choices." }).check( + Schema.isInt() + ), + "message": ChatCompletionResponseMessage, + "logprobs": Schema.Union([ + Schema.Struct({ + "content": Schema.Union([ + Schema.Array(ChatCompletionTokenLogprob).annotate({ + "description": "A list of message content tokens with log probability information." + }), + Schema.Null + ]), + "refusal": Schema.Union([ + Schema.Array(ChatCompletionTokenLogprob).annotate({ + "description": "A list of message refusal tokens with log probability information." + }), + Schema.Null + ]) + }).annotate({ "description": "Log probability information for the choice." }), + Schema.Null + ]) + })).annotate({ "description": "A list of chat completion choices. Can be more than one if `n` is greater than 1." }), + "created": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) of when the chat completion was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "model": Schema.String.annotate({ "description": "The model used for the chat completion." }), + "service_tier": Schema.optionalKey(ServiceTier), + "system_fingerprint": Schema.optionalKey( + Schema.String.annotate({ + "description": + "This fingerprint represents the backend configuration that the model runs with.\n\nCan be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism.\n" + }) + ), + "object": Schema.Literal("chat.completion").annotate({ + "description": "The object type, which is always `chat.completion`." + }), + "usage": Schema.optionalKey(CompletionUsage) +}).annotate({ "description": "Represents a chat completion response returned by model, based on the provided input." }) +export type ChatCompletionRequestMessage = + | ChatCompletionRequestDeveloperMessage + | ChatCompletionRequestSystemMessage + | ChatCompletionRequestUserMessage + | ChatCompletionRequestAssistantMessage + | ChatCompletionRequestToolMessage + | ChatCompletionRequestFunctionMessage +export const ChatCompletionRequestMessage = Schema.Union([ + ChatCompletionRequestDeveloperMessage, + ChatCompletionRequestSystemMessage, + ChatCompletionRequestUserMessage, + ChatCompletionRequestAssistantMessage, + ChatCompletionRequestToolMessage, + ChatCompletionRequestFunctionMessage +], { mode: "oneOf" }) +export type RunStepObject = { + readonly "id": string + readonly "object": "thread.run.step" + readonly "created_at": number + readonly "assistant_id": string + readonly "thread_id": string + readonly "run_id": string + readonly "type": "message_creation" | "tool_calls" + readonly "status": "in_progress" | "cancelled" | "failed" | "completed" | "expired" + readonly "step_details": { + readonly "type": "message_creation" + readonly "message_creation": { readonly "message_id": string } + } | { + readonly "type": "tool_calls" + readonly "tool_calls": ReadonlyArray< + | RunStepDetailsToolCallsCodeObject + | RunStepDetailsToolCallsFileSearchObject + | RunStepDetailsToolCallsFunctionObject + > + } + readonly "last_error": { readonly "code": "server_error" | "rate_limit_exceeded"; readonly "message": string } | null + readonly "expired_at": number | null + readonly "cancelled_at": number | null + readonly "failed_at": number | null + readonly "completed_at": number | null + readonly "metadata": Metadata + readonly "usage": RunStepCompletionUsage +} +export const RunStepObject = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The identifier of the run step, which can be referenced in API endpoints." + }), + "object": Schema.Literal("thread.run.step").annotate({ + "description": "The object type, which is always `thread.run.step`." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the run step was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "assistant_id": Schema.String.annotate({ + "description": "The ID of the [assistant](/docs/api-reference/assistants) associated with the run step." + }), + "thread_id": Schema.String.annotate({ + "description": "The ID of the [thread](/docs/api-reference/threads) that was run." + }), + "run_id": Schema.String.annotate({ + "description": "The ID of the [run](/docs/api-reference/runs) that this run step is a part of." + }), + "type": Schema.Literals(["message_creation", "tool_calls"]).annotate({ + "description": "The type of run step, which can be either `message_creation` or `tool_calls`." + }), + "status": Schema.Literals(["in_progress", "cancelled", "failed", "completed", "expired"]).annotate({ + "description": + "The status of the run step, which can be either `in_progress`, `cancelled`, `failed`, `completed`, or `expired`." + }), + "step_details": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("message_creation").annotate({ "description": "Always `message_creation`." }), + "message_creation": Schema.Struct({ + "message_id": Schema.String.annotate({ + "description": "The ID of the message that was created by this run step." + }) + }) + }).annotate({ "title": "Message creation", "description": "The details of the run step." }), + Schema.Struct({ + "type": Schema.Literal("tool_calls").annotate({ "description": "Always `tool_calls`." }), + "tool_calls": Schema.Array( + Schema.Union([ + RunStepDetailsToolCallsCodeObject, + RunStepDetailsToolCallsFileSearchObject, + RunStepDetailsToolCallsFunctionObject + ], { mode: "oneOf" }) + ).annotate({ + "description": + "An array of tool calls the run step was involved in. These can be associated with one of three types of tools: `code_interpreter`, `file_search`, or `function`.\n" + }) + }).annotate({ "title": "Tool calls", "description": "The details of the run step." }) + ], { mode: "oneOf" }), + "last_error": Schema.Union([ + Schema.Struct({ + "code": Schema.Literals(["server_error", "rate_limit_exceeded"]).annotate({ + "description": "One of `server_error` or `rate_limit_exceeded`." + }), + "message": Schema.String.annotate({ "description": "A human-readable description of the error." }) + }).annotate({ + "description": "The last error associated with this run step. Will be `null` if there are no errors." + }), + Schema.Null + ]), + "expired_at": Schema.Union([ + Schema.Number.annotate({ + "description": + "The Unix timestamp (in seconds) for when the run step expired. A step is considered expired if the parent run is expired.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "cancelled_at": Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the run step was cancelled.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "failed_at": Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the run step failed.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "completed_at": Schema.Union([ + Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the run step completed.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "metadata": Metadata, + "usage": RunStepCompletionUsage +}).annotate({ "title": "Run steps", "description": "Represents a step in execution of a run.\n" }) +export type ListMessagesResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean + readonly [x: string]: unknown +} +export const ListMessagesResponse = Schema.StructWithRest( + Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(MessageObject), + "first_id": Schema.String, + "last_id": Schema.String, + "has_more": Schema.Boolean + }), + [Schema.Record(Schema.String, Schema.Json)] +) +export type MessageStreamEvent = + | { readonly "event": "thread.message.created"; readonly "data": MessageObject } + | { readonly "event": "thread.message.in_progress"; readonly "data": MessageObject } + | { readonly "event": "thread.message.delta"; readonly "data": MessageDeltaObject } + | { readonly "event": "thread.message.completed"; readonly "data": MessageObject } + | { readonly "event": "thread.message.incomplete"; readonly "data": MessageObject } +export const MessageStreamEvent = Schema.Union([ + Schema.Struct({ "event": Schema.Literal("thread.message.created"), "data": MessageObject }).annotate({ + "description": "Occurs when a [message](/docs/api-reference/messages/object) is created." + }), + Schema.Struct({ "event": Schema.Literal("thread.message.in_progress"), "data": MessageObject }).annotate({ + "description": "Occurs when a [message](/docs/api-reference/messages/object) moves to an `in_progress` state." + }), + Schema.Struct({ "event": Schema.Literal("thread.message.delta"), "data": MessageDeltaObject }).annotate({ + "description": "Occurs when parts of a [Message](/docs/api-reference/messages/object) are being streamed." + }), + Schema.Struct({ "event": Schema.Literal("thread.message.completed"), "data": MessageObject }).annotate({ + "description": "Occurs when a [message](/docs/api-reference/messages/object) is completed." + }), + Schema.Struct({ "event": Schema.Literal("thread.message.incomplete"), "data": MessageObject }).annotate({ + "description": "Occurs when a [message](/docs/api-reference/messages/object) ends before it is completed." + }) +], { mode: "oneOf" }) +export type RealtimeBetaResponse = { + readonly "id"?: string + readonly "object"?: "realtime.response" + readonly "status"?: "completed" | "cancelled" | "failed" | "incomplete" | "in_progress" + readonly "status_details"?: { + readonly "type"?: "completed" | "cancelled" | "failed" | "incomplete" + readonly "reason"?: "turn_detected" | "client_cancelled" | "max_output_tokens" | "content_filter" + readonly "error"?: { readonly "type"?: string; readonly "code"?: string } + } + readonly "output"?: ReadonlyArray + readonly "metadata"?: Metadata + readonly "usage"?: { + readonly "total_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "input_token_details"?: { + readonly "cached_tokens"?: number + readonly "text_tokens"?: number + readonly "image_tokens"?: number + readonly "audio_tokens"?: number + readonly "cached_tokens_details"?: { + readonly "text_tokens"?: number + readonly "image_tokens"?: number + readonly "audio_tokens"?: number + } + } + readonly "output_token_details"?: { readonly "text_tokens"?: number; readonly "audio_tokens"?: number } + } + readonly "conversation_id"?: string + readonly "voice"?: + | string + | "alloy" + | "ash" + | "ballad" + | "coral" + | "echo" + | "sage" + | "shimmer" + | "verse" + | "marin" + | "cedar" + readonly "modalities"?: ReadonlyArray<"text" | "audio"> + readonly "output_audio_format"?: "pcm16" | "g711_ulaw" | "g711_alaw" + readonly "temperature"?: number + readonly "max_output_tokens"?: number | "inf" +} +export const RealtimeBetaResponse = Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The unique ID of the response." })), + "object": Schema.optionalKey( + Schema.Literal("realtime.response").annotate({ "description": "The object type, must be `realtime.response`." }) + ), + "status": Schema.optionalKey( + Schema.Literals(["completed", "cancelled", "failed", "incomplete", "in_progress"]).annotate({ + "description": + "The final status of the response (`completed`, `cancelled`, `failed`, or \n`incomplete`, `in_progress`).\n" + }) + ), + "status_details": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["completed", "cancelled", "failed", "incomplete"]).annotate({ + "description": + "The type of error that caused the response to fail, corresponding \nwith the `status` field (`completed`, `cancelled`, `incomplete`, \n`failed`).\n" + }) + ), + "reason": Schema.optionalKey( + Schema.Literals(["turn_detected", "client_cancelled", "max_output_tokens", "content_filter"]).annotate({ + "description": + "The reason the Response did not complete. For a `cancelled` Response, \none of `turn_detected` (the server VAD detected a new start of speech) \nor `client_cancelled` (the client sent a cancel event). For an \n`incomplete` Response, one of `max_output_tokens` or `content_filter` \n(the server-side safety filter activated and cut off the response).\n" + }) + ), + "error": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey(Schema.String.annotate({ "description": "The type of error." })), + "code": Schema.optionalKey(Schema.String.annotate({ "description": "Error code, if any." })) + }).annotate({ + "description": + "A description of the error that caused the response to fail, \npopulated when the `status` is `failed`.\n" + }) + ) + }).annotate({ "description": "Additional details about the status." }) + ), + "output": Schema.optionalKey( + Schema.Array(RealtimeConversationItem).annotate({ + "description": "The list of output items generated by the response." + }) + ), + "metadata": Schema.optionalKey(Metadata), + "usage": Schema.optionalKey( + Schema.Struct({ + "total_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The total number of tokens in the Response including input and output \ntext and audio tokens.\n" + }).check(Schema.isInt()) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of input tokens used in the Response, including text and \naudio tokens.\n" + }).check(Schema.isInt()) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of output tokens sent in the Response, including text and \naudio tokens.\n" + }).check(Schema.isInt()) + ), + "input_token_details": Schema.optionalKey( + Schema.Struct({ + "cached_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of cached tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of text tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "image_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of image tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of audio tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "cached_tokens_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of cached text tokens used as input for the Response." + }).check(Schema.isInt()) + ), + "image_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of cached image tokens used as input for the Response." + }).check(Schema.isInt()) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of cached audio tokens used as input for the Response." + }).check(Schema.isInt()) + ) + }).annotate({ "description": "Details about the cached tokens used as input for the Response." }) + ) + }).annotate({ "description": "Details about the input tokens used in the Response." }) + ), + "output_token_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of text tokens used in the Response." }).check( + Schema.isInt() + ) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of audio tokens used in the Response." }).check( + Schema.isInt() + ) + ) + }).annotate({ "description": "Details about the output tokens used in the Response." }) + ) + }).annotate({ + "description": + "Usage statistics for the Response, this will correspond to billing. A \nRealtime API session will maintain a conversation context and append new \nItems to the Conversation, thus output from previous turns (text and \naudio tokens) will become the input for later turns.\n" + }) + ), + "conversation_id": Schema.optionalKey(Schema.String.annotate({ + "description": + "Which conversation the response is added to, determined by the `conversation`\nfield in the `response.create` event. If `auto`, the response will be added to\nthe default conversation and the value of `conversation_id` will be an id like\n`conv_1234`. If `none`, the response will not be added to any conversation and\nthe value of `conversation_id` will be `null`. If responses are being triggered\nby server VAD, the response will be added to the default conversation, thus\nthe `conversation_id` will be an id like `conv_1234`.\n" + })), + "voice": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]) + ]).annotate({ + "description": + "The voice the model used to respond.\nCurrent voice options are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`,\n`shimmer`, and `verse`.\n" + }) + ), + "modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "The set of modalities the model used to respond. If there are multiple modalities,\nthe model will pick one, for example if `modalities` is `[\"text\", \"audio\"]`, the model\ncould be responding in either text or audio.\n" + }) + ), + "output_audio_format": Schema.optionalKey( + Schema.Literals(["pcm16", "g711_ulaw", "g711_alaw"]).annotate({ + "description": "The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\n" + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Sampling temperature for the model, limited to [0.6, 1.2]. Defaults to 0.8.\n" + }).check(Schema.isFinite()) + ), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls, that was used in this response.\n" + }) + ) +}).annotate({ "description": "The response resource." }) +export type RealtimeBetaResponseCreateParams = { + readonly "modalities"?: ReadonlyArray<"text" | "audio"> + readonly "instructions"?: string + readonly "voice"?: VoiceIdsShared | { readonly "id": string } + readonly "output_audio_format"?: "pcm16" | "g711_ulaw" | "g711_alaw" + readonly "tools"?: ReadonlyArray< + { + readonly "type"?: "function" + readonly "name"?: string + readonly "description"?: string + readonly "parameters"?: {} + } + > + readonly "tool_choice"?: ToolChoiceOptions | ToolChoiceFunction | ToolChoiceMCP + readonly "temperature"?: number + readonly "max_output_tokens"?: number | "inf" + readonly "conversation"?: string | "auto" | "none" + readonly "metadata"?: Metadata + readonly "prompt"?: Prompt + readonly "input"?: ReadonlyArray +} +export const RealtimeBetaResponseCreateParams = Schema.Struct({ + "modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": "The set of modalities the model can respond with. To disable audio,\nset this to [\"text\"].\n" + }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model \ncalls. This field allows the client to guide the model on desired \nresponses. The model can be instructed on response content and format, \n(e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good \nresponses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion \ninto your voice\", \"laugh frequently\"). The instructions are not guaranteed \nto be followed by the model, but they provide guidance to the model on the \ndesired behavior.\n\nNote that the server sets default instructions which will be used if this \nfield is not set and are visible in the `session.created` event at the \nstart of the session.\n" + })), + "voice": Schema.optionalKey( + Schema.Union([ + VoiceIdsShared, + Schema.Struct({ "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) }) + .annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice the model uses to respond. Supported built-in voices are\n`alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`,\n`marin`, and `cedar`. You may also provide a custom voice object with an\n`id`, for example `{ \"id\": \"voice_1234\" }`. Voice cannot be changed during\nthe session once the model has responded with audio at least once.\n" + }) + ), + "output_audio_format": Schema.optionalKey( + Schema.Literals(["pcm16", "g711_ulaw", "g711_alaw"]).annotate({ + "description": "The format of output audio. Options are `pcm16`, `g711_ulaw`, or `g711_alaw`.\n" + }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("function").annotate({ "description": "The type of the tool, i.e. `function`." }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the function." })), + "description": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The description of the function, including guidance on when and how \nto call it, and guidance about what to tell the user when calling \n(if anything).\n" + }) + ), + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Parameters of the function in JSON Schema." }) + ) + })).annotate({ "description": "Tools (functions) available to the model." }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMCP], { mode: "oneOf" }).annotate({ + "description": + "How the model chooses tools. Provide one of the string modes or force a specific\nfunction/MCP tool.\n" + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Sampling temperature for the model, limited to [0.6, 1.2]. Defaults to 0.8.\n" + }).check(Schema.isFinite()) + ), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "conversation": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Literals(["auto", "none"])], { mode: "oneOf" }).annotate({ + "description": + "Controls which conversation the response is added to. Currently supports\n`auto` and `none`, with `auto` as the default value. The `auto` value\nmeans that the contents of the response will be added to the default\nconversation. Set this to `none` to create an out-of-band response which \nwill not add items to default conversation.\n" + }) + ), + "metadata": Schema.optionalKey(Metadata), + "prompt": Schema.optionalKey(Prompt), + "input": Schema.optionalKey( + Schema.Array(RealtimeConversationItem).annotate({ + "description": + "Input items to include in the prompt for the model. Using this field\ncreates a new context for this Response instead of using the default\nconversation. An empty array `[]` will clear the context for this Response.\nNote that this can include references to items from the default conversation.\n" + }) + ) +}).annotate({ "description": "Create a new Realtime response with these parameters" }) +export type RealtimeClientEventConversationItemCreate = { + readonly "event_id"?: string + readonly "type": "conversation.item.create" + readonly "previous_item_id"?: string + readonly "item": RealtimeConversationItem +} +export const RealtimeClientEventConversationItemCreate = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("conversation.item.create").annotate({ + "description": "The event type, must be `conversation.item.create`." + }), + "previous_item_id": Schema.optionalKey(Schema.String.annotate({ + "description": + "The ID of the preceding item after which the new item will be inserted. If not set, the new item will be appended to the end of the conversation.\n\nIf set to `root`, the new item will be added to the beginning of the conversation.\n\nIf set to an existing ID, it allows an item to be inserted mid-conversation. If the ID cannot be found, an error will be returned and the item will not be added.\n" + })), + "item": RealtimeConversationItem +}).annotate({ + "description": + "Add a new Item to the Conversation's context, including messages, function \ncalls, and function call responses. This event can be used both to populate a \n\"history\" of the conversation and to add new items mid-stream, but has the \ncurrent limitation that it cannot populate assistant audio messages.\n\nIf successful, the server will respond with a `conversation.item.created` \nevent, otherwise an `error` event will be sent.\n" +}) +export type RealtimeResponse = { + readonly "id"?: string + readonly "object"?: "realtime.response" + readonly "status"?: "completed" | "cancelled" | "failed" | "incomplete" | "in_progress" + readonly "status_details"?: { + readonly "type"?: "completed" | "cancelled" | "failed" | "incomplete" + readonly "reason"?: "turn_detected" | "client_cancelled" | "max_output_tokens" | "content_filter" + readonly "error"?: { readonly "type"?: string; readonly "code"?: string } + } + readonly "output"?: ReadonlyArray + readonly "metadata"?: Metadata + readonly "audio"?: { + readonly "output"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "voice"?: + | string + | "alloy" + | "ash" + | "ballad" + | "coral" + | "echo" + | "sage" + | "shimmer" + | "verse" + | "marin" + | "cedar" + } + } + readonly "usage"?: { + readonly "total_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "input_token_details"?: { + readonly "cached_tokens"?: number + readonly "text_tokens"?: number + readonly "image_tokens"?: number + readonly "audio_tokens"?: number + readonly "cached_tokens_details"?: { + readonly "text_tokens"?: number + readonly "image_tokens"?: number + readonly "audio_tokens"?: number + } + } + readonly "output_token_details"?: { readonly "text_tokens"?: number; readonly "audio_tokens"?: number } + } + readonly "conversation_id"?: string + readonly "output_modalities"?: ReadonlyArray<"text" | "audio"> + readonly "max_output_tokens"?: number | "inf" +} +export const RealtimeResponse = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The unique ID of the response, will look like `resp_1234`." }) + ), + "object": Schema.optionalKey( + Schema.Literal("realtime.response").annotate({ "description": "The object type, must be `realtime.response`." }) + ), + "status": Schema.optionalKey( + Schema.Literals(["completed", "cancelled", "failed", "incomplete", "in_progress"]).annotate({ + "description": + "The final status of the response (`completed`, `cancelled`, `failed`, or \n`incomplete`, `in_progress`).\n" + }) + ), + "status_details": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literals(["completed", "cancelled", "failed", "incomplete"]).annotate({ + "description": + "The type of error that caused the response to fail, corresponding \nwith the `status` field (`completed`, `cancelled`, `incomplete`, \n`failed`).\n" + }) + ), + "reason": Schema.optionalKey( + Schema.Literals(["turn_detected", "client_cancelled", "max_output_tokens", "content_filter"]).annotate({ + "description": + "The reason the Response did not complete. For a `cancelled` Response, one of `turn_detected` (the server VAD detected a new start of speech) or `client_cancelled` (the client sent a cancel event). For an `incomplete` Response, one of `max_output_tokens` or `content_filter` (the server-side safety filter activated and cut off the response).\n" + }) + ), + "error": Schema.optionalKey( + Schema.Struct({ + "type": Schema.optionalKey(Schema.String.annotate({ "description": "The type of error." })), + "code": Schema.optionalKey(Schema.String.annotate({ "description": "Error code, if any." })) + }).annotate({ + "description": + "A description of the error that caused the response to fail, \npopulated when the `status` is `failed`.\n" + }) + ) + }).annotate({ "description": "Additional details about the status." }) + ), + "output": Schema.optionalKey( + Schema.Array(RealtimeConversationItem).annotate({ + "description": "The list of output items generated by the response." + }) + ), + "metadata": Schema.optionalKey(Metadata), + "audio": Schema.optionalKey( + Schema.Struct({ + "output": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the output audio." }) + ), + "voice": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Literals(["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]) + ]).annotate({ + "description": + "The voice the model uses to respond. Voice cannot be changed during the\nsession once the model has responded with audio at least once. Current\nvoice options are `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`,\n`shimmer`, `verse`, `marin`, and `cedar`. We recommend `marin` and `cedar` for\nbest quality.\n" + }) + ) + })) + }).annotate({ "description": "Configuration for audio output." }) + ), + "usage": Schema.optionalKey( + Schema.Struct({ + "total_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The total number of tokens in the Response including input and output \ntext and audio tokens.\n" + }).check(Schema.isInt()) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of input tokens used in the Response, including text and \naudio tokens.\n" + }).check(Schema.isInt()) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of output tokens sent in the Response, including text and \naudio tokens.\n" + }).check(Schema.isInt()) + ), + "input_token_details": Schema.optionalKey( + Schema.Struct({ + "cached_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of cached tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of text tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "image_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of image tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of audio tokens used as input for the Response." }) + .check(Schema.isInt()) + ), + "cached_tokens_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of cached text tokens used as input for the Response." + }).check(Schema.isInt()) + ), + "image_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of cached image tokens used as input for the Response." + }).check(Schema.isInt()) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The number of cached audio tokens used as input for the Response." + }).check(Schema.isInt()) + ) + }).annotate({ "description": "Details about the cached tokens used as input for the Response." }) + ) + }).annotate({ + "description": + "Details about the input tokens used in the Response. Cached tokens are tokens from previous turns in the conversation that are included as context for the current response. Cached tokens here are counted as a subset of input tokens, meaning input tokens will include cached and uncached tokens." + }) + ), + "output_token_details": Schema.optionalKey( + Schema.Struct({ + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of text tokens used in the Response." }).check( + Schema.isInt() + ) + ), + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of audio tokens used in the Response." }).check( + Schema.isInt() + ) + ) + }).annotate({ "description": "Details about the output tokens used in the Response." }) + ) + }).annotate({ + "description": + "Usage statistics for the Response, this will correspond to billing. A \nRealtime API session will maintain a conversation context and append new \nItems to the Conversation, thus output from previous turns (text and \naudio tokens) will become the input for later turns.\n" + }) + ), + "conversation_id": Schema.optionalKey(Schema.String.annotate({ + "description": + "Which conversation the response is added to, determined by the `conversation`\nfield in the `response.create` event. If `auto`, the response will be added to\nthe default conversation and the value of `conversation_id` will be an id like\n`conv_1234`. If `none`, the response will not be added to any conversation and\nthe value of `conversation_id` will be `null`. If responses are being triggered\nautomatically by VAD the response will be added to the default conversation\n" + })), + "output_modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "The set of modalities the model used to respond, currently the only possible values are\n`[\\\"audio\\\"]`, `[\\\"text\\\"]`. Audio output always include a text transcript. Setting the\noutput to mode `text` will disable audio output from the model.\n" + }) + ), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls, that was used in this response.\n" + }) + ) +}).annotate({ "description": "The response resource." }) +export type RealtimeResponseCreateParams = { + readonly "output_modalities"?: ReadonlyArray<"text" | "audio"> + readonly "instructions"?: string + readonly "audio"?: { + readonly "output"?: { + readonly "format"?: { readonly "type"?: "audio/pcm"; readonly "rate"?: 24000 } | { + readonly "type"?: "audio/pcmu" + } | { readonly "type"?: "audio/pcma" } + readonly "voice"?: VoiceIdsShared | { readonly "id": string } + } + } + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: ToolChoiceOptions | ToolChoiceFunction | ToolChoiceMCP + readonly "parallel_tool_calls"?: boolean + readonly "reasoning"?: RealtimeReasoning + readonly "max_output_tokens"?: number | "inf" + readonly "conversation"?: string | "auto" | "none" + readonly "metadata"?: Metadata + readonly "prompt"?: Prompt + readonly "input"?: ReadonlyArray +} +export const RealtimeResponseCreateParams = Schema.Struct({ + "output_modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "audio"])).annotate({ + "description": + "The set of modalities the model used to respond, currently the only possible values are\n`[\\\"audio\\\"]`, `[\\\"text\\\"]`. Audio output always include a text transcript. Setting the\noutput to mode `text` will disable audio output from the model.\n" + }) + ), + "instructions": Schema.optionalKey(Schema.String.annotate({ + "description": + "The default system instructions (i.e. system message) prepended to model calls. This field allows the client to guide the model on desired responses. The model can be instructed on response content and format, (e.g. \"be extremely succinct\", \"act friendly\", \"here are examples of good responses\") and on audio behavior (e.g. \"talk quickly\", \"inject emotion into your voice\", \"laugh frequently\"). The instructions are not guaranteed to be followed by the model, but they provide guidance to the model on the desired behavior.\nNote that the server sets default instructions which will be used if this field is not set and are visible in the `session.created` event at the start of the session.\n" + })), + "audio": Schema.optionalKey( + Schema.Struct({ + "output": Schema.optionalKey(Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcm").annotate({ "description": "The audio format. Always `audio/pcm`." }) + ), + "rate": Schema.optionalKey( + Schema.Literal(24000).annotate({ "description": "The sample rate of the audio. Always `24000`." }) + ) + }).annotate({ + "title": "PCM audio format", + "description": "The PCM audio format. Only a 24kHz sample rate is supported." + }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcmu").annotate({ "description": "The audio format. Always `audio/pcmu`." }) + ) + }).annotate({ "title": "PCMU audio format", "description": "The G.711 μ-law format." }), + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("audio/pcma").annotate({ "description": "The audio format. Always `audio/pcma`." }) + ) + }).annotate({ "title": "PCMA audio format", "description": "The G.711 A-law format." }) + ]).annotate({ "description": "The format of the output audio." }) + ), + "voice": Schema.optionalKey( + Schema.Union([ + VoiceIdsShared, + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) + }).annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice the model uses to respond. Supported built-in voices are\n`alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`,\n`marin`, and `cedar`. You may also provide a custom voice object with\nan `id`, for example `{ \"id\": \"voice_1234\" }`. Voice cannot be changed\nduring the session once the model has responded with audio at least once.\nWe recommend `marin` and `cedar` for best quality.\n" + }) + ) + })) + }).annotate({ "description": "Configuration for audio input and output." }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([RealtimeFunctionTool, MCPTool], { mode: "oneOf" })).annotate({ + "description": "Tools available to the model." + }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ToolChoiceOptions, ToolChoiceFunction, ToolChoiceMCP], { mode: "oneOf" }).annotate({ + "description": + "How the model chooses tools. Provide one of the string modes or force a specific\nfunction/MCP tool.\n" + }) + ), + "parallel_tool_calls": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether the model may call multiple tools in parallel. Only supported by\nreasoning Realtime models such as `gpt-realtime-2`.\n" + }) + ), + "reasoning": Schema.optionalKey(RealtimeReasoning), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Literal("inf")], { mode: "oneOf" }).annotate({ + "description": + "Maximum number of output tokens for a single assistant response,\ninclusive of tool calls. Provide an integer between 1 and 4096 to\nlimit output tokens, or `inf` for the maximum available tokens for a\ngiven model. Defaults to `inf`.\n" + }) + ), + "conversation": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Literals(["auto", "none"])], { mode: "oneOf" }).annotate({ + "description": + "Controls which conversation the response is added to. Currently supports\n`auto` and `none`, with `auto` as the default value. The `auto` value\nmeans that the contents of the response will be added to the default\nconversation. Set this to `none` to create an out-of-band response which\nwill not add items to default conversation.\n" + }) + ), + "metadata": Schema.optionalKey(Metadata), + "prompt": Schema.optionalKey(Prompt), + "input": Schema.optionalKey( + Schema.Array(RealtimeConversationItem).annotate({ + "description": + "Input items to include in the prompt for the model. Using this field\ncreates a new context for this Response instead of using the default\nconversation. An empty array `[]` will clear the context for this Response.\nNote that this can include references to items that previously appeared in the session\nusing their id.\n" + }) + ) +}).annotate({ "description": "Create a new Realtime response with these parameters" }) +export type RealtimeServerEventConversationItemAdded = { + readonly "event_id": string + readonly "type": "conversation.item.added" + readonly "previous_item_id"?: string | null + readonly "item": RealtimeConversationItem +} +export const RealtimeServerEventConversationItemAdded = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.added").annotate({ + "description": "The event type, must be `conversation.item.added`." + }), + "previous_item_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The ID of the item that precedes this one, if any. This is used to\nmaintain ordering when items are inserted.\n" + }), + Schema.Null + ]) + ), + "item": RealtimeConversationItem +}).annotate({ + "description": + "Sent by the server when an Item is added to the default Conversation. This can happen in several cases:\n- When the client sends a `conversation.item.create` event.\n- When the input audio buffer is committed. In this case the item will be a user message containing the audio from the buffer.\n- When the model is generating a Response. In this case the `conversation.item.added` event will be sent when the model starts generating a specific Item, and thus it will not yet have any content (and `status` will be `in_progress`).\n\nThe event will include the full content of the Item (except when model is generating a Response) except for audio data, which can be retrieved separately with a `conversation.item.retrieve` event if necessary.\n" +}) +export type RealtimeServerEventConversationItemCreated = { + readonly "event_id": string + readonly "type": "conversation.item.created" + readonly "previous_item_id"?: string | null + readonly "item": RealtimeConversationItem +} +export const RealtimeServerEventConversationItemCreated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.created").annotate({ + "description": "The event type, must be `conversation.item.created`." + }), + "previous_item_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The ID of the preceding item in the Conversation context, allows the\nclient to understand the order of the conversation. Can be `null` if the\nitem has no predecessor.\n" + }), + Schema.Null + ]) + ), + "item": RealtimeConversationItem +}).annotate({ + "description": + "Returned when a conversation item is created. There are several scenarios that produce this event:\n - The server is generating a Response, which if successful will produce\n either one or two Items, which will be of type `message`\n (role `assistant`) or type `function_call`.\n - The input audio buffer has been committed, either by the client or the\n server (in `server_vad` mode). The server will take the content of the\n input audio buffer and add it to a new user message Item.\n - The client has sent a `conversation.item.create` event to add a new Item\n to the Conversation.\n" +}) +export type RealtimeServerEventConversationItemDone = { + readonly "event_id": string + readonly "type": "conversation.item.done" + readonly "previous_item_id"?: string | null + readonly "item": RealtimeConversationItem +} +export const RealtimeServerEventConversationItemDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.done").annotate({ + "description": "The event type, must be `conversation.item.done`." + }), + "previous_item_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The ID of the item that precedes this one, if any. This is used to\nmaintain ordering when items are inserted.\n" + }), + Schema.Null + ]) + ), + "item": RealtimeConversationItem +}).annotate({ + "description": + "Returned when a conversation item is finalized.\n\nThe event will include the full content of the Item except for audio data, which can be retrieved separately with a `conversation.item.retrieve` event if needed.\n" +}) +export type RealtimeServerEventConversationItemRetrieved = { + readonly "event_id": string + readonly "type": "conversation.item.retrieved" + readonly "item": RealtimeConversationItem +} +export const RealtimeServerEventConversationItemRetrieved = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("conversation.item.retrieved").annotate({ + "description": "The event type, must be `conversation.item.retrieved`." + }), + "item": RealtimeConversationItem +}).annotate({ + "description": + "Returned when a conversation item is retrieved with `conversation.item.retrieve`. This is provided as a way to fetch the server's representation of an item, for example to get access to the post-processed audio data after noise cancellation and VAD. It includes the full content of the Item, including audio data.\n" +}) +export type RealtimeServerEventResponseOutputItemAdded = { + readonly "event_id": string + readonly "type": "response.output_item.added" + readonly "response_id": string + readonly "output_index": number + readonly "item": RealtimeConversationItem +} +export const RealtimeServerEventResponseOutputItemAdded = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_item.added").annotate({ + "description": "The event type, must be `response.output_item.added`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the Response to which the item belongs." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the Response." }).check( + Schema.isInt() + ), + "item": RealtimeConversationItem +}).annotate({ "description": "Returned when a new Item is created during Response generation." }) +export type RealtimeServerEventResponseOutputItemDone = { + readonly "event_id": string + readonly "type": "response.output_item.done" + readonly "response_id": string + readonly "output_index": number + readonly "item": RealtimeConversationItem +} +export const RealtimeServerEventResponseOutputItemDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.output_item.done").annotate({ + "description": "The event type, must be `response.output_item.done`." + }), + "response_id": Schema.String.annotate({ "description": "The ID of the Response to which the item belongs." }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item in the Response." }).check( + Schema.isInt() + ), + "item": RealtimeConversationItem +}).annotate({ + "description": + "Returned when an Item is done streaming. Also emitted when a Response is \ninterrupted, incomplete, or cancelled.\n" +}) +export type AssistantObject = { + readonly "id": string + readonly "object": "assistant" + readonly "created_at": number + readonly "name": string | null + readonly "description": string | null + readonly "model": string + readonly "instructions": string | null + readonly "tools": ReadonlyArray + readonly "tool_resources"?: { + readonly "code_interpreter"?: { readonly "file_ids"?: ReadonlyArray } + readonly "file_search"?: { readonly "vector_store_ids"?: ReadonlyArray } + } | null + readonly "metadata": Metadata + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "response_format"?: AssistantsApiResponseFormatOption | null +} +export const AssistantObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "object": Schema.Literal("assistant").annotate({ "description": "The object type, which is always `assistant`." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the assistant was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "name": Schema.Union([ + Schema.String.annotate({ "description": "The name of the assistant. The maximum length is 256 characters.\n" }) + .check(Schema.isMaxLength(256)), + Schema.Null + ]), + "description": Schema.Union([ + Schema.String.annotate({ + "description": "The description of the assistant. The maximum length is 512 characters.\n" + }).check(Schema.isMaxLength(512)), + Schema.Null + ]), + "model": Schema.String.annotate({ + "description": + "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n" + }), + "instructions": Schema.Union([ + Schema.String.annotate({ + "description": "The system instructions that the assistant uses. The maximum length is 256,000 characters.\n" + }).check(Schema.isMaxLength(256000)), + Schema.Null + ]), + "tools": Schema.Array( + Schema.Union([AssistantToolsCode, AssistantToolsFileSearch, AssistantToolsFunction], { mode: "oneOf" }) + ).annotate({ + "description": + "A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`.\n" + }).check(Schema.isMaxLength(128)), + "tool_resources": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "code_interpreter": Schema.optionalKey(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs made available to the `code_interpreter`` tool. There can be a maximum of 20 files associated with the tool.\n" + }).check(Schema.isMaxLength(20)) + ) + })), + "file_search": Schema.optionalKey(Schema.Struct({ + "vector_store_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "The ID of the [vector store](/docs/api-reference/vector-stores/object) attached to this assistant. There can be a maximum of 1 vector store attached to the assistant.\n" + }).check(Schema.isMaxLength(1)) + ) + })) + }).annotate({ + "description": + "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n" + }), + Schema.Null + ])), + "metadata": Metadata, + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ])), + "response_format": Schema.optionalKey(Schema.Union([AssistantsApiResponseFormatOption, Schema.Null])) +}).annotate({ "title": "Assistant", "description": "Represents an `assistant` that can call the model and use tools." }) +export type CreateAssistantRequest = { + readonly "model": string | AssistantSupportedModels + readonly "name"?: string | null + readonly "description"?: string | null + readonly "instructions"?: string | null + readonly "reasoning_effort"?: ReasoningEffort + readonly "tools"?: ReadonlyArray + readonly "tool_resources"?: { + readonly "code_interpreter"?: { readonly "file_ids"?: ReadonlyArray } + readonly "file_search"?: { + readonly "vector_store_ids": ReadonlyArray + readonly "vector_stores"?: ReadonlyArray< + { + readonly "file_ids"?: ReadonlyArray + readonly "chunking_strategy"?: { readonly "type": "auto" } | { + readonly "type": "static" + readonly "static": { readonly "max_chunk_size_tokens": number; readonly "chunk_overlap_tokens": number } + } + readonly "metadata"?: Metadata + } + > + } | { + readonly "vector_stores": ReadonlyArray< + { + readonly "file_ids"?: ReadonlyArray + readonly "chunking_strategy"?: { readonly "type": "auto" } | { + readonly "type": "static" + readonly "static": { readonly "max_chunk_size_tokens": number; readonly "chunk_overlap_tokens": number } + } + readonly "metadata"?: Metadata + } + > + readonly "vector_store_ids"?: ReadonlyArray + } + } | null + readonly "metadata"?: Metadata + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "response_format"?: AssistantsApiResponseFormatOption | null +} +export const CreateAssistantRequest = Schema.Struct({ + "model": Schema.Union([Schema.String, AssistantSupportedModels]).annotate({ + "description": + "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n" + }), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The name of the assistant. The maximum length is 256 characters.\n" }) + .check(Schema.isMaxLength(256)), + Schema.Null + ]) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The description of the assistant. The maximum length is 512 characters.\n" + }).check(Schema.isMaxLength(512)), + Schema.Null + ]) + ), + "instructions": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The system instructions that the assistant uses. The maximum length is 256,000 characters.\n" + }).check(Schema.isMaxLength(256000)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([AssistantToolsCode, AssistantToolsFileSearch, AssistantToolsFunction], { mode: "oneOf" }) + ).annotate({ + "description": + "A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`.\n" + }).check(Schema.isMaxLength(128)) + ), + "tool_resources": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "code_interpreter": Schema.optionalKey(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files associated with the tool.\n" + }).check(Schema.isMaxLength(20)) + ) + })), + "file_search": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "vector_store_ids": Schema.Array(Schema.String).annotate({ + "description": + "The [vector store](/docs/api-reference/vector-stores/object) attached to this assistant. There can be a maximum of 1 vector store attached to the assistant.\n" + }).check(Schema.isMaxLength(1)), + "vector_stores": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs to add to the vector store. For vector stores created before Nov 2025, there can be a maximum of 10,000 files in a vector store. For vector stores created starting in Nov 2025, the limit is 100,000,000 files.\n" + }).check(Schema.isMaxLength(100000000)) + ), + "chunking_strategy": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("auto").annotate({ "description": "Always `auto`." }) }) + .annotate({ + "title": "Auto Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }), + Schema.Struct({ + "type": Schema.Literal("static").annotate({ "description": "Always `static`." }), + "static": Schema.Struct({ + "max_chunk_size_tokens": Schema.Number.annotate({ + "description": + "The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(100)).check( + Schema.isLessThanOrEqualTo(4096) + ), + "chunk_overlap_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that overlap between chunks. The default value is `400`.\n\nNote that the overlap must not exceed half of `max_chunk_size_tokens`.\n" + }).check(Schema.isInt()) + }) + }).annotate({ + "title": "Static Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }) + ], { mode: "oneOf" }) + ), + "metadata": Schema.optionalKey(Metadata) + })).annotate({ + "description": + "A helper to create a [vector store](/docs/api-reference/vector-stores/object) with file_ids and attach it to this assistant. There can be a maximum of 1 vector store attached to the assistant.\n" + }).check(Schema.isMaxLength(1)) + ) + }), + Schema.Struct({ + "vector_stores": Schema.Array(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs to add to the vector store. For vector stores created before Nov 2025, there can be a maximum of 10,000 files in a vector store. For vector stores created starting in Nov 2025, the limit is 100,000,000 files.\n" + }).check(Schema.isMaxLength(100000000)) + ), + "chunking_strategy": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("auto").annotate({ "description": "Always `auto`." }) }) + .annotate({ + "title": "Auto Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }), + Schema.Struct({ + "type": Schema.Literal("static").annotate({ "description": "Always `static`." }), + "static": Schema.Struct({ + "max_chunk_size_tokens": Schema.Number.annotate({ + "description": + "The maximum number of tokens in each chunk. The default value is `800`. The minimum value is `100` and the maximum value is `4096`." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(100)).check( + Schema.isLessThanOrEqualTo(4096) + ), + "chunk_overlap_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that overlap between chunks. The default value is `400`.\n\nNote that the overlap must not exceed half of `max_chunk_size_tokens`.\n" + }).check(Schema.isInt()) + }) + }).annotate({ + "title": "Static Chunking Strategy", + "description": + "The chunking strategy used to chunk the file(s). If not set, will use the `auto` strategy." + }) + ], { mode: "oneOf" }) + ), + "metadata": Schema.optionalKey(Metadata) + })).annotate({ + "description": + "A helper to create a [vector store](/docs/api-reference/vector-stores/object) with file_ids and attach it to this assistant. There can be a maximum of 1 vector store attached to the assistant.\n" + }).check(Schema.isMaxLength(1)), + "vector_store_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "The [vector store](/docs/api-reference/vector-stores/object) attached to this assistant. There can be a maximum of 1 vector store attached to the assistant.\n" + }).check(Schema.isMaxLength(1)) + ) + }) + ], { mode: "oneOf" })) + }).annotate({ + "description": + "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n" + }), + Schema.Null + ])), + "metadata": Schema.optionalKey(Metadata), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ])), + "response_format": Schema.optionalKey(Schema.Union([AssistantsApiResponseFormatOption, Schema.Null])) +}) +export type CreateRunRequest = { + readonly "assistant_id": string + readonly "model"?: string | AssistantSupportedModels | null + readonly "reasoning_effort"?: ReasoningEffort + readonly "instructions"?: string | null + readonly "additional_instructions"?: string | null + readonly "additional_messages"?: ReadonlyArray< + { + readonly "role": "user" | "assistant" + readonly "content": + | string + | ReadonlyArray + readonly "attachments"?: + | ReadonlyArray< + { + readonly "file_id"?: string + readonly "tools"?: ReadonlyArray + } + > + | null + readonly "metadata"?: Metadata + } + > + readonly "tools"?: ReadonlyArray + readonly "metadata"?: Metadata + readonly "temperature"?: number + readonly "top_p"?: number + readonly "stream"?: boolean | null + readonly "max_prompt_tokens"?: number + readonly "max_completion_tokens"?: number + readonly "truncation_strategy"?: { + readonly "type": "auto" | "last_messages" + readonly "last_messages"?: number | null + } + readonly "tool_choice"?: "none" | "auto" | "required" | { + readonly "type": "function" | "code_interpreter" | "file_search" + readonly "function"?: { readonly "name": string } + } + readonly "parallel_tool_calls"?: ParallelToolCalls + readonly "response_format"?: AssistantsApiResponseFormatOption | null +} +export const CreateRunRequest = Schema.Struct({ + "assistant_id": Schema.String.annotate({ + "description": "The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run." + }), + "model": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.String, AssistantSupportedModels]).annotate({ + "description": + "The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used." + }), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "instructions": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Overrides the [instructions](/docs/api-reference/assistants/createAssistant) of the assistant. This is useful for modifying the behavior on a per-run basis." + }) + ), + "additional_instructions": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Appends additional instructions at the end of the instructions for the run. This is useful for modifying the behavior on a per-run basis without overriding other instructions." + }) + ), + "additional_messages": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Struct({ + "role": Schema.Literals(["user", "assistant"]).annotate({ + "description": + "The role of the entity that is creating the message. Allowed values include:\n- `user`: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.\n- `assistant`: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.\n" + }), + "content": Schema.Union([ + Schema.String.annotate({ "title": "Text content", "description": "The text contents of the message." }), + Schema.Array( + Schema.Union([MessageContentImageFileObject, MessageContentImageUrlObject, MessageRequestContentTextObject], { + mode: "oneOf" + }) + ).annotate({ + "title": "Array of content parts", + "description": + "An array of content parts with a defined type, each can be of type `text` or images can be passed with `image_url` or `image_file`. Image types are only supported on [Vision-compatible models](/docs/models)." + }).check(Schema.isMinLength(1)) + ], { mode: "oneOf" }), + "attachments": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Struct({ + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The ID of the file to attach to the message." }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([AssistantToolsCode, AssistantToolsFileSearchTypeOnly], { mode: "oneOf" })) + .annotate({ "description": "The tools to add this file to." }) + ) + })).annotate({ + "description": "A list of files attached to the message, and the tools they should be added to." + }), + Schema.Null + ])), + "metadata": Schema.optionalKey(Metadata) + })).annotate({ "description": "Adds additional messages to the thread before creating the run." }) + ])), + "tools": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([AssistantToolsCode, AssistantToolsFileSearch, AssistantToolsFunction], { mode: "oneOf" }) + ).check( + Schema.isMaxLength(20, { + "description": + "Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis." + }) + ) + ]) + ), + "metadata": Schema.optionalKey(Metadata), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(2)], { + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n" + }) + ) + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1)], { + "description": + "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n" + }) + ) + ]) + ), + "stream": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n" + }) + ), + "max_prompt_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(256)], { + "description": + "The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n" + }) + ) + ]) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(256)], { + "description": + "The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n" + }) + ) + ]) + ), + "truncation_strategy": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "type": Schema.Literals(["auto", "last_messages"]).annotate({ + "description": + "The truncation strategy to use for the thread. The default is `auto`. If set to `last_messages`, the thread will be truncated to the n most recent messages in the thread. When set to `auto`, messages in the middle of the thread will be dropped to fit the context length of the model, `max_prompt_tokens`." + }), + "last_messages": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The number of most recent messages from the thread when constructing the context for the run." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ) + }).annotate({ + "title": "Thread Truncation Controls", + "description": + "Controls for how a thread will be truncated prior to the run. Use this to control the initial context window of the run." + }) + ])), + "tool_choice": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.Literal("none"), Schema.Literal("auto"), Schema.Literal("required")]).annotate({ + "description": + "`none` means the model will not call any tools and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools before responding to the user.\n" + }), + Schema.Union([ + Schema.Struct({ + "type": Schema.Literals(["function", "code_interpreter", "file_search"]).annotate({ + "description": "The type of the tool. If type is `function`, the function name must be set" + }), + "function": Schema.optionalKey( + Schema.Struct({ "name": Schema.String.annotate({ "description": "The name of the function to call." }) }) + ) + }).annotate({ + "description": "Specifies a tool the model should use. Use to force the model to call a specific tool." + }) + ]) + ], { mode: "oneOf" }).annotate({ + "description": + "Controls which (if any) tool is called by the model.\n`none` means the model will not call any tools and instead generates a message.\n`auto` is the default value and means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools before responding to the user.\nSpecifying a particular tool like `{\"type\": \"file_search\"}` or `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n" + }) + ), + "parallel_tool_calls": Schema.optionalKey(ParallelToolCalls), + "response_format": Schema.optionalKey(Schema.Union([AssistantsApiResponseFormatOption, Schema.Null])) +}) +export type CreateThreadAndRunRequest = { + readonly "assistant_id": string + readonly "thread"?: CreateThreadRequest + readonly "model"?: + | string + | "gpt-5" + | "gpt-5-mini" + | "gpt-5-nano" + | "gpt-5-2025-08-07" + | "gpt-5-mini-2025-08-07" + | "gpt-5-nano-2025-08-07" + | "gpt-4.1" + | "gpt-4.1-mini" + | "gpt-4.1-nano" + | "gpt-4.1-2025-04-14" + | "gpt-4.1-mini-2025-04-14" + | "gpt-4.1-nano-2025-04-14" + | "gpt-4o" + | "gpt-4o-2024-11-20" + | "gpt-4o-2024-08-06" + | "gpt-4o-2024-05-13" + | "gpt-4o-mini" + | "gpt-4o-mini-2024-07-18" + | "gpt-4.5-preview" + | "gpt-4.5-preview-2025-02-27" + | "gpt-4-turbo" + | "gpt-4-turbo-2024-04-09" + | "gpt-4-0125-preview" + | "gpt-4-turbo-preview" + | "gpt-4-1106-preview" + | "gpt-4-vision-preview" + | "gpt-4" + | "gpt-4-0314" + | "gpt-4-0613" + | "gpt-4-32k" + | "gpt-4-32k-0314" + | "gpt-4-32k-0613" + | "gpt-3.5-turbo" + | "gpt-3.5-turbo-16k" + | "gpt-3.5-turbo-0613" + | "gpt-3.5-turbo-1106" + | "gpt-3.5-turbo-0125" + | "gpt-3.5-turbo-16k-0613" + | null + readonly "instructions"?: string | null + readonly "tools"?: ReadonlyArray + readonly "tool_resources"?: { + readonly "code_interpreter"?: { readonly "file_ids"?: ReadonlyArray } + readonly "file_search"?: { readonly "vector_store_ids"?: ReadonlyArray } + } + readonly "metadata"?: Metadata + readonly "temperature"?: number + readonly "top_p"?: number + readonly "stream"?: boolean | null + readonly "max_prompt_tokens"?: number + readonly "max_completion_tokens"?: number + readonly "truncation_strategy"?: { + readonly "type": "auto" | "last_messages" + readonly "last_messages"?: number | null + } + readonly "tool_choice"?: "none" | "auto" | "required" | { + readonly "type": "function" | "code_interpreter" | "file_search" + readonly "function"?: { readonly "name": string } + } + readonly "parallel_tool_calls"?: ParallelToolCalls + readonly "response_format"?: AssistantsApiResponseFormatOption | null +} +export const CreateThreadAndRunRequest = Schema.Struct({ + "assistant_id": Schema.String.annotate({ + "description": "The ID of the [assistant](/docs/api-reference/assistants) to use to execute this run." + }), + "thread": Schema.optionalKey(CreateThreadRequest), + "model": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4.5-preview", + "gpt-4.5-preview-2025-02-27", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613" + ]) + ]).annotate({ + "description": + "The ID of the [Model](/docs/api-reference/models) to be used to execute this run. If a value is provided here, it will override the model associated with the assistant. If not, the model associated with the assistant will be used." + }), + Schema.Null + ]) + ), + "instructions": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Override the default system message of the assistant. This is useful for modifying the behavior on a per-run basis." + }) + ), + "tools": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([AssistantToolsCode, AssistantToolsFileSearch, AssistantToolsFunction], { mode: "oneOf" }) + ).check( + Schema.isMaxLength(20, { + "description": + "Override the tools the assistant can use for this run. This is useful for modifying the behavior on a per-run basis." + }) + ) + ]) + ), + "tool_resources": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "code_interpreter": Schema.optionalKey(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [file](/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files associated with the tool.\n" + }).check(Schema.isMaxLength(20)) + ) + })), + "file_search": Schema.optionalKey(Schema.Struct({ + "vector_store_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "The ID of the [vector store](/docs/api-reference/vector-stores/object) attached to this assistant. There can be a maximum of 1 vector store attached to the assistant.\n" + }).check(Schema.isMaxLength(1)) + ) + })) + }).annotate({ + "description": + "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n" + }) + ])), + "metadata": Schema.optionalKey(Metadata), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(2)], { + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n" + }) + ) + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(1)], { + "description": + "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n" + }) + ) + ]) + ), + "stream": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "If `true`, returns a stream of events that happen during the Run as server-sent events, terminating when the Run enters a terminal state with a `data: [DONE]` message.\n" + }) + ), + "max_prompt_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(256)], { + "description": + "The maximum number of prompt tokens that may be used over the course of the run. The run will make a best effort to use only the number of prompt tokens specified, across multiple turns of the run. If the run exceeds the number of prompt tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n" + }) + ) + ]) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(256)], { + "description": + "The maximum number of completion tokens that may be used over the course of the run. The run will make a best effort to use only the number of completion tokens specified, across multiple turns of the run. If the run exceeds the number of completion tokens specified, the run will end with status `incomplete`. See `incomplete_details` for more info.\n" + }) + ) + ]) + ), + "truncation_strategy": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "type": Schema.Literals(["auto", "last_messages"]).annotate({ + "description": + "The truncation strategy to use for the thread. The default is `auto`. If set to `last_messages`, the thread will be truncated to the n most recent messages in the thread. When set to `auto`, messages in the middle of the thread will be dropped to fit the context length of the model, `max_prompt_tokens`." + }), + "last_messages": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The number of most recent messages from the thread when constructing the context for the run." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ) + }).annotate({ + "title": "Thread Truncation Controls", + "description": + "Controls for how a thread will be truncated prior to the run. Use this to control the initial context window of the run." + }) + ])), + "tool_choice": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.Literal("none"), Schema.Literal("auto"), Schema.Literal("required")]).annotate({ + "description": + "`none` means the model will not call any tools and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools before responding to the user.\n" + }), + Schema.Union([ + Schema.Struct({ + "type": Schema.Literals(["function", "code_interpreter", "file_search"]).annotate({ + "description": "The type of the tool. If type is `function`, the function name must be set" + }), + "function": Schema.optionalKey( + Schema.Struct({ "name": Schema.String.annotate({ "description": "The name of the function to call." }) }) + ) + }).annotate({ + "description": "Specifies a tool the model should use. Use to force the model to call a specific tool." + }) + ]) + ], { mode: "oneOf" }).annotate({ + "description": + "Controls which (if any) tool is called by the model.\n`none` means the model will not call any tools and instead generates a message.\n`auto` is the default value and means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools before responding to the user.\nSpecifying a particular tool like `{\"type\": \"file_search\"}` or `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n" + }) + ), + "parallel_tool_calls": Schema.optionalKey(ParallelToolCalls), + "response_format": Schema.optionalKey(Schema.Union([AssistantsApiResponseFormatOption, Schema.Null])) +}) +export type ModifyAssistantRequest = { + readonly "model"?: string | AssistantSupportedModels + readonly "reasoning_effort"?: ReasoningEffort + readonly "name"?: string | null + readonly "description"?: string | null + readonly "instructions"?: string | null + readonly "tools"?: ReadonlyArray + readonly "tool_resources"?: { + readonly "code_interpreter"?: { readonly "file_ids"?: ReadonlyArray } + readonly "file_search"?: { readonly "vector_store_ids"?: ReadonlyArray } + } | null + readonly "metadata"?: Metadata + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "response_format"?: AssistantsApiResponseFormatOption | null +} +export const ModifyAssistantRequest = Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([Schema.String, AssistantSupportedModels]).annotate({ + "description": + "ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models) for descriptions of them.\n" + }) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The name of the assistant. The maximum length is 256 characters.\n" }) + .check(Schema.isMaxLength(256)), + Schema.Null + ]) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The description of the assistant. The maximum length is 512 characters.\n" + }).check(Schema.isMaxLength(512)), + Schema.Null + ]) + ), + "instructions": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The system instructions that the assistant uses. The maximum length is 256,000 characters.\n" + }).check(Schema.isMaxLength(256000)), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Array( + Schema.Union([AssistantToolsCode, AssistantToolsFileSearch, AssistantToolsFunction], { mode: "oneOf" }) + ).annotate({ + "description": + "A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant. Tools can be of types `code_interpreter`, `file_search`, or `function`.\n" + }).check(Schema.isMaxLength(128)) + ), + "tool_resources": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "code_interpreter": Schema.optionalKey(Schema.Struct({ + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Overrides the list of [file](/docs/api-reference/files) IDs made available to the `code_interpreter` tool. There can be a maximum of 20 files associated with the tool.\n" + }).check(Schema.isMaxLength(20)) + ) + })), + "file_search": Schema.optionalKey(Schema.Struct({ + "vector_store_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Overrides the [vector store](/docs/api-reference/vector-stores/object) attached to this assistant. There can be a maximum of 1 vector store attached to the assistant.\n" + }).check(Schema.isMaxLength(1)) + ) + })) + }).annotate({ + "description": + "A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs.\n" + }), + Schema.Null + ])), + "metadata": Schema.optionalKey(Metadata), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n\nWe generally recommend altering this or temperature but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ])), + "response_format": Schema.optionalKey(Schema.Union([AssistantsApiResponseFormatOption, Schema.Null])) +}) +export type RunObject = { + readonly "id": string + readonly "object": "thread.run" + readonly "created_at": number + readonly "thread_id": string + readonly "assistant_id": string + readonly "status": + | "queued" + | "in_progress" + | "requires_action" + | "cancelling" + | "cancelled" + | "failed" + | "completed" + | "incomplete" + | "expired" + readonly "required_action": { + readonly "type": "submit_tool_outputs" + readonly "submit_tool_outputs": { readonly "tool_calls": ReadonlyArray } + } + readonly "last_error": { + readonly "code": "server_error" | "rate_limit_exceeded" | "invalid_prompt" + readonly "message": string + } + readonly "expires_at": never + readonly "started_at": never + readonly "cancelled_at": never + readonly "failed_at": never + readonly "completed_at": never + readonly "incomplete_details": { readonly "reason"?: "max_completion_tokens" | "max_prompt_tokens" } + readonly "model": string + readonly "instructions": string + readonly "tools": ReadonlyArray + readonly "metadata": Metadata + readonly "usage": RunCompletionUsage + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "max_prompt_tokens": number + readonly "max_completion_tokens": number + readonly "truncation_strategy": { + readonly "type": "auto" | "last_messages" + readonly "last_messages"?: number | null + } + readonly "tool_choice": "none" | "auto" | "required" | { + readonly "type": "function" | "code_interpreter" | "file_search" + readonly "function"?: { readonly "name": string } + } + readonly "parallel_tool_calls": ParallelToolCalls + readonly "response_format": AssistantsApiResponseFormatOption | null +} +export const RunObject = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The identifier, which can be referenced in API endpoints." }), + "object": Schema.Literal("thread.run").annotate({ "description": "The object type, which is always `thread.run`." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the run was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "thread_id": Schema.String.annotate({ + "description": "The ID of the [thread](/docs/api-reference/threads) that was executed on as a part of this run." + }), + "assistant_id": Schema.String.annotate({ + "description": "The ID of the [assistant](/docs/api-reference/assistants) used for execution of this run." + }), + "status": Schema.Literals([ + "queued", + "in_progress", + "requires_action", + "cancelling", + "cancelled", + "failed", + "completed", + "incomplete", + "expired" + ]).annotate({ + "description": + "The status of the run, which can be either `queued`, `in_progress`, `requires_action`, `cancelling`, `cancelled`, `failed`, `completed`, `incomplete`, or `expired`." + }), + "required_action": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("submit_tool_outputs").annotate({ + "description": "For now, this is always `submit_tool_outputs`." + }), + "submit_tool_outputs": Schema.Struct({ + "tool_calls": Schema.Array(RunToolCallObject).annotate({ "description": "A list of the relevant tool calls." }) + }).annotate({ "description": "Details on the tool outputs needed for this run to continue." }) + }).annotate({ + "description": "Details on the action required to continue the run. Will be `null` if no action is required." + }) + ]), + "last_error": Schema.Union([ + Schema.Struct({ + "code": Schema.Literals(["server_error", "rate_limit_exceeded", "invalid_prompt"]).annotate({ + "description": "One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`." + }), + "message": Schema.String.annotate({ "description": "A human-readable description of the error." }) + }).annotate({ "description": "The last error associated with this run. Will be `null` if there are no errors." }) + ]), + "expires_at": Schema.Never, + "started_at": Schema.Never, + "cancelled_at": Schema.Never, + "failed_at": Schema.Never, + "completed_at": Schema.Never, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_completion_tokens", "max_prompt_tokens"]).annotate({ + "description": + "The reason why the run is incomplete. This will point to which specific token limit was reached over the course of the run." + }) + ) + }).annotate({ "description": "Details on why the run is incomplete. Will be `null` if the run is not incomplete." }) + ]), + "model": Schema.String.annotate({ + "description": "The model that the [assistant](/docs/api-reference/assistants) used for this run." + }), + "instructions": Schema.String.annotate({ + "description": "The instructions that the [assistant](/docs/api-reference/assistants) used for this run." + }), + "tools": Schema.Array( + Schema.Union([AssistantToolsCode, AssistantToolsFileSearch, AssistantToolsFunction], { mode: "oneOf" }) + ).annotate({ + "description": "The list of tools that the [assistant](/docs/api-reference/assistants) used for this run." + }).check(Schema.isMaxLength(20)), + "metadata": Metadata, + "usage": RunCompletionUsage, + "temperature": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null]).annotate({ + "description": "The sampling temperature used for this run. If not set, defaults to 1." + }) + ), + "top_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null]).annotate({ + "description": "The nucleus sampling value used for this run. If not set, defaults to 1." + }) + ), + "max_prompt_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(256)], { + "description": "The maximum number of prompt tokens specified to have been used over the course of the run.\n" + }) + ) + ]), + "max_completion_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(256)], { + "description": + "The maximum number of completion tokens specified to have been used over the course of the run.\n" + }) + ) + ]), + "truncation_strategy": Schema.Union([ + Schema.Struct({ + "type": Schema.Literals(["auto", "last_messages"]).annotate({ + "description": + "The truncation strategy to use for the thread. The default is `auto`. If set to `last_messages`, the thread will be truncated to the n most recent messages in the thread. When set to `auto`, messages in the middle of the thread will be dropped to fit the context length of the model, `max_prompt_tokens`." + }), + "last_messages": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The number of most recent messages from the thread when constructing the context for the run." + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ) + }).annotate({ + "title": "Thread Truncation Controls", + "description": + "Controls for how a thread will be truncated prior to the run. Use this to control the initial context window of the run." + }) + ]), + "tool_choice": Schema.Union([ + Schema.Union([Schema.Literal("none"), Schema.Literal("auto"), Schema.Literal("required")]).annotate({ + "description": + "`none` means the model will not call any tools and instead generates a message. `auto` means the model can pick between generating a message or calling one or more tools. `required` means the model must call one or more tools before responding to the user.\n" + }), + Schema.Union([ + Schema.Struct({ + "type": Schema.Literals(["function", "code_interpreter", "file_search"]).annotate({ + "description": "The type of the tool. If type is `function`, the function name must be set" + }), + "function": Schema.optionalKey( + Schema.Struct({ "name": Schema.String.annotate({ "description": "The name of the function to call." }) }) + ) + }).annotate({ + "description": "Specifies a tool the model should use. Use to force the model to call a specific tool." + }) + ]) + ], { mode: "oneOf" }).annotate({ + "description": + "Controls which (if any) tool is called by the model.\n`none` means the model will not call any tools and instead generates a message.\n`auto` is the default value and means the model can pick between generating a message or calling one or more tools.\n`required` means the model must call one or more tools before responding to the user.\nSpecifying a particular tool like `{\"type\": \"file_search\"}` or `{\"type\": \"function\", \"function\": {\"name\": \"my_function\"}}` forces the model to call that tool.\n" + }), + "parallel_tool_calls": ParallelToolCalls, + "response_format": Schema.Union([AssistantsApiResponseFormatOption, Schema.Null]) +}).annotate({ + "title": "A run on a thread", + "description": "Represents an execution run on a [thread](/docs/api-reference/threads)." +}) +export type ResponseTextParam = { + readonly "format"?: TextResponseFormatConfiguration + readonly "verbosity"?: Verbosity +} +export const ResponseTextParam = Schema.Struct({ + "format": Schema.optionalKey(TextResponseFormatConfiguration), + "verbosity": Schema.optionalKey(Verbosity) +}).annotate({ + "description": + "Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n" +}) +export type RealtimeCreateClientSecretRequest = { + readonly "expires_after"?: { readonly "anchor"?: "created_at"; readonly "seconds"?: number } + readonly "session"?: RealtimeSessionCreateRequestGA | RealtimeTranscriptionSessionCreateRequestGA +} +export const RealtimeCreateClientSecretRequest = Schema.Struct({ + "expires_after": Schema.optionalKey( + Schema.Struct({ + "anchor": Schema.optionalKey( + Schema.Literal("created_at").annotate({ + "description": + "The anchor point for the client secret expiration, meaning that `seconds` will be added to the `created_at` time of the client secret to produce an expiration timestamp. Only `created_at` is currently supported.\n" + }) + ), + "seconds": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "The number of seconds from the anchor point to the expiration. Select a value between `10` and `7200` (2 hours). This default to 600 seconds (10 minutes) if not specified.\n", + "format": "int64" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(10)).check(Schema.isLessThanOrEqualTo(7200)) + ) + }).annotate({ + "title": "Client secret expiration", + "description": + "Configuration for the client secret expiration. Expiration refers to the time after which\na client secret will no longer be valid for creating sessions. The session itself may\ncontinue after that time once started. A secret can be used to create multiple sessions\nuntil it expires.\n" + }) + ), + "session": Schema.optionalKey( + Schema.Union([RealtimeSessionCreateRequestGA, RealtimeTranscriptionSessionCreateRequestGA], { mode: "oneOf" }) + .annotate({ + "title": "Session configuration", + "description": + "Session configuration to use for the client secret. Choose either a realtime\nsession or a transcription session.\n" + }) + ) +}).annotate({ + "title": "Realtime client secret creation request", + "description": + "Create a session and client secret for the Realtime API. The request can specify\neither a realtime or a transcription session configuration.\n[Learn more about the Realtime API](/docs/guides/realtime).\n" +}) +export type RealtimeCreateClientSecretResponse = { + readonly "value": string + readonly "expires_at": number + readonly "session": RealtimeSessionCreateResponseGA | RealtimeTranscriptionSessionCreateResponseGA +} +export const RealtimeCreateClientSecretResponse = Schema.Struct({ + "value": Schema.String.annotate({ "description": "The generated client secret value." }), + "expires_at": Schema.Number.annotate({ + "description": "Expiration timestamp for the client secret, in seconds since epoch.", + "format": "unixtime" + }).check(Schema.isInt()), + "session": Schema.Union([RealtimeSessionCreateResponseGA, RealtimeTranscriptionSessionCreateResponseGA], { + mode: "oneOf" + }).annotate({ + "title": "Session configuration", + "description": "The session configuration for either a realtime or transcription session.\n" + }) +}).annotate({ + "title": "Realtime session and client secret", + "description": "Response from creating a session and client secret for the Realtime API.\n" +}) +export type RealtimeServerEventSessionCreated = { + readonly "event_id": string + readonly "type": "session.created" + readonly "session": RealtimeSessionCreateResponseGA | RealtimeTranscriptionSessionCreateResponseGA +} +export const RealtimeServerEventSessionCreated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.created").annotate({ "description": "The event type, must be `session.created`." }), + "session": Schema.Union([RealtimeSessionCreateResponseGA, RealtimeTranscriptionSessionCreateResponseGA], { + mode: "oneOf" + }).annotate({ "description": "The session configuration." }) +}).annotate({ + "description": + "Returned when a Session is created. Emitted automatically when a new\nconnection is established as the first server event. This event will contain\nthe default Session configuration.\n" +}) +export type RealtimeServerEventSessionUpdated = { + readonly "event_id": string + readonly "type": "session.updated" + readonly "session": RealtimeSessionCreateResponseGA | RealtimeTranscriptionSessionCreateResponseGA +} +export const RealtimeServerEventSessionUpdated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("session.updated").annotate({ "description": "The event type, must be `session.updated`." }), + "session": Schema.Union([RealtimeSessionCreateResponseGA, RealtimeTranscriptionSessionCreateResponseGA], { + mode: "oneOf" + }).annotate({ "description": "The session configuration." }) +}).annotate({ + "description": "Returned when a session is updated with a `session.update` event, unless\nthere is an error.\n" +}) +export type CreateVectorStoreFileBatchRequest = { + readonly "file_ids": ReadonlyArray + readonly "files"?: ReadonlyArray + readonly "chunking_strategy"?: ChunkingStrategyRequestParam + readonly "attributes"?: VectorStoreFileAttributes +} | { + readonly "files": ReadonlyArray + readonly "file_ids"?: ReadonlyArray + readonly "chunking_strategy"?: ChunkingStrategyRequestParam + readonly "attributes"?: VectorStoreFileAttributes +} +export const CreateVectorStoreFileBatchRequest = Schema.Union([ + Schema.Struct({ + "file_ids": Schema.Array(Schema.String).annotate({ + "description": + "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files. If `attributes` or `chunking_strategy` are provided, they will be applied to all files in the batch. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `files`." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2000)), + "files": Schema.optionalKey( + Schema.Array(CreateVectorStoreFileRequest).annotate({ + "description": + "A list of objects that each include a `file_id` plus optional `attributes` or `chunking_strategy`. Use this when you need to override metadata for specific files. The global `attributes` or `chunking_strategy` will be ignored and must be specified for each file. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `file_ids`." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2000)) + ), + "chunking_strategy": Schema.optionalKey(ChunkingStrategyRequestParam), + "attributes": Schema.optionalKey(VectorStoreFileAttributes) + }), + Schema.Struct({ + "files": Schema.Array(CreateVectorStoreFileRequest).annotate({ + "description": + "A list of objects that each include a `file_id` plus optional `attributes` or `chunking_strategy`. Use this when you need to override metadata for specific files. The global `attributes` or `chunking_strategy` will be ignored and must be specified for each file. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `file_ids`." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2000)), + "file_ids": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of [File](/docs/api-reference/files) IDs that the vector store should use. Useful for tools like `file_search` that can access files. If `attributes` or `chunking_strategy` are provided, they will be applied to all files in the batch. The maximum batch size is 2000 files. This endpoint is recommended for multi-file ingestion and helps reduce per-vector-store write request pressure. Mutually exclusive with `files`." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(2000)) + ), + "chunking_strategy": Schema.optionalKey(ChunkingStrategyRequestParam), + "attributes": Schema.optionalKey(VectorStoreFileAttributes) + }) +]) +export type CodeInterpreterTool = { + readonly "type": "code_interpreter" + readonly "container": string | AutoCodeInterpreterToolParam +} +export const CodeInterpreterTool = Schema.Struct({ + "type": Schema.Literal("code_interpreter").annotate({ + "description": "The type of the code interpreter tool. Always `code_interpreter`.\n" + }), + "container": Schema.Union([ + Schema.String.annotate({ "description": "The container ID." }), + AutoCodeInterpreterToolParam + ], { mode: "oneOf" }).annotate({ + "description": + "The code interpreter container. Can be a container ID or an object that\nspecifies uploaded file IDs to make available to your code, along with an\noptional `memory_limit` setting.\n" + }) +}).annotate({ + "title": "Code interpreter", + "description": "A tool that runs Python code to help generate a response to a prompt.\n" +}) +export type FunctionShellToolParam = { + readonly "type": "shell" + readonly "environment"?: ContainerAutoParam | LocalEnvironmentParam | ContainerReferenceParam | null +} +export const FunctionShellToolParam = Schema.Struct({ + "type": Schema.Literal("shell").annotate({ "description": "The type of the shell tool. Always `shell`." }), + "environment": Schema.optionalKey( + Schema.Union([ + Schema.Union([ContainerAutoParam, LocalEnvironmentParam, ContainerReferenceParam], { mode: "oneOf" }), + Schema.Null + ]) + ) +}).annotate({ "title": "Shell tool", "description": "A tool that allows the model to execute shell commands." }) +export type EvalItemContent = EvalItemContentItem | EvalItemContentArray +export const EvalItemContent = Schema.Union([EvalItemContentItem, EvalItemContentArray], { mode: "oneOf" }).annotate({ + "title": "Eval content", + "description": + "Inputs to the model - can contain template strings. Supports text, output text, input images, and input audio, either as a single item or an array of items.\n" +}) +export type OutputMessageContent = OutputTextContent | RefusalContent +export const OutputMessageContent = Schema.Union([OutputTextContent, RefusalContent], { mode: "oneOf" }) +export type ResponseContentPartAddedEvent = { + readonly "type": "response.content_part.added" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "part": OutputTextContent | RefusalContent | ReasoningTextContent + readonly "sequence_number": number +} +export const ResponseContentPartAddedEvent = Schema.Struct({ + "type": Schema.Literal("response.content_part.added").annotate({ + "description": "The type of the event. Always `response.content_part.added`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the content part was added to.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the content part was added to.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ "description": "The index of the content part that was added.\n" }).check( + Schema.isInt() + ), + "part": Schema.Union([OutputTextContent, RefusalContent, ReasoningTextContent], { mode: "oneOf" }).annotate({ + "description": "The content part that was added.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when a new content part is added." }) +export type ResponseContentPartDoneEvent = { + readonly "type": "response.content_part.done" + readonly "item_id": string + readonly "output_index": number + readonly "content_index": number + readonly "sequence_number": number + readonly "part": OutputTextContent | RefusalContent | ReasoningTextContent +} +export const ResponseContentPartDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.content_part.done").annotate({ + "description": "The type of the event. Always `response.content_part.done`.\n" + }), + "item_id": Schema.String.annotate({ + "description": "The ID of the output item that the content part was added to.\n" + }), + "output_index": Schema.Number.annotate({ + "description": "The index of the output item that the content part was added to.\n" + }).check(Schema.isInt()), + "content_index": Schema.Number.annotate({ "description": "The index of the content part that is done.\n" }).check( + Schema.isInt() + ), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "part": Schema.Union([OutputTextContent, RefusalContent, ReasoningTextContent], { mode: "oneOf" }).annotate({ + "description": "The content part that is done.\n" + }) +}).annotate({ "description": "Emitted when a content part is done." }) +export type Message = { + readonly "type": "message" + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "role": "unknown" | "user" | "assistant" | "system" | "critic" | "discriminator" | "developer" | "tool" + readonly "content": ReadonlyArray< + | InputTextContent + | OutputTextContent + | TextContent + | SummaryTextContent + | ReasoningTextContent + | RefusalContent + | InputImageContent + | ComputerScreenshotContent + | InputFileContent + > + readonly "phase"?: "commentary" | "final_answer" | null +} +export const Message = Schema.Struct({ + "type": Schema.Literal("message").annotate({ "description": "The type of the message. Always set to `message`." }), + "id": Schema.String.annotate({ "description": "The unique ID of the message." }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of item. One of `in_progress`, `completed`, or `incomplete`. Populated when items are returned via API." + }), + "role": Schema.Literals(["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"]) + .annotate({ + "description": + "The role of the message. One of `unknown`, `user`, `assistant`, `system`, `critic`, `discriminator`, `developer`, or `tool`." + }), + "content": Schema.Array( + Schema.Union([ + InputTextContent, + OutputTextContent, + TextContent, + SummaryTextContent, + ReasoningTextContent, + RefusalContent, + InputImageContent, + ComputerScreenshotContent, + InputFileContent + ], { mode: "oneOf" }).annotate({ "description": "A content part that makes up an input or output item." }) + ).annotate({ "description": "The content of the message" }), + "phase": Schema.optionalKey(Schema.Union([ + Schema.Literals(["commentary", "final_answer"]).annotate({ + "description": + "Labels an `assistant` message as intermediate commentary (`commentary`) or the final answer (`final_answer`). For models like `gpt-5.3-codex` and beyond, when sending follow-up requests, preserve and resend phase on all assistant messages — dropping it can degrade performance. Not used for user messages." + }), + Schema.Null + ])) +}).annotate({ "title": "Message", "description": "A message to or from the model." }) +export type EasyInputMessage = { + readonly "role": "user" | "assistant" | "system" | "developer" + readonly "content": string | InputMessageContentList + readonly "phase"?: MessagePhase | null + readonly "type"?: "message" +} +export const EasyInputMessage = Schema.Struct({ + "role": Schema.Literals(["user", "assistant", "system", "developer"]).annotate({ + "description": "The role of the message input. One of `user`, `assistant`, `system`, or\n`developer`.\n" + }), + "content": Schema.Union([ + Schema.String.annotate({ "title": "Text input", "description": "A text input to the model.\n" }), + InputMessageContentList + ], { mode: "oneOf" }).annotate({ + "description": + "Text, image, or audio input to the model, used to generate a response.\nCan also contain previous assistant responses.\n" + }), + "phase": Schema.optionalKey(Schema.Union([MessagePhase, Schema.Null])), + "type": Schema.optionalKey( + Schema.Literal("message").annotate({ "description": "The type of the message input. Always `message`.\n" }) + ) +}).annotate({ + "title": "Input message", + "description": + "A message input to the model with a role indicating instruction following\nhierarchy. Instructions given with the `developer` or `system` role take\nprecedence over instructions given with the `user` role. Messages with the\n`assistant` role are presumed to have been generated by the model in previous\ninteractions.\n" +}) +export type InputMessageResource = { + readonly "type": "message" + readonly "role": "user" | "system" | "developer" + readonly "status"?: "in_progress" | "completed" | "incomplete" + readonly "content": InputMessageContentList + readonly "id": string +} +export const InputMessageResource = Schema.Struct({ + "type": Schema.Literal("message").annotate({ + "description": "The type of the message input. Always set to `message`.\n" + }), + "role": Schema.Literals(["user", "system", "developer"]).annotate({ + "description": "The role of the message input. One of `user`, `system`, or `developer`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ), + "content": InputMessageContentList, + "id": Schema.String.annotate({ "description": "The unique ID of the message input.\n" }) +}).annotate({ + "title": "Input message", + "description": + "A message input to the model with a role indicating instruction following\nhierarchy. Instructions given with the `developer` or `system` role take\nprecedence over instructions given with the `user` role.\n" +}) +export type ComputerActionList = ReadonlyArray +export const ComputerActionList = Schema.Array(ComputerAction).annotate({ + "title": "Computer Action List", + "description": + "Flattened batched actions for `computer_use`. Each action includes an\n`type` discriminator and action-specific fields.\n" +}) +export type ThreadItem = + | UserMessageItem + | AssistantMessageItem + | WidgetMessageItem + | ClientToolCallItem + | TaskItem + | TaskGroupItem +export const ThreadItem = Schema.Union([ + UserMessageItem, + AssistantMessageItem, + WidgetMessageItem, + ClientToolCallItem, + TaskItem, + TaskGroupItem +], { mode: "oneOf" }).annotate({ "title": "The thread item" }) +export type ListAuditLogsResponse = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id"?: string | null + readonly "last_id"?: string | null + readonly "has_more": boolean +} +export const ListAuditLogsResponse = Schema.Struct({ + "object": Schema.Literal("list"), + "data": Schema.Array(AuditLog), + "first_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "last_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "has_more": Schema.Boolean +}) +export type ChatCompletionList = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const ChatCompletionList = Schema.Struct({ + "object": Schema.Literal("list").annotate({ + "description": "The type of this object. It is always set to \"list\".\n" + }), + "data": Schema.Array(CreateChatCompletionResponse).annotate({ + "description": "An array of chat completion objects.\n" + }), + "first_id": Schema.String.annotate({ + "description": "The identifier of the first chat completion in the data array." + }), + "last_id": Schema.String.annotate({ "description": "The identifier of the last chat completion in the data array." }), + "has_more": Schema.Boolean.annotate({ "description": "Indicates whether there are more Chat Completions available." }) +}).annotate({ "title": "ChatCompletionList", "description": "An object representing a list of Chat Completions.\n" }) +export type CreateChatCompletionRequest = { + readonly "metadata"?: Metadata + readonly "top_logprobs"?: number + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "messages": ReadonlyArray + readonly "model": + | string + | "gpt-5.4" + | "gpt-5.4-mini" + | "gpt-5.4-nano" + | "gpt-5.4-mini-2026-03-17" + | "gpt-5.4-nano-2026-03-17" + | "gpt-5.3-chat-latest" + | "gpt-5.2" + | "gpt-5.2-2025-12-11" + | "gpt-5.2-chat-latest" + | "gpt-5.2-pro" + | "gpt-5.2-pro-2025-12-11" + | "gpt-5.1" + | "gpt-5.1-2025-11-13" + | "gpt-5.1-codex" + | "gpt-5.1-mini" + | "gpt-5.1-chat-latest" + | "gpt-5" + | "gpt-5-mini" + | "gpt-5-nano" + | "gpt-5-2025-08-07" + | "gpt-5-mini-2025-08-07" + | "gpt-5-nano-2025-08-07" + | "gpt-5-chat-latest" + | "gpt-4.1" + | "gpt-4.1-mini" + | "gpt-4.1-nano" + | "gpt-4.1-2025-04-14" + | "gpt-4.1-mini-2025-04-14" + | "gpt-4.1-nano-2025-04-14" + | "o4-mini" + | "o4-mini-2025-04-16" + | "o3" + | "o3-2025-04-16" + | "o3-mini" + | "o3-mini-2025-01-31" + | "o1" + | "o1-2024-12-17" + | "o1-preview" + | "o1-preview-2024-09-12" + | "o1-mini" + | "o1-mini-2024-09-12" + | "gpt-4o" + | "gpt-4o-2024-11-20" + | "gpt-4o-2024-08-06" + | "gpt-4o-2024-05-13" + | "gpt-4o-audio-preview" + | "gpt-4o-audio-preview-2024-10-01" + | "gpt-4o-audio-preview-2024-12-17" + | "gpt-4o-audio-preview-2025-06-03" + | "gpt-4o-mini-audio-preview" + | "gpt-4o-mini-audio-preview-2024-12-17" + | "gpt-4o-search-preview" + | "gpt-4o-mini-search-preview" + | "gpt-4o-search-preview-2025-03-11" + | "gpt-4o-mini-search-preview-2025-03-11" + | "chatgpt-4o-latest" + | "codex-mini-latest" + | "gpt-4o-mini" + | "gpt-4o-mini-2024-07-18" + | "gpt-4-turbo" + | "gpt-4-turbo-2024-04-09" + | "gpt-4-0125-preview" + | "gpt-4-turbo-preview" + | "gpt-4-1106-preview" + | "gpt-4-vision-preview" + | "gpt-4" + | "gpt-4-0314" + | "gpt-4-0613" + | "gpt-4-32k" + | "gpt-4-32k-0314" + | "gpt-4-32k-0613" + | "gpt-3.5-turbo" + | "gpt-3.5-turbo-16k" + | "gpt-3.5-turbo-0301" + | "gpt-3.5-turbo-0613" + | "gpt-3.5-turbo-1106" + | "gpt-3.5-turbo-0125" + | "gpt-3.5-turbo-16k-0613" + readonly "modalities"?: ResponseModalities + readonly "verbosity"?: Verbosity + readonly "reasoning_effort"?: ReasoningEffort + readonly "max_completion_tokens"?: number | null + readonly "frequency_penalty"?: number + readonly "presence_penalty"?: number + readonly "web_search_options"?: { + readonly "user_location"?: { readonly "type": "approximate"; readonly "approximate": WebSearchLocation } + readonly "search_context_size"?: WebSearchContextSize + } + readonly "response_format"?: ResponseFormatText | ResponseFormatJsonSchema | ResponseFormatJsonObject + readonly "audio"?: { + readonly "voice": VoiceIdsShared | { readonly "id": string } + readonly "format": "wav" | "aac" | "mp3" | "flac" | "opus" | "pcm16" + } + readonly "store"?: boolean | null + readonly "stream"?: boolean | null + readonly "stop"?: StopConfiguration + readonly "logit_bias"?: {} + readonly "logprobs"?: boolean | null + readonly "max_tokens"?: number | null + readonly "n"?: number + readonly "prediction"?: PredictionContent | null + readonly "seed"?: number + readonly "stream_options"?: ChatCompletionStreamOptions + readonly "tools"?: ReadonlyArray + readonly "tool_choice"?: ChatCompletionToolChoiceOption + readonly "parallel_tool_calls"?: ParallelToolCalls + readonly "function_call"?: "none" | "auto" | ChatCompletionFunctionCallOption + readonly "functions"?: ReadonlyArray +} +export const CreateChatCompletionRequest = Schema.Struct({ + "metadata": Schema.optionalKey(Metadata), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup( + [Schema.isFinite(), Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(20)], + { + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n`logprobs` must be set to `true` if this parameter is used.\n" + } + ) + ).check( + Schema.makeFilterGroup([ + Schema.isGreaterThanOrEqualTo(0), + Schema.isLessThanOrEqualTo(20), + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(20)], { + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }) + ], { + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }) + ) + ]) + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ])), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "messages": Schema.Array(ChatCompletionRequestMessage).annotate({ + "description": + "A list of messages comprising the conversation so far. Depending on the\n[model](/docs/models) you use, different message types (modalities) are\nsupported, like [text](/docs/guides/text-generation),\n[images](/docs/guides/vision), and [audio](/docs/guides/audio).\n" + }).check(Schema.isMinLength(1)), + "model": Schema.Union([ + Schema.String, + Schema.Literals([ + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.4-mini-2026-03-17", + "gpt-5.4-nano-2026-03-17", + "gpt-5.3-chat-latest", + "gpt-5.2", + "gpt-5.2-2025-12-11", + "gpt-5.2-chat-latest", + "gpt-5.2-pro", + "gpt-5.2-pro-2025-12-11", + "gpt-5.1", + "gpt-5.1-2025-11-13", + "gpt-5.1-codex", + "gpt-5.1-mini", + "gpt-5.1-chat-latest", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", + "gpt-5-2025-08-07", + "gpt-5-mini-2025-08-07", + "gpt-5-nano-2025-08-07", + "gpt-5-chat-latest", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.1-2025-04-14", + "gpt-4.1-mini-2025-04-14", + "gpt-4.1-nano-2025-04-14", + "o4-mini", + "o4-mini-2025-04-16", + "o3", + "o3-2025-04-16", + "o3-mini", + "o3-mini-2025-01-31", + "o1", + "o1-2024-12-17", + "o1-preview", + "o1-preview-2024-09-12", + "o1-mini", + "o1-mini-2024-09-12", + "gpt-4o", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-audio-preview", + "gpt-4o-audio-preview-2024-10-01", + "gpt-4o-audio-preview-2024-12-17", + "gpt-4o-audio-preview-2025-06-03", + "gpt-4o-mini-audio-preview", + "gpt-4o-mini-audio-preview-2024-12-17", + "gpt-4o-search-preview", + "gpt-4o-mini-search-preview", + "gpt-4o-search-preview-2025-03-11", + "gpt-4o-mini-search-preview-2025-03-11", + "chatgpt-4o-latest", + "codex-mini-latest", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo-16k-0613" + ]) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "modalities": Schema.optionalKey(ResponseModalities), + "verbosity": Schema.optionalKey(Verbosity), + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "max_completion_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }) + ), + "frequency_penalty": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(-2), Schema.isLessThanOrEqualTo(2)], { + "description": + "Number between -2.0 and 2.0. Positive values penalize new tokens based on\ntheir existing frequency in the text so far, decreasing the model's\nlikelihood to repeat the same line verbatim.\n" + }) + ) + ]) + ), + "presence_penalty": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite()).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(-2), Schema.isLessThanOrEqualTo(2)], { + "description": + "Number between -2.0 and 2.0. Positive values penalize new tokens based on\nwhether they appear in the text so far, increasing the model's likelihood\nto talk about new topics.\n" + }) + ) + ]) + ), + "web_search_options": Schema.optionalKey( + Schema.Struct({ + "user_location": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("approximate").annotate({ + "description": "The type of location approximation. Always `approximate`.\n" + }), + "approximate": WebSearchLocation + }).annotate({ "description": "Approximate location parameters for the search.\n" }) + ]) + ), + "search_context_size": Schema.optionalKey(WebSearchContextSize) + }).annotate({ + "title": "Web search", + "description": + "This tool searches the web for relevant results to use in a response.\nLearn more about the [web search tool](/docs/guides/tools-web-search?api-mode=chat).\n" + }) + ), + "response_format": Schema.optionalKey( + Schema.Union([ResponseFormatText, ResponseFormatJsonSchema, ResponseFormatJsonObject], { mode: "oneOf" }).annotate({ + "description": + "An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables\nStructured Outputs which ensures the model will match your supplied JSON\nschema. Learn more in the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n" + }) + ), + "audio": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "voice": Schema.Union([ + VoiceIdsShared, + Schema.Struct({ "id": Schema.String.annotate({ "description": "The custom voice ID, e.g. `voice_1234`." }) }) + .annotate({ "description": "Custom voice reference." }) + ]).annotate({ + "title": "Voice", + "description": + "The voice the model uses to respond. Supported built-in voices are\n`alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `nova`, `onyx`,\n`sage`, `shimmer`, `marin`, and `cedar`. You may also provide a\ncustom voice object with an `id`, for example `{ \"id\": \"voice_1234\" }`.\n" + }), + "format": Schema.Literals(["wav", "aac", "mp3", "flac", "opus", "pcm16"]).annotate({ + "description": "Specifies the output audio format. Must be one of `wav`, `mp3`, `flac`,\n`opus`, or `pcm16`.\n" + }) + }).annotate({ + "description": + "Parameters for audio output. Required when audio output is requested with\n`modalities: [\"audio\"]`. [Learn more](/docs/guides/audio).\n" + }) + ])), + "store": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether or not to store the output of this chat completion request for\nuse in our [model distillation](/docs/guides/distillation) or\n[evals](/docs/guides/evals) products.\n\nSupports text and image inputs. Note: image inputs over 8MB will be dropped.\n" + }) + ), + "stream": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/chat/streaming)\nfor more information, along with the [streaming responses](/docs/guides/streaming-responses)\nguide for more information on how to handle the streaming events.\n" + }) + ), + "stop": Schema.optionalKey(StopConfiguration), + "logit_bias": Schema.optionalKey(Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Modify the likelihood of specified tokens appearing in the completion.\n\nAccepts a JSON object that maps tokens (specified by their token ID in the\ntokenizer) to an associated bias value from -100 to 100. Mathematically,\nthe bias is added to the logits generated by the model prior to sampling.\nThe exact effect will vary per model, but values between -1 and 1 should\ndecrease or increase likelihood of selection; values like -100 or 100\nshould result in a ban or exclusive selection of the relevant token.\n" + }) + ])), + "logprobs": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to return log probabilities of the output tokens or not. If true,\nreturns the log probabilities of each output token returned in the\n`content` of `message`.\n" + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt()), Schema.Null]).annotate({ + "description": + "The maximum number of [tokens](/tokenizer) that can be generated in the\nchat completion. This value can be used to control\n[costs](https://openai.com/api/pricing/) for text generated via API.\n\nThis value is now deprecated in favor of `max_completion_tokens`, and is\nnot compatible with [o-series models](/docs/guides/reasoning).\n" + }) + ), + "n": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([Schema.isFinite(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(128)], { + "description": + "How many chat completion choices to generate for each input message. Note that you will be charged based on the number of generated tokens across all of the choices. Keep `n` as `1` to minimize costs." + }) + ) + ]) + ), + "prediction": Schema.optionalKey(Schema.Union([ + Schema.Union([PredictionContent], { mode: "oneOf" }).annotate({ + "description": + "Configuration for a [Predicted Output](/docs/guides/predicted-outputs),\nwhich can greatly improve response times when large parts of the model\nresponse are known ahead of time. This is most common when you are\nregenerating a file with only minor changes to most of the content.\n" + }), + Schema.Null + ])), + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([ + Schema.isFinite(), + Schema.isGreaterThanOrEqualTo(-9223372036854776000), + Schema.isLessThanOrEqualTo(9223372036854776000) + ], { + "description": + "This feature is in Beta.\nIf specified, our system will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.\nDeterminism is not guaranteed, and you should refer to the `system_fingerprint` response parameter to monitor changes in the backend.\n" + }) + ) + ]) + ), + "stream_options": Schema.optionalKey(ChatCompletionStreamOptions), + "tools": Schema.optionalKey( + Schema.Array(Schema.Union([ChatCompletionTool, CustomToolChatCompletions], { mode: "oneOf" })).annotate({ + "description": + "A list of tools the model may call. You can provide either\n[custom tools](/docs/guides/function-calling#custom-tools) or\n[function tools](/docs/guides/function-calling).\n" + }) + ), + "tool_choice": Schema.optionalKey(ChatCompletionToolChoiceOption), + "parallel_tool_calls": Schema.optionalKey(ParallelToolCalls), + "function_call": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["none", "auto"]).annotate({ + "description": + "`none` means the model will not call a function and instead generates a message. `auto` means the model can pick between generating a message or calling a function.\n" + }), + ChatCompletionFunctionCallOption + ], { mode: "oneOf" }).annotate({ + "description": + "Deprecated in favor of `tool_choice`.\n\nControls which (if any) function is called by the model.\n\n`none` means the model will not call a function and instead generates a\nmessage.\n\n`auto` means the model can pick between generating a message or calling a\nfunction.\n\nSpecifying a particular function via `{\"name\": \"my_function\"}` forces the\nmodel to call that function.\n\n`none` is the default when no functions are present. `auto` is the default\nif functions are present.\n" + }) + ), + "functions": Schema.optionalKey( + Schema.Array(ChatCompletionFunctions).annotate({ + "description": "Deprecated in favor of `tools`.\n\nA list of functions the model may generate JSON inputs for.\n" + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(128)) + ) +}) +export type ListRunStepsResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean + readonly [x: string]: unknown +} +export const ListRunStepsResponse = Schema.StructWithRest( + Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(RunStepObject), + "first_id": Schema.String, + "last_id": Schema.String, + "has_more": Schema.Boolean + }), + [Schema.Record(Schema.String, Schema.Json)] +) +export type RunStepStreamEvent = + | { readonly "event": "thread.run.step.created"; readonly "data": RunStepObject } + | { readonly "event": "thread.run.step.in_progress"; readonly "data": RunStepObject } + | { readonly "event": "thread.run.step.delta"; readonly "data": RunStepDeltaObject } + | { readonly "event": "thread.run.step.completed"; readonly "data": RunStepObject } + | { readonly "event": "thread.run.step.failed"; readonly "data": RunStepObject } + | { readonly "event": "thread.run.step.cancelled"; readonly "data": RunStepObject } + | { readonly "event": "thread.run.step.expired"; readonly "data": RunStepObject } +export const RunStepStreamEvent = Schema.Union([ + Schema.Struct({ "event": Schema.Literal("thread.run.step.created"), "data": RunStepObject }).annotate({ + "description": "Occurs when a [run step](/docs/api-reference/run-steps/step-object) is created." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.step.in_progress"), "data": RunStepObject }).annotate({ + "description": + "Occurs when a [run step](/docs/api-reference/run-steps/step-object) moves to an `in_progress` state." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.step.delta"), "data": RunStepDeltaObject }).annotate({ + "description": "Occurs when parts of a [run step](/docs/api-reference/run-steps/step-object) are being streamed." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.step.completed"), "data": RunStepObject }).annotate({ + "description": "Occurs when a [run step](/docs/api-reference/run-steps/step-object) is completed." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.step.failed"), "data": RunStepObject }).annotate({ + "description": "Occurs when a [run step](/docs/api-reference/run-steps/step-object) fails." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.step.cancelled"), "data": RunStepObject }).annotate({ + "description": "Occurs when a [run step](/docs/api-reference/run-steps/step-object) is cancelled." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.step.expired"), "data": RunStepObject }).annotate({ + "description": "Occurs when a [run step](/docs/api-reference/run-steps/step-object) expires." + }) +], { mode: "oneOf" }) +export type RealtimeServerEventResponseCreated = { + readonly "event_id": string + readonly "type": "response.created" + readonly "response": RealtimeResponse +} +export const RealtimeServerEventResponseCreated = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.created").annotate({ "description": "The event type, must be `response.created`." }), + "response": RealtimeResponse +}).annotate({ + "description": + "Returned when a new Response is created. The first event of response creation,\nwhere the response is in an initial state of `in_progress`.\n" +}) +export type RealtimeServerEventResponseDone = { + readonly "event_id": string + readonly "type": "response.done" + readonly "response": RealtimeResponse +} +export const RealtimeServerEventResponseDone = Schema.Struct({ + "event_id": Schema.String.annotate({ "description": "The unique ID of the server event." }), + "type": Schema.Literal("response.done").annotate({ "description": "The event type, must be `response.done`." }), + "response": RealtimeResponse +}).annotate({ + "description": + "Returned when a Response is done streaming. Always emitted, no matter the \nfinal state. The Response object included in the `response.done` event will \ninclude all output Items in the Response but will omit the raw audio data.\n\nClients should check the `status` field of the Response to determine if it was successful\n(`completed`) or if there was another outcome: `cancelled`, `failed`, or `incomplete`.\n\nA response will contain all output items that were generated during the response, excluding\nany audio content.\n" +}) +export type RealtimeClientEventResponseCreate = { + readonly "event_id"?: string + readonly "type": "response.create" + readonly "response"?: RealtimeResponseCreateParams +} +export const RealtimeClientEventResponseCreate = Schema.Struct({ + "event_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional client-generated ID used to identify this event." }).check( + Schema.isMaxLength(512) + ) + ), + "type": Schema.Literal("response.create").annotate({ "description": "The event type, must be `response.create`." }), + "response": Schema.optionalKey(RealtimeResponseCreateParams) +}).annotate({ + "description": + "This event instructs the server to create a Response, which means triggering \nmodel inference. When in Server VAD mode, the server will create Responses \nautomatically.\n\nA Response will include at least one Item, and may have two, in which case \nthe second will be a function call. These Items will be appended to the \nconversation history by default.\n\nThe server will respond with a `response.created` event, events for Items \nand content created, and finally a `response.done` event to indicate the \nResponse is complete.\n\nThe `response.create` event includes inference configuration like \n`instructions` and `tools`. If these are set, they will override the Session's \nconfiguration for this Response only.\n\nResponses can be created out-of-band of the default Conversation, meaning that they can\nhave arbitrary input, and it's possible to disable writing the output to the Conversation.\nOnly one Response can write to the default Conversation at a time, but otherwise multiple\nResponses can be created in parallel. The `metadata` field is a good way to disambiguate\nmultiple simultaneous Responses.\n\nClients can set `conversation` to `none` to create a Response that does not write to the default\nConversation. Arbitrary input can be provided with the `input` field, which is an array accepting\nraw Items and references to existing Items.\n" +}) +export type ListAssistantsResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const ListAssistantsResponse = Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(AssistantObject), + "first_id": Schema.String, + "last_id": Schema.String, + "has_more": Schema.Boolean +}) +export type ListRunsResponse = { + readonly "object": string + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const ListRunsResponse = Schema.Struct({ + "object": Schema.String, + "data": Schema.Array(RunObject), + "first_id": Schema.String, + "last_id": Schema.String, + "has_more": Schema.Boolean +}) +export type RunStreamEvent = + | { readonly "event": "thread.run.created"; readonly "data": RunObject } + | { readonly "event": "thread.run.queued"; readonly "data": RunObject } + | { readonly "event": "thread.run.in_progress"; readonly "data": RunObject } + | { readonly "event": "thread.run.requires_action"; readonly "data": RunObject } + | { readonly "event": "thread.run.completed"; readonly "data": RunObject } + | { readonly "event": "thread.run.incomplete"; readonly "data": RunObject } + | { readonly "event": "thread.run.failed"; readonly "data": RunObject } + | { readonly "event": "thread.run.cancelling"; readonly "data": RunObject } + | { readonly "event": "thread.run.cancelled"; readonly "data": RunObject } + | { readonly "event": "thread.run.expired"; readonly "data": RunObject } +export const RunStreamEvent = Schema.Union([ + Schema.Struct({ "event": Schema.Literal("thread.run.created"), "data": RunObject }).annotate({ + "description": "Occurs when a new [run](/docs/api-reference/runs/object) is created." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.queued"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) moves to a `queued` status." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.in_progress"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) moves to an `in_progress` status." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.requires_action"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) moves to a `requires_action` status." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.completed"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) is completed." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.incomplete"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) ends with status `incomplete`." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.failed"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) fails." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.cancelling"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) moves to a `cancelling` status." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.cancelled"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) is cancelled." + }), + Schema.Struct({ "event": Schema.Literal("thread.run.expired"), "data": RunObject }).annotate({ + "description": "Occurs when a [run](/docs/api-reference/runs/object) expires." + }) +], { mode: "oneOf" }) +export type Tool = + | FunctionTool + | FileSearchTool + | ComputerTool + | ComputerUsePreviewTool + | WebSearchTool + | MCPTool + | CodeInterpreterTool + | ImageGenTool + | LocalShellToolParam + | FunctionShellToolParam + | CustomToolParam + | NamespaceToolParam + | ToolSearchToolParam + | WebSearchPreviewTool + | ApplyPatchToolParam +export const Tool = Schema.Union([ + FunctionTool, + FileSearchTool, + ComputerTool, + ComputerUsePreviewTool, + WebSearchTool, + MCPTool, + CodeInterpreterTool, + ImageGenTool, + LocalShellToolParam, + FunctionShellToolParam, + CustomToolParam, + NamespaceToolParam, + ToolSearchToolParam, + WebSearchPreviewTool, + ApplyPatchToolParam +], { mode: "oneOf" }).annotate({ "description": "A tool that can be used to generate a response.\n" }) +export type CreateEvalItem = { readonly "role": string; readonly "content": string } | { + readonly "role": "user" | "assistant" | "system" | "developer" + readonly "content": EvalItemContent + readonly "type"?: "message" +} +export const CreateEvalItem = Schema.Union([ + Schema.Struct({ + "role": Schema.String.annotate({ + "description": "The role of the message (e.g. \"system\", \"assistant\", \"user\")." + }), + "content": Schema.String.annotate({ "description": "The content of the message." }) + }).annotate({ + "title": "CreateEvalItem", + "description": + "A chat message that makes up the prompt or context. May include variable references to the `item` namespace, ie {{item.name}}." + }), + Schema.Struct({ + "role": Schema.Literals(["user", "assistant", "system", "developer"]).annotate({ + "description": "The role of the message input. One of `user`, `assistant`, `system`, or\n`developer`.\n" + }), + "content": EvalItemContent, + "type": Schema.optionalKey( + Schema.Literal("message").annotate({ "description": "The type of the message input. Always `message`.\n" }) + ) + }).annotate({ + "title": "CreateEvalItem", + "description": + "A chat message that makes up the prompt or context. May include variable references to the `item` namespace, ie {{item.name}}." + }) +], { mode: "oneOf" }) +export type EvalItem = { + readonly "role": "user" | "assistant" | "system" | "developer" + readonly "content": EvalItemContent + readonly "type"?: "message" +} +export const EvalItem = Schema.Struct({ + "role": Schema.Literals(["user", "assistant", "system", "developer"]).annotate({ + "description": "The role of the message input. One of `user`, `assistant`, `system`, or\n`developer`.\n" + }), + "content": EvalItemContent, + "type": Schema.optionalKey( + Schema.Literal("message").annotate({ "description": "The type of the message input. Always `message`.\n" }) + ) +}).annotate({ + "title": "Eval message object", + "description": + "A message input to the model with a role indicating instruction following\nhierarchy. Instructions given with the `developer` or `system` role take\nprecedence over instructions given with the `user` role. Messages with the\n`assistant` role are presumed to have been generated by the model in previous\ninteractions.\n" +}) +export type OutputMessage = { + readonly "id": string + readonly "type": "message" + readonly "role": "assistant" + readonly "content": ReadonlyArray + readonly "phase"?: MessagePhase | null + readonly "status": "in_progress" | "completed" | "incomplete" +} +export const OutputMessage = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the output message.\n" }), + "type": Schema.Literal("message").annotate({ "description": "The type of the output message. Always `message`.\n" }), + "role": Schema.Literal("assistant").annotate({ + "description": "The role of the output message. Always `assistant`.\n" + }), + "content": Schema.Array(OutputMessageContent).annotate({ "description": "The content of the output message.\n" }), + "phase": Schema.optionalKey(Schema.Union([MessagePhase, Schema.Null])), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the message input. One of `in_progress`, `completed`, or\n`incomplete`. Populated when input items are returned via API.\n" + }) +}).annotate({ "title": "Output message", "description": "An output message from the model.\n" }) +export type ComputerToolCall = { + readonly "type": "computer_call" + readonly "id": string + readonly "call_id": string + readonly "action"?: ComputerAction + readonly "actions"?: ComputerActionList + readonly "pending_safety_checks": ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" +} +export const ComputerToolCall = Schema.Struct({ + "type": Schema.Literal("computer_call").annotate({ + "description": "The type of the computer call. Always `computer_call`." + }), + "id": Schema.String.annotate({ "description": "The unique ID of the computer call." }), + "call_id": Schema.String.annotate({ + "description": "An identifier used when responding to the tool call with output.\n" + }), + "action": Schema.optionalKey(ComputerAction), + "actions": Schema.optionalKey(ComputerActionList), + "pending_safety_checks": Schema.Array(ComputerCallSafetyCheckParam).annotate({ + "description": "The pending safety checks for the computer call.\n" + }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) +}).annotate({ + "title": "Computer tool call", + "description": + "A tool call to a computer use tool. See the\n[computer use guide](/docs/guides/tools-computer-use) for more information.\n" +}) +export type ThreadItemListResource = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "last_id": string | null + readonly "has_more": boolean +} +export const ThreadItemListResource = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be `list`." }), + "data": Schema.Array(ThreadItem).annotate({ "description": "A list of items" }), + "first_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the first item in the list." }), + Schema.Null + ]), + "last_id": Schema.Union([ + Schema.String.annotate({ "description": "The ID of the last item in the list." }), + Schema.Null + ]), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more items available." }) +}).annotate({ + "title": "Thread Items", + "description": "A paginated list of thread items rendered for the ChatKit API." +}) +export type InputItem = + | EasyInputMessage + | { + readonly "type"?: "message" + readonly "role": "user" | "system" | "developer" + readonly "status"?: "in_progress" | "completed" | "incomplete" + readonly "content": InputMessageContentList + } + | { + readonly "id": string + readonly "type": "message" + readonly "role": "assistant" + readonly "content": ReadonlyArray + readonly "phase"?: MessagePhase | null + readonly "status": "in_progress" | "completed" | "incomplete" + } + | { + readonly "id": string + readonly "type": "file_search_call" + readonly "status": "in_progress" | "searching" | "completed" | "incomplete" | "failed" + readonly "queries": ReadonlyArray + readonly "results"?: + | ReadonlyArray< + { + readonly "file_id"?: string + readonly "text"?: string + readonly "filename"?: string + readonly "attributes"?: VectorStoreFileAttributes + readonly "score"?: number + } + > + | null + } + | { + readonly "type": "computer_call" + readonly "id": string + readonly "call_id": string + readonly "action"?: ComputerAction + readonly "actions"?: ComputerActionList + readonly "pending_safety_checks": ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + } + | { + readonly "id"?: string | null + readonly "call_id": string + readonly "type": "computer_call_output" + readonly "output": ComputerScreenshotImage + readonly "acknowledged_safety_checks"?: ReadonlyArray | null + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + } + | { + readonly "id": string + readonly "type": "web_search_call" + readonly "status": "in_progress" | "searching" | "completed" | "failed" + readonly "action": + | { + readonly "type": "search" + readonly "query"?: string + readonly "queries"?: ReadonlyArray + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "type": "find_in_page"; readonly "url": string; readonly "pattern": string } + } + | { + readonly "id"?: string + readonly "type": "function_call" + readonly "call_id": string + readonly "namespace"?: string + readonly "name": string + readonly "arguments": string + readonly "status"?: "in_progress" | "completed" | "incomplete" + } + | { + readonly "id"?: string | null + readonly "call_id": string + readonly "type": "function_call_output" + readonly "output": + | string + | ReadonlyArray + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + } + | { + readonly "id"?: string | null + readonly "call_id"?: string | null + readonly "type": "tool_search_call" + readonly "execution"?: "server" | "client" + readonly "arguments": {} + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + } + | { + readonly "id"?: string | null + readonly "call_id"?: string | null + readonly "type": "tool_search_output" + readonly "execution"?: "server" | "client" + readonly "tools": ReadonlyArray + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + } + | { + readonly "type": "reasoning" + readonly "id": string + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + readonly "content"?: ReadonlyArray + readonly "status"?: "in_progress" | "completed" | "incomplete" + } + | { readonly "id"?: string | null; readonly "type": "compaction"; readonly "encrypted_content": string } + | { + readonly "type": "image_generation_call" + readonly "id": string + readonly "status": "in_progress" | "completed" | "generating" | "failed" + readonly "result": string | null + } + | { + readonly "type": "code_interpreter_call" + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" | "interpreting" | "failed" + readonly "container_id": string + readonly "code": string | null + readonly "outputs": ReadonlyArray | null + } + | { + readonly "type": "local_shell_call" + readonly "id": string + readonly "call_id": string + readonly "action": LocalShellExecAction + readonly "status": "in_progress" | "completed" | "incomplete" + } + | { + readonly "type": "local_shell_call_output" + readonly "id": string + readonly "output": string + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + readonly "call_id": unknown + } + | { + readonly "id"?: string | null + readonly "call_id": string + readonly "type": "shell_call" + readonly "action": { + readonly "commands": ReadonlyArray + readonly "timeout_ms"?: number | null + readonly "max_output_length"?: number | null + } + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + readonly "environment"?: LocalEnvironmentParam | ContainerReferenceParam | null + } + | { + readonly "id"?: string | null + readonly "call_id": string + readonly "type": "shell_call_output" + readonly "output": ReadonlyArray + readonly "status"?: "in_progress" | "completed" | "incomplete" | null + readonly "max_output_length"?: number | null + } + | { + readonly "type": "apply_patch_call" + readonly "id"?: string | null + readonly "call_id": string + readonly "status": "in_progress" | "completed" + readonly "operation": + | ApplyPatchCreateFileOperationParam + | ApplyPatchDeleteFileOperationParam + | ApplyPatchUpdateFileOperationParam + } + | { + readonly "type": "apply_patch_call_output" + readonly "id"?: string | null + readonly "call_id": string + readonly "status": "completed" | "failed" + readonly "output"?: string | null + } + | { + readonly "type": "mcp_list_tools" + readonly "id": string + readonly "server_label": string + readonly "tools": ReadonlyArray + readonly "error"?: string | null + } + | { + readonly "type": "mcp_approval_request" + readonly "id": string + readonly "server_label": string + readonly "name": string + readonly "arguments": string + } + | { + readonly "type": "mcp_approval_response" + readonly "id"?: string | null + readonly "approval_request_id": string + readonly "approve": boolean + readonly "reason"?: string | null + readonly "request_id": unknown + } + | { + readonly "type": "mcp_call" + readonly "id": string + readonly "server_label": string + readonly "name": string + readonly "arguments": string + readonly "output"?: string | null + readonly "error"?: string | null + readonly "status"?: "in_progress" | "completed" | "incomplete" | "calling" | "failed" + readonly "approval_request_id"?: string | null + } + | { + readonly "type": "custom_tool_call_output" + readonly "id"?: string + readonly "call_id": string + readonly "output": string | ReadonlyArray + } + | { + readonly "type": "custom_tool_call" + readonly "id"?: string + readonly "call_id": string + readonly "namespace"?: string + readonly "name": string + readonly "input": string + } + | CompactionTriggerItemParam + | ItemReferenceParam +export const InputItem = Schema.Union([ + EasyInputMessage, + Schema.Union([ + Schema.Struct({ + "type": Schema.optionalKey( + Schema.Literal("message").annotate({ + "description": "The type of the message input. Always set to `message`.\n" + }) + ), + "role": Schema.Literals(["user", "system", "developer"]).annotate({ + "description": "The role of the message input. One of `user`, `system`, or `developer`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ), + "content": InputMessageContentList + }).annotate({ "title": "Input message", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the output message.\n" }), + "type": Schema.Literal("message").annotate({ + "description": "The type of the output message. Always `message`.\n" + }), + "role": Schema.Literal("assistant").annotate({ + "description": "The role of the output message. Always `assistant`.\n" + }), + "content": Schema.Array(OutputMessageContent).annotate({ "description": "The content of the output message.\n" }), + "phase": Schema.optionalKey(Schema.Union([MessagePhase, Schema.Null])), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the message input. One of `in_progress`, `completed`, or\n`incomplete`. Populated when input items are returned via API.\n" + }) + }).annotate({ "title": "Output message", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the file search tool call.\n" }), + "type": Schema.Literal("file_search_call").annotate({ + "description": "The type of the file search tool call. Always `file_search_call`.\n" + }), + "status": Schema.Literals(["in_progress", "searching", "completed", "incomplete", "failed"]).annotate({ + "description": + "The status of the file search tool call. One of `in_progress`,\n`searching`, `incomplete` or `failed`,\n" + }), + "queries": Schema.Array(Schema.String).annotate({ "description": "The queries used to search for files.\n" }), + "results": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Struct({ + "file_id": Schema.optionalKey(Schema.String.annotate({ "description": "The unique ID of the file.\n" })), + "text": Schema.optionalKey( + Schema.String.annotate({ "description": "The text that was retrieved from the file.\n" }) + ), + "filename": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the file.\n" })), + "attributes": Schema.optionalKey(VectorStoreFileAttributes), + "score": Schema.optionalKey( + Schema.Number.annotate({ + "description": "The relevance score of the file - a value between 0 and 1.\n", + "format": "float" + }).check(Schema.isFinite()) + ) + })).annotate({ "description": "The results of the file search tool call.\n" }), + Schema.Null + ])) + }).annotate({ "title": "File search tool call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("computer_call").annotate({ + "description": "The type of the computer call. Always `computer_call`." + }), + "id": Schema.String.annotate({ "description": "The unique ID of the computer call." }), + "call_id": Schema.String.annotate({ + "description": "An identifier used when responding to the tool call with output.\n" + }), + "action": Schema.optionalKey(ComputerAction), + "actions": Schema.optionalKey(ComputerActionList), + "pending_safety_checks": Schema.Array(ComputerCallSafetyCheckParam).annotate({ + "description": "The pending safety checks for the computer call.\n" + }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + }).annotate({ "title": "Computer tool call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The ID of the computer tool call output." }), + Schema.Null + ]) + ), + "call_id": Schema.String.annotate({ "description": "The ID of the computer tool call that produced the output." }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + "type": Schema.Literal("computer_call_output").annotate({ + "description": "The type of the computer tool call output. Always `computer_call_output`." + }), + "output": ComputerScreenshotImage, + "acknowledged_safety_checks": Schema.optionalKey( + Schema.Union([ + Schema.Array(ComputerCallSafetyCheckParam).annotate({ + "description": "The safety checks reported by the API that have been acknowledged by the developer." + }), + Schema.Null + ]) + ), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the message input. One of `in_progress`, `completed`, or `incomplete`. Populated when input items are returned via API." + }), + Schema.Null + ]) + ) + }).annotate({ "title": "Computer tool call output", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique ID of the web search tool call.\n" }), + "type": Schema.Literal("web_search_call").annotate({ + "description": "The type of the web search tool call. Always `web_search_call`.\n" + }), + "status": Schema.Literals(["in_progress", "searching", "completed", "failed"]).annotate({ + "description": "The status of the web search tool call.\n" + }), + "action": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("search").annotate({ "description": "The action type.\n" }), + "query": Schema.optionalKey(Schema.String.annotate({ "description": "[DEPRECATED] The search query.\n" })), + "queries": Schema.optionalKey( + Schema.Array(Schema.String.annotate({ "description": "A search query.\n" })).annotate({ + "title": "Search queries", + "description": "The search queries.\n" + }) + ), + "sources": Schema.optionalKey( + Schema.Array( + Schema.Struct({ + "type": Schema.Literal("url").annotate({ "description": "The type of source. Always `url`.\n" }), + "url": Schema.String.annotate({ "description": "The URL of the source.\n", "format": "uri" }) + }).annotate({ "title": "Web search source", "description": "A source used in the search.\n" }) + ).annotate({ "title": "Web search sources", "description": "The sources used in the search.\n" }) + ) + }).annotate({ + "title": "Search action", + "description": + "An object describing the specific action taken in this web search call.\nIncludes details on how the model used the web (search, open_page, find_in_page).\n" + }), + Schema.Struct({ + "type": Schema.Literal("open_page").annotate({ "description": "The action type. Always `open_page`.\n" }), + "url": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "format": "uri" }), Schema.Null]).annotate({ + "description": "The URL opened by the model.\n" + }) + ) + }).annotate({ + "title": "Open page action", + "description": + "An object describing the specific action taken in this web search call.\nIncludes details on how the model used the web (search, open_page, find_in_page).\n" + }), + Schema.Struct({ + "type": Schema.Literal("find_in_page").annotate({ "description": "The action type.\n" }), + "url": Schema.String.annotate({ + "description": "The URL of the page searched for the pattern.\n", + "format": "uri" + }), + "pattern": Schema.String.annotate({ "description": "The pattern or text to search for within the page.\n" }) + }).annotate({ + "title": "Find action", + "description": + "An object describing the specific action taken in this web search call.\nIncludes details on how the model used the web (search, open_page, find_in_page).\n" + }) + ], { mode: "oneOf" }) + }).annotate({ "title": "Web search tool call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String.annotate({ "description": "The unique ID of the function tool call.\n" })), + "type": Schema.Literal("function_call").annotate({ + "description": "The type of the function tool call. Always `function_call`.\n" + }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the function tool call generated by the model.\n" + }), + "namespace": Schema.optionalKey( + Schema.String.annotate({ "description": "The namespace of the function to run.\n" }) + ), + "name": Schema.String.annotate({ "description": "The name of the function to run.\n" }), + "arguments": Schema.String.annotate({ + "description": "A JSON string of the arguments to pass to the function.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ) + }).annotate({ "title": "Function tool call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the function tool call output. Populated when this item is returned via API." + }), + Schema.Null + ]) + ), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the function tool call generated by the model." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + "type": Schema.Literal("function_call_output").annotate({ + "description": "The type of the function tool call output. Always `function_call_output`." + }), + "output": Schema.Union([ + Schema.String.annotate({ "description": "A JSON string of the output of the function tool call." }).check( + Schema.isMaxLength(10485760) + ), + Schema.Array( + Schema.Union([InputTextContentParam, InputImageContentParamAutoParam, InputFileContentParam], { + mode: "oneOf" + }).annotate({ "description": "A piece of message content, such as text, an image, or a file." }) + ).annotate({ "description": "An array of content outputs (text, image, file) for the function tool call." }) + ], { mode: "oneOf" }).annotate({ "description": "Text, image, or file output of the function tool call." }), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or `incomplete`. Populated when items are returned via API." + }), + Schema.Null + ]) + ) + }).annotate({ "title": "Function tool call output", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The unique ID of this tool search call." }), + Schema.Null + ]) + ), + "call_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The unique ID of the tool search call generated by the model." }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + Schema.Null + ]) + ), + "type": Schema.Literal("tool_search_call").annotate({ + "description": "The item type. Always `tool_search_call`." + }), + "execution": Schema.optionalKey( + Schema.Literals(["server", "client"]).annotate({ + "description": "Whether tool search was executed by the server or by the client." + }) + ), + "arguments": Schema.Struct({}).annotate({ "description": "The arguments supplied to the tool search call." }), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the tool search call." + }), + Schema.Null + ]) + ) + }).annotate({ "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The unique ID of this tool search output." }), + Schema.Null + ]) + ), + "call_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The unique ID of the tool search call generated by the model." }) + .check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + Schema.Null + ]) + ), + "type": Schema.Literal("tool_search_output").annotate({ + "description": "The item type. Always `tool_search_output`." + }), + "execution": Schema.optionalKey( + Schema.Literals(["server", "client"]).annotate({ + "description": "Whether tool search was executed by the server or by the client." + }) + ), + "tools": Schema.Array(Tool).annotate({ + "description": "The loaded tool definitions returned by the tool search output." + }), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the tool search output." + }), + Schema.Null + ]) + ) + }).annotate({ "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("reasoning").annotate({ "description": "The type of the object. Always `reasoning`.\n" }), + "id": Schema.String.annotate({ "description": "The unique identifier of the reasoning content.\n" }), + "encrypted_content": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The encrypted content of the reasoning item - populated when a response is\ngenerated with `reasoning.encrypted_content` in the `include` parameter.\n" + }), + Schema.Null + ]) + ), + "summary": Schema.Array(SummaryTextContent).annotate({ "description": "Reasoning summary content.\n" }), + "content": Schema.optionalKey( + Schema.Array(ReasoningTextContent).annotate({ "description": "Reasoning text content.\n" }) + ), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": + "The status of the item. One of `in_progress`, `completed`, or\n`incomplete`. Populated when items are returned via API.\n" + }) + ) + }).annotate({ "title": "Reasoning", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The ID of the compaction item." }), Schema.Null]) + ), + "type": Schema.Literal("compaction").annotate({ "description": "The type of the item. Always `compaction`." }), + "encrypted_content": Schema.String.annotate({ "description": "The encrypted content of the compaction summary." }) + .check(Schema.isMaxLength(10485760)) + }).annotate({ "title": "Compaction item", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("image_generation_call").annotate({ + "description": "The type of the image generation call. Always `image_generation_call`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the image generation call.\n" }), + "status": Schema.Literals(["in_progress", "completed", "generating", "failed"]).annotate({ + "description": "The status of the image generation call.\n" + }), + "result": Schema.Union([ + Schema.String.annotate({ "description": "The generated image encoded in base64.\n" }), + Schema.Null + ]) + }).annotate({ "title": "Image generation call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("code_interpreter_call").annotate({ + "description": "The type of the code interpreter tool call. Always `code_interpreter_call`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the code interpreter tool call.\n" }), + "status": Schema.Literals(["in_progress", "completed", "incomplete", "interpreting", "failed"]).annotate({ + "description": + "The status of the code interpreter tool call. Valid values are `in_progress`, `completed`, `incomplete`, `interpreting`, and `failed`.\n" + }), + "container_id": Schema.String.annotate({ "description": "The ID of the container used to run the code.\n" }), + "code": Schema.Union([ + Schema.String.annotate({ "description": "The code to run, or null if not available.\n" }), + Schema.Null + ]), + "outputs": Schema.Union([ + Schema.Array(Schema.Union([CodeInterpreterOutputLogs, CodeInterpreterOutputImage], { mode: "oneOf" })).annotate( + { + "description": + "The outputs generated by the code interpreter, such as logs or images.\nCan be null if no outputs are available.\n" + } + ), + Schema.Null + ]) + }).annotate({ + "title": "Code interpreter tool call", + "description": "Content item used to generate a response.\n" + }), + Schema.Struct({ + "type": Schema.Literal("local_shell_call").annotate({ + "description": "The type of the local shell call. Always `local_shell_call`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the local shell call.\n" }), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the local shell tool call generated by the model.\n" + }), + "action": LocalShellExecAction, + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the local shell call.\n" + }) + }).annotate({ "title": "Local shell call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("local_shell_call_output").annotate({ + "description": "The type of the local shell tool call output. Always `local_shell_call_output`.\n" + }), + "id": Schema.String.annotate({ + "description": "The unique ID of the local shell tool call generated by the model.\n" + }), + "output": Schema.String.annotate({ + "description": "A JSON string of the output of the local shell tool call.\n" + }), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the item. One of `in_progress`, `completed`, or `incomplete`.\n" + }), + Schema.Null + ]) + ), + "call_id": Schema.Unknown + }).annotate({ "title": "Local shell call output", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The unique ID of the shell tool call. Populated when this item is returned via API." + }), + Schema.Null + ]) + ), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the shell tool call generated by the model." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + "type": Schema.Literal("shell_call").annotate({ "description": "The type of the item. Always `shell_call`." }), + "action": Schema.Struct({ + "commands": Schema.Array(Schema.String).annotate({ + "description": "Ordered shell commands for the execution environment to run." + }), + "timeout_ms": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "Maximum wall-clock time in milliseconds to allow the shell commands to run." + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "max_output_length": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "Maximum number of UTF-8 characters to capture from combined stdout and stderr output." + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ + "title": "Shell action", + "description": "The shell commands and limits that describe how to run the tool call." + }), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "title": "Shell call status", + "description": "The status of the shell call. One of `in_progress`, `completed`, or `incomplete`." + }), + Schema.Null + ]) + ), + "environment": Schema.optionalKey( + Schema.Union([ + Schema.Union([LocalEnvironmentParam, ContainerReferenceParam], { mode: "oneOf" }).annotate({ + "description": "The environment to execute the shell commands in." + }), + Schema.Null + ]) + ) + }).annotate({ "title": "Shell tool call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The unique ID of the shell tool call output. Populated when this item is returned via API." + }), + Schema.Null + ]) + ), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the shell tool call generated by the model." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + "type": Schema.Literal("shell_call_output").annotate({ + "description": "The type of the item. Always `shell_call_output`." + }), + "output": Schema.Array(FunctionShellCallOutputContentParam).annotate({ + "description": "Captured chunks of stdout and stderr output, along with their associated outcomes." + }), + "status": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "title": "Shell call status", + "description": "The status of the shell call output." + }), + Schema.Null + ]) + ), + "max_output_length": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The maximum number of UTF-8 characters captured for this shell call's combined output." + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ "title": "Shell tool call output", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("apply_patch_call").annotate({ + "description": "The type of the item. Always `apply_patch_call`." + }), + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "The unique ID of the apply patch tool call. Populated when this item is returned via API." + }), + Schema.Null + ]) + ), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the apply patch tool call generated by the model." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + "status": Schema.Literals(["in_progress", "completed"]).annotate({ + "title": "Apply patch call status", + "description": "The status of the apply patch tool call. One of `in_progress` or `completed`." + }), + "operation": Schema.Union([ + ApplyPatchCreateFileOperationParam, + ApplyPatchDeleteFileOperationParam, + ApplyPatchUpdateFileOperationParam + ], { mode: "oneOf" }).annotate({ + "title": "Apply patch operation", + "description": "The specific create, delete, or update instruction for the apply_patch tool call." + }) + }).annotate({ "title": "Apply patch tool call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("apply_patch_call_output").annotate({ + "description": "The type of the item. Always `apply_patch_call_output`." + }), + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the apply patch tool call output. Populated when this item is returned via API." + }), + Schema.Null + ]) + ), + "call_id": Schema.String.annotate({ + "description": "The unique ID of the apply patch tool call generated by the model." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(64)), + "status": Schema.Literals(["completed", "failed"]).annotate({ + "title": "Apply patch call output status", + "description": "The status of the apply patch tool call output. One of `completed` or `failed`." + }), + "output": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": "Optional human-readable log text from the apply patch tool (e.g., patch results or errors)." + }).check(Schema.isMaxLength(10485760)), + Schema.Null + ]) + ) + }).annotate({ + "title": "Apply patch tool call output", + "description": "Content item used to generate a response.\n" + }), + Schema.Struct({ + "type": Schema.Literal("mcp_list_tools").annotate({ + "description": "The type of the item. Always `mcp_list_tools`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the list.\n" }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server.\n" }), + "tools": Schema.Array(MCPListToolsTool).annotate({ "description": "The tools available on the server.\n" }), + "error": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "Error message if the server could not list tools.\n" }), + Schema.Null + ]) + ) + }).annotate({ "title": "MCP list tools", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("mcp_approval_request").annotate({ + "description": "The type of the item. Always `mcp_approval_request`.\n" + }), + "id": Schema.String.annotate({ "description": "The unique ID of the approval request.\n" }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server making the request.\n" }), + "name": Schema.String.annotate({ "description": "The name of the tool to run.\n" }), + "arguments": Schema.String.annotate({ "description": "A JSON string of arguments for the tool.\n" }) + }).annotate({ "title": "MCP approval request", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("mcp_approval_response").annotate({ + "description": "The type of the item. Always `mcp_approval_response`.\n" + }), + "id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The unique ID of the approval response\n" }), + Schema.Null + ]) + ), + "approval_request_id": Schema.String.annotate({ + "description": "The ID of the approval request being answered.\n" + }), + "approve": Schema.Boolean.annotate({ "description": "Whether the request was approved.\n" }), + "reason": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "Optional reason for the decision.\n" }), Schema.Null]) + ), + "request_id": Schema.Unknown + }).annotate({ "title": "MCP approval response", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("mcp_call").annotate({ "description": "The type of the item. Always `mcp_call`.\n" }), + "id": Schema.String.annotate({ "description": "The unique ID of the tool call.\n" }), + "server_label": Schema.String.annotate({ "description": "The label of the MCP server running the tool.\n" }), + "name": Schema.String.annotate({ "description": "The name of the tool that was run.\n" }), + "arguments": Schema.String.annotate({ "description": "A JSON string of the arguments passed to the tool.\n" }), + "output": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "The output from the tool call.\n" }), Schema.Null]) + ), + "error": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "The error from the tool call, if any.\n" }), + Schema.Null + ]) + ), + "status": Schema.optionalKey( + Schema.Literals(["in_progress", "completed", "incomplete", "calling", "failed"]).annotate({ + "description": + "The status of the tool call. One of `in_progress`, `completed`, `incomplete`, `calling`, or `failed`.\n" + }) + ), + "approval_request_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "Unique identifier for the MCP tool call approval request.\nInclude this value in a subsequent `mcp_approval_response` input to approve or reject the corresponding tool call.\n" + }), + Schema.Null + ]) + ) + }).annotate({ "title": "MCP tool call", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("custom_tool_call_output").annotate({ + "description": "The type of the custom tool call output. Always `custom_tool_call_output`.\n" + }), + "id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The unique ID of the custom tool call output in the OpenAI platform.\n" + }) + ), + "call_id": Schema.String.annotate({ + "description": "The call ID, used to map this custom tool call output to a custom tool call.\n" + }), + "output": Schema.Union([ + Schema.String.annotate({ + "title": "string output", + "description": "A string of the output of the custom tool call.\n" + }), + Schema.Array(FunctionAndCustomToolCallOutput).annotate({ + "title": "output content list", + "description": "Text, image, or file output of the custom tool call.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "The output from the custom tool call generated by your code.\nCan be a string or an list of output content.\n" + }) + }).annotate({ "title": "Custom tool call output", "description": "Content item used to generate a response.\n" }), + Schema.Struct({ + "type": Schema.Literal("custom_tool_call").annotate({ + "description": "The type of the custom tool call. Always `custom_tool_call`.\n" + }), + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "The unique ID of the custom tool call in the OpenAI platform.\n" }) + ), + "call_id": Schema.String.annotate({ + "description": "An identifier used to map this custom tool call to a tool call output.\n" + }), + "namespace": Schema.optionalKey( + Schema.String.annotate({ "description": "The namespace of the custom tool being called.\n" }) + ), + "name": Schema.String.annotate({ "description": "The name of the custom tool being called.\n" }), + "input": Schema.String.annotate({ "description": "The input for the custom tool call generated by the model.\n" }) + }).annotate({ "title": "Custom tool call", "description": "Content item used to generate a response.\n" }) + ], { mode: "oneOf" }).annotate({ + "title": "Item", + "description": + "An item representing part of the context for the response to be\ngenerated by the model. Can contain text, images, and audio inputs,\nas well as previous assistant responses and tool call outputs.\n" + }), + CompactionTriggerItemParam, + ItemReferenceParam +], { mode: "oneOf" }) +export type ToolsArray = ReadonlyArray +export const ToolsArray = Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" +}) +export type ToolSearchOutput = { + readonly "type": "tool_search_output" + readonly "id": string + readonly "call_id": string | null + readonly "execution": "server" | "client" + readonly "tools": ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "created_by"?: string +} +export const ToolSearchOutput = Schema.Struct({ + "type": Schema.Literal("tool_search_output").annotate({ + "description": "The type of the item. Always `tool_search_output`." + }), + "id": Schema.String.annotate({ "description": "The unique ID of the tool search output item." }), + "call_id": Schema.Union([ + Schema.String.annotate({ "description": "The unique ID of the tool search call generated by the model." }), + Schema.Null + ]), + "execution": Schema.Literals(["server", "client"]).annotate({ + "description": "Whether tool search was executed by the server or by the client." + }), + "tools": Schema.Array(Tool).annotate({ "description": "The loaded tool definitions returned by tool search." }), + "status": Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "The status of the tool search output item that was recorded." + }), + "created_by": Schema.optionalKey( + Schema.String.annotate({ "description": "The identifier of the actor that created the item." }) + ) +}) +export type CreateEvalLabelModelGrader = { + readonly "type": "label_model" + readonly "name": string + readonly "model": string + readonly "input": ReadonlyArray + readonly "labels": ReadonlyArray + readonly "passing_labels": ReadonlyArray +} +export const CreateEvalLabelModelGrader = Schema.Struct({ + "type": Schema.Literal("label_model").annotate({ "description": "The object type, which is always `label_model`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ + "description": "The model to use for the evaluation. Must support structured outputs." + }), + "input": Schema.Array(CreateEvalItem).annotate({ + "description": + "A list of chat messages forming the prompt or context. May include variable references to the `item` namespace, ie {{item.name}}." + }), + "labels": Schema.Array(Schema.String).annotate({ + "description": "The labels to classify to each item in the evaluation." + }), + "passing_labels": Schema.Array(Schema.String).annotate({ + "description": "The labels that indicate a passing result. Must be a subset of labels." + }) +}).annotate({ + "title": "LabelModelGrader", + "description": "A LabelModelGrader object which uses a model to assign labels to each item\nin the evaluation.\n" +}) +export type CreateEvalRunRequest = { + readonly "name"?: string + readonly "metadata"?: Metadata + readonly "data_source": { + readonly "type": "jsonl" + readonly "source": EvalJsonlFileContentSource | EvalJsonlFileIdSource + } | { + readonly "type": "completions" + readonly "input_messages"?: { + readonly "type": "template" + readonly "template": ReadonlyArray + } | { readonly "type": "item_reference"; readonly "item_reference": string } + readonly "sampling_params"?: { + readonly "reasoning_effort"?: ReasoningEffort + readonly "temperature"?: number + readonly "max_completion_tokens"?: number + readonly "top_p"?: number + readonly "seed"?: number + readonly "response_format"?: ResponseFormatText | ResponseFormatJsonSchema | ResponseFormatJsonObject + readonly "tools"?: ReadonlyArray + } + readonly "model"?: string + readonly "source": EvalJsonlFileContentSource | EvalJsonlFileIdSource | EvalStoredCompletionsSource + } | { + readonly "type": "responses" + readonly "input_messages"?: { + readonly "type": "template" + readonly "template": ReadonlyArray<{ readonly "role": string; readonly "content": string } | EvalItem> + } | { readonly "type": "item_reference"; readonly "item_reference": string } + readonly "sampling_params"?: { + readonly "reasoning_effort"?: ReasoningEffort + readonly "temperature"?: number + readonly "max_completion_tokens"?: number + readonly "top_p"?: number + readonly "seed"?: number + readonly "tools"?: ReadonlyArray + readonly "text"?: { readonly "format"?: TextResponseFormatConfiguration } + } + readonly "model"?: string + readonly "source": EvalJsonlFileContentSource | EvalJsonlFileIdSource | EvalResponsesSource + } +} +export const CreateEvalRunRequest = Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the run." })), + "metadata": Schema.optionalKey(Metadata), + "data_source": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("jsonl").annotate({ "description": "The type of data source. Always `jsonl`." }), + "source": Schema.Union([EvalJsonlFileContentSource, EvalJsonlFileIdSource], { mode: "oneOf" }).annotate({ + "description": "Determines what populates the `item` namespace in the data source." + }) + }).annotate({ "title": "JsonlRunDataSource", "description": "Details about the run's data source." }), + Schema.Struct({ + "type": Schema.Literal("completions").annotate({ + "description": "The type of run data source. Always `completions`." + }), + "input_messages": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("template").annotate({ + "description": "The type of input messages. Always `template`." + }), + "template": Schema.Array(Schema.Union([EasyInputMessage, EvalItem], { mode: "oneOf" })).annotate({ + "description": + "A list of chat messages forming the prompt or context. May include variable references to the `item` namespace, ie {{item.name}}." + }) + }).annotate({ "title": "TemplateInputMessages" }), + Schema.Struct({ + "type": Schema.Literal("item_reference").annotate({ + "description": "The type of input messages. Always `item_reference`." + }), + "item_reference": Schema.String.annotate({ + "description": "A reference to a variable in the `item` namespace. Ie, \"item.input_trajectory\"" + }) + }).annotate({ "title": "ItemReferenceInputMessages" }) + ], { mode: "oneOf" }).annotate({ + "description": + "Used when sampling from a model. Dictates the structure of the messages passed into the model. Can either be a reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template with variable references to the `item` namespace." + }) + ), + "sampling_params": Schema.optionalKey(Schema.Struct({ + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs." }).check( + Schema.isFinite() + ) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum number of tokens in the generated output." }).check( + Schema.isInt() + ) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens." + }).check(Schema.isFinite()) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling." }) + .check(Schema.isInt()) + ), + "response_format": Schema.optionalKey( + Schema.Union([ResponseFormatText, ResponseFormatJsonSchema, ResponseFormatJsonObject], { mode: "oneOf" }) + .annotate({ + "description": + "An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables\nStructured Outputs which ensures the model will match your supplied JSON\nschema. Learn more in the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n" + }) + ), + "tools": Schema.optionalKey( + Schema.Array(ChatCompletionTool).annotate({ + "description": + "A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.\n" + }) + ) + })), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": "The name of the model to use for generating completions (e.g. \"o3-mini\")." + }) + ), + "source": Schema.Union([EvalJsonlFileContentSource, EvalJsonlFileIdSource, EvalStoredCompletionsSource], { + mode: "oneOf" + }).annotate({ "description": "Determines what populates the `item` namespace in this run's data source." }) + }).annotate({ "title": "CompletionsRunDataSource", "description": "Details about the run's data source." }), + Schema.Struct({ + "type": Schema.Literal("responses").annotate({ + "description": "The type of run data source. Always `responses`." + }), + "input_messages": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("template").annotate({ + "description": "The type of input messages. Always `template`." + }), + "template": Schema.Array( + Schema.Union([ + Schema.Struct({ + "role": Schema.String.annotate({ + "description": "The role of the message (e.g. \"system\", \"assistant\", \"user\")." + }), + "content": Schema.String.annotate({ "description": "The content of the message." }) + }).annotate({ "title": "ChatMessage" }), + EvalItem + ], { mode: "oneOf" }) + ).annotate({ + "description": + "A list of chat messages forming the prompt or context. May include variable references to the `item` namespace, ie {{item.name}}." + }) + }).annotate({ "title": "InputMessagesTemplate" }), + Schema.Struct({ + "type": Schema.Literal("item_reference").annotate({ + "description": "The type of input messages. Always `item_reference`." + }), + "item_reference": Schema.String.annotate({ + "description": "A reference to a variable in the `item` namespace. Ie, \"item.name\"" + }) + }).annotate({ "title": "InputMessagesItemReference" }) + ], { mode: "oneOf" }).annotate({ + "description": + "Used when sampling from a model. Dictates the structure of the messages passed into the model. Can either be a reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template with variable references to the `item` namespace." + }) + ), + "sampling_params": Schema.optionalKey(Schema.Struct({ + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs." }).check( + Schema.isFinite() + ) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum number of tokens in the generated output." }).check( + Schema.isInt() + ) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens." + }).check(Schema.isFinite()) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling." }) + .check(Schema.isInt()) + ), + "tools": Schema.optionalKey( + Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nThe two categories of tools you can provide the model are:\n\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code. Learn more about\n [function calling](/docs/guides/function-calling).\n" + }) + ), + "text": Schema.optionalKey( + Schema.Struct({ "format": Schema.optionalKey(TextResponseFormatConfiguration) }).annotate({ + "description": + "Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n" + }) + ) + })), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": "The name of the model to use for generating completions (e.g. \"o3-mini\")." + }) + ), + "source": Schema.Union([EvalJsonlFileContentSource, EvalJsonlFileIdSource, EvalResponsesSource], { + mode: "oneOf" + }).annotate({ "description": "Determines what populates the `item` namespace in this run's data source." }) + }).annotate({ "title": "ResponsesRunDataSource", "description": "Details about the run's data source." }) + ], { mode: "oneOf" }) +}).annotate({ "title": "CreateEvalRunRequest" }) +export type EvalGraderLabelModel = { + readonly "type": "label_model" + readonly "name": string + readonly "model": string + readonly "input": ReadonlyArray + readonly "labels": ReadonlyArray + readonly "passing_labels": ReadonlyArray +} +export const EvalGraderLabelModel = Schema.Struct({ + "type": Schema.Literal("label_model").annotate({ "description": "The object type, which is always `label_model`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ + "description": "The model to use for the evaluation. Must support structured outputs." + }), + "input": Schema.Array(EvalItem), + "labels": Schema.Array(Schema.String).annotate({ + "description": "The labels to assign to each item in the evaluation." + }), + "passing_labels": Schema.Array(Schema.String).annotate({ + "description": "The labels that indicate a passing result. Must be a subset of labels." + }) +}).annotate({ + "title": "LabelModelGrader", + "description": "A LabelModelGrader object which uses a model to assign labels to each item\nin the evaluation.\n" +}) +export type EvalGraderScoreModel = { + readonly "type": "score_model" + readonly "name": string + readonly "model": string + readonly "sampling_params"?: { + readonly "seed"?: number | null + readonly "top_p"?: number | null + readonly "temperature"?: number | null + readonly "max_completions_tokens"?: number | null + readonly "reasoning_effort"?: ReasoningEffort + } + readonly "input": ReadonlyArray + readonly "range"?: ReadonlyArray + readonly "pass_threshold"?: number +} +export const EvalGraderScoreModel = Schema.Struct({ + "type": Schema.Literal("score_model").annotate({ "description": "The object type, which is always `score_model`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ "description": "The model to use for the evaluation." }), + "sampling_params": Schema.optionalKey( + Schema.Struct({ + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling.\n" }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens.\n" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs.\n" }) + .check(Schema.isFinite()), + Schema.Null + ]) + ), + "max_completions_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The maximum number of tokens the grader model may generate in its response.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort) + }).annotate({ "description": "The sampling parameters for the model." }) + ), + "input": Schema.Array(EvalItem).annotate({ + "description": + "The input messages evaluated by the grader. Supports text, output text, input image, and input audio content blocks, and may include template strings.\n" + }), + "range": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isFinite())).annotate({ + "description": "The range of the score. Defaults to `[0, 1]`." + }) + ), + "pass_threshold": Schema.optionalKey( + Schema.Number.annotate({ "description": "The threshold for the score." }).check(Schema.isFinite()) + ) +}).annotate({ + "title": "ScoreModelGrader", + "description": "A ScoreModelGrader object that uses a model to assign a score to the input.\n" +}) +export type EvalRun = { + readonly "object": "eval.run" + readonly "id": string + readonly "eval_id": string + readonly "status": string + readonly "model": string + readonly "name": string + readonly "created_at": number + readonly "report_url": string + readonly "result_counts": { + readonly "total": number + readonly "errored": number + readonly "failed": number + readonly "passed": number + } + readonly "per_model_usage": ReadonlyArray< + { + readonly "model_name": string + readonly "invocation_count": number + readonly "prompt_tokens": number + readonly "completion_tokens": number + readonly "total_tokens": number + readonly "cached_tokens": number + } + > + readonly "per_testing_criteria_results": ReadonlyArray< + { readonly "testing_criteria": string; readonly "passed": number; readonly "failed": number } + > + readonly "data_source": { + readonly "type": "jsonl" + readonly "source": EvalJsonlFileContentSource | EvalJsonlFileIdSource + } | { + readonly "type": "completions" + readonly "input_messages"?: { + readonly "type": "template" + readonly "template": ReadonlyArray + } | { readonly "type": "item_reference"; readonly "item_reference": string } + readonly "sampling_params"?: { + readonly "reasoning_effort"?: ReasoningEffort + readonly "temperature"?: number + readonly "max_completion_tokens"?: number + readonly "top_p"?: number + readonly "seed"?: number + readonly "response_format"?: ResponseFormatText | ResponseFormatJsonSchema | ResponseFormatJsonObject + readonly "tools"?: ReadonlyArray + } + readonly "model"?: string + readonly "source": EvalJsonlFileContentSource | EvalJsonlFileIdSource | EvalStoredCompletionsSource + } | { + readonly "type": "responses" + readonly "input_messages"?: { + readonly "type": "template" + readonly "template": ReadonlyArray<{ readonly "role": string; readonly "content": string } | EvalItem> + } | { readonly "type": "item_reference"; readonly "item_reference": string } + readonly "sampling_params"?: { + readonly "reasoning_effort"?: ReasoningEffort + readonly "temperature"?: number + readonly "max_completion_tokens"?: number + readonly "top_p"?: number + readonly "seed"?: number + readonly "tools"?: ReadonlyArray + readonly "text"?: { readonly "format"?: TextResponseFormatConfiguration } + } + readonly "model"?: string + readonly "source": EvalJsonlFileContentSource | EvalJsonlFileIdSource | EvalResponsesSource + } + readonly "metadata": Metadata + readonly "error": EvalApiError +} +export const EvalRun = Schema.Struct({ + "object": Schema.Literal("eval.run").annotate({ "description": "The type of the object. Always \"eval.run\"." }), + "id": Schema.String.annotate({ "description": "Unique identifier for the evaluation run." }), + "eval_id": Schema.String.annotate({ "description": "The identifier of the associated evaluation." }), + "status": Schema.String.annotate({ "description": "The status of the evaluation run." }), + "model": Schema.String.annotate({ "description": "The model that is evaluated, if applicable." }), + "name": Schema.String.annotate({ "description": "The name of the evaluation run." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the evaluation run was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "report_url": Schema.String.annotate({ + "description": "The URL to the rendered evaluation run report on the UI dashboard.", + "format": "uri" + }), + "result_counts": Schema.Struct({ + "total": Schema.Number.annotate({ "description": "Total number of executed output items." }).check(Schema.isInt()), + "errored": Schema.Number.annotate({ "description": "Number of output items that resulted in an error." }).check( + Schema.isInt() + ), + "failed": Schema.Number.annotate({ "description": "Number of output items that failed to pass the evaluation." }) + .check(Schema.isInt()), + "passed": Schema.Number.annotate({ "description": "Number of output items that passed the evaluation." }).check( + Schema.isInt() + ) + }).annotate({ "description": "Counters summarizing the outcomes of the evaluation run." }), + "per_model_usage": Schema.Array(Schema.Struct({ + "model_name": Schema.String.annotate({ "description": "The name of the model." }), + "invocation_count": Schema.Number.annotate({ "description": "The number of invocations." }).check(Schema.isInt()), + "prompt_tokens": Schema.Number.annotate({ "description": "The number of prompt tokens used." }).check( + Schema.isInt() + ), + "completion_tokens": Schema.Number.annotate({ "description": "The number of completion tokens generated." }).check( + Schema.isInt() + ), + "total_tokens": Schema.Number.annotate({ "description": "The total number of tokens used." }).check(Schema.isInt()), + "cached_tokens": Schema.Number.annotate({ "description": "The number of tokens retrieved from cache." }).check( + Schema.isInt() + ) + })).annotate({ "description": "Usage statistics for each model during the evaluation run." }), + "per_testing_criteria_results": Schema.Array(Schema.Struct({ + "testing_criteria": Schema.String.annotate({ "description": "A description of the testing criteria." }), + "passed": Schema.Number.annotate({ "description": "Number of tests passed for this criteria." }).check( + Schema.isInt() + ), + "failed": Schema.Number.annotate({ "description": "Number of tests failed for this criteria." }).check( + Schema.isInt() + ) + })).annotate({ "description": "Results per testing criteria applied during the evaluation run." }), + "data_source": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("jsonl").annotate({ "description": "The type of data source. Always `jsonl`." }), + "source": Schema.Union([EvalJsonlFileContentSource, EvalJsonlFileIdSource], { mode: "oneOf" }).annotate({ + "description": "Determines what populates the `item` namespace in the data source." + }) + }).annotate({ "title": "JsonlRunDataSource", "description": "Information about the run's data source." }), + Schema.Struct({ + "type": Schema.Literal("completions").annotate({ + "description": "The type of run data source. Always `completions`." + }), + "input_messages": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("template").annotate({ + "description": "The type of input messages. Always `template`." + }), + "template": Schema.Array(Schema.Union([EasyInputMessage, EvalItem], { mode: "oneOf" })).annotate({ + "description": + "A list of chat messages forming the prompt or context. May include variable references to the `item` namespace, ie {{item.name}}." + }) + }).annotate({ "title": "TemplateInputMessages" }), + Schema.Struct({ + "type": Schema.Literal("item_reference").annotate({ + "description": "The type of input messages. Always `item_reference`." + }), + "item_reference": Schema.String.annotate({ + "description": "A reference to a variable in the `item` namespace. Ie, \"item.input_trajectory\"" + }) + }).annotate({ "title": "ItemReferenceInputMessages" }) + ], { mode: "oneOf" }).annotate({ + "description": + "Used when sampling from a model. Dictates the structure of the messages passed into the model. Can either be a reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template with variable references to the `item` namespace." + }) + ), + "sampling_params": Schema.optionalKey(Schema.Struct({ + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs." }).check( + Schema.isFinite() + ) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum number of tokens in the generated output." }).check( + Schema.isInt() + ) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens." + }).check(Schema.isFinite()) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling." }) + .check(Schema.isInt()) + ), + "response_format": Schema.optionalKey( + Schema.Union([ResponseFormatText, ResponseFormatJsonSchema, ResponseFormatJsonObject], { mode: "oneOf" }) + .annotate({ + "description": + "An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables\nStructured Outputs which ensures the model will match your supplied JSON\nschema. Learn more in the [Structured Outputs\nguide](/docs/guides/structured-outputs).\n\nSetting to `{ \"type\": \"json_object\" }` enables the older JSON mode, which\nensures the message the model generates is valid JSON. Using `json_schema`\nis preferred for models that support it.\n" + }) + ), + "tools": Schema.optionalKey( + Schema.Array(ChatCompletionTool).annotate({ + "description": + "A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported.\n" + }) + ) + })), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": "The name of the model to use for generating completions (e.g. \"o3-mini\")." + }) + ), + "source": Schema.Union([EvalJsonlFileContentSource, EvalJsonlFileIdSource, EvalStoredCompletionsSource], { + mode: "oneOf" + }).annotate({ "description": "Determines what populates the `item` namespace in this run's data source." }) + }).annotate({ "title": "CompletionsRunDataSource", "description": "Information about the run's data source." }), + Schema.Struct({ + "type": Schema.Literal("responses").annotate({ + "description": "The type of run data source. Always `responses`." + }), + "input_messages": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("template").annotate({ + "description": "The type of input messages. Always `template`." + }), + "template": Schema.Array( + Schema.Union([ + Schema.Struct({ + "role": Schema.String.annotate({ + "description": "The role of the message (e.g. \"system\", \"assistant\", \"user\")." + }), + "content": Schema.String.annotate({ "description": "The content of the message." }) + }).annotate({ "title": "ChatMessage" }), + EvalItem + ], { mode: "oneOf" }) + ).annotate({ + "description": + "A list of chat messages forming the prompt or context. May include variable references to the `item` namespace, ie {{item.name}}." + }) + }).annotate({ "title": "InputMessagesTemplate" }), + Schema.Struct({ + "type": Schema.Literal("item_reference").annotate({ + "description": "The type of input messages. Always `item_reference`." + }), + "item_reference": Schema.String.annotate({ + "description": "A reference to a variable in the `item` namespace. Ie, \"item.name\"" + }) + }).annotate({ "title": "InputMessagesItemReference" }) + ], { mode: "oneOf" }).annotate({ + "description": + "Used when sampling from a model. Dictates the structure of the messages passed into the model. Can either be a reference to a prebuilt trajectory (ie, `item.input_trajectory`), or a template with variable references to the `item` namespace." + }) + ), + "sampling_params": Schema.optionalKey(Schema.Struct({ + "reasoning_effort": Schema.optionalKey(ReasoningEffort), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs." }).check( + Schema.isFinite() + ) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "The maximum number of tokens in the generated output." }).check( + Schema.isInt() + ) + ), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens." + }).check(Schema.isFinite()) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling." }) + .check(Schema.isInt()) + ), + "tools": Schema.optionalKey( + Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nThe two categories of tools you can provide the model are:\n\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code. Learn more about\n [function calling](/docs/guides/function-calling).\n" + }) + ), + "text": Schema.optionalKey( + Schema.Struct({ "format": Schema.optionalKey(TextResponseFormatConfiguration) }).annotate({ + "description": + "Configuration options for a text response from the model. Can be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Structured Outputs](/docs/guides/structured-outputs)\n" + }) + ) + })), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": "The name of the model to use for generating completions (e.g. \"o3-mini\")." + }) + ), + "source": Schema.Union([EvalJsonlFileContentSource, EvalJsonlFileIdSource, EvalResponsesSource], { + mode: "oneOf" + }).annotate({ "description": "Determines what populates the `item` namespace in this run's data source." }) + }).annotate({ "title": "ResponsesRunDataSource", "description": "Information about the run's data source." }) + ], { mode: "oneOf" }), + "metadata": Metadata, + "error": EvalApiError +}).annotate({ "title": "EvalRun", "description": "A schema representing an evaluation run.\n" }) +export type GraderLabelModel = { + readonly "type": "label_model" + readonly "name": string + readonly "model": string + readonly "input": ReadonlyArray + readonly "labels": ReadonlyArray + readonly "passing_labels": ReadonlyArray +} +export const GraderLabelModel = Schema.Struct({ + "type": Schema.Literal("label_model").annotate({ "description": "The object type, which is always `label_model`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ + "description": "The model to use for the evaluation. Must support structured outputs." + }), + "input": Schema.Array(EvalItem), + "labels": Schema.Array(Schema.String).annotate({ + "description": "The labels to assign to each item in the evaluation." + }), + "passing_labels": Schema.Array(Schema.String).annotate({ + "description": "The labels that indicate a passing result. Must be a subset of labels." + }) +}).annotate({ + "title": "LabelModelGrader", + "description": "A LabelModelGrader object which uses a model to assign labels to each item\nin the evaluation.\n" +}) +export type GraderScoreModel = { + readonly "type": "score_model" + readonly "name": string + readonly "model": string + readonly "sampling_params"?: { + readonly "seed"?: number | null + readonly "top_p"?: number | null + readonly "temperature"?: number | null + readonly "max_completions_tokens"?: number | null + readonly "reasoning_effort"?: ReasoningEffort + } + readonly "input": ReadonlyArray + readonly "range"?: ReadonlyArray +} +export const GraderScoreModel = Schema.Struct({ + "type": Schema.Literal("score_model").annotate({ "description": "The object type, which is always `score_model`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ "description": "The model to use for the evaluation." }), + "sampling_params": Schema.optionalKey( + Schema.Struct({ + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling.\n" }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens.\n" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs.\n" }) + .check(Schema.isFinite()), + Schema.Null + ]) + ), + "max_completions_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The maximum number of tokens the grader model may generate in its response.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort) + }).annotate({ "description": "The sampling parameters for the model." }) + ), + "input": Schema.Array(EvalItem).annotate({ + "description": + "The input messages evaluated by the grader. Supports text, output text, input image, and input audio content blocks, and may include template strings.\n" + }), + "range": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isFinite())).annotate({ + "description": "The range of the score. Defaults to `[0, 1]`." + }) + ) +}).annotate({ + "title": "ScoreModelGrader", + "description": "A ScoreModelGrader object that uses a model to assign a score to the input.\n" +}) +export type InputParam = string | ReadonlyArray +export const InputParam = Schema.Union([ + Schema.String.annotate({ + "title": "Text input", + "description": "A text input to the model, equivalent to a text input with the\n`user` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) +], { mode: "oneOf" }).annotate({ + "description": + "Text, image, or file inputs to the model, used to generate a response.\n\nLearn more:\n- [Text inputs and outputs](/docs/guides/text)\n- [Image inputs](/docs/guides/images)\n- [File inputs](/docs/guides/pdf-files)\n- [Conversation state](/docs/guides/conversation-state)\n- [Function calling](/docs/guides/function-calling)\n" +}) +export type CreateConversationBody = { + readonly "metadata"?: {} | null | null + readonly "items"?: ReadonlyArray | null +} +export const CreateConversationBody = Schema.Struct({ + "metadata": Schema.optionalKey(Schema.Union([ + Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format, and querying for objects via API or the dashboard.\n Keys are strings with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters." + }), + Schema.Null + ])), + "items": Schema.optionalKey( + Schema.Union([ + Schema.Array(InputItem).annotate({ + "description": "Initial items to include in the conversation context. You may add up to 20 items at a time." + }).check(Schema.isMaxLength(20)), + Schema.Null + ]) + ) +}) +export type TokenCountsBody = { + readonly "model"?: string | null + readonly "input"?: string | ReadonlyArray | null + readonly "previous_response_id"?: string | null + readonly "tools"?: ReadonlyArray | null + readonly "text"?: ResponseTextParam | null + readonly "reasoning"?: { + readonly "effort"?: ReasoningEffort + readonly "summary"?: "auto" | "concise" | "detailed" | null + readonly "generate_summary"?: "auto" | "concise" | "detailed" | null + } | null + readonly "truncation"?: "auto" | "disabled" + readonly "instructions"?: string | null + readonly "conversation"?: ConversationParam | null + readonly "tool_choice"?: + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + | null + readonly "parallel_tool_calls"?: boolean | null +} +export const TokenCountsBody = Schema.Struct({ + "model": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI offers a wide range of models with different capabilities, performance characteristics, and price points. Refer to the [model guide](/docs/models) to browse and compare available models." + }), + Schema.Null + ]) + ), + "input": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the `user` role." + }).check(Schema.isMaxLength(10485760)), + Schema.Array(InputItem).annotate({ + "description": "A list of one or many input items to the model, containing different content types." + }) + ], { mode: "oneOf" }).annotate({ + "description": "Text, image, or file inputs to the model, used to generate a response" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to create multi-turn conversations. Learn more about [conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`." + }), + Schema.Null + ]) + ), + "tools": Schema.optionalKey( + Schema.Union([ + Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You can specify which tool to use by setting the `tool_choice` parameter." + }), + Schema.Null + ]) + ), + "text": Schema.optionalKey(Schema.Union([ResponseTextParam, Schema.Null])), + "reasoning": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "effort": Schema.optionalKey(ReasoningEffort), + "summary": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "concise", "detailed"]).annotate({ + "description": + "A summary of the reasoning performed by the model. This can be\nuseful for debugging and understanding the model's reasoning process.\nOne of `auto`, `concise`, or `detailed`.\n\n`concise` is supported for `computer-use-preview` models and all reasoning models after `gpt-5`.\n" + }), + Schema.Null + ])), + "generate_summary": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["auto", "concise", "detailed"]).annotate({ + "description": + "**Deprecated:** use `summary` instead.\n\nA summary of the reasoning performed by the model. This can be\nuseful for debugging and understanding the model's reasoning process.\nOne of `auto`, `concise`, or `detailed`.\n" + }), + Schema.Null + ]) + ) + }).annotate({ + "title": "Reasoning", + "description": + "**gpt-5 and o-series models only** Configuration options for [reasoning models](https://platform.openai.com/docs/guides/reasoning)." + }), + Schema.Null + ])), + "truncation": Schema.optionalKey( + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response. - `auto`: If the input to this Response exceeds the model's context window size, the model will truncate the response to fit the context window by dropping items from the beginning of the conversation. - `disabled` (default): If the input size will exceed the context window size for a model, the request will fail with a 400 error." + }) + ), + "instructions": Schema.optionalKey(Schema.Union([ + Schema.String.annotate({ + "description": + "A system (or developer) message inserted into the model's context.\nWhen used along with `previous_response_id`, the instructions from a previous response will not be carried over to the next response. This makes it simple to swap out system (or developer) messages in new responses." + }), + Schema.Null + ])), + "conversation": Schema.optionalKey(Schema.Union([ConversationParam, Schema.Null])), + "tool_choice": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ "description": "Controls which tool the model should use, if any." }), + Schema.Null + ]) + ), + "parallel_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ "description": "Whether to allow the model to run tool calls in parallel." }), + Schema.Null + ]) + ) +}) +export type CompactResponseMethodPublicBody = { + readonly "model": ModelIdsCompaction + readonly "input"?: string | ReadonlyArray | null + readonly "previous_response_id"?: string | null + readonly "instructions"?: string | null + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_retention"?: "in_memory" | "in-memory" | "24h" | null + readonly "service_tier"?: "auto" | "default" | "flex" | "priority" | null +} +export const CompactResponseMethodPublicBody = Schema.Struct({ + "model": ModelIdsCompaction, + "input": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the `user` role." + }).check(Schema.isMaxLength(10485760)), + Schema.Array(InputItem).annotate({ + "description": "A list of one or many input items to the model, containing different content types." + }) + ], { mode: "oneOf" }).annotate({ + "description": "Text, image, or file inputs to the model, used to generate a response" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to create multi-turn conversations. Learn more about [conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`." + }), + Schema.Null + ]) + ), + "instructions": Schema.optionalKey(Schema.Union([ + Schema.String.annotate({ + "description": + "A system (or developer) message inserted into the model's context.\nWhen used along with `previous_response_id`, the instructions from a previous response will not be carried over to the next response. This makes it simple to swap out system (or developer) messages in new responses." + }), + Schema.Null + ])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ "description": "A key to use when reading from or writing to the prompt cache." }).check( + Schema.isMaxLength(64) + ), + Schema.Null + ]) + ), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in-memory", "24h"]).annotate({ + "description": "How long to retain a prompt cache entry created by this request." + }), + Schema.Null + ]) + ), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["auto", "default", "flex", "priority"]).annotate({ + "description": "The service tier to use for this request." + }), + Schema.Null + ]) + ) +}) +export type ConversationItem = + | Message + | FunctionToolCallResource + | FunctionToolCallOutputResource + | FileSearchToolCall + | WebSearchToolCall + | ImageGenToolCall + | ComputerToolCall + | ComputerToolCallOutputResource + | ToolSearchCall + | ToolSearchOutput + | ReasoningItem + | CompactionBody + | CodeInterpreterToolCall + | LocalShellToolCall + | LocalShellToolCallOutput + | FunctionShellCall + | FunctionShellCallOutput + | ApplyPatchToolCall + | ApplyPatchToolCallOutput + | MCPListTools + | MCPApprovalRequest + | MCPApprovalResponseResource + | MCPToolCall + | CustomToolCall + | CustomToolCallOutput +export const ConversationItem = Schema.Union([ + Message, + FunctionToolCallResource, + FunctionToolCallOutputResource, + FileSearchToolCall, + WebSearchToolCall, + ImageGenToolCall, + ComputerToolCall, + ComputerToolCallOutputResource, + ToolSearchCall, + ToolSearchOutput, + ReasoningItem, + CompactionBody, + CodeInterpreterToolCall, + LocalShellToolCall, + LocalShellToolCallOutput, + FunctionShellCall, + FunctionShellCallOutput, + ApplyPatchToolCall, + ApplyPatchToolCallOutput, + MCPListTools, + MCPApprovalRequest, + MCPApprovalResponseResource, + MCPToolCall, + CustomToolCall, + CustomToolCallOutput +], { mode: "oneOf" }).annotate({ + "title": "Conversation item", + "description": + "A single item within a conversation. The set of possible types are the same as the `output` type of a [Response object](/docs/api-reference/responses/object#responses/object-output)." +}) +export type ItemResource = + | InputMessageResource + | OutputMessage + | FileSearchToolCall + | ComputerToolCall + | ComputerToolCallOutputResource + | WebSearchToolCall + | FunctionToolCallResource + | FunctionToolCallOutputResource + | ToolSearchCall + | ToolSearchOutput + | ReasoningItem + | CompactionBody + | ImageGenToolCall + | CodeInterpreterToolCall + | LocalShellToolCall + | LocalShellToolCallOutput + | FunctionShellCall + | FunctionShellCallOutput + | ApplyPatchToolCall + | ApplyPatchToolCallOutput + | MCPListTools + | MCPApprovalRequest + | MCPApprovalResponseResource + | MCPToolCall + | CustomToolCallResource + | CustomToolCallOutputResource +export const ItemResource = Schema.Union([ + InputMessageResource, + OutputMessage, + FileSearchToolCall, + ComputerToolCall, + ComputerToolCallOutputResource, + WebSearchToolCall, + FunctionToolCallResource, + FunctionToolCallOutputResource, + ToolSearchCall, + ToolSearchOutput, + ReasoningItem, + CompactionBody, + ImageGenToolCall, + CodeInterpreterToolCall, + LocalShellToolCall, + LocalShellToolCallOutput, + FunctionShellCall, + FunctionShellCallOutput, + ApplyPatchToolCall, + ApplyPatchToolCallOutput, + MCPListTools, + MCPApprovalRequest, + MCPApprovalResponseResource, + MCPToolCall, + CustomToolCallResource, + CustomToolCallOutputResource +], { mode: "oneOf" }).annotate({ "description": "Content item used to generate a response.\n" }) +export type OutputItem = + | OutputMessage + | FileSearchToolCall + | FunctionToolCall + | FunctionToolCallOutputResource + | WebSearchToolCall + | ComputerToolCall + | ComputerToolCallOutputResource + | ReasoningItem + | ToolSearchCall + | ToolSearchOutput + | CompactionBody + | ImageGenToolCall + | CodeInterpreterToolCall + | LocalShellToolCall + | LocalShellToolCallOutput + | FunctionShellCall + | FunctionShellCallOutput + | ApplyPatchToolCall + | ApplyPatchToolCallOutput + | MCPToolCall + | MCPListTools + | MCPApprovalRequest + | MCPApprovalResponseResource + | CustomToolCall + | CustomToolCallOutputResource +export const OutputItem = Schema.Union([ + OutputMessage, + FileSearchToolCall, + FunctionToolCall, + FunctionToolCallOutputResource, + WebSearchToolCall, + ComputerToolCall, + ComputerToolCallOutputResource, + ReasoningItem, + ToolSearchCall, + ToolSearchOutput, + CompactionBody, + ImageGenToolCall, + CodeInterpreterToolCall, + LocalShellToolCall, + LocalShellToolCallOutput, + FunctionShellCall, + FunctionShellCallOutput, + ApplyPatchToolCall, + ApplyPatchToolCallOutput, + MCPToolCall, + MCPListTools, + MCPApprovalRequest, + MCPApprovalResponseResource, + CustomToolCall, + CustomToolCallOutputResource +], { mode: "oneOf" }) +export type ResponseOutputItemAddedEvent = { + readonly "type": "response.output_item.added" + readonly "output_index": number + readonly "sequence_number": number + readonly "item": + | OutputMessage + | FileSearchToolCall + | FunctionToolCall + | FunctionToolCallOutputResource + | WebSearchToolCall + | ComputerToolCall + | ComputerToolCallOutputResource + | ReasoningItem + | ToolSearchCall + | ToolSearchOutput + | CompactionBody + | ImageGenToolCall + | CodeInterpreterToolCall + | LocalShellToolCall + | LocalShellToolCallOutput + | FunctionShellCall + | FunctionShellCallOutput + | ApplyPatchToolCall + | ApplyPatchToolCallOutput + | MCPToolCall + | MCPListTools + | MCPApprovalRequest + | MCPApprovalResponseResource + | CustomToolCall + | CustomToolCallOutputResource +} +export const ResponseOutputItemAddedEvent = Schema.Struct({ + "type": Schema.Literal("response.output_item.added").annotate({ + "description": "The type of the event. Always `response.output_item.added`.\n" + }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item that was added.\n" }).check( + Schema.isInt() + ), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ), + "item": Schema.Union([ + OutputMessage, + FileSearchToolCall, + FunctionToolCall, + FunctionToolCallOutputResource, + WebSearchToolCall, + ComputerToolCall, + ComputerToolCallOutputResource, + ReasoningItem, + ToolSearchCall, + ToolSearchOutput, + CompactionBody, + ImageGenToolCall, + CodeInterpreterToolCall, + LocalShellToolCall, + LocalShellToolCallOutput, + FunctionShellCall, + FunctionShellCallOutput, + ApplyPatchToolCall, + ApplyPatchToolCallOutput, + MCPToolCall, + MCPListTools, + MCPApprovalRequest, + MCPApprovalResponseResource, + CustomToolCall, + CustomToolCallOutputResource + ], { mode: "oneOf" }).annotate({ "description": "The output item that was added.\n" }) +}).annotate({ "description": "Emitted when a new output item is added." }) +export type ResponseOutputItemDoneEvent = { + readonly "type": "response.output_item.done" + readonly "output_index": number + readonly "sequence_number": number + readonly "item": + | OutputMessage + | FileSearchToolCall + | FunctionToolCall + | FunctionToolCallOutputResource + | WebSearchToolCall + | ComputerToolCall + | ComputerToolCallOutputResource + | ReasoningItem + | ToolSearchCall + | ToolSearchOutput + | CompactionBody + | ImageGenToolCall + | CodeInterpreterToolCall + | LocalShellToolCall + | LocalShellToolCallOutput + | FunctionShellCall + | FunctionShellCallOutput + | ApplyPatchToolCall + | ApplyPatchToolCallOutput + | MCPToolCall + | MCPListTools + | MCPApprovalRequest + | MCPApprovalResponseResource + | CustomToolCall + | CustomToolCallOutputResource +} +export const ResponseOutputItemDoneEvent = Schema.Struct({ + "type": Schema.Literal("response.output_item.done").annotate({ + "description": "The type of the event. Always `response.output_item.done`.\n" + }), + "output_index": Schema.Number.annotate({ "description": "The index of the output item that was marked done.\n" }) + .check(Schema.isInt()), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event.\n" }).check( + Schema.isInt() + ), + "item": Schema.Union([ + OutputMessage, + FileSearchToolCall, + FunctionToolCall, + FunctionToolCallOutputResource, + WebSearchToolCall, + ComputerToolCall, + ComputerToolCallOutputResource, + ReasoningItem, + ToolSearchCall, + ToolSearchOutput, + CompactionBody, + ImageGenToolCall, + CodeInterpreterToolCall, + LocalShellToolCall, + LocalShellToolCallOutput, + FunctionShellCall, + FunctionShellCallOutput, + ApplyPatchToolCall, + ApplyPatchToolCallOutput, + MCPToolCall, + MCPListTools, + MCPApprovalRequest, + MCPApprovalResponseResource, + CustomToolCall, + CustomToolCallOutputResource + ], { mode: "oneOf" }).annotate({ "description": "The output item that was marked done.\n" }) +}).annotate({ "description": "Emitted when an output item is marked done." }) +export type ItemField = + | Message + | FunctionToolCall + | ToolSearchCall + | ToolSearchOutput + | FunctionToolCallOutput + | FileSearchToolCall + | WebSearchToolCall + | ImageGenToolCall + | ComputerToolCall + | ComputerToolCallOutputResource + | ReasoningItem + | CompactionBody + | CodeInterpreterToolCall + | LocalShellToolCall + | LocalShellToolCallOutput + | FunctionShellCall + | FunctionShellCallOutput + | ApplyPatchToolCall + | ApplyPatchToolCallOutput + | MCPListTools + | MCPApprovalRequest + | MCPApprovalResponseResource + | MCPToolCall + | CustomToolCall + | CustomToolCallOutput +export const ItemField = Schema.Union([ + Message, + FunctionToolCall, + ToolSearchCall, + ToolSearchOutput, + FunctionToolCallOutput, + FileSearchToolCall, + WebSearchToolCall, + ImageGenToolCall, + ComputerToolCall, + ComputerToolCallOutputResource, + ReasoningItem, + CompactionBody, + CodeInterpreterToolCall, + LocalShellToolCall, + LocalShellToolCallOutput, + FunctionShellCall, + FunctionShellCallOutput, + ApplyPatchToolCall, + ApplyPatchToolCallOutput, + MCPListTools, + MCPApprovalRequest, + MCPApprovalResponseResource, + MCPToolCall, + CustomToolCall, + CustomToolCallOutput +], { mode: "oneOf" }).annotate({ + "description": "An item representing a message, tool call, tool output, reasoning, or other response element." +}) +export type CreateEvalRequest = { + readonly "name"?: string + readonly "metadata"?: Metadata + readonly "data_source_config": + | { readonly "type": "custom"; readonly "item_schema": {}; readonly "include_sample_schema"?: boolean } + | { readonly "type": "logs"; readonly "metadata"?: {} } + | { readonly "type": "stored_completions"; readonly "metadata"?: {} } + readonly "testing_criteria": ReadonlyArray< + | CreateEvalLabelModelGrader + | EvalGraderStringCheck + | EvalGraderTextSimilarity + | EvalGraderPython + | EvalGraderScoreModel + > +} +export const CreateEvalRequest = Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "The name of the evaluation." })), + "metadata": Schema.optionalKey(Metadata), + "data_source_config": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("custom").annotate({ "description": "The type of data source. Always `custom`." }), + "item_schema": Schema.Struct({}).annotate({ "description": "The json schema for each row in the data source." }), + "include_sample_schema": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether the eval should expect you to populate the sample namespace (ie, by generating responses off of your data source)" + }) + ) + }).annotate({ + "title": "CustomDataSourceConfig", + "description": + "The configuration for the data source used for the evaluation runs. Dictates the schema of the data used in the evaluation." + }), + Schema.Struct({ + "type": Schema.Literal("logs").annotate({ "description": "The type of data source. Always `logs`." }), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Metadata filters for the logs data source." }) + ) + }).annotate({ + "title": "LogsDataSourceConfig", + "description": + "The configuration for the data source used for the evaluation runs. Dictates the schema of the data used in the evaluation." + }), + Schema.Struct({ + "type": Schema.Literal("stored_completions").annotate({ + "description": "The type of data source. Always `stored_completions`." + }), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Metadata filters for the stored completions data source." }) + ) + }).annotate({ + "title": "StoredCompletionsDataSourceConfig", + "description": + "The configuration for the data source used for the evaluation runs. Dictates the schema of the data used in the evaluation." + }) + ], { mode: "oneOf" }), + "testing_criteria": Schema.Array( + Schema.Union([ + CreateEvalLabelModelGrader, + EvalGraderStringCheck, + EvalGraderTextSimilarity, + EvalGraderPython, + EvalGraderScoreModel + ], { mode: "oneOf" }) + ).annotate({ + "description": + "A list of graders for all eval runs in this group. Graders can reference variables in the data source using double curly braces notation, like `{{item.variable_name}}`. To reference the model's output, use the `sample` namespace (ie, `{{sample.output_text}}`)." + }) +}).annotate({ "title": "CreateEvalRequest" }) +export type Eval = { + readonly "object": "eval" + readonly "id": string + readonly "name": string + readonly "data_source_config": { readonly "type": "custom"; readonly "schema": {} } | { + readonly "type": "logs" + readonly "metadata"?: Metadata + readonly "schema": {} + } | { readonly "type": "stored_completions"; readonly "metadata"?: Metadata; readonly "schema": {} } + readonly "testing_criteria": ReadonlyArray< + EvalGraderLabelModel | EvalGraderStringCheck | EvalGraderTextSimilarity | EvalGraderPython | EvalGraderScoreModel + > + readonly "created_at": number + readonly "metadata": Metadata +} +export const Eval = Schema.Struct({ + "object": Schema.Literal("eval").annotate({ "description": "The object type." }), + "id": Schema.String.annotate({ "description": "Unique identifier for the evaluation." }), + "name": Schema.String.annotate({ "description": "The name of the evaluation." }), + "data_source_config": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("custom").annotate({ "description": "The type of data source. Always `custom`." }), + "schema": Schema.Struct({}).annotate({ + "description": + "The json schema for the run data source items.\nLearn how to build JSON schemas [here](https://json-schema.org/).\n" + }) + }).annotate({ + "title": "CustomDataSourceConfig", + "description": "Configuration of data sources used in runs of the evaluation." + }), + Schema.Struct({ + "type": Schema.Literal("logs").annotate({ "description": "The type of data source. Always `logs`." }), + "metadata": Schema.optionalKey(Metadata), + "schema": Schema.Struct({}).annotate({ + "description": + "The json schema for the run data source items.\nLearn how to build JSON schemas [here](https://json-schema.org/).\n" + }) + }).annotate({ + "title": "LogsDataSourceConfig", + "description": "Configuration of data sources used in runs of the evaluation." + }), + Schema.Struct({ + "type": Schema.Literal("stored_completions").annotate({ + "description": "The type of data source. Always `stored_completions`." + }), + "metadata": Schema.optionalKey(Metadata), + "schema": Schema.Struct({}).annotate({ + "description": + "The json schema for the run data source items.\nLearn how to build JSON schemas [here](https://json-schema.org/).\n" + }) + }).annotate({ + "title": "StoredCompletionsDataSourceConfig", + "description": "Configuration of data sources used in runs of the evaluation." + }) + ], { mode: "oneOf" }), + "testing_criteria": Schema.Array( + Schema.Union([ + EvalGraderLabelModel, + EvalGraderStringCheck, + EvalGraderTextSimilarity, + EvalGraderPython, + EvalGraderScoreModel + ], { mode: "oneOf" }) + ).annotate({ "description": "A list of testing criteria." }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the eval was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "metadata": Metadata +}).annotate({ + "title": "Eval", + "description": + "An Eval object with a data source config and testing criteria.\nAn Eval represents a task to be done for your LLM integration.\nLike:\n - Improve the quality of my chatbot\n - See how well my chatbot handles customer support\n - Check if o4-mini is better at my usecase than gpt-4o\n" +}) +export type EvalRunList = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const EvalRunList = Schema.Struct({ + "object": Schema.Literal("list").annotate({ + "description": "The type of this object. It is always set to \"list\".\n" + }), + "data": Schema.Array(EvalRun).annotate({ "description": "An array of eval run objects.\n" }), + "first_id": Schema.String.annotate({ "description": "The identifier of the first eval run in the data array." }), + "last_id": Schema.String.annotate({ "description": "The identifier of the last eval run in the data array." }), + "has_more": Schema.Boolean.annotate({ "description": "Indicates whether there are more evals available." }) +}).annotate({ "title": "EvalRunList", "description": "An object representing a list of runs for an evaluation.\n" }) +export type FineTuneReinforcementMethod = { + readonly "grader": + | { + readonly "type": "string_check" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "operation": "eq" | "ne" | "like" | "ilike" + } + | { + readonly "type": "text_similarity" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "evaluation_metric": + | "cosine" + | "fuzzy_match" + | "bleu" + | "gleu" + | "meteor" + | "rouge_1" + | "rouge_2" + | "rouge_3" + | "rouge_4" + | "rouge_5" + | "rouge_l" + } + | { readonly "type": "python"; readonly "name": string; readonly "source": string; readonly "image_tag"?: string } + | { + readonly "type": "score_model" + readonly "name": string + readonly "model": string + readonly "sampling_params"?: { + readonly "seed"?: number | null + readonly "top_p"?: number | null + readonly "temperature"?: number | null + readonly "max_completions_tokens"?: number | null + readonly "reasoning_effort"?: ReasoningEffort + } + readonly "input": ReadonlyArray + readonly "range"?: ReadonlyArray + } + | { + readonly "type": "multi" + readonly "name": string + readonly "graders": GraderStringCheck | GraderTextSimilarity | GraderPython | GraderScoreModel | GraderLabelModel + readonly "calculate_output": string + } + readonly "hyperparameters"?: FineTuneReinforcementHyperparameters +} +export const FineTuneReinforcementMethod = Schema.Struct({ + "grader": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("string_check").annotate({ + "description": "The object type, which is always `string_check`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The input text. This may include template strings." }), + "reference": Schema.String.annotate({ "description": "The reference text. This may include template strings." }), + "operation": Schema.Literals(["eq", "ne", "like", "ilike"]).annotate({ + "description": "The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`." + }) + }).annotate({ "title": "StringCheckGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("text_similarity").annotate({ "description": "The type of grader." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The text being graded." }), + "reference": Schema.String.annotate({ "description": "The text being graded against." }), + "evaluation_metric": Schema.Literals([ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l" + ]).annotate({ + "description": + "The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, \n`gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, \nor `rouge_l`.\n" + }) + }).annotate({ "title": "TextSimilarityGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("python").annotate({ "description": "The object type, which is always `python`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "source": Schema.String.annotate({ "description": "The source code of the python script." }), + "image_tag": Schema.optionalKey( + Schema.String.annotate({ "description": "The image tag to use for the python script." }) + ) + }).annotate({ "title": "PythonGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("score_model").annotate({ + "description": "The object type, which is always `score_model`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ "description": "The model to use for the evaluation." }), + "sampling_params": Schema.optionalKey( + Schema.Struct({ + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling.\n" }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens.\n" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs.\n" }) + .check(Schema.isFinite()), + Schema.Null + ]) + ), + "max_completions_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The maximum number of tokens the grader model may generate in its response.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort) + }).annotate({ "description": "The sampling parameters for the model." }) + ), + "input": Schema.Array(EvalItem).annotate({ + "description": + "The input messages evaluated by the grader. Supports text, output text, input image, and input audio content blocks, and may include template strings.\n" + }), + "range": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isFinite())).annotate({ + "description": "The range of the score. Defaults to `[0, 1]`." + }) + ) + }).annotate({ "title": "ScoreModelGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("multi").annotate({ "description": "The object type, which is always `multi`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "graders": Schema.Union([ + GraderStringCheck, + GraderTextSimilarity, + GraderPython, + GraderScoreModel, + GraderLabelModel + ], { mode: "oneOf" }), + "calculate_output": Schema.String.annotate({ + "description": "A formula to calculate the output based on grader results." + }) + }).annotate({ "title": "MultiGrader", "description": "The grader used for the fine-tuning job." }) + ], { mode: "oneOf" }), + "hyperparameters": Schema.optionalKey(FineTuneReinforcementHyperparameters) +}).annotate({ "description": "Configuration for the reinforcement fine-tuning method." }) +export type RunGraderRequest = { + readonly "grader": + | { + readonly "type": "string_check" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "operation": "eq" | "ne" | "like" | "ilike" + } + | { + readonly "type": "text_similarity" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "evaluation_metric": + | "cosine" + | "fuzzy_match" + | "bleu" + | "gleu" + | "meteor" + | "rouge_1" + | "rouge_2" + | "rouge_3" + | "rouge_4" + | "rouge_5" + | "rouge_l" + } + | { readonly "type": "python"; readonly "name": string; readonly "source": string; readonly "image_tag"?: string } + | { + readonly "type": "score_model" + readonly "name": string + readonly "model": string + readonly "sampling_params"?: { + readonly "seed"?: number | null + readonly "top_p"?: number | null + readonly "temperature"?: number | null + readonly "max_completions_tokens"?: number | null + readonly "reasoning_effort"?: ReasoningEffort + } + readonly "input": ReadonlyArray + readonly "range"?: ReadonlyArray + } + | { + readonly "type": "multi" + readonly "name": string + readonly "graders": GraderStringCheck | GraderTextSimilarity | GraderPython | GraderScoreModel | GraderLabelModel + readonly "calculate_output": string + } + readonly "item"?: {} + readonly "model_sample": string +} +export const RunGraderRequest = Schema.Struct({ + "grader": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("string_check").annotate({ + "description": "The object type, which is always `string_check`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The input text. This may include template strings." }), + "reference": Schema.String.annotate({ "description": "The reference text. This may include template strings." }), + "operation": Schema.Literals(["eq", "ne", "like", "ilike"]).annotate({ + "description": "The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`." + }) + }).annotate({ "title": "StringCheckGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("text_similarity").annotate({ "description": "The type of grader." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The text being graded." }), + "reference": Schema.String.annotate({ "description": "The text being graded against." }), + "evaluation_metric": Schema.Literals([ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l" + ]).annotate({ + "description": + "The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, \n`gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, \nor `rouge_l`.\n" + }) + }).annotate({ "title": "TextSimilarityGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("python").annotate({ "description": "The object type, which is always `python`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "source": Schema.String.annotate({ "description": "The source code of the python script." }), + "image_tag": Schema.optionalKey( + Schema.String.annotate({ "description": "The image tag to use for the python script." }) + ) + }).annotate({ "title": "PythonGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("score_model").annotate({ + "description": "The object type, which is always `score_model`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ "description": "The model to use for the evaluation." }), + "sampling_params": Schema.optionalKey( + Schema.Struct({ + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling.\n" }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens.\n" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs.\n" }) + .check(Schema.isFinite()), + Schema.Null + ]) + ), + "max_completions_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The maximum number of tokens the grader model may generate in its response.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort) + }).annotate({ "description": "The sampling parameters for the model." }) + ), + "input": Schema.Array(EvalItem).annotate({ + "description": + "The input messages evaluated by the grader. Supports text, output text, input image, and input audio content blocks, and may include template strings.\n" + }), + "range": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isFinite())).annotate({ + "description": "The range of the score. Defaults to `[0, 1]`." + }) + ) + }).annotate({ "title": "ScoreModelGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("multi").annotate({ "description": "The object type, which is always `multi`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "graders": Schema.Union([ + GraderStringCheck, + GraderTextSimilarity, + GraderPython, + GraderScoreModel, + GraderLabelModel + ], { mode: "oneOf" }), + "calculate_output": Schema.String.annotate({ + "description": "A formula to calculate the output based on grader results." + }) + }).annotate({ "title": "MultiGrader", "description": "The grader used for the fine-tuning job." }) + ], { mode: "oneOf" }), + "item": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "The dataset item provided to the grader. This will be used to populate \nthe `item` namespace. See [the guide](/docs/guides/graders) for more details. \n" + }) + ), + "model_sample": Schema.String.annotate({ + "description": + "The model sample to be evaluated. This value will be used to populate \nthe `sample` namespace. See [the guide](/docs/guides/graders) for more details.\nThe `output_json` variable will be populated if the model sample is a \nvalid JSON string.\n \n" + }) +}).annotate({ "title": "RunGraderRequest" }) +export type ValidateGraderRequest = { + readonly "grader": + | { + readonly "type": "string_check" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "operation": "eq" | "ne" | "like" | "ilike" + } + | { + readonly "type": "text_similarity" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "evaluation_metric": + | "cosine" + | "fuzzy_match" + | "bleu" + | "gleu" + | "meteor" + | "rouge_1" + | "rouge_2" + | "rouge_3" + | "rouge_4" + | "rouge_5" + | "rouge_l" + } + | { readonly "type": "python"; readonly "name": string; readonly "source": string; readonly "image_tag"?: string } + | { + readonly "type": "score_model" + readonly "name": string + readonly "model": string + readonly "sampling_params"?: { + readonly "seed"?: number | null + readonly "top_p"?: number | null + readonly "temperature"?: number | null + readonly "max_completions_tokens"?: number | null + readonly "reasoning_effort"?: ReasoningEffort + } + readonly "input": ReadonlyArray + readonly "range"?: ReadonlyArray + } + | { + readonly "type": "multi" + readonly "name": string + readonly "graders": GraderStringCheck | GraderTextSimilarity | GraderPython | GraderScoreModel | GraderLabelModel + readonly "calculate_output": string + } +} +export const ValidateGraderRequest = Schema.Struct({ + "grader": Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("string_check").annotate({ + "description": "The object type, which is always `string_check`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The input text. This may include template strings." }), + "reference": Schema.String.annotate({ "description": "The reference text. This may include template strings." }), + "operation": Schema.Literals(["eq", "ne", "like", "ilike"]).annotate({ + "description": "The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`." + }) + }).annotate({ "title": "StringCheckGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("text_similarity").annotate({ "description": "The type of grader." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The text being graded." }), + "reference": Schema.String.annotate({ "description": "The text being graded against." }), + "evaluation_metric": Schema.Literals([ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l" + ]).annotate({ + "description": + "The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, \n`gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, \nor `rouge_l`.\n" + }) + }).annotate({ "title": "TextSimilarityGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("python").annotate({ "description": "The object type, which is always `python`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "source": Schema.String.annotate({ "description": "The source code of the python script." }), + "image_tag": Schema.optionalKey( + Schema.String.annotate({ "description": "The image tag to use for the python script." }) + ) + }).annotate({ "title": "PythonGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("score_model").annotate({ + "description": "The object type, which is always `score_model`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ "description": "The model to use for the evaluation." }), + "sampling_params": Schema.optionalKey( + Schema.Struct({ + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling.\n" }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens.\n" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs.\n" }) + .check(Schema.isFinite()), + Schema.Null + ]) + ), + "max_completions_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The maximum number of tokens the grader model may generate in its response.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort) + }).annotate({ "description": "The sampling parameters for the model." }) + ), + "input": Schema.Array(EvalItem).annotate({ + "description": + "The input messages evaluated by the grader. Supports text, output text, input image, and input audio content blocks, and may include template strings.\n" + }), + "range": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isFinite())).annotate({ + "description": "The range of the score. Defaults to `[0, 1]`." + }) + ) + }).annotate({ "title": "ScoreModelGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("multi").annotate({ "description": "The object type, which is always `multi`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "graders": Schema.Union([ + GraderStringCheck, + GraderTextSimilarity, + GraderPython, + GraderScoreModel, + GraderLabelModel + ], { mode: "oneOf" }), + "calculate_output": Schema.String.annotate({ + "description": "A formula to calculate the output based on grader results." + }) + }).annotate({ "title": "MultiGrader", "description": "The grader used for the fine-tuning job." }) + ], { mode: "oneOf" }) +}).annotate({ "title": "ValidateGraderRequest" }) +export type ValidateGraderResponse = { + readonly "grader"?: + | { + readonly "type": "string_check" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "operation": "eq" | "ne" | "like" | "ilike" + } + | { + readonly "type": "text_similarity" + readonly "name": string + readonly "input": string + readonly "reference": string + readonly "evaluation_metric": + | "cosine" + | "fuzzy_match" + | "bleu" + | "gleu" + | "meteor" + | "rouge_1" + | "rouge_2" + | "rouge_3" + | "rouge_4" + | "rouge_5" + | "rouge_l" + } + | { readonly "type": "python"; readonly "name": string; readonly "source": string; readonly "image_tag"?: string } + | { + readonly "type": "score_model" + readonly "name": string + readonly "model": string + readonly "sampling_params"?: { + readonly "seed"?: number | null + readonly "top_p"?: number | null + readonly "temperature"?: number | null + readonly "max_completions_tokens"?: number | null + readonly "reasoning_effort"?: ReasoningEffort + } + readonly "input": ReadonlyArray + readonly "range"?: ReadonlyArray + } + | { + readonly "type": "multi" + readonly "name": string + readonly "graders": GraderStringCheck | GraderTextSimilarity | GraderPython | GraderScoreModel | GraderLabelModel + readonly "calculate_output": string + } +} +export const ValidateGraderResponse = Schema.Struct({ + "grader": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("string_check").annotate({ + "description": "The object type, which is always `string_check`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The input text. This may include template strings." }), + "reference": Schema.String.annotate({ "description": "The reference text. This may include template strings." }), + "operation": Schema.Literals(["eq", "ne", "like", "ilike"]).annotate({ + "description": "The string check operation to perform. One of `eq`, `ne`, `like`, or `ilike`." + }) + }).annotate({ "title": "StringCheckGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("text_similarity").annotate({ "description": "The type of grader." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "input": Schema.String.annotate({ "description": "The text being graded." }), + "reference": Schema.String.annotate({ "description": "The text being graded against." }), + "evaluation_metric": Schema.Literals([ + "cosine", + "fuzzy_match", + "bleu", + "gleu", + "meteor", + "rouge_1", + "rouge_2", + "rouge_3", + "rouge_4", + "rouge_5", + "rouge_l" + ]).annotate({ + "description": + "The evaluation metric to use. One of `cosine`, `fuzzy_match`, `bleu`, \n`gleu`, `meteor`, `rouge_1`, `rouge_2`, `rouge_3`, `rouge_4`, `rouge_5`, \nor `rouge_l`.\n" + }) + }).annotate({ "title": "TextSimilarityGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("python").annotate({ "description": "The object type, which is always `python`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "source": Schema.String.annotate({ "description": "The source code of the python script." }), + "image_tag": Schema.optionalKey( + Schema.String.annotate({ "description": "The image tag to use for the python script." }) + ) + }).annotate({ "title": "PythonGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("score_model").annotate({ + "description": "The object type, which is always `score_model`." + }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "model": Schema.String.annotate({ "description": "The model to use for the evaluation." }), + "sampling_params": Schema.optionalKey( + Schema.Struct({ + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A seed value to initialize the randomness, during sampling.\n" }) + .check(Schema.isInt()), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "An alternative to temperature for nucleus sampling; 1.0 includes all tokens.\n" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ "description": "A higher temperature increases randomness in the outputs.\n" }) + .check(Schema.isFinite()), + Schema.Null + ]) + ), + "max_completions_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": "The maximum number of tokens the grader model may generate in its response.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null + ]) + ), + "reasoning_effort": Schema.optionalKey(ReasoningEffort) + }).annotate({ "description": "The sampling parameters for the model." }) + ), + "input": Schema.Array(EvalItem).annotate({ + "description": + "The input messages evaluated by the grader. Supports text, output text, input image, and input audio content blocks, and may include template strings.\n" + }), + "range": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isFinite())).annotate({ + "description": "The range of the score. Defaults to `[0, 1]`." + }) + ) + }).annotate({ "title": "ScoreModelGrader", "description": "The grader used for the fine-tuning job." }), + Schema.Struct({ + "type": Schema.Literal("multi").annotate({ "description": "The object type, which is always `multi`." }), + "name": Schema.String.annotate({ "description": "The name of the grader." }), + "graders": Schema.Union([ + GraderStringCheck, + GraderTextSimilarity, + GraderPython, + GraderScoreModel, + GraderLabelModel + ], { mode: "oneOf" }), + "calculate_output": Schema.String.annotate({ + "description": "A formula to calculate the output based on grader results." + }) + }).annotate({ "title": "MultiGrader", "description": "The grader used for the fine-tuning job." }) + ], { mode: "oneOf" })) +}).annotate({ "title": "ValidateGraderResponse" }) +export type CreateResponse = { + readonly "metadata"?: Metadata + readonly "top_logprobs"?: number + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model"?: + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools"?: ToolsArray + readonly "tool_choice"?: ToolChoiceParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "input"?: InputParam + readonly "include"?: ReadonlyArray | null + readonly "parallel_tool_calls"?: boolean | null + readonly "store"?: boolean | null + readonly "instructions"?: string | null + readonly "stream"?: boolean | null + readonly "stream_options"?: ResponseStreamOptions + readonly "conversation"?: ConversationParam | null + readonly "context_management"?: ReadonlyArray | null + readonly "max_output_tokens"?: number | null +} +export const CreateResponse = Schema.Struct({ + "metadata": Schema.optionalKey(Metadata), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(20)], { + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }) + ) + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ])), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }) + ), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.optionalKey(ToolsArray), + "tool_choice": Schema.optionalKey(ToolChoiceParam), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "input": Schema.optionalKey(InputParam), + "include": Schema.optionalKey(Schema.Union([ + Schema.Array(IncludeEnum).annotate({ + "description": + "Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program)." + }), + Schema.Null + ])), + "parallel_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ "description": "Whether to allow the model to run tool calls in parallel.\n" }), + Schema.Null + ]) + ), + "store": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": "Whether to store the generated model response for later retrieval via\nAPI.\n" + }), + Schema.Null + ]) + ), + "instructions": Schema.optionalKey(Schema.Union([ + Schema.String.annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ])), + "stream": Schema.optionalKey(Schema.Union([ + Schema.Boolean.annotate({ + "description": + "If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/responses-streaming)\nfor more information.\n" + }), + Schema.Null + ])), + "stream_options": Schema.optionalKey(ResponseStreamOptions), + "conversation": Schema.optionalKey(Schema.Union([ConversationParam, Schema.Null])), + "context_management": Schema.optionalKey( + Schema.Union([ + Schema.Array(ContextManagementParam).annotate({ + "description": "Context management configuration for this request.\n" + }).check(Schema.isMinLength(1)), + Schema.Null + ]) + ), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(16)), + Schema.Null + ]) + ) +}) +export type ResponsesClientEventResponseCreate = { + readonly "type": "response.create" + readonly "metadata"?: Metadata + readonly "top_logprobs"?: number + readonly "temperature"?: number | null + readonly "top_p"?: number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model"?: + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools"?: ToolsArray + readonly "tool_choice"?: ToolChoiceParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "input"?: InputParam + readonly "include"?: ReadonlyArray | null + readonly "parallel_tool_calls"?: boolean | null + readonly "store"?: boolean | null + readonly "instructions"?: string | null + readonly "stream"?: boolean | null + readonly "stream_options"?: ResponseStreamOptions + readonly "conversation"?: ConversationParam | null + readonly "context_management"?: ReadonlyArray | null + readonly "max_output_tokens"?: number | null +} +export const ResponsesClientEventResponseCreate = Schema.Struct({ + "type": Schema.Literal("response.create").annotate({ + "description": "The type of the client event. Always `response.create`.\n" + }), + "metadata": Schema.optionalKey(Metadata), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)).check( + Schema.makeFilterGroup([Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(20)], { + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }) + ) + ]) + ), + "temperature": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]) + ), + "top_p": Schema.optionalKey(Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ])), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.optionalKey( + Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }) + ), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.optionalKey(ToolsArray), + "tool_choice": Schema.optionalKey(ToolChoiceParam), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "input": Schema.optionalKey(InputParam), + "include": Schema.optionalKey(Schema.Union([ + Schema.Array(IncludeEnum).annotate({ + "description": + "Specify additional output data to include in the model response. Currently supported values are:\n- `web_search_call.action.sources`: Include the sources of the web search tool call.\n- `code_interpreter_call.outputs`: Includes the outputs of python code execution in code interpreter tool call items.\n- `computer_call_output.output.image_url`: Include image urls from the computer call output.\n- `file_search_call.results`: Include the search results of the file search tool call.\n- `message.input_image.image_url`: Include image urls from the input message.\n- `message.output_text.logprobs`: Include logprobs with assistant messages.\n- `reasoning.encrypted_content`: Includes an encrypted version of reasoning tokens in reasoning item outputs. This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly (like when the `store` parameter is set to `false`, or when an organization is enrolled in the zero data retention program)." + }), + Schema.Null + ])), + "parallel_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ "description": "Whether to allow the model to run tool calls in parallel.\n" }), + Schema.Null + ]) + ), + "store": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": "Whether to store the generated model response for later retrieval via\nAPI.\n" + }), + Schema.Null + ]) + ), + "instructions": Schema.optionalKey(Schema.Union([ + Schema.String.annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ])), + "stream": Schema.optionalKey(Schema.Union([ + Schema.Boolean.annotate({ + "description": + "If set to true, the model response data will be streamed to the client\nas it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).\nSee the [Streaming section below](/docs/api-reference/responses-streaming)\nfor more information.\n" + }), + Schema.Null + ])), + "stream_options": Schema.optionalKey(ResponseStreamOptions), + "conversation": Schema.optionalKey(Schema.Union([ConversationParam, Schema.Null])), + "context_management": Schema.optionalKey( + Schema.Union([ + Schema.Array(ContextManagementParam).annotate({ + "description": "Context management configuration for this request.\n" + }).check(Schema.isMinLength(1)), + Schema.Null + ]) + ), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(16)), + Schema.Null + ]) + ) +}).annotate({ + "description": + "Client event for creating a response over a persistent WebSocket connection.\nThis payload uses the same top-level fields as `POST /v1/responses`.\n\nNotes:\n- `stream` is implicit over WebSocket and should not be sent.\n- `background` is not supported over WebSocket.\n" +}) +export type ConversationItemList = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "first_id": string + readonly "last_id": string +} +export const ConversationItemList = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be `list`." }), + "data": Schema.Array(ConversationItem).annotate({ "description": "A list of conversation items." }), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more items available." }), + "first_id": Schema.String.annotate({ "description": "The ID of the first item in the list." }), + "last_id": Schema.String.annotate({ "description": "The ID of the last item in the list." }) +}).annotate({ "title": "The conversation item list", "description": "A list of Conversation items." }) +export type ResponseItemList = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "first_id": string + readonly "last_id": string +} +export const ResponseItemList = Schema.Struct({ + "object": Schema.Literal("list").annotate({ "description": "The type of object returned, must be `list`." }), + "data": Schema.Array(ItemResource).annotate({ "description": "A list of items used to generate this response." }), + "has_more": Schema.Boolean.annotate({ "description": "Whether there are more items available." }), + "first_id": Schema.String.annotate({ "description": "The ID of the first item in the list." }), + "last_id": Schema.String.annotate({ "description": "The ID of the last item in the list." }) +}).annotate({ "description": "A list of Response items." }) +export type Response = { + readonly "metadata": {} | null + readonly "top_logprobs"?: number | null + readonly "temperature": number | null + readonly "top_p": number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model": + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools": ReadonlyArray + readonly "tool_choice": + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "id": string + readonly "object": "response" + readonly "status"?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" + readonly "created_at": number + readonly "completed_at"?: number | null + readonly "error": ResponseError + readonly "incomplete_details": { readonly "reason"?: "max_output_tokens" | "content_filter" } | null + readonly "output": ReadonlyArray + readonly "instructions": string | ReadonlyArray | null + readonly "output_text"?: string | null + readonly "usage"?: ResponseUsage | null + readonly "parallel_tool_calls": boolean + readonly "conversation"?: Conversation_2 | null + readonly "max_output_tokens"?: number | null +} +export const Response = Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)), + Schema.Null + ]) + ), + "temperature": Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]), + "top_p": Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ]), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" + }), + "tool_choice": Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" + }), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "Unique identifier for this Response.\n" }), + "object": Schema.Literal("response").annotate({ + "description": "The object type of this resource - always set to `response`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]).annotate({ + "description": + "The status of the response generation. One of `completed`, `failed`,\n`in_progress`, `cancelled`, `queued`, or `incomplete`.\n" + }) + ), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) of when this Response was created.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + "completed_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Unix timestamp (in seconds) of when this Response was completed.\nOnly present when the status is `completed`.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "error": ResponseError, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_output_tokens", "content_filter"]).annotate({ + "description": "The reason why the response is incomplete." + }) + ) + }).annotate({ "description": "Details about why the response is incomplete.\n" }), + Schema.Null + ]), + "output": Schema.Array(OutputItem).annotate({ + "description": + "An array of content items generated by the model.\n\n- The length and order of items in the `output` array is dependent\n on the model's response.\n- Rather than accessing the first item in the `output` array and\n assuming it's an `assistant` message with the content generated by\n the model, you might consider using the `output_text` property where\n supported in SDKs.\n" + }), + "instructions": Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the\n`developer` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ]), + "output_text": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "SDK-only convenience property that contains the aggregated text output\nfrom all `output_text` items in the `output` array, if any are present.\nSupported in the Python and JavaScript SDKs.\n" + }), + Schema.Null + ]) + ), + "usage": Schema.optionalKey(Schema.Union([ResponseUsage, Schema.Null])), + "parallel_tool_calls": Schema.Boolean.annotate({ + "description": "Whether to allow the model to run tool calls in parallel.\n" + }), + "conversation": Schema.optionalKey(Schema.Union([Conversation_2, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) +}).annotate({ "title": "The response object" }) +export type ResponseCompletedEvent = { + readonly "type": "response.completed" + readonly "response": { + readonly "metadata": {} | null + readonly "top_logprobs"?: number | null + readonly "temperature": number | null + readonly "top_p": number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model": + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools": ReadonlyArray + readonly "tool_choice": + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "id": string + readonly "object": "response" + readonly "status"?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" + readonly "created_at": number + readonly "completed_at"?: number | null + readonly "error": ResponseError + readonly "incomplete_details": { readonly "reason"?: "max_output_tokens" | "content_filter" } | null + readonly "output": ReadonlyArray + readonly "instructions": string | ReadonlyArray | null + readonly "output_text"?: string | null + readonly "usage"?: ResponseUsage | null + readonly "parallel_tool_calls": boolean + readonly "conversation"?: Conversation_2 | null + readonly "max_output_tokens"?: number | null + } + readonly "sequence_number": number +} +export const ResponseCompletedEvent = Schema.Struct({ + "type": Schema.Literal("response.completed").annotate({ + "description": "The type of the event. Always `response.completed`.\n" + }), + "response": Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)), + Schema.Null + ]) + ), + "temperature": Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]), + "top_p": Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ]), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" + }), + "tool_choice": Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" + }), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "Unique identifier for this Response.\n" }), + "object": Schema.Literal("response").annotate({ + "description": "The object type of this resource - always set to `response`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]).annotate({ + "description": + "The status of the response generation. One of `completed`, `failed`,\n`in_progress`, `cancelled`, `queued`, or `incomplete`.\n" + }) + ), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) of when this Response was created.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + "completed_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Unix timestamp (in seconds) of when this Response was completed.\nOnly present when the status is `completed`.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "error": ResponseError, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_output_tokens", "content_filter"]).annotate({ + "description": "The reason why the response is incomplete." + }) + ) + }).annotate({ "description": "Details about why the response is incomplete.\n" }), + Schema.Null + ]), + "output": Schema.Array(OutputItem).annotate({ + "description": + "An array of content items generated by the model.\n\n- The length and order of items in the `output` array is dependent\n on the model's response.\n- Rather than accessing the first item in the `output` array and\n assuming it's an `assistant` message with the content generated by\n the model, you might consider using the `output_text` property where\n supported in SDKs.\n" + }), + "instructions": Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the\n`developer` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ]), + "output_text": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "SDK-only convenience property that contains the aggregated text output\nfrom all `output_text` items in the `output` array, if any are present.\nSupported in the Python and JavaScript SDKs.\n" + }), + Schema.Null + ]) + ), + "usage": Schema.optionalKey(Schema.Union([ResponseUsage, Schema.Null])), + "parallel_tool_calls": Schema.Boolean.annotate({ + "description": "Whether to allow the model to run tool calls in parallel.\n" + }), + "conversation": Schema.optionalKey(Schema.Union([Conversation_2, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ "title": "The response object", "description": "Properties of the completed response.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number for this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when the model response is complete." }) +export type ResponseCreatedEvent = { + readonly "type": "response.created" + readonly "response": { + readonly "metadata": {} | null + readonly "top_logprobs"?: number | null + readonly "temperature": number | null + readonly "top_p": number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model": + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools": ReadonlyArray + readonly "tool_choice": + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "id": string + readonly "object": "response" + readonly "status"?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" + readonly "created_at": number + readonly "completed_at"?: number | null + readonly "error": ResponseError + readonly "incomplete_details": { readonly "reason"?: "max_output_tokens" | "content_filter" } | null + readonly "output": ReadonlyArray + readonly "instructions": string | ReadonlyArray | null + readonly "output_text"?: string | null + readonly "usage"?: ResponseUsage | null + readonly "parallel_tool_calls": boolean + readonly "conversation"?: Conversation_2 | null + readonly "max_output_tokens"?: number | null + } + readonly "sequence_number": number +} +export const ResponseCreatedEvent = Schema.Struct({ + "type": Schema.Literal("response.created").annotate({ + "description": "The type of the event. Always `response.created`.\n" + }), + "response": Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)), + Schema.Null + ]) + ), + "temperature": Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]), + "top_p": Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ]), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" + }), + "tool_choice": Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" + }), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "Unique identifier for this Response.\n" }), + "object": Schema.Literal("response").annotate({ + "description": "The object type of this resource - always set to `response`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]).annotate({ + "description": + "The status of the response generation. One of `completed`, `failed`,\n`in_progress`, `cancelled`, `queued`, or `incomplete`.\n" + }) + ), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) of when this Response was created.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + "completed_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Unix timestamp (in seconds) of when this Response was completed.\nOnly present when the status is `completed`.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "error": ResponseError, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_output_tokens", "content_filter"]).annotate({ + "description": "The reason why the response is incomplete." + }) + ) + }).annotate({ "description": "Details about why the response is incomplete.\n" }), + Schema.Null + ]), + "output": Schema.Array(OutputItem).annotate({ + "description": + "An array of content items generated by the model.\n\n- The length and order of items in the `output` array is dependent\n on the model's response.\n- Rather than accessing the first item in the `output` array and\n assuming it's an `assistant` message with the content generated by\n the model, you might consider using the `output_text` property where\n supported in SDKs.\n" + }), + "instructions": Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the\n`developer` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ]), + "output_text": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "SDK-only convenience property that contains the aggregated text output\nfrom all `output_text` items in the `output` array, if any are present.\nSupported in the Python and JavaScript SDKs.\n" + }), + Schema.Null + ]) + ), + "usage": Schema.optionalKey(Schema.Union([ResponseUsage, Schema.Null])), + "parallel_tool_calls": Schema.Boolean.annotate({ + "description": "Whether to allow the model to run tool calls in parallel.\n" + }), + "conversation": Schema.optionalKey(Schema.Union([Conversation_2, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ "title": "The response object", "description": "The response that was created.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number for this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "An event that is emitted when a response is created.\n" }) +export type ResponseFailedEvent = { + readonly "type": "response.failed" + readonly "sequence_number": number + readonly "response": { + readonly "metadata": {} | null + readonly "top_logprobs"?: number | null + readonly "temperature": number | null + readonly "top_p": number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model": + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools": ReadonlyArray + readonly "tool_choice": + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "id": string + readonly "object": "response" + readonly "status"?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" + readonly "created_at": number + readonly "completed_at"?: number | null + readonly "error": ResponseError + readonly "incomplete_details": { readonly "reason"?: "max_output_tokens" | "content_filter" } | null + readonly "output": ReadonlyArray + readonly "instructions": string | ReadonlyArray | null + readonly "output_text"?: string | null + readonly "usage"?: ResponseUsage | null + readonly "parallel_tool_calls": boolean + readonly "conversation"?: Conversation_2 | null + readonly "max_output_tokens"?: number | null + } +} +export const ResponseFailedEvent = Schema.Struct({ + "type": Schema.Literal("response.failed").annotate({ + "description": "The type of the event. Always `response.failed`.\n" + }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ), + "response": Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)), + Schema.Null + ]) + ), + "temperature": Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]), + "top_p": Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ]), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" + }), + "tool_choice": Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" + }), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "Unique identifier for this Response.\n" }), + "object": Schema.Literal("response").annotate({ + "description": "The object type of this resource - always set to `response`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]).annotate({ + "description": + "The status of the response generation. One of `completed`, `failed`,\n`in_progress`, `cancelled`, `queued`, or `incomplete`.\n" + }) + ), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) of when this Response was created.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + "completed_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Unix timestamp (in seconds) of when this Response was completed.\nOnly present when the status is `completed`.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "error": ResponseError, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_output_tokens", "content_filter"]).annotate({ + "description": "The reason why the response is incomplete." + }) + ) + }).annotate({ "description": "Details about why the response is incomplete.\n" }), + Schema.Null + ]), + "output": Schema.Array(OutputItem).annotate({ + "description": + "An array of content items generated by the model.\n\n- The length and order of items in the `output` array is dependent\n on the model's response.\n- Rather than accessing the first item in the `output` array and\n assuming it's an `assistant` message with the content generated by\n the model, you might consider using the `output_text` property where\n supported in SDKs.\n" + }), + "instructions": Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the\n`developer` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ]), + "output_text": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "SDK-only convenience property that contains the aggregated text output\nfrom all `output_text` items in the `output` array, if any are present.\nSupported in the Python and JavaScript SDKs.\n" + }), + Schema.Null + ]) + ), + "usage": Schema.optionalKey(Schema.Union([ResponseUsage, Schema.Null])), + "parallel_tool_calls": Schema.Boolean.annotate({ + "description": "Whether to allow the model to run tool calls in parallel.\n" + }), + "conversation": Schema.optionalKey(Schema.Union([Conversation_2, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ "title": "The response object", "description": "The response that failed.\n" }) +}).annotate({ "description": "An event that is emitted when a response fails.\n" }) +export type ResponseInProgressEvent = { + readonly "type": "response.in_progress" + readonly "response": { + readonly "metadata": {} | null + readonly "top_logprobs"?: number | null + readonly "temperature": number | null + readonly "top_p": number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model": + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools": ReadonlyArray + readonly "tool_choice": + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "id": string + readonly "object": "response" + readonly "status"?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" + readonly "created_at": number + readonly "completed_at"?: number | null + readonly "error": ResponseError + readonly "incomplete_details": { readonly "reason"?: "max_output_tokens" | "content_filter" } | null + readonly "output": ReadonlyArray + readonly "instructions": string | ReadonlyArray | null + readonly "output_text"?: string | null + readonly "usage"?: ResponseUsage | null + readonly "parallel_tool_calls": boolean + readonly "conversation"?: Conversation_2 | null + readonly "max_output_tokens"?: number | null + } + readonly "sequence_number": number +} +export const ResponseInProgressEvent = Schema.Struct({ + "type": Schema.Literal("response.in_progress").annotate({ + "description": "The type of the event. Always `response.in_progress`.\n" + }), + "response": Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)), + Schema.Null + ]) + ), + "temperature": Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]), + "top_p": Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ]), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" + }), + "tool_choice": Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" + }), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "Unique identifier for this Response.\n" }), + "object": Schema.Literal("response").annotate({ + "description": "The object type of this resource - always set to `response`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]).annotate({ + "description": + "The status of the response generation. One of `completed`, `failed`,\n`in_progress`, `cancelled`, `queued`, or `incomplete`.\n" + }) + ), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) of when this Response was created.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + "completed_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Unix timestamp (in seconds) of when this Response was completed.\nOnly present when the status is `completed`.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "error": ResponseError, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_output_tokens", "content_filter"]).annotate({ + "description": "The reason why the response is incomplete." + }) + ) + }).annotate({ "description": "Details about why the response is incomplete.\n" }), + Schema.Null + ]), + "output": Schema.Array(OutputItem).annotate({ + "description": + "An array of content items generated by the model.\n\n- The length and order of items in the `output` array is dependent\n on the model's response.\n- Rather than accessing the first item in the `output` array and\n assuming it's an `assistant` message with the content generated by\n the model, you might consider using the `output_text` property where\n supported in SDKs.\n" + }), + "instructions": Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the\n`developer` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ]), + "output_text": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "SDK-only convenience property that contains the aggregated text output\nfrom all `output_text` items in the `output` array, if any are present.\nSupported in the Python and JavaScript SDKs.\n" + }), + Schema.Null + ]) + ), + "usage": Schema.optionalKey(Schema.Union([ResponseUsage, Schema.Null])), + "parallel_tool_calls": Schema.Boolean.annotate({ + "description": "Whether to allow the model to run tool calls in parallel.\n" + }), + "conversation": Schema.optionalKey(Schema.Union([Conversation_2, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ "title": "The response object", "description": "The response that is in progress.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "Emitted when the response is in progress." }) +export type ResponseIncompleteEvent = { + readonly "type": "response.incomplete" + readonly "response": { + readonly "metadata": {} | null + readonly "top_logprobs"?: number | null + readonly "temperature": number | null + readonly "top_p": number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model": + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools": ReadonlyArray + readonly "tool_choice": + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "id": string + readonly "object": "response" + readonly "status"?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" + readonly "created_at": number + readonly "completed_at"?: number | null + readonly "error": ResponseError + readonly "incomplete_details": { readonly "reason"?: "max_output_tokens" | "content_filter" } | null + readonly "output": ReadonlyArray + readonly "instructions": string | ReadonlyArray | null + readonly "output_text"?: string | null + readonly "usage"?: ResponseUsage | null + readonly "parallel_tool_calls": boolean + readonly "conversation"?: Conversation_2 | null + readonly "max_output_tokens"?: number | null + } + readonly "sequence_number": number +} +export const ResponseIncompleteEvent = Schema.Struct({ + "type": Schema.Literal("response.incomplete").annotate({ + "description": "The type of the event. Always `response.incomplete`.\n" + }), + "response": Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)), + Schema.Null + ]) + ), + "temperature": Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]), + "top_p": Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ]), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" + }), + "tool_choice": Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" + }), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "Unique identifier for this Response.\n" }), + "object": Schema.Literal("response").annotate({ + "description": "The object type of this resource - always set to `response`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]).annotate({ + "description": + "The status of the response generation. One of `completed`, `failed`,\n`in_progress`, `cancelled`, `queued`, or `incomplete`.\n" + }) + ), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) of when this Response was created.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + "completed_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Unix timestamp (in seconds) of when this Response was completed.\nOnly present when the status is `completed`.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "error": ResponseError, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_output_tokens", "content_filter"]).annotate({ + "description": "The reason why the response is incomplete." + }) + ) + }).annotate({ "description": "Details about why the response is incomplete.\n" }), + Schema.Null + ]), + "output": Schema.Array(OutputItem).annotate({ + "description": + "An array of content items generated by the model.\n\n- The length and order of items in the `output` array is dependent\n on the model's response.\n- Rather than accessing the first item in the `output` array and\n assuming it's an `assistant` message with the content generated by\n the model, you might consider using the `output_text` property where\n supported in SDKs.\n" + }), + "instructions": Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the\n`developer` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ]), + "output_text": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "SDK-only convenience property that contains the aggregated text output\nfrom all `output_text` items in the `output` array, if any are present.\nSupported in the Python and JavaScript SDKs.\n" + }), + Schema.Null + ]) + ), + "usage": Schema.optionalKey(Schema.Union([ResponseUsage, Schema.Null])), + "parallel_tool_calls": Schema.Boolean.annotate({ + "description": "Whether to allow the model to run tool calls in parallel.\n" + }), + "conversation": Schema.optionalKey(Schema.Union([Conversation_2, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ "title": "The response object", "description": "The response that was incomplete.\n" }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number of this event." }).check( + Schema.isInt() + ) +}).annotate({ "description": "An event that is emitted when a response finishes as incomplete.\n" }) +export type ResponseQueuedEvent = { + readonly "type": "response.queued" + readonly "response": { + readonly "metadata": {} | null + readonly "top_logprobs"?: number | null + readonly "temperature": number | null + readonly "top_p": number | null + readonly "user"?: string | null + readonly "safety_identifier"?: string + readonly "prompt_cache_key"?: string | null + readonly "service_tier"?: ServiceTier + readonly "prompt_cache_retention"?: "in_memory" | "in_memory" | "24h" | null + readonly "previous_response_id"?: string | null + readonly "model": + | ModelIdsShared + | "o1-pro" + | "o1-pro-2025-03-19" + | "o3-pro" + | "o3-pro-2025-06-10" + | "o3-deep-research" + | "o3-deep-research-2025-06-26" + | "o4-mini-deep-research" + | "o4-mini-deep-research-2025-06-26" + | "computer-use-preview" + | "computer-use-preview-2025-03-11" + | "gpt-5-codex" + | "gpt-5-pro" + | "gpt-5-pro-2025-10-06" + | "gpt-5.1-codex-max" + readonly "reasoning"?: Reasoning | null + readonly "background"?: boolean | null + readonly "max_tool_calls"?: number | null + readonly "text"?: ResponseTextParam + readonly "tools": ReadonlyArray + readonly "tool_choice": + | ToolChoiceOptions + | ToolChoiceAllowed + | ToolChoiceTypes + | ToolChoiceFunction + | ToolChoiceMCP + | ToolChoiceCustom + | SpecificApplyPatchParam + | SpecificFunctionShellParam + readonly "prompt"?: Prompt + readonly "truncation"?: "auto" | "disabled" | null + readonly "id": string + readonly "object": "response" + readonly "status"?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" + readonly "created_at": number + readonly "completed_at"?: number | null + readonly "error": ResponseError + readonly "incomplete_details": { readonly "reason"?: "max_output_tokens" | "content_filter" } | null + readonly "output": ReadonlyArray + readonly "instructions": string | ReadonlyArray | null + readonly "output_text"?: string | null + readonly "usage"?: ResponseUsage | null + readonly "parallel_tool_calls": boolean + readonly "conversation"?: Conversation_2 | null + readonly "max_output_tokens"?: number | null + } + readonly "sequence_number": number +} +export const ResponseQueuedEvent = Schema.Struct({ + "type": Schema.Literal("response.queued").annotate({ + "description": "The type of the event. Always 'response.queued'." + }), + "response": Schema.Struct({ + "metadata": Schema.Union([ + Schema.Struct({}).annotate({ + "description": + "Set of 16 key-value pairs that can be attached to an object. This can be\nuseful for storing additional information about the object in a structured\nformat, and querying for objects via API or the dashboard.\n\nKeys are strings with a maximum length of 64 characters. Values are strings\nwith a maximum length of 512 characters.\n" + }), + Schema.Null + ]), + "top_logprobs": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An integer between 0 and 20 specifying the maximum number of most likely\ntokens to return at each token position, each with an associated log\nprobability. In some cases, the number of returned tokens may be fewer than\nrequested.\n" + }).check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)), + Schema.Null + ]) + ), + "temperature": Schema.Union([ + Schema.Number.annotate({ + "description": + "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or `top_p` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)), + Schema.Null + ]), + "top_p": Schema.Union([ + Schema.Number.annotate({ + "description": + "An alternative to sampling with temperature, called nucleus sampling,\nwhere the model considers the results of the tokens with top_p probability\nmass. So 0.1 means only the tokens comprising the top 10% probability mass\nare considered.\n\nWe generally recommend altering this or `temperature` but not both.\n" + }).check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)), + Schema.Null + ]), + "user": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use `prompt_cache_key` instead to maintain caching optimizations.\nA stable identifier for your end-users.\nUsed to boost cache hit rates by better bucketing similar requests and to help OpenAI detect and prevent abuse. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }) + ), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String.check(Schema.isMaxLength(64, { + "description": + "A stable identifier used to help detect users of your application that may be violating OpenAI's usage policies.\nThe IDs should be a string that uniquely identifies each user, with a maximum length of 64 characters. We recommend hashing their username or email address, in order to avoid sending us any identifying information. [Learn more](/docs/guides/safety-best-practices#safety-identifiers).\n" + }))])), + "prompt_cache_key": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the `user` field. [Learn more](/docs/guides/prompt-caching).\n" + }) + ), + "service_tier": Schema.optionalKey(ServiceTier), + "prompt_cache_retention": Schema.optionalKey( + Schema.Union([ + Schema.Literals(["in_memory", "in_memory", "24h"]).annotate({ + "description": + "The retention policy for the prompt cache. Set to `24h` to enable extended prompt caching, which keeps cached prefixes active for longer, up to a maximum of 24 hours. [Learn more](/docs/guides/prompt-caching#prompt-cache-retention).\n" + }), + Schema.Null + ]) + ), + "previous_response_id": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "The unique ID of the previous response to the model. Use this to\ncreate multi-turn conversations. Learn more about\n[conversation state](/docs/guides/conversation-state). Cannot be used in conjunction with `conversation`.\n" + }), + Schema.Null + ]) + ), + "model": Schema.Union([ + ModelIdsShared, + Schema.Literals([ + "o1-pro", + "o1-pro-2025-03-19", + "o3-pro", + "o3-pro-2025-06-10", + "o3-deep-research", + "o3-deep-research-2025-06-26", + "o4-mini-deep-research", + "o4-mini-deep-research-2025-06-26", + "computer-use-preview", + "computer-use-preview-2025-03-11", + "gpt-5-codex", + "gpt-5-pro", + "gpt-5-pro-2025-10-06", + "gpt-5.1-codex-max" + ]).annotate({ "title": "ResponsesOnlyModel" }) + ]).annotate({ + "description": + "Model ID used to generate the response, like `gpt-4o` or `o3`. OpenAI\noffers a wide range of models with different capabilities, performance\ncharacteristics, and price points. Refer to the [model guide](/docs/models)\nto browse and compare available models.\n" + }), + "reasoning": Schema.optionalKey(Schema.Union([Reasoning, Schema.Null])), + "background": Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + "description": + "Whether to run the model response in the background.\n[Learn more](/docs/guides/background).\n" + }), + Schema.Null + ]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The maximum number of total calls to built-in tools that can be processed in a response. This maximum number applies across all built-in tool calls, not per individual tool. Any further attempts to call a tool by the model will be ignored.\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "text": Schema.optionalKey(ResponseTextParam), + "tools": Schema.Array(Tool).annotate({ + "description": + "An array of tools the model may call while generating a response. You\ncan specify which tool to use by setting the `tool_choice` parameter.\n\nWe support the following categories of tools:\n- **Built-in tools**: Tools that are provided by OpenAI that extend the\n model's capabilities, like [web search](/docs/guides/tools-web-search)\n or [file search](/docs/guides/tools-file-search). Learn more about\n [built-in tools](/docs/guides/tools).\n- **MCP Tools**: Integrations with third-party systems via custom MCP servers\n or predefined connectors such as Google Drive and SharePoint. Learn more about\n [MCP Tools](/docs/guides/tools-connectors-mcp).\n- **Function calls (custom tools)**: Functions that are defined by you,\n enabling the model to call your own code with strongly typed arguments\n and outputs. Learn more about\n [function calling](/docs/guides/function-calling). You can also use\n custom tools to call your own code.\n" + }), + "tool_choice": Schema.Union([ + ToolChoiceOptions, + ToolChoiceAllowed, + ToolChoiceTypes, + ToolChoiceFunction, + ToolChoiceMCP, + ToolChoiceCustom, + SpecificApplyPatchParam, + SpecificFunctionShellParam + ], { mode: "oneOf" }).annotate({ + "description": + "How the model should select which tool (or tools) to use when generating\na response. See the `tools` parameter to see how to specify which tools\nthe model can call.\n" + }), + "prompt": Schema.optionalKey(Prompt), + "truncation": Schema.optionalKey(Schema.Union([ + Schema.Literals(["auto", "disabled"]).annotate({ + "description": + "The truncation strategy to use for the model response.\n- `auto`: If the input to this Response exceeds\n the model's context window size, the model will truncate the\n response to fit the context window by dropping items from the beginning of the conversation.\n- `disabled` (default): If the input size will exceed the context window\n size for a model, the request will fail with a 400 error.\n" + }), + Schema.Null + ])), + "id": Schema.String.annotate({ "description": "Unique identifier for this Response.\n" }), + "object": Schema.Literal("response").annotate({ + "description": "The object type of this resource - always set to `response`.\n" + }), + "status": Schema.optionalKey( + Schema.Literals(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]).annotate({ + "description": + "The status of the response generation. One of `completed`, `failed`,\n`in_progress`, `cancelled`, `queued`, or `incomplete`.\n" + }) + ), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) of when this Response was created.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + "completed_at": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "Unix timestamp (in seconds) of when this Response was completed.\nOnly present when the status is `completed`.\n", + "format": "unixtime" + }).check(Schema.isFinite()), + Schema.Null + ]) + ), + "error": ResponseError, + "incomplete_details": Schema.Union([ + Schema.Struct({ + "reason": Schema.optionalKey( + Schema.Literals(["max_output_tokens", "content_filter"]).annotate({ + "description": "The reason why the response is incomplete." + }) + ) + }).annotate({ "description": "Details about why the response is incomplete.\n" }), + Schema.Null + ]), + "output": Schema.Array(OutputItem).annotate({ + "description": + "An array of content items generated by the model.\n\n- The length and order of items in the `output` array is dependent\n on the model's response.\n- Rather than accessing the first item in the `output` array and\n assuming it's an `assistant` message with the content generated by\n the model, you might consider using the `output_text` property where\n supported in SDKs.\n" + }), + "instructions": Schema.Union([ + Schema.Union([ + Schema.String.annotate({ + "description": "A text input to the model, equivalent to a text input with the\n`developer` role.\n" + }), + Schema.Array(InputItem).annotate({ + "title": "Input item list", + "description": "A list of one or many input items to the model, containing\ndifferent content types.\n" + }) + ], { mode: "oneOf" }).annotate({ + "description": + "A system (or developer) message inserted into the model's context.\n\nWhen using along with `previous_response_id`, the instructions from a previous\nresponse will not be carried over to the next response. This makes it simple\nto swap out system (or developer) messages in new responses.\n" + }), + Schema.Null + ]), + "output_text": Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + "description": + "SDK-only convenience property that contains the aggregated text output\nfrom all `output_text` items in the `output` array, if any are present.\nSupported in the Python and JavaScript SDKs.\n" + }), + Schema.Null + ]) + ), + "usage": Schema.optionalKey(Schema.Union([ResponseUsage, Schema.Null])), + "parallel_tool_calls": Schema.Boolean.annotate({ + "description": "Whether to allow the model to run tool calls in parallel.\n" + }), + "conversation": Schema.optionalKey(Schema.Union([Conversation_2, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "An upper bound for the number of tokens that can be generated for a response, including visible output tokens and [reasoning tokens](/docs/guides/reasoning).\n" + }).check(Schema.isInt()), + Schema.Null + ]) + ) + }).annotate({ "title": "The response object", "description": "The full response object that is queued." }), + "sequence_number": Schema.Number.annotate({ "description": "The sequence number for this event." }).check( + Schema.isInt() + ) +}).annotate({ + "title": "ResponseQueuedEvent", + "description": "Emitted when a response is queued and waiting to be processed.\n" +}) +export type CompactResource = { + readonly "id": string + readonly "object": "response.compaction" + readonly "output": ReadonlyArray + readonly "created_at": number + 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 + } +} +export const CompactResource = Schema.Struct({ + "id": Schema.String.annotate({ "description": "The unique identifier for the compacted response." }), + "object": Schema.Literal("response.compaction").annotate({ + "description": "The object type. Always `response.compaction`." + }), + "output": Schema.Array(ItemField).annotate({ "description": "The compacted list of output items." }), + "created_at": Schema.Number.annotate({ + "description": "Unix timestamp (in seconds) when the compacted conversation was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "usage": Schema.Struct({ + "input_tokens": Schema.Number.annotate({ "description": "The number of input tokens." }).check(Schema.isInt()), + "input_tokens_details": Schema.Struct({ + "cached_tokens": Schema.Number.annotate({ + "description": + "The number of tokens that were retrieved from the cache. \n[More on prompt caching](/docs/guides/prompt-caching).\n" + }).check(Schema.isInt()) + }).annotate({ "description": "A detailed breakdown of the input tokens." }), + "output_tokens": Schema.Number.annotate({ "description": "The number of output tokens." }).check(Schema.isInt()), + "output_tokens_details": Schema.Struct({ + "reasoning_tokens": Schema.Number.annotate({ "description": "The number of reasoning tokens." }).check( + Schema.isInt() + ) + }).annotate({ "description": "A detailed breakdown of the output tokens." }), + "total_tokens": Schema.Number.annotate({ "description": "The total number of tokens used." }).check(Schema.isInt()) + }).annotate({ + "description": "Token accounting for the compaction pass, including cached, reasoning, and total tokens." + }) +}).annotate({ "title": "The compacted response object" }) +export type EvalList = { + readonly "object": "list" + readonly "data": ReadonlyArray + readonly "first_id": string + readonly "last_id": string + readonly "has_more": boolean +} +export const EvalList = Schema.Struct({ + "object": Schema.Literal("list").annotate({ + "description": "The type of this object. It is always set to \"list\".\n" + }), + "data": Schema.Array(Eval).annotate({ "description": "An array of eval objects.\n" }), + "first_id": Schema.String.annotate({ "description": "The identifier of the first eval in the data array." }), + "last_id": Schema.String.annotate({ "description": "The identifier of the last eval in the data array." }), + "has_more": Schema.Boolean.annotate({ "description": "Indicates whether there are more evals available." }) +}).annotate({ "title": "EvalList", "description": "An object representing a list of evals.\n" }) +export type FineTuneMethod = { + readonly "type": "supervised" | "dpo" | "reinforcement" + readonly "supervised"?: FineTuneSupervisedMethod + readonly "dpo"?: FineTuneDPOMethod + readonly "reinforcement"?: FineTuneReinforcementMethod +} +export const FineTuneMethod = Schema.Struct({ + "type": Schema.Literals(["supervised", "dpo", "reinforcement"]).annotate({ + "description": "The type of method. Is either `supervised`, `dpo`, or `reinforcement`." + }), + "supervised": Schema.optionalKey(FineTuneSupervisedMethod), + "dpo": Schema.optionalKey(FineTuneDPOMethod), + "reinforcement": Schema.optionalKey(FineTuneReinforcementMethod) +}).annotate({ "description": "The method used for fine-tuning." }) +export type ResponseStreamEvent = + | ResponseAudioDeltaEvent + | ResponseAudioDoneEvent + | ResponseAudioTranscriptDeltaEvent + | ResponseAudioTranscriptDoneEvent + | ResponseCodeInterpreterCallCodeDeltaEvent + | ResponseCodeInterpreterCallCodeDoneEvent + | ResponseCodeInterpreterCallCompletedEvent + | ResponseCodeInterpreterCallInProgressEvent + | ResponseCodeInterpreterCallInterpretingEvent + | ResponseCompletedEvent + | ResponseContentPartAddedEvent + | ResponseContentPartDoneEvent + | ResponseCreatedEvent + | ResponseErrorEvent + | ResponseFileSearchCallCompletedEvent + | ResponseFileSearchCallInProgressEvent + | ResponseFileSearchCallSearchingEvent + | ResponseFunctionCallArgumentsDeltaEvent + | ResponseFunctionCallArgumentsDoneEvent + | ResponseInProgressEvent + | ResponseFailedEvent + | ResponseIncompleteEvent + | ResponseOutputItemAddedEvent + | ResponseOutputItemDoneEvent + | ResponseReasoningSummaryPartAddedEvent + | ResponseReasoningSummaryPartDoneEvent + | ResponseReasoningSummaryTextDeltaEvent + | ResponseReasoningSummaryTextDoneEvent + | ResponseReasoningTextDeltaEvent + | ResponseReasoningTextDoneEvent + | ResponseRefusalDeltaEvent + | ResponseRefusalDoneEvent + | ResponseTextDeltaEvent + | ResponseTextDoneEvent + | ResponseWebSearchCallCompletedEvent + | ResponseWebSearchCallInProgressEvent + | ResponseWebSearchCallSearchingEvent + | ResponseImageGenCallCompletedEvent + | ResponseImageGenCallGeneratingEvent + | ResponseImageGenCallInProgressEvent + | ResponseImageGenCallPartialImageEvent + | ResponseMCPCallArgumentsDeltaEvent + | ResponseMCPCallArgumentsDoneEvent + | ResponseMCPCallCompletedEvent + | ResponseMCPCallFailedEvent + | ResponseMCPCallInProgressEvent + | ResponseMCPListToolsCompletedEvent + | ResponseMCPListToolsFailedEvent + | ResponseMCPListToolsInProgressEvent + | ResponseOutputTextAnnotationAddedEvent + | ResponseQueuedEvent + | ResponseCustomToolCallInputDeltaEvent + | ResponseCustomToolCallInputDoneEvent + | ResponseKeepAliveEvent + | ResponseApplyPatchCallOperationDiffDeltaEvent + | ResponseApplyPatchCallOperationDiffDoneEvent +export const ResponseStreamEvent = Schema.Union([ + ResponseAudioDeltaEvent, + ResponseAudioDoneEvent, + ResponseAudioTranscriptDeltaEvent, + ResponseAudioTranscriptDoneEvent, + ResponseCodeInterpreterCallCodeDeltaEvent, + ResponseCodeInterpreterCallCodeDoneEvent, + ResponseCodeInterpreterCallCompletedEvent, + ResponseCodeInterpreterCallInProgressEvent, + ResponseCodeInterpreterCallInterpretingEvent, + ResponseCompletedEvent, + ResponseContentPartAddedEvent, + ResponseContentPartDoneEvent, + ResponseCreatedEvent, + ResponseErrorEvent, + ResponseFileSearchCallCompletedEvent, + ResponseFileSearchCallInProgressEvent, + ResponseFileSearchCallSearchingEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseInProgressEvent, + ResponseFailedEvent, + ResponseIncompleteEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseReasoningSummaryPartAddedEvent, + ResponseReasoningSummaryPartDoneEvent, + ResponseReasoningSummaryTextDeltaEvent, + ResponseReasoningSummaryTextDoneEvent, + ResponseReasoningTextDeltaEvent, + ResponseReasoningTextDoneEvent, + ResponseRefusalDeltaEvent, + ResponseRefusalDoneEvent, + ResponseTextDeltaEvent, + ResponseTextDoneEvent, + ResponseWebSearchCallCompletedEvent, + ResponseWebSearchCallInProgressEvent, + ResponseWebSearchCallSearchingEvent, + ResponseImageGenCallCompletedEvent, + ResponseImageGenCallGeneratingEvent, + ResponseImageGenCallInProgressEvent, + ResponseImageGenCallPartialImageEvent, + ResponseMCPCallArgumentsDeltaEvent, + ResponseMCPCallArgumentsDoneEvent, + ResponseMCPCallCompletedEvent, + ResponseMCPCallFailedEvent, + ResponseMCPCallInProgressEvent, + ResponseMCPListToolsCompletedEvent, + ResponseMCPListToolsFailedEvent, + ResponseMCPListToolsInProgressEvent, + ResponseOutputTextAnnotationAddedEvent, + ResponseQueuedEvent, + ResponseCustomToolCallInputDeltaEvent, + ResponseCustomToolCallInputDoneEvent, + ResponseKeepAliveEvent, + ResponseApplyPatchCallOperationDiffDeltaEvent, + ResponseApplyPatchCallOperationDiffDoneEvent +]) +export type CreateFineTuningJobRequest = { + readonly "model": string | "babbage-002" | "davinci-002" | "gpt-3.5-turbo" | "gpt-4o-mini" + readonly "training_file": string + readonly "hyperparameters"?: { + readonly "batch_size"?: "auto" | number + readonly "learning_rate_multiplier"?: "auto" | number + readonly "n_epochs"?: "auto" | number + } + readonly "suffix"?: string + readonly "validation_file"?: string | null + readonly "integrations"?: ReadonlyArray< + { + readonly "type": "wandb" + readonly "wandb": { + readonly "project": string + readonly "name"?: string | null + readonly "entity"?: string | null + readonly "tags"?: ReadonlyArray + } + } + > + readonly "seed"?: number + readonly "method"?: FineTuneMethod + readonly "metadata"?: Metadata +} +export const CreateFineTuningJobRequest = Schema.Struct({ + "model": Schema.Union([ + Schema.String, + Schema.Literals(["babbage-002", "davinci-002", "gpt-3.5-turbo", "gpt-4o-mini"]) + ]).annotate({ + "description": + "The name of the model to fine-tune. You can select one of the\n[supported models](/docs/guides/fine-tuning#which-models-can-be-fine-tuned).\n" + }), + "training_file": Schema.String.annotate({ + "description": + "The ID of an uploaded file that contains training data.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`.\n\nThe contents of the file should differ depending on if the model uses the [chat](/docs/api-reference/fine-tuning/chat-input), [completions](/docs/api-reference/fine-tuning/completions-input) format, or if the fine-tuning method uses the [preference](/docs/api-reference/fine-tuning/preference-input) format.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n" + }), + "hyperparameters": Schema.optionalKey( + Schema.Struct({ + "batch_size": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check( + Schema.isLessThanOrEqualTo(256) + ) + ], { mode: "oneOf" }).annotate({ + "description": + "Number of examples in each batch. A larger batch size means that model parameters\nare updated less frequently, but with lower variance.\n" + }) + ), + "learning_rate_multiplier": Schema.optionalKey( + Schema.Union([Schema.Literal("auto"), Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThan(0))], { + mode: "oneOf" + }).annotate({ + "description": + "Scaling factor for the learning rate. A smaller learning rate may be useful to avoid\noverfitting.\n" + }) + ), + "n_epochs": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check( + Schema.isLessThanOrEqualTo(50) + ) + ], { mode: "oneOf" }).annotate({ + "description": + "The number of epochs to train the model for. An epoch refers to one full cycle\nthrough the training dataset.\n" + }) + ) + }).annotate({ + "description": + "The hyperparameters used for the fine-tuning job.\nThis value is now deprecated in favor of `method`, and should be passed in under the `method` parameter.\n" + }) + ), + "suffix": Schema.optionalKey( + Schema.Union([ + Schema.String.check( + Schema.makeFilterGroup([Schema.isMinLength(1), Schema.isMaxLength(64)], { + "description": + "A string of up to 64 characters that will be added to your fine-tuned model name.\n\nFor example, a `suffix` of \"custom-model-name\" would produce a model name like `ft:gpt-4o-mini:openai:custom-model-name:7p4lURel`.\n" + }) + ) + ]) + ), + "validation_file": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The ID of an uploaded file that contains validation data.\n\nIf you provide this file, the data is used to generate validation\nmetrics periodically during fine-tuning. These metrics can be viewed in\nthe fine-tuning results file.\nThe same data should not be present in both train and validation files.\n\nYour dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`.\n\nSee the [fine-tuning guide](/docs/guides/model-optimization) for more details.\n" + }) + ), + "integrations": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Struct({ + "type": Schema.Literal("wandb").annotate({ + "description": + "The type of integration to enable. Currently, only \"wandb\" (Weights and Biases) is supported.\n" + }), + "wandb": Schema.Struct({ + "project": Schema.String.annotate({ + "description": "The name of the project that the new run will be created under.\n" + }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A display name to set for the run. If not set, we will use the Job ID as the name.\n" + }) + ), + "entity": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The entity to use for the run. This allows you to set the team or username of the WandB user that you would\nlike associated with the run. If not set, the default entity for the registered WandB API key is used.\n" + }) + ), + "tags": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of tags to be attached to the newly created run. These tags are passed through directly to WandB. Some\ndefault tags are generated by OpenAI: \"openai/finetune\", \"openai/{base-model}\", \"openai/{ftjob-abcdef}\".\n" + }) + ) + }).annotate({ + "description": + "The settings for your integration with Weights and Biases. This payload specifies the project that\nmetrics will be sent to. Optionally, you can set an explicit display name for your run, add tags\nto your run, and set a default entity (team, username, etc) to be associated with your run.\n" + }) + })).annotate({ "description": "A list of integrations to enable for your fine-tuning job." }) + ])), + "seed": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt()).check( + Schema.makeFilterGroup([ + Schema.isFinite(), + Schema.isGreaterThanOrEqualTo(0), + Schema.isLessThanOrEqualTo(2147483647) + ], { + "description": + "The seed controls the reproducibility of the job. Passing in the same seed and job parameters should produce the same results, but may differ in rare cases.\nIf a seed is not specified, one will be generated for you.\n" + }) + ) + ]) + ), + "method": Schema.optionalKey(FineTuneMethod), + "metadata": Schema.optionalKey(Metadata) +}) +export type FineTuningJob = { + readonly "id": string + readonly "created_at": number + readonly "error": { readonly "code": string; readonly "message": string; readonly "param": string | null } | null + readonly "fine_tuned_model": string | null + readonly "finished_at": number | null + readonly "hyperparameters": { + readonly "batch_size"?: "auto" | number | null + readonly "learning_rate_multiplier"?: "auto" | number + readonly "n_epochs"?: "auto" | number + } + readonly "model": string + readonly "object": "fine_tuning.job" + readonly "organization_id": string + readonly "result_files": ReadonlyArray + readonly "status": "validating_files" | "queued" | "running" | "succeeded" | "failed" | "cancelled" + readonly "trained_tokens": number | null + readonly "training_file": string + readonly "validation_file": string | null + readonly "integrations"?: ReadonlyArray | null + readonly "seed": number + readonly "estimated_finish"?: number | null + readonly "method"?: FineTuneMethod + readonly "metadata"?: Metadata +} +export const FineTuningJob = Schema.Struct({ + "id": Schema.String.annotate({ + "description": "The object identifier, which can be referenced in the API endpoints." + }), + "created_at": Schema.Number.annotate({ + "description": "The Unix timestamp (in seconds) for when the fine-tuning job was created.", + "format": "unixtime" + }).check(Schema.isInt()), + "error": Schema.Union([ + Schema.Struct({ + "code": Schema.String.annotate({ "description": "A machine-readable error code." }), + "message": Schema.String.annotate({ "description": "A human-readable error message." }), + "param": Schema.Union([ + Schema.String.annotate({ + "description": + "The parameter that was invalid, usually `training_file` or `validation_file`. This field will be null if the failure was not parameter-specific." + }), + Schema.Null + ]) + }).annotate({ + "description": + "For fine-tuning jobs that have `failed`, this will contain more information on the cause of the failure." + }), + Schema.Null + ]), + "fine_tuned_model": Schema.Union([ + Schema.String.annotate({ + "description": + "The name of the fine-tuned model that is being created. The value will be null if the fine-tuning job is still running." + }), + Schema.Null + ]), + "finished_at": Schema.Union([ + Schema.Number.annotate({ + "description": + "The Unix timestamp (in seconds) for when the fine-tuning job was finished. The value will be null if the fine-tuning job is still running.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]), + "hyperparameters": Schema.Struct({ + "batch_size": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check( + Schema.isLessThanOrEqualTo(256) + ) + ], { mode: "oneOf" }).annotate({ + "description": + "Number of examples in each batch. A larger batch size means that model parameters\nare updated less frequently, but with lower variance.\n" + }), + Schema.Null + ]) + ), + "learning_rate_multiplier": Schema.optionalKey( + Schema.Union([Schema.Literal("auto"), Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThan(0))], { + mode: "oneOf" + }).annotate({ + "description": + "Scaling factor for the learning rate. A smaller learning rate may be useful to avoid\noverfitting.\n" + }) + ), + "n_epochs": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(1)).check( + Schema.isLessThanOrEqualTo(50) + ) + ], { mode: "oneOf" }).annotate({ + "description": + "The number of epochs to train the model for. An epoch refers to one full cycle\nthrough the training dataset.\n" + }) + ) + }).annotate({ + "description": + "The hyperparameters used for the fine-tuning job. This value will only be returned when running `supervised` jobs." + }), + "model": Schema.String.annotate({ "description": "The base model that is being fine-tuned." }), + "object": Schema.Literal("fine_tuning.job").annotate({ + "description": "The object type, which is always \"fine_tuning.job\"." + }), + "organization_id": Schema.String.annotate({ "description": "The organization that owns the fine-tuning job." }), + "result_files": Schema.Array(Schema.String).annotate({ + "description": + "The compiled results file ID(s) for the fine-tuning job. You can retrieve the results with the [Files API](/docs/api-reference/files/retrieve-contents)." + }), + "status": Schema.Literals(["validating_files", "queued", "running", "succeeded", "failed", "cancelled"]).annotate({ + "description": + "The current status of the fine-tuning job, which can be either `validating_files`, `queued`, `running`, `succeeded`, `failed`, or `cancelled`." + }), + "trained_tokens": Schema.Union([ + Schema.Number.annotate({ + "description": + "The total number of billable tokens processed by this fine-tuning job. The value will be null if the fine-tuning job is still running." + }).check(Schema.isInt()), + Schema.Null + ]), + "training_file": Schema.String.annotate({ + "description": + "The file ID used for training. You can retrieve the training data with the [Files API](/docs/api-reference/files/retrieve-contents)." + }), + "validation_file": Schema.Union([ + Schema.String.annotate({ + "description": + "The file ID used for validation. You can retrieve the validation results with the [Files API](/docs/api-reference/files/retrieve-contents)." + }), + Schema.Null + ]), + "integrations": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Union([FineTuningIntegration], { mode: "oneOf" })).annotate({ + "description": "A list of integrations to enable for this fine-tuning job." + }).check(Schema.isMaxLength(5)), + Schema.Null + ]) + ), + "seed": Schema.Number.annotate({ "description": "The seed used for the fine-tuning job." }).check(Schema.isInt()), + "estimated_finish": Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + "description": + "The Unix timestamp (in seconds) for when the fine-tuning job is estimated to finish. The value will be null if the fine-tuning job is not running.", + "format": "unixtime" + }).check(Schema.isInt()), + Schema.Null + ]) + ), + "method": Schema.optionalKey(FineTuneMethod), + "metadata": Schema.optionalKey(Metadata) +}).annotate({ + "title": "FineTuningJob", + "description": "The `fine_tuning.job` object represents a fine-tuning job that has been created through the API.\n" +}) +export type ListPaginatedFineTuningJobsResponse = { + readonly "data": ReadonlyArray + readonly "has_more": boolean + readonly "object": "list" +} +export const ListPaginatedFineTuningJobsResponse = Schema.Struct({ + "data": Schema.Array(FineTuningJob), + "has_more": Schema.Boolean, + "object": Schema.Literal("list") +}) +// schemas +export type ListAssistantsParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "before"?: string +} +export const ListAssistantsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String) +}) +export type ListAssistants200 = ListAssistantsResponse +export const ListAssistants200 = ListAssistantsResponse +export type CreateAssistantRequestJson = CreateAssistantRequest +export const CreateAssistantRequestJson = CreateAssistantRequest +export type CreateAssistant200 = AssistantObject +export const CreateAssistant200 = AssistantObject +export type GetAssistant200 = AssistantObject +export const GetAssistant200 = AssistantObject +export type ModifyAssistantRequestJson = ModifyAssistantRequest +export const ModifyAssistantRequestJson = ModifyAssistantRequest +export type ModifyAssistant200 = AssistantObject +export const ModifyAssistant200 = AssistantObject +export type DeleteAssistant200 = DeleteAssistantResponse +export const DeleteAssistant200 = DeleteAssistantResponse +export type CreateSpeechRequestJson = CreateSpeechRequest +export const CreateSpeechRequestJson = CreateSpeechRequest +export type CreateSpeech200Sse = CreateSpeechResponseStreamEvent +export const CreateSpeech200Sse = CreateSpeechResponseStreamEvent +export type CreateTranscriptionRequestFormData = CreateTranscriptionRequest +export const CreateTranscriptionRequestFormData = CreateTranscriptionRequest +export type CreateTranscription200 = + | CreateTranscriptionResponseJson + | CreateTranscriptionResponseDiarizedJson + | CreateTranscriptionResponseVerboseJson +export const CreateTranscription200 = Schema.Union([ + CreateTranscriptionResponseJson, + CreateTranscriptionResponseDiarizedJson, + CreateTranscriptionResponseVerboseJson +], { mode: "oneOf" }) +export type CreateTranscription200Sse = CreateTranscriptionResponseStreamEvent +export const CreateTranscription200Sse = CreateTranscriptionResponseStreamEvent +export type CreateTranslationRequestFormData = CreateTranslationRequest +export const CreateTranslationRequestFormData = CreateTranslationRequest +export type CreateTranslation200 = CreateTranslationResponseJson | CreateTranslationResponseVerboseJson +export const CreateTranslation200 = Schema.Union( + [CreateTranslationResponseJson, CreateTranslationResponseVerboseJson], + { mode: "oneOf" } +) +export type ListVoiceConsentsParams = { readonly "after"?: string; readonly "limit"?: number } +export const ListVoiceConsentsParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())) +}) +export type ListVoiceConsents200 = VoiceConsentListResource +export const ListVoiceConsents200 = VoiceConsentListResource +export type CreateVoiceConsentRequestFormData = CreateVoiceConsentRequest +export const CreateVoiceConsentRequestFormData = CreateVoiceConsentRequest +export type CreateVoiceConsent200 = VoiceConsentResource +export const CreateVoiceConsent200 = VoiceConsentResource +export type GetVoiceConsent200 = VoiceConsentResource +export const GetVoiceConsent200 = VoiceConsentResource +export type UpdateVoiceConsentRequestJson = UpdateVoiceConsentRequest +export const UpdateVoiceConsentRequestJson = UpdateVoiceConsentRequest +export type UpdateVoiceConsent200 = VoiceConsentResource +export const UpdateVoiceConsent200 = VoiceConsentResource +export type DeleteVoiceConsent200 = VoiceConsentDeletedResource +export const DeleteVoiceConsent200 = VoiceConsentDeletedResource +export type CreateVoiceRequestFormData = CreateVoiceRequest +export const CreateVoiceRequestFormData = CreateVoiceRequest +export type CreateVoice200 = VoiceResource +export const CreateVoice200 = VoiceResource +export type ListBatchesParams = { readonly "after"?: string; readonly "limit"?: number } +export const ListBatchesParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())) +}) +export type ListBatches200 = ListBatchesResponse +export const ListBatches200 = ListBatchesResponse +export type CreateBatchRequestJson = { + readonly "input_file_id": string + readonly "endpoint": + | "/v1/responses" + | "/v1/chat/completions" + | "/v1/embeddings" + | "/v1/completions" + | "/v1/moderations" + | "/v1/images/generations" + | "/v1/images/edits" + | "/v1/videos" + readonly "completion_window": "24h" + readonly "metadata"?: Metadata + readonly "output_expires_after"?: BatchFileExpirationAfter +} +export const CreateBatchRequestJson = Schema.Struct({ + "input_file_id": Schema.String.annotate({ + "description": + "The ID of an uploaded file that contains requests for the new batch.\n\nSee [upload file](/docs/api-reference/files/create) for how to upload a file.\n\nYour input file must be formatted as a [JSONL file](/docs/api-reference/batch/request-input), and must be uploaded with the purpose `batch`. The file can contain up to 50,000 requests, and can be up to 200 MB in size.\n" + }), + "endpoint": Schema.Literals([ + "/v1/responses", + "/v1/chat/completions", + "/v1/embeddings", + "/v1/completions", + "/v1/moderations", + "/v1/images/generations", + "/v1/images/edits", + "/v1/videos" + ]).annotate({ + "description": + "The endpoint to be used for all requests in the batch. Currently `/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, and `/v1/videos` are supported. Note that `/v1/embeddings` batches are also restricted to a maximum of 50,000 embedding inputs across all requests in the batch." + }), + "completion_window": Schema.Literal("24h").annotate({ + "description": "The time frame within which the batch should be processed. Currently only `24h` is supported." + }), + "metadata": Schema.optionalKey(Metadata), + "output_expires_after": Schema.optionalKey(BatchFileExpirationAfter) +}) +export type CreateBatch200 = Batch +export const CreateBatch200 = Batch +export type RetrieveBatch200 = Batch +export const RetrieveBatch200 = Batch +export type CancelBatch200 = Batch +export const CancelBatch200 = Batch +export type ListChatCompletionsParams = { + readonly "model"?: string + readonly "metadata"?: Metadata + readonly "after"?: string + readonly "limit"?: number + readonly "order"?: "asc" | "desc" +} +export const ListChatCompletionsParams = Schema.Struct({ + "model": Schema.optionalKey(Schema.String), + "metadata": Schema.optionalKey(Metadata), + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListChatCompletions200 = ChatCompletionList +export const ListChatCompletions200 = ChatCompletionList +export type CreateChatCompletionRequestJson = CreateChatCompletionRequest +export const CreateChatCompletionRequestJson = CreateChatCompletionRequest +export type CreateChatCompletion200 = CreateChatCompletionResponse +export const CreateChatCompletion200 = CreateChatCompletionResponse +export type CreateChatCompletion200Sse = CreateChatCompletionStreamResponse +export const CreateChatCompletion200Sse = CreateChatCompletionStreamResponse +export type GetChatCompletion200 = CreateChatCompletionResponse +export const GetChatCompletion200 = CreateChatCompletionResponse +export type UpdateChatCompletionRequestJson = { readonly "metadata": Metadata } +export const UpdateChatCompletionRequestJson = Schema.Struct({ "metadata": Metadata }) +export type UpdateChatCompletion200 = CreateChatCompletionResponse +export const UpdateChatCompletion200 = CreateChatCompletionResponse +export type DeleteChatCompletion200 = ChatCompletionDeleted +export const DeleteChatCompletion200 = ChatCompletionDeleted +export type GetChatCompletionMessagesParams = { + readonly "after"?: string + readonly "limit"?: number + readonly "order"?: "asc" | "desc" +} +export const GetChatCompletionMessagesParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type GetChatCompletionMessages200 = ChatCompletionMessageList +export const GetChatCompletionMessages200 = ChatCompletionMessageList +export type CreateCompletionRequestJson = CreateCompletionRequest +export const CreateCompletionRequestJson = CreateCompletionRequest +export type CreateCompletion200 = CreateCompletionResponse +export const CreateCompletion200 = CreateCompletionResponse +export type ListContainersParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "name"?: string +} +export const ListContainersParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "name": Schema.optionalKey(Schema.String) +}) +export type ListContainers200 = ContainerListResource +export const ListContainers200 = ContainerListResource +export type CreateContainerRequestJson = CreateContainerBody +export const CreateContainerRequestJson = CreateContainerBody +export type CreateContainer200 = ContainerResource +export const CreateContainer200 = ContainerResource +export type RetrieveContainer200 = ContainerResource +export const RetrieveContainer200 = ContainerResource +export type ListContainerFilesParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string +} +export const ListContainerFilesParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String) +}) +export type ListContainerFiles200 = ContainerFileListResource +export const ListContainerFiles200 = ContainerFileListResource +export type CreateContainerFileRequestJson = CreateContainerFileBody +export const CreateContainerFileRequestJson = CreateContainerFileBody +export type CreateContainerFileRequestFormData = CreateContainerFileBody +export const CreateContainerFileRequestFormData = CreateContainerFileBody +export type CreateContainerFile200 = ContainerFileResource +export const CreateContainerFile200 = ContainerFileResource +export type RetrieveContainerFile200 = ContainerFileResource +export const RetrieveContainerFile200 = ContainerFileResource +export type ListConversationItemsParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "include"?: ReadonlyArray +} +export const ListConversationItemsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "include": Schema.optionalKey(Schema.Array(IncludeEnum)) +}) +export type ListConversationItems200 = ConversationItemList +export const ListConversationItems200 = ConversationItemList +export type CreateConversationItemsParams = { readonly "include"?: ReadonlyArray } +export const CreateConversationItemsParams = Schema.Struct({ "include": Schema.optionalKey(Schema.Array(IncludeEnum)) }) +export type CreateConversationItemsRequestJson = { + readonly "items": ReadonlyArray + readonly [x: string]: unknown +} +export const CreateConversationItemsRequestJson = Schema.StructWithRest( + Schema.Struct({ + "items": Schema.Array(InputItem).annotate({ + "description": "The items to add to the conversation. You may add up to 20 items at a time.\n" + }).check(Schema.isMaxLength(20)) + }), + [Schema.Record(Schema.String, Schema.Unknown)] +) +export type CreateConversationItems200 = ConversationItemList +export const CreateConversationItems200 = ConversationItemList +export type GetConversationItemParams = { readonly "include"?: ReadonlyArray } +export const GetConversationItemParams = Schema.Struct({ "include": Schema.optionalKey(Schema.Array(IncludeEnum)) }) +export type GetConversationItem200 = ConversationItem +export const GetConversationItem200 = ConversationItem +export type DeleteConversationItem200 = ConversationResource +export const DeleteConversationItem200 = ConversationResource +export type CreateEmbeddingRequestJson = CreateEmbeddingRequest +export const CreateEmbeddingRequestJson = CreateEmbeddingRequest +export type CreateEmbedding200 = CreateEmbeddingResponse +export const CreateEmbedding200 = CreateEmbeddingResponse +export type ListEvalsParams = { + readonly "after"?: string + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "order_by"?: "created_at" | "updated_at" +} +export const ListEvalsParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "order_by": Schema.optionalKey(Schema.Literals(["created_at", "updated_at"])) +}) +export type ListEvals200 = EvalList +export const ListEvals200 = EvalList +export type CreateEvalRequestJson = CreateEvalRequest +export const CreateEvalRequestJson = CreateEvalRequest +export type CreateEval201 = Eval +export const CreateEval201 = Eval +export type GetEval200 = Eval +export const GetEval200 = Eval +export type UpdateEvalRequestJson = { readonly "name"?: string; readonly "metadata"?: Metadata } +export const UpdateEvalRequestJson = Schema.Struct({ + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Rename the evaluation." })), + "metadata": Schema.optionalKey(Metadata) +}) +export type UpdateEval200 = Eval +export const UpdateEval200 = Eval +export type DeleteEval200 = { readonly "object": string; readonly "deleted": boolean; readonly "eval_id": string } +export const DeleteEval200 = Schema.Struct({ + "object": Schema.String, + "deleted": Schema.Boolean, + "eval_id": Schema.String +}) +export type DeleteEval404 = Error +export const DeleteEval404 = Error +export type GetEvalRunsParams = { + readonly "after"?: string + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "status"?: "queued" | "in_progress" | "completed" | "canceled" | "failed" +} +export const GetEvalRunsParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "status": Schema.optionalKey(Schema.Literals(["queued", "in_progress", "completed", "canceled", "failed"])) +}) +export type GetEvalRuns200 = EvalRunList +export const GetEvalRuns200 = EvalRunList +export type CreateEvalRunRequestJson = CreateEvalRunRequest +export const CreateEvalRunRequestJson = CreateEvalRunRequest +export type CreateEvalRun201 = EvalRun +export const CreateEvalRun201 = EvalRun +export type CreateEvalRun400 = Error +export const CreateEvalRun400 = Error +export type GetEvalRun200 = EvalRun +export const GetEvalRun200 = EvalRun +export type CancelEvalRun200 = EvalRun +export const CancelEvalRun200 = EvalRun +export type DeleteEvalRun200 = { readonly "object"?: string; readonly "deleted"?: boolean; readonly "run_id"?: string } +export const DeleteEvalRun200 = Schema.Struct({ + "object": Schema.optionalKey(Schema.String), + "deleted": Schema.optionalKey(Schema.Boolean), + "run_id": Schema.optionalKey(Schema.String) +}) +export type DeleteEvalRun404 = Error +export const DeleteEvalRun404 = Error +export type GetEvalRunOutputItemsParams = { + readonly "after"?: string + readonly "limit"?: number + readonly "status"?: "fail" | "pass" + readonly "order"?: "asc" | "desc" +} +export const GetEvalRunOutputItemsParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "status": Schema.optionalKey(Schema.Literals(["fail", "pass"])), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type GetEvalRunOutputItems200 = EvalRunOutputItemList +export const GetEvalRunOutputItems200 = EvalRunOutputItemList +export type GetEvalRunOutputItem200 = EvalRunOutputItem +export const GetEvalRunOutputItem200 = EvalRunOutputItem +export type ListFilesParams = { + readonly "purpose"?: string + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string +} +export const ListFilesParams = Schema.Struct({ + "purpose": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String) +}) +export type ListFiles200 = ListFilesResponse +export const ListFiles200 = ListFilesResponse +export type CreateFileRequestFormData = CreateFileRequest +export const CreateFileRequestFormData = CreateFileRequest +export type CreateFile200 = OpenAIFile +export const CreateFile200 = OpenAIFile +export type RetrieveFile200 = OpenAIFile +export const RetrieveFile200 = OpenAIFile +export type DeleteFile200 = DeleteFileResponse +export const DeleteFile200 = DeleteFileResponse +export type DownloadFile200 = string +export const DownloadFile200 = Schema.String +export type RunGraderRequestJson = RunGraderRequest +export const RunGraderRequestJson = RunGraderRequest +export type RunGrader200 = RunGraderResponse +export const RunGrader200 = RunGraderResponse +export type ValidateGraderRequestJson = ValidateGraderRequest +export const ValidateGraderRequestJson = ValidateGraderRequest +export type ValidateGrader200 = ValidateGraderResponse +export const ValidateGrader200 = ValidateGraderResponse +export type ListFineTuningCheckpointPermissionsParams = { + readonly "project_id"?: string + readonly "after"?: string + readonly "limit"?: number + readonly "order"?: "ascending" | "descending" +} +export const ListFineTuningCheckpointPermissionsParams = Schema.Struct({ + "project_id": Schema.optionalKey(Schema.String), + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["ascending", "descending"])) +}) +export type ListFineTuningCheckpointPermissions200 = ListFineTuningCheckpointPermissionResponse +export const ListFineTuningCheckpointPermissions200 = ListFineTuningCheckpointPermissionResponse +export type CreateFineTuningCheckpointPermissionRequestJson = CreateFineTuningCheckpointPermissionRequest +export const CreateFineTuningCheckpointPermissionRequestJson = CreateFineTuningCheckpointPermissionRequest +export type CreateFineTuningCheckpointPermission200 = ListFineTuningCheckpointPermissionResponse +export const CreateFineTuningCheckpointPermission200 = ListFineTuningCheckpointPermissionResponse +export type DeleteFineTuningCheckpointPermission200 = DeleteFineTuningCheckpointPermissionResponse +export const DeleteFineTuningCheckpointPermission200 = DeleteFineTuningCheckpointPermissionResponse +export type ListPaginatedFineTuningJobsParams = { + readonly "after"?: string + readonly "limit"?: number + readonly "metadata"?: {} +} +export const ListPaginatedFineTuningJobsParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "metadata": Schema.optionalKey(Schema.Union([Schema.Struct({})])) +}) +export type ListPaginatedFineTuningJobs200 = ListPaginatedFineTuningJobsResponse +export const ListPaginatedFineTuningJobs200 = ListPaginatedFineTuningJobsResponse +export type CreateFineTuningJobRequestJson = CreateFineTuningJobRequest +export const CreateFineTuningJobRequestJson = CreateFineTuningJobRequest +export type CreateFineTuningJob200 = FineTuningJob +export const CreateFineTuningJob200 = FineTuningJob +export type RetrieveFineTuningJob200 = FineTuningJob +export const RetrieveFineTuningJob200 = FineTuningJob +export type CancelFineTuningJob200 = FineTuningJob +export const CancelFineTuningJob200 = FineTuningJob +export type ListFineTuningJobCheckpointsParams = { readonly "after"?: string; readonly "limit"?: number } +export const ListFineTuningJobCheckpointsParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())) +}) +export type ListFineTuningJobCheckpoints200 = ListFineTuningJobCheckpointsResponse +export const ListFineTuningJobCheckpoints200 = ListFineTuningJobCheckpointsResponse +export type ListFineTuningEventsParams = { readonly "after"?: string; readonly "limit"?: number } +export const ListFineTuningEventsParams = Schema.Struct({ + "after": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())) +}) +export type ListFineTuningEvents200 = ListFineTuningJobEventsResponse +export const ListFineTuningEvents200 = ListFineTuningJobEventsResponse +export type PauseFineTuningJob200 = FineTuningJob +export const PauseFineTuningJob200 = FineTuningJob +export type ResumeFineTuningJob200 = FineTuningJob +export const ResumeFineTuningJob200 = FineTuningJob +export type CreateImageEditRequestJson = EditImageBodyJsonParam +export const CreateImageEditRequestJson = EditImageBodyJsonParam +export type CreateImageEditRequestFormData = CreateImageEditRequest +export const CreateImageEditRequestFormData = CreateImageEditRequest +export type CreateImageEdit200 = ImagesResponse +export const CreateImageEdit200 = ImagesResponse +export type CreateImageEdit200Sse = ImageEditStreamEvent +export const CreateImageEdit200Sse = ImageEditStreamEvent +export type CreateImageRequestJson = CreateImageRequest +export const CreateImageRequestJson = CreateImageRequest +export type CreateImage200 = ImagesResponse +export const CreateImage200 = ImagesResponse +export type CreateImage200Sse = ImageGenStreamEvent +export const CreateImage200Sse = ImageGenStreamEvent +export type CreateImageVariationRequestFormData = CreateImageVariationRequest +export const CreateImageVariationRequestFormData = CreateImageVariationRequest +export type CreateImageVariation200 = ImagesResponse +export const CreateImageVariation200 = ImagesResponse +export type ListModels200 = ListModelsResponse +export const ListModels200 = ListModelsResponse +export type RetrieveModel200 = Model +export const RetrieveModel200 = Model +export type DeleteModel200 = DeleteModelResponse +export const DeleteModel200 = DeleteModelResponse +export type CreateModerationRequestJson = CreateModerationRequest +export const CreateModerationRequestJson = CreateModerationRequest +export type CreateModeration200 = CreateModerationResponse +export const CreateModeration200 = CreateModerationResponse +export type AdminApiKeysListParams = { + readonly "after"?: string | null + readonly "order"?: "asc" | "desc" + readonly "limit"?: number +} +export const AdminApiKeysListParams = Schema.Struct({ + "after": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Return keys with IDs that come after this ID in the pagination order." + }) + ), + "order": Schema.optionalKey( + Schema.Literals(["asc", "desc"]).annotate({ + "description": "Order results by creation time, ascending or descending." + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of keys to return." }).check(Schema.isInt()) + ) +}) +export type AdminApiKeysList200 = ApiKeyList +export const AdminApiKeysList200 = ApiKeyList +export type AdminApiKeysCreateRequestJson = { readonly "name": string } +export const AdminApiKeysCreateRequestJson = Schema.Struct({ "name": Schema.String }) +export type AdminApiKeysCreate200 = AdminApiKeyCreateResponse +export const AdminApiKeysCreate200 = AdminApiKeyCreateResponse +export type AdminApiKeysGet200 = AdminApiKey +export const AdminApiKeysGet200 = AdminApiKey +export type AdminApiKeysDelete200 = { + readonly "id": string + readonly "object": "organization.admin_api_key.deleted" + readonly "deleted": boolean +} +export const AdminApiKeysDelete200 = Schema.Struct({ + "id": Schema.String, + "object": Schema.Literal("organization.admin_api_key.deleted"), + "deleted": Schema.Boolean +}) +export type ListAuditLogsParams = { + readonly "effective_at[gt]"?: number + readonly "effective_at[gte]"?: number + readonly "effective_at[lt]"?: number + readonly "effective_at[lte]"?: number + readonly "project_ids[]"?: ReadonlyArray + readonly "event_types[]"?: ReadonlyArray + readonly "actor_ids[]"?: ReadonlyArray + readonly "actor_emails[]"?: ReadonlyArray + readonly "resource_ids[]"?: ReadonlyArray + readonly "limit"?: number + readonly "after"?: string + readonly "before"?: string +} +export const ListAuditLogsParams = Schema.Struct({ + "effective_at[gt]": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Return only events whose `effective_at` (Unix seconds) is greater than this value." + }).check(Schema.isInt()) + ), + "effective_at[gte]": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Return only events whose `effective_at` (Unix seconds) is greater than or equal to this value." + }).check(Schema.isInt()) + ), + "effective_at[lt]": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Return only events whose `effective_at` (Unix seconds) is less than this value." + }).check(Schema.isInt()) + ), + "effective_at[lte]": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Return only events whose `effective_at` (Unix seconds) is less than or equal to this value." + }).check(Schema.isInt()) + ), + "project_ids[]": Schema.optionalKey(Schema.Array(Schema.String)), + "event_types[]": Schema.optionalKey(Schema.Array(AuditLogEventType)), + "actor_ids[]": Schema.optionalKey(Schema.Array(Schema.String)), + "actor_emails[]": Schema.optionalKey(Schema.Array(Schema.String)), + "resource_ids[]": Schema.optionalKey(Schema.Array(Schema.String)), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String) +}) +export type ListAuditLogs200 = ListAuditLogsResponse +export const ListAuditLogs200 = ListAuditLogsResponse +export type ListOrganizationCertificatesParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListOrganizationCertificatesParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListOrganizationCertificates200 = ListCertificatesResponse +export const ListOrganizationCertificates200 = ListCertificatesResponse +export type UploadCertificateRequestJson = UploadCertificateRequest +export const UploadCertificateRequestJson = UploadCertificateRequest +export type UploadCertificate200 = Certificate +export const UploadCertificate200 = Certificate +export type ActivateOrganizationCertificatesRequestJson = ToggleCertificatesRequest +export const ActivateOrganizationCertificatesRequestJson = ToggleCertificatesRequest +export type ActivateOrganizationCertificates200 = OrganizationCertificateActivationResponse +export const ActivateOrganizationCertificates200 = OrganizationCertificateActivationResponse +export type DeactivateOrganizationCertificatesRequestJson = ToggleCertificatesRequest +export const DeactivateOrganizationCertificatesRequestJson = ToggleCertificatesRequest +export type DeactivateOrganizationCertificates200 = OrganizationCertificateDeactivationResponse +export const DeactivateOrganizationCertificates200 = OrganizationCertificateDeactivationResponse +export type GetCertificateParams = { readonly "include"?: ReadonlyArray<"content"> } +export const GetCertificateParams = Schema.Struct({ + "include": Schema.optionalKey(Schema.Array(Schema.Literal("content"))) +}) +export type GetCertificate200 = Certificate +export const GetCertificate200 = Certificate +export type ModifyCertificateRequestJson = ModifyCertificateRequest +export const ModifyCertificateRequestJson = ModifyCertificateRequest +export type ModifyCertificate200 = Certificate +export const ModifyCertificate200 = Certificate +export type DeleteCertificate200 = DeleteCertificateResponse +export const DeleteCertificate200 = DeleteCertificateResponse +export type UsageCostsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1d" + readonly "project_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id" | "line_item" | "api_key_id"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageCostsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literal("1d")), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey(Schema.Array(Schema.Literals(["project_id", "line_item", "api_key_id"]))), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageCosts200 = UsageResponse +export const UsageCosts200 = UsageResponse +export type ListGroupsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListGroupsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListGroups200 = GroupListResource +export const ListGroups200 = GroupListResource +export type CreateGroupRequestJson = CreateGroupBody +export const CreateGroupRequestJson = CreateGroupBody +export type CreateGroup200 = GroupResponse +export const CreateGroup200 = GroupResponse +export type UpdateGroupRequestJson = UpdateGroupBody +export const UpdateGroupRequestJson = UpdateGroupBody +export type UpdateGroup200 = GroupResourceWithSuccess +export const UpdateGroup200 = GroupResourceWithSuccess +export type DeleteGroup200 = GroupDeletedResource +export const DeleteGroup200 = GroupDeletedResource +export type ListGroupRoleAssignmentsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListGroupRoleAssignmentsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListGroupRoleAssignments200 = RoleListResource +export const ListGroupRoleAssignments200 = RoleListResource +export type AssignGroupRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export const AssignGroupRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export type AssignGroupRole200 = GroupRoleAssignment +export const AssignGroupRole200 = GroupRoleAssignment +export type UnassignGroupRole200 = DeletedRoleAssignmentResource +export const UnassignGroupRole200 = DeletedRoleAssignmentResource +export type ListGroupUsersParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListGroupUsersParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListGroupUsers200 = UserListResource +export const ListGroupUsers200 = UserListResource +export type AddGroupUserRequestJson = CreateGroupUserBody +export const AddGroupUserRequestJson = CreateGroupUserBody +export type AddGroupUser200 = GroupUserAssignment +export const AddGroupUser200 = GroupUserAssignment +export type RemoveGroupUser200 = GroupUserDeletedResource +export const RemoveGroupUser200 = GroupUserDeletedResource +export type ListInvitesParams = { readonly "limit"?: number; readonly "after"?: string } +export const ListInvitesParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String) +}) +export type ListInvites200 = InviteListResponse +export const ListInvites200 = InviteListResponse +export type InviteUserRequestJson = InviteRequest +export const InviteUserRequestJson = InviteRequest +export type InviteUser200 = Invite +export const InviteUser200 = Invite +export type RetrieveInvite200 = Invite +export const RetrieveInvite200 = Invite +export type DeleteInvite200 = InviteDeleteResponse +export const DeleteInvite200 = InviteDeleteResponse +export type ListProjectsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "include_archived"?: boolean +} +export const ListProjectsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String), + "include_archived": Schema.optionalKey(Schema.Boolean) +}) +export type ListProjects200 = ProjectListResponse +export const ListProjects200 = ProjectListResponse +export type CreateProjectRequestJson = ProjectCreateRequest +export const CreateProjectRequestJson = ProjectCreateRequest +export type CreateProject200 = Project +export const CreateProject200 = Project +export type RetrieveProject200 = Project +export const RetrieveProject200 = Project +export type ModifyProjectRequestJson = ProjectUpdateRequest +export const ModifyProjectRequestJson = ProjectUpdateRequest +export type ModifyProject200 = Project +export const ModifyProject200 = Project +export type ModifyProject400 = ErrorResponse +export const ModifyProject400 = ErrorResponse +export type ListProjectApiKeysParams = { readonly "limit"?: number; readonly "after"?: string } +export const ListProjectApiKeysParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String) +}) +export type ListProjectApiKeys200 = ProjectApiKeyListResponse +export const ListProjectApiKeys200 = ProjectApiKeyListResponse +export type RetrieveProjectApiKey200 = ProjectApiKey +export const RetrieveProjectApiKey200 = ProjectApiKey +export type DeleteProjectApiKey200 = ProjectApiKeyDeleteResponse +export const DeleteProjectApiKey200 = ProjectApiKeyDeleteResponse +export type DeleteProjectApiKey400 = ErrorResponse +export const DeleteProjectApiKey400 = ErrorResponse +export type ArchiveProject200 = Project +export const ArchiveProject200 = Project +export type ListProjectCertificatesParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListProjectCertificatesParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListProjectCertificates200 = ListProjectCertificatesResponse +export const ListProjectCertificates200 = ListProjectCertificatesResponse +export type ActivateProjectCertificatesRequestJson = ToggleCertificatesRequest +export const ActivateProjectCertificatesRequestJson = ToggleCertificatesRequest +export type ActivateProjectCertificates200 = OrganizationProjectCertificateActivationResponse +export const ActivateProjectCertificates200 = OrganizationProjectCertificateActivationResponse +export type DeactivateProjectCertificatesRequestJson = ToggleCertificatesRequest +export const DeactivateProjectCertificatesRequestJson = ToggleCertificatesRequest +export type DeactivateProjectCertificates200 = OrganizationProjectCertificateDeactivationResponse +export const DeactivateProjectCertificates200 = OrganizationProjectCertificateDeactivationResponse +export type ListProjectGroupsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListProjectGroupsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(100)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListProjectGroups200 = ProjectGroupListResource +export const ListProjectGroups200 = ProjectGroupListResource +export type AddProjectGroupRequestJson = InviteProjectGroupBody +export const AddProjectGroupRequestJson = InviteProjectGroupBody +export type AddProjectGroup200 = ProjectGroup +export const AddProjectGroup200 = ProjectGroup +export type RemoveProjectGroup200 = ProjectGroupDeletedResource +export const RemoveProjectGroup200 = ProjectGroupDeletedResource +export type RetrieveProjectHostedToolPermissions200 = ProjectHostedToolPermissions +export const RetrieveProjectHostedToolPermissions200 = ProjectHostedToolPermissions +export type UpdateProjectHostedToolPermissionsRequestJson = ProjectHostedToolPermissionsUpdateRequest +export const UpdateProjectHostedToolPermissionsRequestJson = ProjectHostedToolPermissionsUpdateRequest +export type UpdateProjectHostedToolPermissions200 = ProjectHostedToolPermissions +export const UpdateProjectHostedToolPermissions200 = ProjectHostedToolPermissions +export type RetrieveProjectModelPermissions200 = ProjectModelPermissions +export const RetrieveProjectModelPermissions200 = ProjectModelPermissions +export type UpdateProjectModelPermissionsRequestJson = ProjectModelPermissionsUpdateRequest +export const UpdateProjectModelPermissionsRequestJson = ProjectModelPermissionsUpdateRequest +export type UpdateProjectModelPermissions200 = ProjectModelPermissions +export const UpdateProjectModelPermissions200 = ProjectModelPermissions +export type DeleteProjectModelPermissions200 = ProjectModelPermissionsDeleteResponse +export const DeleteProjectModelPermissions200 = ProjectModelPermissionsDeleteResponse +export type ListProjectRateLimitsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "before"?: string +} +export const ListProjectRateLimitsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String) +}) +export type ListProjectRateLimits200 = ProjectRateLimitListResponse +export const ListProjectRateLimits200 = ProjectRateLimitListResponse +export type UpdateProjectRateLimitsRequestJson = ProjectRateLimitUpdateRequest +export const UpdateProjectRateLimitsRequestJson = ProjectRateLimitUpdateRequest +export type UpdateProjectRateLimits200 = ProjectRateLimit +export const UpdateProjectRateLimits200 = ProjectRateLimit +export type UpdateProjectRateLimits400 = ErrorResponse +export const UpdateProjectRateLimits400 = ErrorResponse +export type ListProjectServiceAccountsParams = { readonly "limit"?: number; readonly "after"?: string } +export const ListProjectServiceAccountsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String) +}) +export type ListProjectServiceAccounts200 = ProjectServiceAccountListResponse +export const ListProjectServiceAccounts200 = ProjectServiceAccountListResponse +export type ListProjectServiceAccounts400 = ErrorResponse +export const ListProjectServiceAccounts400 = ErrorResponse +export type CreateProjectServiceAccountRequestJson = ProjectServiceAccountCreateRequest +export const CreateProjectServiceAccountRequestJson = ProjectServiceAccountCreateRequest +export type CreateProjectServiceAccount200 = ProjectServiceAccountCreateResponse +export const CreateProjectServiceAccount200 = ProjectServiceAccountCreateResponse +export type CreateProjectServiceAccount400 = ErrorResponse +export const CreateProjectServiceAccount400 = ErrorResponse +export type RetrieveProjectServiceAccount200 = ProjectServiceAccount +export const RetrieveProjectServiceAccount200 = ProjectServiceAccount +export type DeleteProjectServiceAccount200 = ProjectServiceAccountDeleteResponse +export const DeleteProjectServiceAccount200 = ProjectServiceAccountDeleteResponse +export type ListProjectUsersParams = { readonly "limit"?: number; readonly "after"?: string } +export const ListProjectUsersParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String) +}) +export type ListProjectUsers200 = ProjectUserListResponse +export const ListProjectUsers200 = ProjectUserListResponse +export type ListProjectUsers400 = ErrorResponse +export const ListProjectUsers400 = ErrorResponse +export type CreateProjectUserRequestJson = ProjectUserCreateRequest +export const CreateProjectUserRequestJson = ProjectUserCreateRequest +export type CreateProjectUser200 = ProjectUser +export const CreateProjectUser200 = ProjectUser +export type CreateProjectUser400 = ErrorResponse +export const CreateProjectUser400 = ErrorResponse +export type RetrieveProjectUser200 = ProjectUser +export const RetrieveProjectUser200 = ProjectUser +export type ModifyProjectUserRequestJson = ProjectUserUpdateRequest +export const ModifyProjectUserRequestJson = ProjectUserUpdateRequest +export type ModifyProjectUser200 = ProjectUser +export const ModifyProjectUser200 = ProjectUser +export type ModifyProjectUser400 = ErrorResponse +export const ModifyProjectUser400 = ErrorResponse +export type DeleteProjectUser200 = ProjectUserDeleteResponse +export const DeleteProjectUser200 = ProjectUserDeleteResponse +export type DeleteProjectUser400 = ErrorResponse +export const DeleteProjectUser400 = ErrorResponse +export type ListRolesParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListRolesParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListRoles200 = PublicRoleListResource +export const ListRoles200 = PublicRoleListResource +export type CreateRoleRequestJson = PublicCreateOrganizationRoleBody +export const CreateRoleRequestJson = PublicCreateOrganizationRoleBody +export type CreateRole200 = Role +export const CreateRole200 = Role +export type UpdateRoleRequestJson = PublicUpdateOrganizationRoleBody +export const UpdateRoleRequestJson = PublicUpdateOrganizationRoleBody +export type UpdateRole200 = Role +export const UpdateRole200 = Role +export type DeleteRole200 = RoleDeletedResource +export const DeleteRole200 = RoleDeletedResource +export type UsageAudioSpeechesParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "models"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "model"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageAudioSpeechesParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey(Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "model"]))), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageAudioSpeeches200 = UsageResponse +export const UsageAudioSpeeches200 = UsageResponse +export type UsageAudioTranscriptionsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "models"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "model"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageAudioTranscriptionsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey(Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "model"]))), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageAudioTranscriptions200 = UsageResponse +export const UsageAudioTranscriptions200 = UsageResponse +export type UsageCodeInterpreterSessionsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageCodeInterpreterSessionsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey(Schema.Array(Schema.Literal("project_id"))), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageCodeInterpreterSessions200 = UsageResponse +export const UsageCodeInterpreterSessions200 = UsageResponse +export type UsageCompletionsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "models"?: ReadonlyArray + readonly "batch"?: boolean + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "model" | "batch" | "service_tier"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageCompletionsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "batch": Schema.optionalKey(Schema.Boolean), + "group_by": Schema.optionalKey( + Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "model", "batch", "service_tier"])) + ), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageCompletions200 = UsageResponse +export const UsageCompletions200 = UsageResponse +export type UsageEmbeddingsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "models"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "model"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageEmbeddingsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey(Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "model"]))), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageEmbeddings200 = UsageResponse +export const UsageEmbeddings200 = UsageResponse +export type UsageFileSearchCallsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "vector_store_ids"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "vector_store_id"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageFileSearchCallsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "vector_store_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey( + Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "vector_store_id"])) + ), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageFileSearchCalls200 = UsageResponse +export const UsageFileSearchCalls200 = UsageResponse +export type UsageImagesParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "sources"?: ReadonlyArray<"image.generation" | "image.edit" | "image.variation"> + readonly "sizes"?: ReadonlyArray<"256x256" | "512x512" | "1024x1024" | "1792x1792" | "1024x1792"> + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "models"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "model" | "size" | "source"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageImagesParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "sources": Schema.optionalKey(Schema.Array(Schema.Literals(["image.generation", "image.edit", "image.variation"]))), + "sizes": Schema.optionalKey( + Schema.Array(Schema.Literals(["256x256", "512x512", "1024x1024", "1792x1792", "1024x1792"])) + ), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey( + Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "model", "size", "source"])) + ), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageImages200 = UsageResponse +export const UsageImages200 = UsageResponse +export type UsageModerationsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "models"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "model"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageModerationsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey(Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "model"]))), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageModerations200 = UsageResponse +export const UsageModerations200 = UsageResponse +export type UsageVectorStoresParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "group_by"?: ReadonlyArray<"project_id"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageVectorStoresParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "group_by": Schema.optionalKey(Schema.Array(Schema.Literal("project_id"))), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageVectorStores200 = UsageResponse +export const UsageVectorStores200 = UsageResponse +export type UsageWebSearchCallsParams = { + readonly "start_time": number + readonly "end_time"?: number + readonly "bucket_width"?: "1m" | "1h" | "1d" + readonly "project_ids"?: ReadonlyArray + readonly "user_ids"?: ReadonlyArray + readonly "api_key_ids"?: ReadonlyArray + readonly "models"?: ReadonlyArray + readonly "context_levels"?: ReadonlyArray<"low" | "medium" | "high"> + readonly "group_by"?: ReadonlyArray<"project_id" | "user_id" | "api_key_id" | "model" | "context_level"> + readonly "limit"?: number + readonly "page"?: string +} +export const UsageWebSearchCallsParams = Schema.Struct({ + "start_time": Schema.Number.check(Schema.isInt()), + "end_time": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "bucket_width": Schema.optionalKey(Schema.Literals(["1m", "1h", "1d"])), + "project_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "user_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "api_key_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "context_levels": Schema.optionalKey(Schema.Array(Schema.Literals(["low", "medium", "high"]))), + "group_by": Schema.optionalKey( + Schema.Array(Schema.Literals(["project_id", "user_id", "api_key_id", "model", "context_level"])) + ), + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "page": Schema.optionalKey(Schema.String) +}) +export type UsageWebSearchCalls200 = UsageResponse +export const UsageWebSearchCalls200 = UsageResponse +export type ListUsersParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "emails"?: ReadonlyArray +} +export const ListUsersParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "after": Schema.optionalKey(Schema.String), + "emails": Schema.optionalKey(Schema.Array(Schema.String)) +}) +export type ListUsers200 = UserListResponse +export const ListUsers200 = UserListResponse +export type RetrieveUser200 = User +export const RetrieveUser200 = User +export type ModifyUserRequestJson = UserRoleUpdateRequest +export const ModifyUserRequestJson = UserRoleUpdateRequest +export type ModifyUser200 = User +export const ModifyUser200 = User +export type DeleteUser200 = UserDeleteResponse +export const DeleteUser200 = UserDeleteResponse +export type ListUserRoleAssignmentsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListUserRoleAssignmentsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListUserRoleAssignments200 = RoleListResource +export const ListUserRoleAssignments200 = RoleListResource +export type AssignUserRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export const AssignUserRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export type AssignUserRole200 = UserRoleAssignment +export const AssignUserRole200 = UserRoleAssignment +export type UnassignUserRole200 = DeletedRoleAssignmentResource +export const UnassignUserRole200 = DeletedRoleAssignmentResource +export type ListProjectGroupRoleAssignmentsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListProjectGroupRoleAssignmentsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListProjectGroupRoleAssignments200 = RoleListResource +export const ListProjectGroupRoleAssignments200 = RoleListResource +export type AssignProjectGroupRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export const AssignProjectGroupRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export type AssignProjectGroupRole200 = GroupRoleAssignment +export const AssignProjectGroupRole200 = GroupRoleAssignment +export type UnassignProjectGroupRole200 = DeletedRoleAssignmentResource +export const UnassignProjectGroupRole200 = DeletedRoleAssignmentResource +export type ListProjectRolesParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListProjectRolesParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListProjectRoles200 = PublicRoleListResource +export const ListProjectRoles200 = PublicRoleListResource +export type CreateProjectRoleRequestJson = PublicCreateOrganizationRoleBody +export const CreateProjectRoleRequestJson = PublicCreateOrganizationRoleBody +export type CreateProjectRole200 = Role +export const CreateProjectRole200 = Role +export type UpdateProjectRoleRequestJson = PublicUpdateOrganizationRoleBody +export const UpdateProjectRoleRequestJson = PublicUpdateOrganizationRoleBody +export type UpdateProjectRole200 = Role +export const UpdateProjectRole200 = Role +export type DeleteProjectRole200 = RoleDeletedResource +export const DeleteProjectRole200 = RoleDeletedResource +export type ListProjectUserRoleAssignmentsParams = { + readonly "limit"?: number + readonly "after"?: string + readonly "order"?: "asc" | "desc" +} +export const ListProjectUserRoleAssignmentsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1000)) + ), + "after": Schema.optionalKey(Schema.String), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])) +}) +export type ListProjectUserRoleAssignments200 = RoleListResource +export const ListProjectUserRoleAssignments200 = RoleListResource +export type AssignProjectUserRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export const AssignProjectUserRoleRequestJson = PublicAssignOrganizationGroupRoleBody +export type AssignProjectUserRole200 = UserRoleAssignment +export const AssignProjectUserRole200 = UserRoleAssignment +export type UnassignProjectUserRole200 = DeletedRoleAssignmentResource +export const UnassignProjectUserRole200 = DeletedRoleAssignmentResource +export type CreateRealtimeCallRequestFormData = RealtimeCallCreateRequest +export const CreateRealtimeCallRequestFormData = RealtimeCallCreateRequest +export type AcceptRealtimeCallRequestJson = RealtimeSessionCreateRequestGA +export const AcceptRealtimeCallRequestJson = RealtimeSessionCreateRequestGA +export type ReferRealtimeCallRequestJson = RealtimeCallReferRequest +export const ReferRealtimeCallRequestJson = RealtimeCallReferRequest +export type RejectRealtimeCallRequestJson = RealtimeCallRejectRequest +export const RejectRealtimeCallRequestJson = RealtimeCallRejectRequest +export type CreateRealtimeClientSecretRequestJson = RealtimeCreateClientSecretRequest +export const CreateRealtimeClientSecretRequestJson = RealtimeCreateClientSecretRequest +export type CreateRealtimeClientSecret200 = RealtimeCreateClientSecretResponse +export const CreateRealtimeClientSecret200 = RealtimeCreateClientSecretResponse +export type CreateRealtimeSessionRequestJson = RealtimeSessionCreateRequest +export const CreateRealtimeSessionRequestJson = RealtimeSessionCreateRequest +export type CreateRealtimeSession200 = RealtimeSessionCreateResponse +export const CreateRealtimeSession200 = RealtimeSessionCreateResponse +export type CreateRealtimeTranscriptionSessionRequestJson = RealtimeTranscriptionSessionCreateRequest +export const CreateRealtimeTranscriptionSessionRequestJson = RealtimeTranscriptionSessionCreateRequest +export type CreateRealtimeTranscriptionSession200 = RealtimeTranscriptionSessionCreateResponse +export const CreateRealtimeTranscriptionSession200 = RealtimeTranscriptionSessionCreateResponse +export type CreateRealtimeTranslationClientSecretRequestJson = RealtimeTranslationClientSecretCreateRequest +export const CreateRealtimeTranslationClientSecretRequestJson = RealtimeTranslationClientSecretCreateRequest +export type CreateRealtimeTranslationClientSecret200 = RealtimeTranslationClientSecretCreateResponse +export const CreateRealtimeTranslationClientSecret200 = RealtimeTranslationClientSecretCreateResponse +export type CreateResponseRequestJson = CreateResponse +export const CreateResponseRequestJson = CreateResponse +export type CreateResponse200 = Response +export const CreateResponse200 = Response +export type CreateResponse200Sse = ResponseStreamEvent +export const CreateResponse200Sse = ResponseStreamEvent +export type GetResponseParams = { + readonly "include"?: ReadonlyArray + readonly "stream"?: boolean + readonly "starting_after"?: number + readonly "include_obfuscation"?: boolean +} +export const GetResponseParams = Schema.Struct({ + "include": Schema.optionalKey(Schema.Array(IncludeEnum)), + "stream": Schema.optionalKey(Schema.Boolean), + "starting_after": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "include_obfuscation": Schema.optionalKey(Schema.Boolean) +}) +export type GetResponse200 = Response +export const GetResponse200 = Response +export type DeleteResponse404 = Error +export const DeleteResponse404 = Error +export type CancelResponse200 = Response +export const CancelResponse200 = Response +export type CancelResponse404 = Error +export const CancelResponse404 = Error +export type ListInputItemsParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "include"?: ReadonlyArray +} +export const ListInputItemsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "include": Schema.optionalKey(Schema.Array(IncludeEnum)) +}) +export type ListInputItems200 = ResponseItemList +export const ListInputItems200 = ResponseItemList +export type CreateThreadRequestJson = CreateThreadRequest +export const CreateThreadRequestJson = CreateThreadRequest +export type CreateThread200 = ThreadObject +export const CreateThread200 = ThreadObject +export type CreateThreadAndRunRequestJson = CreateThreadAndRunRequest +export const CreateThreadAndRunRequestJson = CreateThreadAndRunRequest +export type CreateThreadAndRun200 = RunObject +export const CreateThreadAndRun200 = RunObject +export type GetThread200 = ThreadObject +export const GetThread200 = ThreadObject +export type ModifyThreadRequestJson = ModifyThreadRequest +export const ModifyThreadRequestJson = ModifyThreadRequest +export type ModifyThread200 = ThreadObject +export const ModifyThread200 = ThreadObject +export type DeleteThread200 = DeleteThreadResponse +export const DeleteThread200 = DeleteThreadResponse +export type ListMessagesParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "before"?: string + readonly "run_id"?: string +} +export const ListMessagesParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String), + "run_id": Schema.optionalKey(Schema.String) +}) +export type ListMessages200 = ListMessagesResponse +export const ListMessages200 = ListMessagesResponse +export type CreateMessageRequestJson = CreateMessageRequest +export const CreateMessageRequestJson = CreateMessageRequest +export type CreateMessage200 = MessageObject +export const CreateMessage200 = MessageObject +export type GetMessage200 = MessageObject +export const GetMessage200 = MessageObject +export type ModifyMessageRequestJson = ModifyMessageRequest +export const ModifyMessageRequestJson = ModifyMessageRequest +export type ModifyMessage200 = MessageObject +export const ModifyMessage200 = MessageObject +export type DeleteMessage200 = DeleteMessageResponse +export const DeleteMessage200 = DeleteMessageResponse +export type ListRunsParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "before"?: string +} +export const ListRunsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String) +}) +export type ListRuns200 = ListRunsResponse +export const ListRuns200 = ListRunsResponse +export type CreateRunParams = { + readonly "include[]"?: ReadonlyArray<"step_details.tool_calls[*].file_search.results[*].content"> +} +export const CreateRunParams = Schema.Struct({ + "include[]": Schema.optionalKey( + Schema.Array(Schema.Literal("step_details.tool_calls[*].file_search.results[*].content")) + ) +}) +export type CreateRunRequestJson = CreateRunRequest +export const CreateRunRequestJson = CreateRunRequest +export type CreateRun200 = RunObject +export const CreateRun200 = RunObject +export type GetRun200 = RunObject +export const GetRun200 = RunObject +export type ModifyRunRequestJson = ModifyRunRequest +export const ModifyRunRequestJson = ModifyRunRequest +export type ModifyRun200 = RunObject +export const ModifyRun200 = RunObject +export type CancelRun200 = RunObject +export const CancelRun200 = RunObject +export type ListRunStepsParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "before"?: string + readonly "include[]"?: ReadonlyArray<"step_details.tool_calls[*].file_search.results[*].content"> +} +export const ListRunStepsParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String), + "include[]": Schema.optionalKey( + Schema.Array(Schema.Literal("step_details.tool_calls[*].file_search.results[*].content")) + ) +}) +export type ListRunSteps200 = ListRunStepsResponse +export const ListRunSteps200 = ListRunStepsResponse +export type GetRunStepParams = { + readonly "include[]"?: ReadonlyArray<"step_details.tool_calls[*].file_search.results[*].content"> +} +export const GetRunStepParams = Schema.Struct({ + "include[]": Schema.optionalKey( + Schema.Array(Schema.Literal("step_details.tool_calls[*].file_search.results[*].content")) + ) +}) +export type GetRunStep200 = RunStepObject +export const GetRunStep200 = RunStepObject +export type SubmitToolOuputsToRunRequestJson = SubmitToolOutputsRunRequest +export const SubmitToolOuputsToRunRequestJson = SubmitToolOutputsRunRequest +export type SubmitToolOuputsToRun200 = RunObject +export const SubmitToolOuputsToRun200 = RunObject +export type CreateUploadRequestJson = CreateUploadRequest +export const CreateUploadRequestJson = CreateUploadRequest +export type CreateUpload200 = Upload +export const CreateUpload200 = Upload +export type CancelUpload200 = Upload +export const CancelUpload200 = Upload +export type CompleteUploadRequestJson = CompleteUploadRequest +export const CompleteUploadRequestJson = CompleteUploadRequest +export type CompleteUpload200 = Upload +export const CompleteUpload200 = Upload +export type AddUploadPartRequestFormData = AddUploadPartRequest +export const AddUploadPartRequestFormData = AddUploadPartRequest +export type AddUploadPart200 = UploadPart +export const AddUploadPart200 = UploadPart +export type ListVectorStoresParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "before"?: string +} +export const ListVectorStoresParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String) +}) +export type ListVectorStores200 = ListVectorStoresResponse +export const ListVectorStores200 = ListVectorStoresResponse +export type CreateVectorStoreRequestJson = CreateVectorStoreRequest +export const CreateVectorStoreRequestJson = CreateVectorStoreRequest +export type CreateVectorStore200 = VectorStoreObject +export const CreateVectorStore200 = VectorStoreObject +export type GetVectorStore200 = VectorStoreObject +export const GetVectorStore200 = VectorStoreObject +export type ModifyVectorStoreRequestJson = UpdateVectorStoreRequest +export const ModifyVectorStoreRequestJson = UpdateVectorStoreRequest +export type ModifyVectorStore200 = VectorStoreObject +export const ModifyVectorStore200 = VectorStoreObject +export type DeleteVectorStore200 = DeleteVectorStoreResponse +export const DeleteVectorStore200 = DeleteVectorStoreResponse +export type CreateVectorStoreFileBatchRequestJson = CreateVectorStoreFileBatchRequest +export const CreateVectorStoreFileBatchRequestJson = CreateVectorStoreFileBatchRequest +export type CreateVectorStoreFileBatch200 = VectorStoreFileBatchObject +export const CreateVectorStoreFileBatch200 = VectorStoreFileBatchObject +export type GetVectorStoreFileBatch200 = VectorStoreFileBatchObject +export const GetVectorStoreFileBatch200 = VectorStoreFileBatchObject +export type CancelVectorStoreFileBatch200 = VectorStoreFileBatchObject +export const CancelVectorStoreFileBatch200 = VectorStoreFileBatchObject +export type ListFilesInVectorStoreBatchParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "before"?: string + readonly "filter"?: "in_progress" | "completed" | "failed" | "cancelled" +} +export const ListFilesInVectorStoreBatchParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String), + "filter": Schema.optionalKey(Schema.Literals(["in_progress", "completed", "failed", "cancelled"])) +}) +export type ListFilesInVectorStoreBatch200 = ListVectorStoreFilesResponse +export const ListFilesInVectorStoreBatch200 = ListVectorStoreFilesResponse +export type ListVectorStoreFilesParams = { + readonly "limit"?: number + readonly "order"?: "asc" | "desc" + readonly "after"?: string + readonly "before"?: string + readonly "filter"?: "in_progress" | "completed" | "failed" | "cancelled" +} +export const ListVectorStoreFilesParams = Schema.Struct({ + "limit": Schema.optionalKey(Schema.Number.check(Schema.isInt())), + "order": Schema.optionalKey(Schema.Literals(["asc", "desc"])), + "after": Schema.optionalKey(Schema.String), + "before": Schema.optionalKey(Schema.String), + "filter": Schema.optionalKey(Schema.Literals(["in_progress", "completed", "failed", "cancelled"])) +}) +export type ListVectorStoreFiles200 = ListVectorStoreFilesResponse +export const ListVectorStoreFiles200 = ListVectorStoreFilesResponse +export type CreateVectorStoreFileRequestJson = CreateVectorStoreFileRequest +export const CreateVectorStoreFileRequestJson = CreateVectorStoreFileRequest +export type CreateVectorStoreFile200 = VectorStoreFileObject +export const CreateVectorStoreFile200 = VectorStoreFileObject +export type GetVectorStoreFile200 = VectorStoreFileObject +export const GetVectorStoreFile200 = VectorStoreFileObject +export type UpdateVectorStoreFileAttributesRequestJson = UpdateVectorStoreFileAttributesRequest +export const UpdateVectorStoreFileAttributesRequestJson = UpdateVectorStoreFileAttributesRequest +export type UpdateVectorStoreFileAttributes200 = VectorStoreFileObject +export const UpdateVectorStoreFileAttributes200 = VectorStoreFileObject +export type DeleteVectorStoreFile200 = DeleteVectorStoreFileResponse +export const DeleteVectorStoreFile200 = DeleteVectorStoreFileResponse +export type RetrieveVectorStoreFileContent200 = VectorStoreFileContentResponse +export const RetrieveVectorStoreFileContent200 = VectorStoreFileContentResponse +export type SearchVectorStoreRequestJson = VectorStoreSearchRequest +export const SearchVectorStoreRequestJson = VectorStoreSearchRequest +export type SearchVectorStore200 = VectorStoreSearchResultsPage +export const SearchVectorStore200 = VectorStoreSearchResultsPage +export type CreateConversationRequestJson = CreateConversationBody +export const CreateConversationRequestJson = CreateConversationBody +export type CreateConversation200 = ConversationResource +export const CreateConversation200 = ConversationResource +export type GetConversation200 = ConversationResource +export const GetConversation200 = ConversationResource +export type UpdateConversationRequestJson = UpdateConversationBody +export const UpdateConversationRequestJson = UpdateConversationBody +export type UpdateConversation200 = ConversationResource +export const UpdateConversation200 = ConversationResource +export type DeleteConversation200 = DeletedConversationResource +export const DeleteConversation200 = DeletedConversationResource +export type ListVideosParams = { readonly "limit"?: number; readonly "order"?: OrderEnum; readonly "after"?: string } +export const ListVideosParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(100)) + ), + "order": Schema.optionalKey(OrderEnum), + "after": Schema.optionalKey( + Schema.String.annotate({ "description": "Identifier for the last item from the previous pagination request" }) + ) +}) +export type ListVideos200 = VideoListResource +export const ListVideos200 = VideoListResource +export type CreateVideoRequestJson = CreateVideoJsonBody +export const CreateVideoRequestJson = CreateVideoJsonBody +export type CreateVideoRequestFormData = CreateVideoMultipartBody +export const CreateVideoRequestFormData = CreateVideoMultipartBody +export type CreateVideo200 = VideoResource +export const CreateVideo200 = VideoResource +export type CreateVideoCharacterRequestFormData = CreateVideoCharacterBody +export const CreateVideoCharacterRequestFormData = CreateVideoCharacterBody +export type CreateVideoCharacter200 = VideoCharacterResource +export const CreateVideoCharacter200 = VideoCharacterResource +export type GetVideoCharacter200 = VideoCharacterResource +export const GetVideoCharacter200 = VideoCharacterResource +export type CreateVideoEditRequestJson = CreateVideoEditJsonBody +export const CreateVideoEditRequestJson = CreateVideoEditJsonBody +export type CreateVideoEditRequestFormData = CreateVideoEditMultipartBody +export const CreateVideoEditRequestFormData = CreateVideoEditMultipartBody +export type CreateVideoEdit200 = VideoResource +export const CreateVideoEdit200 = VideoResource +export type CreateVideoExtendRequestJson = CreateVideoExtendJsonBody +export const CreateVideoExtendRequestJson = CreateVideoExtendJsonBody +export type CreateVideoExtendRequestFormData = CreateVideoExtendMultipartBody +export const CreateVideoExtendRequestFormData = CreateVideoExtendMultipartBody +export type CreateVideoExtend200 = VideoResource +export const CreateVideoExtend200 = VideoResource +export type GetVideo200 = VideoResource +export const GetVideo200 = VideoResource +export type DeleteVideo200 = DeletedVideoResource +export const DeleteVideo200 = DeletedVideoResource +export type RetrieveVideoContentParams = { readonly "variant"?: VideoContentVariant } +export const RetrieveVideoContentParams = Schema.Struct({ "variant": Schema.optionalKey(VideoContentVariant) }) +export type RetrieveVideoContent200 = string +export const RetrieveVideoContent200 = Schema.String +export type CreateVideoRemixRequestJson = CreateVideoRemixBody +export const CreateVideoRemixRequestJson = CreateVideoRemixBody +export type CreateVideoRemixRequestFormData = CreateVideoRemixBody +export const CreateVideoRemixRequestFormData = CreateVideoRemixBody +export type CreateVideoRemix200 = VideoResource +export const CreateVideoRemix200 = VideoResource +export type GetinputtokencountsRequestJson = TokenCountsBody +export const GetinputtokencountsRequestJson = TokenCountsBody +export type GetinputtokencountsRequestFormUrlEncoded = TokenCountsBody +export const GetinputtokencountsRequestFormUrlEncoded = TokenCountsBody +export type Getinputtokencounts200 = TokenCountsResource +export const Getinputtokencounts200 = TokenCountsResource +export type CompactconversationRequestJson = CompactResponseMethodPublicBody +export const CompactconversationRequestJson = CompactResponseMethodPublicBody +export type CompactconversationRequestFormUrlEncoded = CompactResponseMethodPublicBody +export const CompactconversationRequestFormUrlEncoded = CompactResponseMethodPublicBody +export type Compactconversation200 = CompactResource +export const Compactconversation200 = CompactResource +export type ListSkillsParams = { readonly "limit"?: number; readonly "order"?: OrderEnum; readonly "after"?: string } +export const ListSkillsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(100)) + ), + "order": Schema.optionalKey(OrderEnum), + "after": Schema.optionalKey( + Schema.String.annotate({ "description": "Identifier for the last item from the previous pagination request" }) + ) +}) +export type ListSkills200 = SkillListResource +export const ListSkills200 = SkillListResource +export type CreateSkillRequestJson = CreateSkillBody +export const CreateSkillRequestJson = CreateSkillBody +export type CreateSkillRequestFormData = CreateSkillBody +export const CreateSkillRequestFormData = CreateSkillBody +export type CreateSkill200 = SkillResource +export const CreateSkill200 = SkillResource +export type GetSkill200 = SkillResource +export const GetSkill200 = SkillResource +export type UpdateSkillDefaultVersionRequestJson = SetDefaultSkillVersionBody +export const UpdateSkillDefaultVersionRequestJson = SetDefaultSkillVersionBody +export type UpdateSkillDefaultVersionRequestFormUrlEncoded = SetDefaultSkillVersionBody +export const UpdateSkillDefaultVersionRequestFormUrlEncoded = SetDefaultSkillVersionBody +export type UpdateSkillDefaultVersion200 = SkillResource +export const UpdateSkillDefaultVersion200 = SkillResource +export type DeleteSkill200 = DeletedSkillResource +export const DeleteSkill200 = DeletedSkillResource +export type GetSkillContent200 = string +export const GetSkillContent200 = Schema.String +export type ListSkillVersionsParams = { + readonly "limit"?: number + readonly "order"?: OrderEnum + readonly "after"?: string +} +export const ListSkillVersionsParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(100)) + ), + "order": Schema.optionalKey(OrderEnum), + "after": Schema.optionalKey(Schema.String) +}) +export type ListSkillVersions200 = SkillVersionListResource +export const ListSkillVersions200 = SkillVersionListResource +export type CreateSkillVersionRequestJson = CreateSkillVersionBody +export const CreateSkillVersionRequestJson = CreateSkillVersionBody +export type CreateSkillVersionRequestFormData = CreateSkillVersionBody +export const CreateSkillVersionRequestFormData = CreateSkillVersionBody +export type CreateSkillVersion200 = SkillVersionResource +export const CreateSkillVersion200 = SkillVersionResource +export type GetSkillVersion200 = SkillVersionResource +export const GetSkillVersion200 = SkillVersionResource +export type DeleteSkillVersion200 = DeletedSkillVersionResource +export const DeleteSkillVersion200 = DeletedSkillVersionResource +export type GetSkillVersionContent200 = string +export const GetSkillVersionContent200 = Schema.String +export type CancelChatSessionMethod200 = ChatSessionResource +export const CancelChatSessionMethod200 = ChatSessionResource +export type CreateChatSessionMethodRequestJson = CreateChatSessionBody +export const CreateChatSessionMethodRequestJson = CreateChatSessionBody +export type CreateChatSessionMethod200 = ChatSessionResource +export const CreateChatSessionMethod200 = ChatSessionResource +export type ListThreadItemsMethodParams = { + readonly "limit"?: number + readonly "order"?: OrderEnum + readonly "after"?: string + readonly "before"?: string +} +export const ListThreadItemsMethodParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(100)) + ), + "order": Schema.optionalKey(OrderEnum), + "after": Schema.optionalKey( + Schema.String.annotate({ + "description": "List items created after this thread item ID. Defaults to null for the first page." + }) + ), + "before": Schema.optionalKey( + Schema.String.annotate({ + "description": "List items created before this thread item ID. Defaults to null for the newest results." + }) + ) +}) +export type ListThreadItemsMethod200 = ThreadItemListResource +export const ListThreadItemsMethod200 = ThreadItemListResource +export type GetThreadMethod200 = ThreadResource +export const GetThreadMethod200 = ThreadResource +export type DeleteThreadMethod200 = DeletedThreadResource +export const DeleteThreadMethod200 = DeletedThreadResource +export type ListThreadsMethodParams = { + readonly "limit"?: number + readonly "order"?: OrderEnum + readonly "after"?: string + readonly "before"?: string + readonly "user"?: string +} +export const ListThreadsMethodParams = Schema.Struct({ + "limit": Schema.optionalKey( + Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(100)) + ), + "order": Schema.optionalKey(OrderEnum), + "after": Schema.optionalKey( + Schema.String.annotate({ + "description": "List items created after this thread item ID. Defaults to null for the first page." + }) + ), + "before": Schema.optionalKey( + Schema.String.annotate({ + "description": "List items created before this thread item ID. Defaults to null for the newest results." + }) + ), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter threads that belong to this user identifier. Defaults to null to return all users." + }).check(Schema.isMinLength(1)).check(Schema.isMaxLength(512)) + ) +}) +export type ListThreadsMethod200 = ThreadListResource +export const ListThreadsMethod200 = ThreadListResource + +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 + } = {} +): OpenAiClient => { + 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(OpenAiClientError(tag, cause, response)) + ) + return { + httpClient, + "listAssistants": (options) => + HttpClientRequest.get(`/assistants`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListAssistants200), + orElse: unexpectedStatus + })) + ), + "createAssistant": (options) => + HttpClientRequest.post(`/assistants`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateAssistant200), + orElse: unexpectedStatus + })) + ), + "getAssistant": (assistantId, options) => + HttpClientRequest.get(`/assistants/${assistantId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetAssistant200), + orElse: unexpectedStatus + })) + ), + "modifyAssistant": (assistantId, options) => + HttpClientRequest.post(`/assistants/${assistantId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyAssistant200), + orElse: unexpectedStatus + })) + ), + "deleteAssistant": (assistantId, options) => + HttpClientRequest.delete(`/assistants/${assistantId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteAssistant200), + orElse: unexpectedStatus + })) + ), + "createSpeech": (options) => + HttpClientRequest.post(`/audio/speech`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + orElse: unexpectedStatus + })) + ), + "createSpeechSse": (options) => + HttpClientRequest.post(`/audio/speech`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateSpeech200Sse) + ), + "createSpeechStream": (options) => + HttpClientRequest.post(`/audio/speech`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + binaryRequest + ), + "createTranscription": (options) => + HttpClientRequest.post(`/audio/transcriptions`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateTranscription200), + orElse: unexpectedStatus + })) + ), + "createTranscriptionSse": (options) => + HttpClientRequest.post(`/audio/transcriptions`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + sseRequest(CreateTranscription200Sse) + ), + "createTranslation": (options) => + HttpClientRequest.post(`/audio/translations`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateTranslation200), + orElse: unexpectedStatus + })) + ), + "listVoiceConsents": (options) => + HttpClientRequest.get(`/audio/voice_consents`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListVoiceConsents200), + orElse: unexpectedStatus + })) + ), + "createVoiceConsent": (options) => + HttpClientRequest.post(`/audio/voice_consents`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVoiceConsent200), + orElse: unexpectedStatus + })) + ), + "getVoiceConsent": (consentId, options) => + HttpClientRequest.get(`/audio/voice_consents/${consentId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVoiceConsent200), + orElse: unexpectedStatus + })) + ), + "updateVoiceConsent": (consentId, options) => + HttpClientRequest.post(`/audio/voice_consents/${consentId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateVoiceConsent200), + orElse: unexpectedStatus + })) + ), + "deleteVoiceConsent": (consentId, options) => + HttpClientRequest.delete(`/audio/voice_consents/${consentId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteVoiceConsent200), + orElse: unexpectedStatus + })) + ), + "createVoice": (options) => + HttpClientRequest.post(`/audio/voices`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVoice200), + orElse: unexpectedStatus + })) + ), + "listBatches": (options) => + HttpClientRequest.get(`/batches`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListBatches200), + orElse: unexpectedStatus + })) + ), + "createBatch": (options) => + HttpClientRequest.post(`/batches`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateBatch200), + orElse: unexpectedStatus + })) + ), + "retrieveBatch": (batchId, options) => + HttpClientRequest.get(`/batches/${batchId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveBatch200), + orElse: unexpectedStatus + })) + ), + "cancelBatch": (batchId, options) => + HttpClientRequest.post(`/batches/${batchId}/cancel`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelBatch200), + orElse: unexpectedStatus + })) + ), + "listChatCompletions": (options) => + HttpClientRequest.get(`/chat/completions`).pipe( + HttpClientRequest.setUrlParams({ + "model": options?.params?.["model"] as any, + "metadata": options?.params?.["metadata"] as any, + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListChatCompletions200), + orElse: unexpectedStatus + })) + ), + "createChatCompletion": (options) => + HttpClientRequest.post(`/chat/completions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateChatCompletion200), + orElse: unexpectedStatus + })) + ), + "createChatCompletionSse": (options) => + HttpClientRequest.post(`/chat/completions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateChatCompletion200Sse) + ), + "getChatCompletion": (completionId, options) => + HttpClientRequest.get(`/chat/completions/${completionId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetChatCompletion200), + orElse: unexpectedStatus + })) + ), + "updateChatCompletion": (completionId, options) => + HttpClientRequest.post(`/chat/completions/${completionId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateChatCompletion200), + orElse: unexpectedStatus + })) + ), + "deleteChatCompletion": (completionId, options) => + HttpClientRequest.delete(`/chat/completions/${completionId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteChatCompletion200), + orElse: unexpectedStatus + })) + ), + "getChatCompletionMessages": (completionId, options) => + HttpClientRequest.get(`/chat/completions/${completionId}/messages`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetChatCompletionMessages200), + orElse: unexpectedStatus + })) + ), + "createCompletion": (options) => + HttpClientRequest.post(`/completions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateCompletion200), + orElse: unexpectedStatus + })) + ), + "ListContainers": (options) => + HttpClientRequest.get(`/containers`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "name": options?.params?.["name"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListContainers200), + orElse: unexpectedStatus + })) + ), + "CreateContainer": (options) => + HttpClientRequest.post(`/containers`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateContainer200), + orElse: unexpectedStatus + })) + ), + "RetrieveContainer": (containerId, options) => + HttpClientRequest.get(`/containers/${containerId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveContainer200), + orElse: unexpectedStatus + })) + ), + "DeleteContainer": (containerId, options) => + HttpClientRequest.delete(`/containers/${containerId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "ListContainerFiles": (containerId, options) => + HttpClientRequest.get(`/containers/${containerId}/files`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListContainerFiles200), + orElse: unexpectedStatus + })) + ), + "CreateContainerFile": (containerId, options) => + HttpClientRequest.post(`/containers/${containerId}/files`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateContainerFile200), + orElse: unexpectedStatus + })) + ), + "RetrieveContainerFile": (containerId, fileId, options) => + HttpClientRequest.get(`/containers/${containerId}/files/${fileId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveContainerFile200), + orElse: unexpectedStatus + })) + ), + "DeleteContainerFile": (containerId, fileId, options) => + HttpClientRequest.delete(`/containers/${containerId}/files/${fileId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "RetrieveContainerFileContent": (containerId, fileId, options) => + HttpClientRequest.get(`/containers/${containerId}/files/${fileId}/content`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "listConversationItems": (conversationId, options) => + HttpClientRequest.get(`/conversations/${conversationId}/items`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "include": options?.params?.["include"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListConversationItems200), + orElse: unexpectedStatus + })) + ), + "createConversationItems": (conversationId, options) => + HttpClientRequest.post(`/conversations/${conversationId}/items`).pipe( + HttpClientRequest.setUrlParams({ "include": options.params?.["include"] as any }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateConversationItems200), + orElse: unexpectedStatus + })) + ), + "getConversationItem": (conversationId, itemId, options) => + HttpClientRequest.get(`/conversations/${conversationId}/items/${itemId}`).pipe( + HttpClientRequest.setUrlParams({ "include": options?.params?.["include"] as any }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetConversationItem200), + orElse: unexpectedStatus + })) + ), + "deleteConversationItem": (conversationId, itemId, options) => + HttpClientRequest.delete(`/conversations/${conversationId}/items/${itemId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteConversationItem200), + orElse: unexpectedStatus + })) + ), + "createEmbedding": (options) => + HttpClientRequest.post(`/embeddings`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateEmbedding200), + orElse: unexpectedStatus + })) + ), + "listEvals": (options) => + HttpClientRequest.get(`/evals`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "order_by": options?.params?.["order_by"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListEvals200), + orElse: unexpectedStatus + })) + ), + "createEval": (options) => + HttpClientRequest.post(`/evals`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateEval201), + orElse: unexpectedStatus + })) + ), + "getEval": (evalId, options) => + HttpClientRequest.get(`/evals/${evalId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetEval200), + orElse: unexpectedStatus + })) + ), + "updateEval": (evalId, options) => + HttpClientRequest.post(`/evals/${evalId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateEval200), + orElse: unexpectedStatus + })) + ), + "deleteEval": (evalId, options) => + HttpClientRequest.delete(`/evals/${evalId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteEval200), + "404": decodeError("DeleteEval404", DeleteEval404), + orElse: unexpectedStatus + })) + ), + "getEvalRuns": (evalId, options) => + HttpClientRequest.get(`/evals/${evalId}/runs`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "status": options?.params?.["status"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetEvalRuns200), + orElse: unexpectedStatus + })) + ), + "createEvalRun": (evalId, options) => + HttpClientRequest.post(`/evals/${evalId}/runs`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateEvalRun201), + "400": decodeError("CreateEvalRun400", CreateEvalRun400), + orElse: unexpectedStatus + })) + ), + "getEvalRun": (evalId, runId, options) => + HttpClientRequest.get(`/evals/${evalId}/runs/${runId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetEvalRun200), + orElse: unexpectedStatus + })) + ), + "cancelEvalRun": (evalId, runId, options) => + HttpClientRequest.post(`/evals/${evalId}/runs/${runId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelEvalRun200), + orElse: unexpectedStatus + })) + ), + "deleteEvalRun": (evalId, runId, options) => + HttpClientRequest.delete(`/evals/${evalId}/runs/${runId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteEvalRun200), + "404": decodeError("DeleteEvalRun404", DeleteEvalRun404), + orElse: unexpectedStatus + })) + ), + "getEvalRunOutputItems": (evalId, runId, options) => + HttpClientRequest.get(`/evals/${evalId}/runs/${runId}/output_items`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any, + "status": options?.params?.["status"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetEvalRunOutputItems200), + orElse: unexpectedStatus + })) + ), + "getEvalRunOutputItem": (evalId, runId, outputItemId, options) => + HttpClientRequest.get(`/evals/${evalId}/runs/${runId}/output_items/${outputItemId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetEvalRunOutputItem200), + orElse: unexpectedStatus + })) + ), + "listFiles": (options) => + HttpClientRequest.get(`/files`).pipe( + HttpClientRequest.setUrlParams({ + "purpose": options?.params?.["purpose"] as any, + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFiles200), + orElse: unexpectedStatus + })) + ), + "createFile": (options) => + HttpClientRequest.post(`/files`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateFile200), + orElse: unexpectedStatus + })) + ), + "retrieveFile": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveFile200), + orElse: unexpectedStatus + })) + ), + "deleteFile": (fileId, options) => + HttpClientRequest.delete(`/files/${fileId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteFile200), + orElse: unexpectedStatus + })) + ), + "downloadFile": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}/content`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DownloadFile200), + orElse: unexpectedStatus + })) + ), + "runGrader": (options) => + HttpClientRequest.post(`/fine_tuning/alpha/graders/run`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RunGrader200), + orElse: unexpectedStatus + })) + ), + "validateGrader": (options) => + HttpClientRequest.post(`/fine_tuning/alpha/graders/validate`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ValidateGrader200), + orElse: unexpectedStatus + })) + ), + "listFineTuningCheckpointPermissions": (fineTunedModelCheckpoint, options) => + HttpClientRequest.get(`/fine_tuning/checkpoints/${fineTunedModelCheckpoint}/permissions`).pipe( + HttpClientRequest.setUrlParams({ + "project_id": options?.params?.["project_id"] as any, + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFineTuningCheckpointPermissions200), + orElse: unexpectedStatus + })) + ), + "createFineTuningCheckpointPermission": (fineTunedModelCheckpoint, options) => + HttpClientRequest.post(`/fine_tuning/checkpoints/${fineTunedModelCheckpoint}/permissions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateFineTuningCheckpointPermission200), + orElse: unexpectedStatus + })) + ), + "deleteFineTuningCheckpointPermission": (fineTunedModelCheckpoint, permissionId, options) => + HttpClientRequest.delete(`/fine_tuning/checkpoints/${fineTunedModelCheckpoint}/permissions/${permissionId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteFineTuningCheckpointPermission200), + orElse: unexpectedStatus + })) + ), + "listPaginatedFineTuningJobs": (options) => + HttpClientRequest.get(`/fine_tuning/jobs`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any, + "metadata": options?.params?.["metadata"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListPaginatedFineTuningJobs200), + orElse: unexpectedStatus + })) + ), + "createFineTuningJob": (options) => + HttpClientRequest.post(`/fine_tuning/jobs`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateFineTuningJob200), + orElse: unexpectedStatus + })) + ), + "retrieveFineTuningJob": (fineTuningJobId, options) => + HttpClientRequest.get(`/fine_tuning/jobs/${fineTuningJobId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveFineTuningJob200), + orElse: unexpectedStatus + })) + ), + "cancelFineTuningJob": (fineTuningJobId, options) => + HttpClientRequest.post(`/fine_tuning/jobs/${fineTuningJobId}/cancel`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelFineTuningJob200), + orElse: unexpectedStatus + })) + ), + "listFineTuningJobCheckpoints": (fineTuningJobId, options) => + HttpClientRequest.get(`/fine_tuning/jobs/${fineTuningJobId}/checkpoints`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFineTuningJobCheckpoints200), + orElse: unexpectedStatus + })) + ), + "listFineTuningEvents": (fineTuningJobId, options) => + HttpClientRequest.get(`/fine_tuning/jobs/${fineTuningJobId}/events`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "limit": options?.params?.["limit"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFineTuningEvents200), + orElse: unexpectedStatus + })) + ), + "pauseFineTuningJob": (fineTuningJobId, options) => + HttpClientRequest.post(`/fine_tuning/jobs/${fineTuningJobId}/pause`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(PauseFineTuningJob200), + orElse: unexpectedStatus + })) + ), + "resumeFineTuningJob": (fineTuningJobId, options) => + HttpClientRequest.post(`/fine_tuning/jobs/${fineTuningJobId}/resume`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ResumeFineTuningJob200), + orElse: unexpectedStatus + })) + ), + "createImageEdit": (options) => + HttpClientRequest.post(`/images/edits`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateImageEdit200), + orElse: unexpectedStatus + })) + ), + "createImageEditSse": (options) => + HttpClientRequest.post(`/images/edits`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + sseRequest(CreateImageEdit200Sse) + ), + "createImage": (options) => + HttpClientRequest.post(`/images/generations`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateImage200), + orElse: unexpectedStatus + })) + ), + "createImageSse": (options) => + HttpClientRequest.post(`/images/generations`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateImage200Sse) + ), + "createImageVariation": (options) => + HttpClientRequest.post(`/images/variations`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateImageVariation200), + orElse: unexpectedStatus + })) + ), + "listModels": (options) => + HttpClientRequest.get(`/models`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListModels200), + orElse: unexpectedStatus + })) + ), + "retrieveModel": (model, options) => + HttpClientRequest.get(`/models/${model}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveModel200), + orElse: unexpectedStatus + })) + ), + "deleteModel": (model, options) => + HttpClientRequest.delete(`/models/${model}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteModel200), + orElse: unexpectedStatus + })) + ), + "createModeration": (options) => + HttpClientRequest.post(`/moderations`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateModeration200), + orElse: unexpectedStatus + })) + ), + "adminApiKeysList": (options) => + HttpClientRequest.get(`/organization/admin_api_keys`).pipe( + HttpClientRequest.setUrlParams({ + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any, + "limit": options?.params?.["limit"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AdminApiKeysList200), + orElse: unexpectedStatus + })) + ), + "adminApiKeysCreate": (options) => + HttpClientRequest.post(`/organization/admin_api_keys`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AdminApiKeysCreate200), + orElse: unexpectedStatus + })) + ), + "adminApiKeysGet": (keyId, options) => + HttpClientRequest.get(`/organization/admin_api_keys/${keyId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AdminApiKeysGet200), + orElse: unexpectedStatus + })) + ), + "adminApiKeysDelete": (keyId, options) => + HttpClientRequest.delete(`/organization/admin_api_keys/${keyId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AdminApiKeysDelete200), + orElse: unexpectedStatus + })) + ), + "listAuditLogs": (options) => + HttpClientRequest.get(`/organization/audit_logs`).pipe( + HttpClientRequest.setUrlParams({ + "effective_at[gt]": options?.params?.["effective_at[gt]"] as any, + "effective_at[gte]": options?.params?.["effective_at[gte]"] as any, + "effective_at[lt]": options?.params?.["effective_at[lt]"] as any, + "effective_at[lte]": options?.params?.["effective_at[lte]"] as any, + "project_ids[]": options?.params?.["project_ids[]"] as any, + "event_types[]": options?.params?.["event_types[]"] as any, + "actor_ids[]": options?.params?.["actor_ids[]"] as any, + "actor_emails[]": options?.params?.["actor_emails[]"] as any, + "resource_ids[]": options?.params?.["resource_ids[]"] as any, + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListAuditLogs200), + orElse: unexpectedStatus + })) + ), + "listOrganizationCertificates": (options) => + HttpClientRequest.get(`/organization/certificates`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListOrganizationCertificates200), + orElse: unexpectedStatus + })) + ), + "uploadCertificate": (options) => + HttpClientRequest.post(`/organization/certificates`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UploadCertificate200), + orElse: unexpectedStatus + })) + ), + "activateOrganizationCertificates": (options) => + HttpClientRequest.post(`/organization/certificates/activate`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ActivateOrganizationCertificates200), + orElse: unexpectedStatus + })) + ), + "deactivateOrganizationCertificates": (options) => + HttpClientRequest.post(`/organization/certificates/deactivate`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeactivateOrganizationCertificates200), + orElse: unexpectedStatus + })) + ), + "getCertificate": (certificateId, options) => + HttpClientRequest.get(`/organization/certificates/${certificateId}`).pipe( + HttpClientRequest.setUrlParams({ "include": options?.params?.["include"] as any }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetCertificate200), + orElse: unexpectedStatus + })) + ), + "modifyCertificate": (certificateId, options) => + HttpClientRequest.post(`/organization/certificates/${certificateId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyCertificate200), + orElse: unexpectedStatus + })) + ), + "deleteCertificate": (certificateId, options) => + HttpClientRequest.delete(`/organization/certificates/${certificateId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteCertificate200), + orElse: unexpectedStatus + })) + ), + "usageCosts": (options) => + HttpClientRequest.get(`/organization/costs`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageCosts200), + orElse: unexpectedStatus + })) + ), + "listGroups": (options) => + HttpClientRequest.get(`/organization/groups`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGroups200), + orElse: unexpectedStatus + })) + ), + "createGroup": (options) => + HttpClientRequest.post(`/organization/groups`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateGroup200), + orElse: unexpectedStatus + })) + ), + "updateGroup": (groupId, options) => + HttpClientRequest.post(`/organization/groups/${groupId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateGroup200), + orElse: unexpectedStatus + })) + ), + "deleteGroup": (groupId, options) => + HttpClientRequest.delete(`/organization/groups/${groupId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteGroup200), + orElse: unexpectedStatus + })) + ), + "listGroupRoleAssignments": (groupId, options) => + HttpClientRequest.get(`/organization/groups/${groupId}/roles`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGroupRoleAssignments200), + orElse: unexpectedStatus + })) + ), + "assignGroupRole": (groupId, options) => + HttpClientRequest.post(`/organization/groups/${groupId}/roles`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AssignGroupRole200), + orElse: unexpectedStatus + })) + ), + "unassignGroupRole": (groupId, roleId, options) => + HttpClientRequest.delete(`/organization/groups/${groupId}/roles/${roleId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UnassignGroupRole200), + orElse: unexpectedStatus + })) + ), + "listGroupUsers": (groupId, options) => + HttpClientRequest.get(`/organization/groups/${groupId}/users`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGroupUsers200), + orElse: unexpectedStatus + })) + ), + "addGroupUser": (groupId, options) => + HttpClientRequest.post(`/organization/groups/${groupId}/users`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AddGroupUser200), + orElse: unexpectedStatus + })) + ), + "removeGroupUser": (groupId, userId, options) => + HttpClientRequest.delete(`/organization/groups/${groupId}/users/${userId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RemoveGroupUser200), + orElse: unexpectedStatus + })) + ), + "listInvites": (options) => + HttpClientRequest.get(`/organization/invites`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListInvites200), + orElse: unexpectedStatus + })) + ), + "inviteUser": (options) => + HttpClientRequest.post(`/organization/invites`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(InviteUser200), + orElse: unexpectedStatus + })) + ), + "retrieveInvite": (inviteId, options) => + HttpClientRequest.get(`/organization/invites/${inviteId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveInvite200), + orElse: unexpectedStatus + })) + ), + "deleteInvite": (inviteId, options) => + HttpClientRequest.delete(`/organization/invites/${inviteId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteInvite200), + orElse: unexpectedStatus + })) + ), + "listProjects": (options) => + HttpClientRequest.get(`/organization/projects`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "include_archived": options?.params?.["include_archived"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjects200), + orElse: unexpectedStatus + })) + ), + "createProject": (options) => + HttpClientRequest.post(`/organization/projects`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateProject200), + orElse: unexpectedStatus + })) + ), + "retrieveProject": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveProject200), + orElse: unexpectedStatus + })) + ), + "modifyProject": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyProject200), + "400": decodeError("ModifyProject400", ModifyProject400), + orElse: unexpectedStatus + })) + ), + "listProjectApiKeys": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/api_keys`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectApiKeys200), + orElse: unexpectedStatus + })) + ), + "retrieveProjectApiKey": (projectId, apiKeyId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/api_keys/${apiKeyId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveProjectApiKey200), + orElse: unexpectedStatus + })) + ), + "deleteProjectApiKey": (projectId, apiKeyId, options) => + HttpClientRequest.delete(`/organization/projects/${projectId}/api_keys/${apiKeyId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteProjectApiKey200), + "400": decodeError("DeleteProjectApiKey400", DeleteProjectApiKey400), + orElse: unexpectedStatus + })) + ), + "archiveProject": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/archive`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ArchiveProject200), + orElse: unexpectedStatus + })) + ), + "listProjectCertificates": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/certificates`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectCertificates200), + orElse: unexpectedStatus + })) + ), + "activateProjectCertificates": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/certificates/activate`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ActivateProjectCertificates200), + orElse: unexpectedStatus + })) + ), + "deactivateProjectCertificates": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/certificates/deactivate`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeactivateProjectCertificates200), + orElse: unexpectedStatus + })) + ), + "listProjectGroups": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/groups`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectGroups200), + orElse: unexpectedStatus + })) + ), + "addProjectGroup": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/groups`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AddProjectGroup200), + orElse: unexpectedStatus + })) + ), + "removeProjectGroup": (projectId, groupId, options) => + HttpClientRequest.delete(`/organization/projects/${projectId}/groups/${groupId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RemoveProjectGroup200), + orElse: unexpectedStatus + })) + ), + "retrieveProjectHostedToolPermissions": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/hosted_tool_permissions`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveProjectHostedToolPermissions200), + orElse: unexpectedStatus + })) + ), + "updateProjectHostedToolPermissions": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/hosted_tool_permissions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateProjectHostedToolPermissions200), + orElse: unexpectedStatus + })) + ), + "retrieveProjectModelPermissions": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/model_permissions`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveProjectModelPermissions200), + orElse: unexpectedStatus + })) + ), + "updateProjectModelPermissions": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/model_permissions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateProjectModelPermissions200), + orElse: unexpectedStatus + })) + ), + "deleteProjectModelPermissions": (projectId, options) => + HttpClientRequest.delete(`/organization/projects/${projectId}/model_permissions`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteProjectModelPermissions200), + orElse: unexpectedStatus + })) + ), + "listProjectRateLimits": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/rate_limits`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectRateLimits200), + orElse: unexpectedStatus + })) + ), + "updateProjectRateLimits": (projectId, rateLimitId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/rate_limits/${rateLimitId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateProjectRateLimits200), + "400": decodeError("UpdateProjectRateLimits400", UpdateProjectRateLimits400), + orElse: unexpectedStatus + })) + ), + "listProjectServiceAccounts": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/service_accounts`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectServiceAccounts200), + "400": decodeError("ListProjectServiceAccounts400", ListProjectServiceAccounts400), + orElse: unexpectedStatus + })) + ), + "createProjectServiceAccount": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/service_accounts`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateProjectServiceAccount200), + "400": decodeError("CreateProjectServiceAccount400", CreateProjectServiceAccount400), + orElse: unexpectedStatus + })) + ), + "retrieveProjectServiceAccount": (projectId, serviceAccountId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/service_accounts/${serviceAccountId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveProjectServiceAccount200), + orElse: unexpectedStatus + })) + ), + "deleteProjectServiceAccount": (projectId, serviceAccountId, options) => + HttpClientRequest.delete(`/organization/projects/${projectId}/service_accounts/${serviceAccountId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteProjectServiceAccount200), + orElse: unexpectedStatus + })) + ), + "listProjectUsers": (projectId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/users`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectUsers200), + "400": decodeError("ListProjectUsers400", ListProjectUsers400), + orElse: unexpectedStatus + })) + ), + "createProjectUser": (projectId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/users`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateProjectUser200), + "400": decodeError("CreateProjectUser400", CreateProjectUser400), + orElse: unexpectedStatus + })) + ), + "retrieveProjectUser": (projectId, userId, options) => + HttpClientRequest.get(`/organization/projects/${projectId}/users/${userId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveProjectUser200), + orElse: unexpectedStatus + })) + ), + "modifyProjectUser": (projectId, userId, options) => + HttpClientRequest.post(`/organization/projects/${projectId}/users/${userId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyProjectUser200), + "400": decodeError("ModifyProjectUser400", ModifyProjectUser400), + orElse: unexpectedStatus + })) + ), + "deleteProjectUser": (projectId, userId, options) => + HttpClientRequest.delete(`/organization/projects/${projectId}/users/${userId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteProjectUser200), + "400": decodeError("DeleteProjectUser400", DeleteProjectUser400), + orElse: unexpectedStatus + })) + ), + "listRoles": (options) => + HttpClientRequest.get(`/organization/roles`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListRoles200), + orElse: unexpectedStatus + })) + ), + "createRole": (options) => + HttpClientRequest.post(`/organization/roles`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRole200), + orElse: unexpectedStatus + })) + ), + "updateRole": (roleId, options) => + HttpClientRequest.post(`/organization/roles/${roleId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateRole200), + orElse: unexpectedStatus + })) + ), + "deleteRole": (roleId, options) => + HttpClientRequest.delete(`/organization/roles/${roleId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteRole200), + orElse: unexpectedStatus + })) + ), + "usageAudioSpeeches": (options) => + HttpClientRequest.get(`/organization/usage/audio_speeches`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "models": options.params["models"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageAudioSpeeches200), + orElse: unexpectedStatus + })) + ), + "usageAudioTranscriptions": (options) => + HttpClientRequest.get(`/organization/usage/audio_transcriptions`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "models": options.params["models"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageAudioTranscriptions200), + orElse: unexpectedStatus + })) + ), + "usageCodeInterpreterSessions": (options) => + HttpClientRequest.get(`/organization/usage/code_interpreter_sessions`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageCodeInterpreterSessions200), + orElse: unexpectedStatus + })) + ), + "usageCompletions": (options) => + HttpClientRequest.get(`/organization/usage/completions`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "models": options.params["models"] as any, + "batch": options.params["batch"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageCompletions200), + orElse: unexpectedStatus + })) + ), + "usageEmbeddings": (options) => + HttpClientRequest.get(`/organization/usage/embeddings`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "models": options.params["models"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageEmbeddings200), + orElse: unexpectedStatus + })) + ), + "usageFileSearchCalls": (options) => + HttpClientRequest.get(`/organization/usage/file_search_calls`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "vector_store_ids": options.params["vector_store_ids"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageFileSearchCalls200), + orElse: unexpectedStatus + })) + ), + "usageImages": (options) => + HttpClientRequest.get(`/organization/usage/images`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "sources": options.params["sources"] as any, + "sizes": options.params["sizes"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "models": options.params["models"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageImages200), + orElse: unexpectedStatus + })) + ), + "usageModerations": (options) => + HttpClientRequest.get(`/organization/usage/moderations`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "models": options.params["models"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageModerations200), + orElse: unexpectedStatus + })) + ), + "usageVectorStores": (options) => + HttpClientRequest.get(`/organization/usage/vector_stores`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageVectorStores200), + orElse: unexpectedStatus + })) + ), + "usageWebSearchCalls": (options) => + HttpClientRequest.get(`/organization/usage/web_search_calls`).pipe( + HttpClientRequest.setUrlParams({ + "start_time": options.params["start_time"] as any, + "end_time": options.params["end_time"] as any, + "bucket_width": options.params["bucket_width"] as any, + "project_ids": options.params["project_ids"] as any, + "user_ids": options.params["user_ids"] as any, + "api_key_ids": options.params["api_key_ids"] as any, + "models": options.params["models"] as any, + "context_levels": options.params["context_levels"] as any, + "group_by": options.params["group_by"] as any, + "limit": options.params["limit"] as any, + "page": options.params["page"] as any + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UsageWebSearchCalls200), + orElse: unexpectedStatus + })) + ), + "listUsers": (options) => + HttpClientRequest.get(`/organization/users`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "emails": options?.params?.["emails"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListUsers200), + orElse: unexpectedStatus + })) + ), + "retrieveUser": (userId, options) => + HttpClientRequest.get(`/organization/users/${userId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveUser200), + orElse: unexpectedStatus + })) + ), + "modifyUser": (userId, options) => + HttpClientRequest.post(`/organization/users/${userId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyUser200), + orElse: unexpectedStatus + })) + ), + "deleteUser": (userId, options) => + HttpClientRequest.delete(`/organization/users/${userId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteUser200), + orElse: unexpectedStatus + })) + ), + "listUserRoleAssignments": (userId, options) => + HttpClientRequest.get(`/organization/users/${userId}/roles`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListUserRoleAssignments200), + orElse: unexpectedStatus + })) + ), + "assignUserRole": (userId, options) => + HttpClientRequest.post(`/organization/users/${userId}/roles`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AssignUserRole200), + orElse: unexpectedStatus + })) + ), + "unassignUserRole": (userId, roleId, options) => + HttpClientRequest.delete(`/organization/users/${userId}/roles/${roleId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UnassignUserRole200), + orElse: unexpectedStatus + })) + ), + "listProjectGroupRoleAssignments": (projectId, groupId, options) => + HttpClientRequest.get(`/projects/${projectId}/groups/${groupId}/roles`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectGroupRoleAssignments200), + orElse: unexpectedStatus + })) + ), + "assignProjectGroupRole": (projectId, groupId, options) => + HttpClientRequest.post(`/projects/${projectId}/groups/${groupId}/roles`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AssignProjectGroupRole200), + orElse: unexpectedStatus + })) + ), + "unassignProjectGroupRole": (projectId, groupId, roleId, options) => + HttpClientRequest.delete(`/projects/${projectId}/groups/${groupId}/roles/${roleId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UnassignProjectGroupRole200), + orElse: unexpectedStatus + })) + ), + "listProjectRoles": (projectId, options) => + HttpClientRequest.get(`/projects/${projectId}/roles`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectRoles200), + orElse: unexpectedStatus + })) + ), + "createProjectRole": (projectId, options) => + HttpClientRequest.post(`/projects/${projectId}/roles`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateProjectRole200), + orElse: unexpectedStatus + })) + ), + "updateProjectRole": (projectId, roleId, options) => + HttpClientRequest.post(`/projects/${projectId}/roles/${roleId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateProjectRole200), + orElse: unexpectedStatus + })) + ), + "deleteProjectRole": (projectId, roleId, options) => + HttpClientRequest.delete(`/projects/${projectId}/roles/${roleId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteProjectRole200), + orElse: unexpectedStatus + })) + ), + "listProjectUserRoleAssignments": (projectId, userId, options) => + HttpClientRequest.get(`/projects/${projectId}/users/${userId}/roles`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "after": options?.params?.["after"] as any, + "order": options?.params?.["order"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProjectUserRoleAssignments200), + orElse: unexpectedStatus + })) + ), + "assignProjectUserRole": (projectId, userId, options) => + HttpClientRequest.post(`/projects/${projectId}/users/${userId}/roles`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AssignProjectUserRole200), + orElse: unexpectedStatus + })) + ), + "unassignProjectUserRole": (projectId, userId, roleId, options) => + HttpClientRequest.delete(`/projects/${projectId}/users/${userId}/roles/${roleId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UnassignProjectUserRole200), + orElse: unexpectedStatus + })) + ), + "createRealtimeCall": (options) => + HttpClientRequest.post(`/realtime/calls`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + orElse: unexpectedStatus + })) + ), + "acceptRealtimeCall": (callId, options) => + HttpClientRequest.post(`/realtime/calls/${callId}/accept`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "hangupRealtimeCall": (callId, options) => + HttpClientRequest.post(`/realtime/calls/${callId}/hangup`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "referRealtimeCall": (callId, options) => + HttpClientRequest.post(`/realtime/calls/${callId}/refer`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "rejectRealtimeCall": (callId, options) => + HttpClientRequest.post(`/realtime/calls/${callId}/reject`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "createRealtimeClientSecret": (options) => + HttpClientRequest.post(`/realtime/client_secrets`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRealtimeClientSecret200), + orElse: unexpectedStatus + })) + ), + "createRealtimeSession": (options) => + HttpClientRequest.post(`/realtime/sessions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRealtimeSession200), + orElse: unexpectedStatus + })) + ), + "createRealtimeTranscriptionSession": (options) => + HttpClientRequest.post(`/realtime/transcription_sessions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRealtimeTranscriptionSession200), + orElse: unexpectedStatus + })) + ), + "createRealtimeTranslationClientSecret": (options) => + HttpClientRequest.post(`/realtime/translations/client_secrets`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRealtimeTranslationClientSecret200), + orElse: unexpectedStatus + })) + ), + "createResponse": (options) => + HttpClientRequest.post(`/responses`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateResponse200), + orElse: unexpectedStatus + })) + ), + "createResponseSse": (options) => + HttpClientRequest.post(`/responses`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateResponse200Sse) + ), + "getResponse": (responseId, options) => + HttpClientRequest.get(`/responses/${responseId}`).pipe( + HttpClientRequest.setUrlParams({ + "include": options?.params?.["include"] as any, + "stream": options?.params?.["stream"] as any, + "starting_after": options?.params?.["starting_after"] as any, + "include_obfuscation": options?.params?.["include_obfuscation"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetResponse200), + orElse: unexpectedStatus + })) + ), + "deleteResponse": (responseId, options) => + HttpClientRequest.delete(`/responses/${responseId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "404": decodeError("DeleteResponse404", DeleteResponse404), + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "cancelResponse": (responseId, options) => + HttpClientRequest.post(`/responses/${responseId}/cancel`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelResponse200), + "404": decodeError("CancelResponse404", CancelResponse404), + orElse: unexpectedStatus + })) + ), + "listInputItems": (responseId, options) => + HttpClientRequest.get(`/responses/${responseId}/input_items`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "include": options?.params?.["include"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListInputItems200), + orElse: unexpectedStatus + })) + ), + "createThread": (options) => + HttpClientRequest.post(`/threads`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateThread200), + orElse: unexpectedStatus + })) + ), + "createThreadAndRun": (options) => + HttpClientRequest.post(`/threads/runs`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateThreadAndRun200), + orElse: unexpectedStatus + })) + ), + "getThread": (threadId, options) => + HttpClientRequest.get(`/threads/${threadId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetThread200), + orElse: unexpectedStatus + })) + ), + "modifyThread": (threadId, options) => + HttpClientRequest.post(`/threads/${threadId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyThread200), + orElse: unexpectedStatus + })) + ), + "deleteThread": (threadId, options) => + HttpClientRequest.delete(`/threads/${threadId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteThread200), + orElse: unexpectedStatus + })) + ), + "listMessages": (threadId, options) => + HttpClientRequest.get(`/threads/${threadId}/messages`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any, + "run_id": options?.params?.["run_id"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListMessages200), + orElse: unexpectedStatus + })) + ), + "createMessage": (threadId, options) => + HttpClientRequest.post(`/threads/${threadId}/messages`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateMessage200), + orElse: unexpectedStatus + })) + ), + "getMessage": (threadId, messageId, options) => + HttpClientRequest.get(`/threads/${threadId}/messages/${messageId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetMessage200), + orElse: unexpectedStatus + })) + ), + "modifyMessage": (threadId, messageId, options) => + HttpClientRequest.post(`/threads/${threadId}/messages/${messageId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyMessage200), + orElse: unexpectedStatus + })) + ), + "deleteMessage": (threadId, messageId, options) => + HttpClientRequest.delete(`/threads/${threadId}/messages/${messageId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteMessage200), + orElse: unexpectedStatus + })) + ), + "listRuns": (threadId, options) => + HttpClientRequest.get(`/threads/${threadId}/runs`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListRuns200), + orElse: unexpectedStatus + })) + ), + "createRun": (threadId, options) => + HttpClientRequest.post(`/threads/${threadId}/runs`).pipe( + HttpClientRequest.setUrlParams({ "include[]": options.params?.["include[]"] as any }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRun200), + orElse: unexpectedStatus + })) + ), + "getRun": (threadId, runId, options) => + HttpClientRequest.get(`/threads/${threadId}/runs/${runId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetRun200), + orElse: unexpectedStatus + })) + ), + "modifyRun": (threadId, runId, options) => + HttpClientRequest.post(`/threads/${threadId}/runs/${runId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyRun200), + orElse: unexpectedStatus + })) + ), + "cancelRun": (threadId, runId, options) => + HttpClientRequest.post(`/threads/${threadId}/runs/${runId}/cancel`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelRun200), + orElse: unexpectedStatus + })) + ), + "listRunSteps": (threadId, runId, options) => + HttpClientRequest.get(`/threads/${threadId}/runs/${runId}/steps`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any, + "include[]": options?.params?.["include[]"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListRunSteps200), + orElse: unexpectedStatus + })) + ), + "getRunStep": (threadId, runId, stepId, options) => + HttpClientRequest.get(`/threads/${threadId}/runs/${runId}/steps/${stepId}`).pipe( + HttpClientRequest.setUrlParams({ "include[]": options?.params?.["include[]"] as any }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetRunStep200), + orElse: unexpectedStatus + })) + ), + "submitToolOuputsToRun": (threadId, runId, options) => + HttpClientRequest.post(`/threads/${threadId}/runs/${runId}/submit_tool_outputs`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(SubmitToolOuputsToRun200), + orElse: unexpectedStatus + })) + ), + "createUpload": (options) => + HttpClientRequest.post(`/uploads`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateUpload200), + orElse: unexpectedStatus + })) + ), + "cancelUpload": (uploadId, options) => + HttpClientRequest.post(`/uploads/${uploadId}/cancel`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelUpload200), + orElse: unexpectedStatus + })) + ), + "completeUpload": (uploadId, options) => + HttpClientRequest.post(`/uploads/${uploadId}/complete`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CompleteUpload200), + orElse: unexpectedStatus + })) + ), + "addUploadPart": (uploadId, options) => + HttpClientRequest.post(`/uploads/${uploadId}/parts`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(AddUploadPart200), + orElse: unexpectedStatus + })) + ), + "listVectorStores": (options) => + HttpClientRequest.get(`/vector_stores`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListVectorStores200), + orElse: unexpectedStatus + })) + ), + "createVectorStore": (options) => + HttpClientRequest.post(`/vector_stores`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVectorStore200), + orElse: unexpectedStatus + })) + ), + "getVectorStore": (vectorStoreId, options) => + HttpClientRequest.get(`/vector_stores/${vectorStoreId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVectorStore200), + orElse: unexpectedStatus + })) + ), + "modifyVectorStore": (vectorStoreId, options) => + HttpClientRequest.post(`/vector_stores/${vectorStoreId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ModifyVectorStore200), + orElse: unexpectedStatus + })) + ), + "deleteVectorStore": (vectorStoreId, options) => + HttpClientRequest.delete(`/vector_stores/${vectorStoreId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteVectorStore200), + orElse: unexpectedStatus + })) + ), + "createVectorStoreFileBatch": (vectorStoreId, options) => + HttpClientRequest.post(`/vector_stores/${vectorStoreId}/file_batches`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVectorStoreFileBatch200), + orElse: unexpectedStatus + })) + ), + "getVectorStoreFileBatch": (vectorStoreId, batchId, options) => + HttpClientRequest.get(`/vector_stores/${vectorStoreId}/file_batches/${batchId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVectorStoreFileBatch200), + orElse: unexpectedStatus + })) + ), + "cancelVectorStoreFileBatch": (vectorStoreId, batchId, options) => + HttpClientRequest.post(`/vector_stores/${vectorStoreId}/file_batches/${batchId}/cancel`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelVectorStoreFileBatch200), + orElse: unexpectedStatus + })) + ), + "listFilesInVectorStoreBatch": (vectorStoreId, batchId, options) => + HttpClientRequest.get(`/vector_stores/${vectorStoreId}/file_batches/${batchId}/files`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any, + "filter": options?.params?.["filter"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFilesInVectorStoreBatch200), + orElse: unexpectedStatus + })) + ), + "listVectorStoreFiles": (vectorStoreId, options) => + HttpClientRequest.get(`/vector_stores/${vectorStoreId}/files`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any, + "filter": options?.params?.["filter"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListVectorStoreFiles200), + orElse: unexpectedStatus + })) + ), + "createVectorStoreFile": (vectorStoreId, options) => + HttpClientRequest.post(`/vector_stores/${vectorStoreId}/files`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVectorStoreFile200), + orElse: unexpectedStatus + })) + ), + "getVectorStoreFile": (vectorStoreId, fileId, options) => + HttpClientRequest.get(`/vector_stores/${vectorStoreId}/files/${fileId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVectorStoreFile200), + orElse: unexpectedStatus + })) + ), + "updateVectorStoreFileAttributes": (vectorStoreId, fileId, options) => + HttpClientRequest.post(`/vector_stores/${vectorStoreId}/files/${fileId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateVectorStoreFileAttributes200), + orElse: unexpectedStatus + })) + ), + "deleteVectorStoreFile": (vectorStoreId, fileId, options) => + HttpClientRequest.delete(`/vector_stores/${vectorStoreId}/files/${fileId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteVectorStoreFile200), + orElse: unexpectedStatus + })) + ), + "retrieveVectorStoreFileContent": (vectorStoreId, fileId, options) => + HttpClientRequest.get(`/vector_stores/${vectorStoreId}/files/${fileId}/content`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveVectorStoreFileContent200), + orElse: unexpectedStatus + })) + ), + "searchVectorStore": (vectorStoreId, options) => + HttpClientRequest.post(`/vector_stores/${vectorStoreId}/search`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(SearchVectorStore200), + orElse: unexpectedStatus + })) + ), + "createConversation": (options) => + HttpClientRequest.post(`/conversations`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateConversation200), + orElse: unexpectedStatus + })) + ), + "getConversation": (conversationId, options) => + HttpClientRequest.get(`/conversations/${conversationId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetConversation200), + orElse: unexpectedStatus + })) + ), + "updateConversation": (conversationId, options) => + HttpClientRequest.post(`/conversations/${conversationId}`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateConversation200), + orElse: unexpectedStatus + })) + ), + "deleteConversation": (conversationId, options) => + HttpClientRequest.delete(`/conversations/${conversationId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteConversation200), + orElse: unexpectedStatus + })) + ), + "ListVideos": (options) => + HttpClientRequest.get(`/videos`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListVideos200), + orElse: unexpectedStatus + })) + ), + "createVideo": (options) => + HttpClientRequest.post(`/videos`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVideo200), + orElse: unexpectedStatus + })) + ), + "CreateVideoCharacter": (options) => + HttpClientRequest.post(`/videos/characters`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVideoCharacter200), + orElse: unexpectedStatus + })) + ), + "GetVideoCharacter": (characterId, options) => + HttpClientRequest.get(`/videos/characters/${characterId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVideoCharacter200), + orElse: unexpectedStatus + })) + ), + "CreateVideoEdit": (options) => + HttpClientRequest.post(`/videos/edits`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVideoEdit200), + orElse: unexpectedStatus + })) + ), + "CreateVideoExtend": (options) => + HttpClientRequest.post(`/videos/extensions`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVideoExtend200), + orElse: unexpectedStatus + })) + ), + "GetVideo": (videoId, options) => + HttpClientRequest.get(`/videos/${videoId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVideo200), + orElse: unexpectedStatus + })) + ), + "DeleteVideo": (videoId, options) => + HttpClientRequest.delete(`/videos/${videoId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteVideo200), + orElse: unexpectedStatus + })) + ), + "RetrieveVideoContent": (videoId, options) => + HttpClientRequest.get(`/videos/${videoId}/content`).pipe( + HttpClientRequest.setUrlParams({ "variant": options?.params?.["variant"] as any }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(RetrieveVideoContent200), + orElse: unexpectedStatus + })) + ), + "CreateVideoRemix": (videoId, options) => + HttpClientRequest.post(`/videos/${videoId}/remix`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVideoRemix200), + orElse: unexpectedStatus + })) + ), + "Getinputtokencounts": (options) => + HttpClientRequest.post(`/responses/input_tokens`).pipe( + HttpClientRequest.bodyUrlParams(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(Getinputtokencounts200), + orElse: unexpectedStatus + })) + ), + "Compactconversation": (options) => + HttpClientRequest.post(`/responses/compact`).pipe( + HttpClientRequest.bodyUrlParams(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(Compactconversation200), + orElse: unexpectedStatus + })) + ), + "ListSkills": (options) => + HttpClientRequest.get(`/skills`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListSkills200), + orElse: unexpectedStatus + })) + ), + "CreateSkill": (options) => + HttpClientRequest.post(`/skills`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateSkill200), + orElse: unexpectedStatus + })) + ), + "GetSkill": (skillId, options) => + HttpClientRequest.get(`/skills/${skillId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetSkill200), + orElse: unexpectedStatus + })) + ), + "UpdateSkillDefaultVersion": (skillId, options) => + HttpClientRequest.post(`/skills/${skillId}`).pipe( + HttpClientRequest.bodyUrlParams(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateSkillDefaultVersion200), + orElse: unexpectedStatus + })) + ), + "DeleteSkill": (skillId, options) => + HttpClientRequest.delete(`/skills/${skillId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteSkill200), + orElse: unexpectedStatus + })) + ), + "GetSkillContent": (skillId, options) => + HttpClientRequest.get(`/skills/${skillId}/content`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetSkillContent200), + orElse: unexpectedStatus + })) + ), + "ListSkillVersions": (skillId, options) => + HttpClientRequest.get(`/skills/${skillId}/versions`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListSkillVersions200), + orElse: unexpectedStatus + })) + ), + "CreateSkillVersion": (skillId, options) => + HttpClientRequest.post(`/skills/${skillId}/versions`).pipe( + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateSkillVersion200), + orElse: unexpectedStatus + })) + ), + "GetSkillVersion": (skillId, version, options) => + HttpClientRequest.get(`/skills/${skillId}/versions/${version}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetSkillVersion200), + orElse: unexpectedStatus + })) + ), + "DeleteSkillVersion": (skillId, version, options) => + HttpClientRequest.delete(`/skills/${skillId}/versions/${version}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteSkillVersion200), + orElse: unexpectedStatus + })) + ), + "GetSkillVersionContent": (skillId, version, options) => + HttpClientRequest.get(`/skills/${skillId}/versions/${version}/content`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetSkillVersionContent200), + orElse: unexpectedStatus + })) + ), + "CancelChatSessionMethod": (sessionId, options) => + HttpClientRequest.post(`/chatkit/sessions/${sessionId}/cancel`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CancelChatSessionMethod200), + orElse: unexpectedStatus + })) + ), + "CreateChatSessionMethod": (options) => + HttpClientRequest.post(`/chatkit/sessions`).pipe( + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateChatSessionMethod200), + orElse: unexpectedStatus + })) + ), + "ListThreadItemsMethod": (threadId, options) => + HttpClientRequest.get(`/chatkit/threads/${threadId}/items`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListThreadItemsMethod200), + orElse: unexpectedStatus + })) + ), + "GetThreadMethod": (threadId, options) => + HttpClientRequest.get(`/chatkit/threads/${threadId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetThreadMethod200), + orElse: unexpectedStatus + })) + ), + "DeleteThreadMethod": (threadId, options) => + HttpClientRequest.delete(`/chatkit/threads/${threadId}`).pipe( + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteThreadMethod200), + orElse: unexpectedStatus + })) + ), + "ListThreadsMethod": (options) => + HttpClientRequest.get(`/chatkit/threads`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "order": options?.params?.["order"] as any, + "after": options?.params?.["after"] as any, + "before": options?.params?.["before"] as any, + "user": options?.params?.["user"] as any + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListThreadsMethod200), + orElse: unexpectedStatus + })) + ) + } +} + +export interface OpenAiClient { + readonly httpClient: HttpClient.HttpClient + /** + * Returns a list of assistants. + */ + readonly "listAssistants": ( + options: + | { readonly params?: typeof ListAssistantsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create an assistant with a model and instructions. + */ + readonly "createAssistant": ( + options: { readonly payload: typeof CreateAssistantRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves an assistant. + */ + readonly "getAssistant": ( + assistantId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modifies an assistant. + */ + readonly "modifyAssistant": ( + assistantId: string, + options: { readonly payload: typeof ModifyAssistantRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete an assistant. + */ + readonly "deleteAssistant": ( + assistantId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Generates audio from the input text. + * + * Returns the audio file content, or a stream of audio events. + */ + readonly "createSpeech": ( + options: { readonly payload: typeof CreateSpeechRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Generates audio from the input text. + * + * Returns the audio file content, or a stream of audio events. + */ + readonly "createSpeechSse": ( + 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 | Sse.SseError, + typeof CreateSpeech200Sse.DecodingServices + > + /** + * Generates audio from the input text. + * + * Returns the audio file content, or a stream of audio events. + */ + readonly "createSpeechStream": ( + options: { readonly payload: typeof CreateSpeechRequestJson.Encoded } + ) => Stream.Stream + /** + * Transcribes audio into the input language. + * + * Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + * format, or a stream of transcript events. + */ + readonly "createTranscription": ( + options: { + readonly payload: typeof CreateTranscriptionRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Transcribes audio into the input language. + * + * Returns a transcription object in `json`, `diarized_json`, or `verbose_json` + * format, or a stream of transcript events. + */ + readonly "createTranscriptionSse": ( + 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 | Sse.SseError, + typeof CreateTranscription200Sse.DecodingServices + > + /** + * Translates audio into English. + */ + readonly "createTranslation": ( + options: { readonly payload: typeof CreateTranslationRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List consent recordings available to your organization for creating custom voices. + * + * See the [custom voices guide](/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers. + */ + readonly "listVoiceConsents": ( + options: { + readonly params?: typeof ListVoiceConsentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Upload a consent recording that authorizes creation of a custom voice. + * + * See the [custom voices guide](/docs/guides/text-to-speech#custom-voices) for requirements and best practices. Custom voices are limited to eligible customers. + */ + readonly "createVoiceConsent": ( + options: { + readonly payload: typeof CreateVoiceConsentRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieve consent recording metadata used for creating custom voices. + * + * See the [custom voices guide](/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers. + */ + readonly "getVoiceConsent": ( + consentId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Update consent recording metadata used for creating custom voices. This endpoint updates metadata only and does not replace the underlying audio. + * + * See the [custom voices guide](/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers. + */ + readonly "updateVoiceConsent": ( + consentId: string, + options: { readonly payload: typeof UpdateVoiceConsentRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a consent recording that was uploaded for creating custom voices. + * + * See the [custom voices guide](/docs/guides/text-to-speech#custom-voices). Custom voices are limited to eligible customers. + */ + readonly "deleteVoiceConsent": ( + consentId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a custom voice you can use for audio output (for example, in Text-to-Speech and the Realtime API). This requires an audio sample and a previously uploaded consent recording. + * + * See the [custom voices guide](/docs/guides/text-to-speech#custom-voices) for requirements and best practices. Custom voices are limited to eligible customers. + */ + readonly "createVoice": ( + options: { readonly payload: typeof CreateVoiceRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List your organization's batches. + */ + readonly "listBatches": ( + options: + | { readonly params?: typeof ListBatchesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates and executes a batch from an uploaded file of requests + */ + readonly "createBatch": ( + options: { readonly payload: typeof CreateBatchRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a batch. + */ + readonly "retrieveBatch": ( + batchId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Cancels an in-progress batch. The batch will be in status `cancelling` for up to 10 minutes, before changing to `cancelled`, where it will have partial results (if any) available in the output file. + */ + readonly "cancelBatch": ( + batchId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List stored Chat Completions. Only Chat Completions that have been stored + * with the `store` parameter set to `true` will be returned. + */ + readonly "listChatCompletions": ( + options: { + readonly params?: typeof ListChatCompletionsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * **Starting a new project?** We recommend trying [Responses](/docs/api-reference/responses) + * to take advantage of the latest OpenAI platform features. Compare + * [Chat Completions with Responses](/docs/guides/responses-vs-chat-completions?api-mode=responses). + * + * --- + * + * Creates a model response for the given chat conversation. Learn more in the + * [text generation](/docs/guides/text-generation), [vision](/docs/guides/vision), + * and [audio](/docs/guides/audio) guides. + * + * Parameter support can differ depending on the model used to generate the + * response, particularly for newer reasoning models. Parameters that are only + * supported for reasoning models are noted below. For the current state of + * unsupported parameters in reasoning models, + * [refer to the reasoning guide](/docs/guides/reasoning). + * + * Returns a chat completion object, or a streamed sequence of chat completion + * chunk objects if the request is streamed. + */ + readonly "createChatCompletion": ( + options: { readonly payload: typeof CreateChatCompletionRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * **Starting a new project?** We recommend trying [Responses](/docs/api-reference/responses) + * to take advantage of the latest OpenAI platform features. Compare + * [Chat Completions with Responses](/docs/guides/responses-vs-chat-completions?api-mode=responses). + * + * --- + * + * Creates a model response for the given chat conversation. Learn more in the + * [text generation](/docs/guides/text-generation), [vision](/docs/guides/vision), + * and [audio](/docs/guides/audio) guides. + * + * Parameter support can differ depending on the model used to generate the + * response, particularly for newer reasoning models. Parameters that are only + * supported for reasoning models are noted below. For the current state of + * unsupported parameters in reasoning models, + * [refer to the reasoning guide](/docs/guides/reasoning). + * + * Returns a chat completion object, or a streamed sequence of chat completion + * chunk objects if the request is streamed. + */ + readonly "createChatCompletionSse": ( + 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 | Sse.SseError, + typeof CreateChatCompletion200Sse.DecodingServices + > + /** + * Get a stored chat completion. Only Chat Completions that have been created + * with the `store` parameter set to `true` will be returned. + */ + readonly "getChatCompletion": ( + completionId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modify a stored chat completion. Only Chat Completions that have been + * created with the `store` parameter set to `true` can be modified. Currently, + * the only supported modification is to update the `metadata` field. + */ + readonly "updateChatCompletion": ( + completionId: string, + options: { readonly payload: typeof UpdateChatCompletionRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a stored chat completion. Only Chat Completions that have been + * created with the `store` parameter set to `true` can be deleted. + */ + readonly "deleteChatCompletion": ( + completionId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get the messages in a stored chat completion. Only Chat Completions that + * have been created with the `store` parameter set to `true` will be + * returned. + */ + readonly "getChatCompletionMessages": ( + completionId: string, + options: { + readonly params?: typeof GetChatCompletionMessagesParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a completion for the provided prompt and parameters. + * + * Returns a completion object, or a sequence of completion objects if the request is streamed. + */ + readonly "createCompletion": ( + options: { readonly payload: typeof CreateCompletionRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists containers. + */ + readonly "ListContainers": ( + options: + | { readonly params?: typeof ListContainersParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a container. + */ + readonly "CreateContainer": ( + options: { readonly payload: typeof CreateContainerRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a container. + */ + readonly "RetrieveContainer": ( + containerId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a container. + */ + readonly "DeleteContainer": ( + containerId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Lists container files. + */ + readonly "ListContainerFiles": ( + containerId: string, + options: { + readonly params?: typeof ListContainerFilesParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a container file. + */ + readonly "CreateContainerFile": ( + containerId: string, + options: { + readonly payload: typeof CreateContainerFileRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a container file. + */ + readonly "RetrieveContainerFile": ( + containerId: string, + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a container file. + */ + readonly "DeleteContainerFile": ( + containerId: string, + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Retrieves a container file content. + */ + readonly "RetrieveContainerFileContent": ( + containerId: string, + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * List all items for a conversation with the given ID. + */ + readonly "listConversationItems": ( + conversationId: string, + options: { + readonly params?: typeof ListConversationItemsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create items in a conversation with the given ID. + */ + readonly "createConversationItems": ( + conversationId: string, + options: { + readonly params?: typeof CreateConversationItemsParams.Encoded | undefined + readonly payload: typeof CreateConversationItemsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get a single item from a conversation with the given IDs. + */ + readonly "getConversationItem": ( + conversationId: string, + itemId: string, + options: { + readonly params?: typeof GetConversationItemParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete an item from a conversation with the given IDs. + */ + readonly "deleteConversationItem": ( + conversationId: string, + itemId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates an embedding vector representing the input text. + */ + readonly "createEmbedding": ( + options: { readonly payload: typeof CreateEmbeddingRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List evaluations for a project. + */ + readonly "listEvals": ( + options: + | { readonly params?: typeof ListEvalsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create the structure of an evaluation that can be used to test a model's performance. + * An evaluation is a set of testing criteria and the config for a data source, which dictates the schema of the data used in the evaluation. After creating an evaluation, you can run it on different models and model parameters. We support several types of graders and datasources. + * For more information, see the [Evals guide](/docs/guides/evals). + */ + readonly "createEval": ( + options: { readonly payload: typeof CreateEvalRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get an evaluation by ID. + */ + readonly "getEval": ( + evalId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Update certain properties of an evaluation. + */ + readonly "updateEval": ( + evalId: string, + options: { readonly payload: typeof UpdateEvalRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete an evaluation. + */ + readonly "deleteEval": ( + evalId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | OpenAiClientError<"DeleteEval404", typeof DeleteEval404.Type> + > + /** + * Get a list of runs for an evaluation. + */ + readonly "getEvalRuns": ( + evalId: string, + options: + | { readonly params?: typeof GetEvalRunsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Kicks off a new run for a given evaluation, specifying the data source, and what model configuration to use to test. The datasource will be validated against the schema specified in the config of the evaluation. + */ + readonly "createEvalRun": ( + evalId: string, + options: { readonly payload: typeof CreateEvalRunRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | OpenAiClientError<"CreateEvalRun400", typeof CreateEvalRun400.Type> + > + /** + * Get an evaluation run by ID. + */ + readonly "getEvalRun": ( + evalId: string, + runId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Cancel an ongoing evaluation run. + */ + readonly "cancelEvalRun": ( + evalId: string, + runId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete an eval run. + */ + readonly "deleteEvalRun": ( + evalId: string, + runId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | OpenAiClientError<"DeleteEvalRun404", typeof DeleteEvalRun404.Type> + > + /** + * Get a list of output items for an evaluation run. + */ + readonly "getEvalRunOutputItems": ( + evalId: string, + runId: string, + options: { + readonly params?: typeof GetEvalRunOutputItemsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get an evaluation run output item by ID. + */ + readonly "getEvalRunOutputItem": ( + evalId: string, + runId: string, + outputItemId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of files. + */ + readonly "listFiles": ( + options: + | { readonly params?: typeof ListFilesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Upload a file that can be used across various endpoints. Individual files + * can be up to 512 MB, and each project can store up to 2.5 TB of files in + * total. There is no organization-wide storage limit. Uploads to this + * endpoint are rate-limited to 1,000 requests per minute per authenticated + * user. + * + * - The Assistants API supports files up to 2 million tokens and of specific + * file types. See the [Assistants Tools guide](/docs/assistants/tools) for + * details. + * - The Fine-tuning API only supports `.jsonl` files. The input also has + * certain required formats for fine-tuning + * [chat](/docs/api-reference/fine-tuning/chat-input) or + * [completions](/docs/api-reference/fine-tuning/completions-input) models. + * - The Batch API only supports `.jsonl` files up to 200 MB in size. The input + * also has a specific required + * [format](/docs/api-reference/batch/request-input). + * - For Retrieval or `file_search` ingestion, upload files here first. If + * you need to attach multiple uploaded files to the same vector store, use + * [`/vector_stores/{vector_store_id}/file_batches`](/docs/api-reference/vector-stores-file-batches/createBatch) + * instead of attaching them one by one. Vector store attachment has separate + * limits from file upload, including 2,000 attached files per minute per + * organization. + * + * Please [contact us](https://help.openai.com/) if you need to increase these + * storage limits. + */ + readonly "createFile": ( + options: { readonly payload: typeof CreateFileRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns information about a specific file. + */ + readonly "retrieveFile": ( + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a file and remove it from all vector stores. + */ + readonly "deleteFile": ( + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns the contents of the specified file. + */ + readonly "downloadFile": ( + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Run a grader. + */ + readonly "runGrader": ( + options: { readonly payload: typeof RunGraderRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Validate a grader. + */ + readonly "validateGrader": ( + options: { readonly payload: typeof ValidateGraderRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + * + * Organization owners can use this endpoint to view all permissions for a fine-tuned model checkpoint. + */ + readonly "listFineTuningCheckpointPermissions": ( + fineTunedModelCheckpoint: string, + options: { + readonly params?: typeof ListFineTuningCheckpointPermissionsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * **NOTE:** Calling this endpoint requires an [admin API key](../admin-api-keys). + * + * This enables organization owners to share fine-tuned models with other projects in their organization. + */ + readonly "createFineTuningCheckpointPermission": ( + fineTunedModelCheckpoint: string, + options: { + readonly payload: typeof CreateFineTuningCheckpointPermissionRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * **NOTE:** This endpoint requires an [admin API key](../admin-api-keys). + * + * Organization owners can use this endpoint to delete a permission for a fine-tuned model checkpoint. + */ + readonly "deleteFineTuningCheckpointPermission": ( + fineTunedModelCheckpoint: string, + permissionId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List your organization's fine-tuning jobs + */ + readonly "listPaginatedFineTuningJobs": ( + options: { + readonly params?: typeof ListPaginatedFineTuningJobsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a fine-tuning job which begins the process of creating a new model from a given dataset. + * + * Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. + * + * [Learn more about fine-tuning](/docs/guides/model-optimization) + */ + readonly "createFineTuningJob": ( + options: { readonly payload: typeof CreateFineTuningJobRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get info about a fine-tuning job. + * + * [Learn more about fine-tuning](/docs/guides/model-optimization) + */ + readonly "retrieveFineTuningJob": ( + fineTuningJobId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Immediately cancel a fine-tune job. + */ + readonly "cancelFineTuningJob": ( + fineTuningJobId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List checkpoints for a fine-tuning job. + */ + readonly "listFineTuningJobCheckpoints": ( + fineTuningJobId: string, + options: { + readonly params?: typeof ListFineTuningJobCheckpointsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get status updates for a fine-tuning job. + */ + readonly "listFineTuningEvents": ( + fineTuningJobId: string, + options: { + readonly params?: typeof ListFineTuningEventsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Pause a fine-tune job. + */ + readonly "pauseFineTuningJob": ( + fineTuningJobId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Resume a fine-tune job. + */ + readonly "resumeFineTuningJob": ( + fineTuningJobId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * You can call this endpoint with either: + * + * - `multipart/form-data`: use binary uploads via `image` (and optional `mask`). + * - `application/json`: use `images` (and optional `mask`) as references with either `image_url` or `file_id`. + * + * Note that JSON requests use `images` (array) instead of the multipart `image` field. + */ + readonly "createImageEdit": ( + options: { readonly payload: typeof CreateImageEditRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * You can call this endpoint with either: + * + * - `multipart/form-data`: use binary uploads via `image` (and optional `mask`). + * - `application/json`: use `images` (and optional `mask`) as references with either `image_url` or `file_id`. + * + * Note that JSON requests use `images` (array) instead of the multipart `image` field. + */ + readonly "createImageEditSse": ( + 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 | Sse.SseError, + typeof CreateImageEdit200Sse.DecodingServices + > + /** + * Creates an image given a prompt. [Learn more](/docs/guides/images). + */ + readonly "createImage": ( + options: { readonly payload: typeof CreateImageRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates an image given a prompt. [Learn more](/docs/guides/images). + */ + readonly "createImageSse": ( + 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 | Sse.SseError, + typeof CreateImage200Sse.DecodingServices + > + /** + * Creates a variation of a given image. This endpoint only supports `dall-e-2`. + */ + readonly "createImageVariation": ( + options: { + readonly payload: typeof CreateImageVariationRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the currently available models, and provides basic information about each one such as the owner and availability. + */ + readonly "listModels": ( + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a model instance, providing basic information about the model such as the owner and permissioning. + */ + readonly "retrieveModel": ( + model: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a fine-tuned model. You must have the Owner role in your organization to delete a model. + */ + readonly "deleteModel": ( + model: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Classifies if text and/or image inputs are potentially harmful. Learn + * more in the [moderation guide](/docs/guides/moderation). + */ + readonly "createModeration": ( + options: { readonly payload: typeof CreateModerationRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieve a paginated list of organization admin API keys. + */ + readonly "adminApiKeysList": ( + options: { + readonly params?: typeof AdminApiKeysListParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a new admin-level API key for the organization. + */ + readonly "adminApiKeysCreate": ( + options: { readonly payload: typeof AdminApiKeysCreateRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get details for a specific organization API key by its ID. + */ + readonly "adminApiKeysGet": ( + keyId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete the specified admin API key. + */ + readonly "adminApiKeysDelete": ( + keyId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List user actions and configuration changes within this organization. + */ + readonly "listAuditLogs": ( + options: + | { readonly params?: typeof ListAuditLogsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List uploaded certificates for this organization. + */ + readonly "listOrganizationCertificates": ( + options: { + readonly params?: typeof ListOrganizationCertificatesParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Upload a certificate to the organization. This does **not** automatically activate the certificate. + * + * Organizations can upload up to 50 certificates. + */ + readonly "uploadCertificate": ( + options: { readonly payload: typeof UploadCertificateRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Activate certificates at the organization level. + * + * You can atomically and idempotently activate up to 10 certificates at a time. + */ + readonly "activateOrganizationCertificates": ( + options: { + readonly payload: typeof ActivateOrganizationCertificatesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deactivate certificates at the organization level. + * + * You can atomically and idempotently deactivate up to 10 certificates at a time. + */ + readonly "deactivateOrganizationCertificates": ( + options: { + readonly payload: typeof DeactivateOrganizationCertificatesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get a certificate that has been uploaded to the organization. + * + * You can get a certificate regardless of whether it is active or not. + */ + readonly "getCertificate": ( + certificateId: string, + options: + | { readonly params?: typeof GetCertificateParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modify a certificate. Note that only the name can be modified. + */ + readonly "modifyCertificate": ( + certificateId: string, + options: { readonly payload: typeof ModifyCertificateRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a certificate from the organization. + * + * The certificate must be inactive for the organization and all projects. + */ + readonly "deleteCertificate": ( + certificateId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get costs details for the organization. + */ + readonly "usageCosts": ( + options: { readonly params: typeof UsageCostsParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists all groups in the organization. + */ + readonly "listGroups": ( + options: + | { readonly params?: typeof ListGroupsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a new group in the organization. + */ + readonly "createGroup": ( + options: { readonly payload: typeof CreateGroupRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Updates a group's information. + */ + readonly "updateGroup": ( + groupId: string, + options: { readonly payload: typeof UpdateGroupRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes a group from the organization. + */ + readonly "deleteGroup": ( + groupId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the organization roles assigned to a group within the organization. + */ + readonly "listGroupRoleAssignments": ( + groupId: string, + options: { + readonly params?: typeof ListGroupRoleAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Assigns an organization role to a group within the organization. + */ + readonly "assignGroupRole": ( + groupId: string, + options: { readonly payload: typeof AssignGroupRoleRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Unassigns an organization role from a group within the organization. + */ + readonly "unassignGroupRole": ( + groupId: string, + roleId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the users assigned to a group. + */ + readonly "listGroupUsers": ( + groupId: string, + options: + | { readonly params?: typeof ListGroupUsersParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Adds a user to a group. + */ + readonly "addGroupUser": ( + groupId: string, + options: { readonly payload: typeof AddGroupUserRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Removes a user from a group. + */ + readonly "removeGroupUser": ( + groupId: string, + userId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of invites in the organization. + */ + readonly "listInvites": ( + options: + | { readonly params?: typeof ListInvitesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create an invite for a user to the organization. The invite must be accepted by the user before they have access to the organization. + */ + readonly "inviteUser": ( + options: { readonly payload: typeof InviteUserRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves an invite. + */ + readonly "retrieveInvite": ( + inviteId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete an invite. If the invite has already been accepted, it cannot be deleted. + */ + readonly "deleteInvite": ( + inviteId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of projects. + */ + readonly "listProjects": ( + options: + | { readonly params?: typeof ListProjectsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a new project in the organization. Projects can be created and archived, but cannot be deleted. + */ + readonly "createProject": ( + options: { readonly payload: typeof CreateProjectRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a project. + */ + readonly "retrieveProject": ( + projectId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modifies a project in the organization. + */ + readonly "modifyProject": ( + projectId: string, + options: { readonly payload: typeof ModifyProjectRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError | OpenAiClientError<"ModifyProject400", typeof ModifyProject400.Type> + > + /** + * Returns a list of API keys in the project. + */ + readonly "listProjectApiKeys": ( + projectId: string, + options: { + readonly params?: typeof ListProjectApiKeysParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves an API key in the project. + */ + readonly "retrieveProjectApiKey": ( + projectId: string, + apiKeyId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes an API key from the project. + * + * Returns confirmation of the key deletion, or an error if the key belonged to + * a service account. + */ + readonly "deleteProjectApiKey": ( + projectId: string, + apiKeyId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"DeleteProjectApiKey400", typeof DeleteProjectApiKey400.Type> + > + /** + * Archives a project in the organization. Archived projects cannot be used or updated. + */ + readonly "archiveProject": ( + projectId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List certificates for this project. + */ + readonly "listProjectCertificates": ( + projectId: string, + options: { + readonly params?: typeof ListProjectCertificatesParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Activate certificates at the project level. + * + * You can atomically and idempotently activate up to 10 certificates at a time. + */ + readonly "activateProjectCertificates": ( + projectId: string, + options: { + readonly payload: typeof ActivateProjectCertificatesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deactivate certificates at the project level. You can atomically and + * idempotently deactivate up to 10 certificates at a time. + */ + readonly "deactivateProjectCertificates": ( + projectId: string, + options: { + readonly payload: typeof DeactivateProjectCertificatesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the groups that have access to a project. + */ + readonly "listProjectGroups": ( + projectId: string, + options: { + readonly params?: typeof ListProjectGroupsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Grants a group access to a project. + */ + readonly "addProjectGroup": ( + projectId: string, + options: { readonly payload: typeof AddProjectGroupRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Revokes a group's access to a project. + */ + readonly "removeProjectGroup": ( + projectId: string, + groupId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns hosted tool permissions for a project. + */ + readonly "retrieveProjectHostedToolPermissions": ( + projectId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Updates hosted tool permissions for a project. + */ + readonly "updateProjectHostedToolPermissions": ( + projectId: string, + options: { + readonly payload: typeof UpdateProjectHostedToolPermissionsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns model permissions for a project. + */ + readonly "retrieveProjectModelPermissions": ( + projectId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Updates model permissions for a project. + */ + readonly "updateProjectModelPermissions": ( + projectId: string, + options: { + readonly payload: typeof UpdateProjectModelPermissionsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes model permissions for a project. + */ + readonly "deleteProjectModelPermissions": ( + projectId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns the rate limits per model for a project. + */ + readonly "listProjectRateLimits": ( + projectId: string, + options: { + readonly params?: typeof ListProjectRateLimitsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Updates a project rate limit. + */ + readonly "updateProjectRateLimits": ( + projectId: string, + rateLimitId: string, + options: { + readonly payload: typeof UpdateProjectRateLimitsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"UpdateProjectRateLimits400", typeof UpdateProjectRateLimits400.Type> + > + /** + * Returns a list of service accounts in the project. + */ + readonly "listProjectServiceAccounts": ( + projectId: string, + options: { + readonly params?: typeof ListProjectServiceAccountsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"ListProjectServiceAccounts400", typeof ListProjectServiceAccounts400.Type> + > + /** + * Creates a new service account in the project. This also returns an unredacted API key for the service account. + */ + readonly "createProjectServiceAccount": ( + projectId: string, + options: { + readonly payload: typeof CreateProjectServiceAccountRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"CreateProjectServiceAccount400", typeof CreateProjectServiceAccount400.Type> + > + /** + * Retrieves a service account in the project. + */ + readonly "retrieveProjectServiceAccount": ( + projectId: string, + serviceAccountId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes a service account from the project. + * + * Returns confirmation of service account deletion, or an error if the project + * is archived (archived projects have no service accounts). + */ + readonly "deleteProjectServiceAccount": ( + projectId: string, + serviceAccountId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of users in the project. + */ + readonly "listProjectUsers": ( + projectId: string, + options: { + readonly params?: typeof ListProjectUsersParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"ListProjectUsers400", typeof ListProjectUsers400.Type> + > + /** + * Adds a user to the project. Users must already be members of the organization to be added to a project. + */ + readonly "createProjectUser": ( + projectId: string, + options: { readonly payload: typeof CreateProjectUserRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"CreateProjectUser400", typeof CreateProjectUser400.Type> + > + /** + * Retrieves a user in the project. + */ + readonly "retrieveProjectUser": ( + projectId: string, + userId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modifies a user's role in the project. + */ + readonly "modifyProjectUser": ( + projectId: string, + userId: string, + options: { readonly payload: typeof ModifyProjectUserRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"ModifyProjectUser400", typeof ModifyProjectUser400.Type> + > + /** + * Deletes a user from the project. + * + * Returns confirmation of project user deletion, or an error if the project is + * archived (archived projects have no users). + */ + readonly "deleteProjectUser": ( + projectId: string, + userId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"DeleteProjectUser400", typeof DeleteProjectUser400.Type> + > + /** + * Lists the roles configured for the organization. + */ + readonly "listRoles": ( + options: + | { readonly params?: typeof ListRolesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a custom role for the organization. + */ + readonly "createRole": ( + options: { readonly payload: typeof CreateRoleRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Updates an existing organization role. + */ + readonly "updateRole": ( + roleId: string, + options: { readonly payload: typeof UpdateRoleRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes a custom role from the organization. + */ + readonly "deleteRole": ( + roleId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get audio speeches usage details for the organization. + */ + readonly "usageAudioSpeeches": ( + options: { readonly params: typeof UsageAudioSpeechesParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get audio transcriptions usage details for the organization. + */ + readonly "usageAudioTranscriptions": ( + options: { readonly params: typeof UsageAudioTranscriptionsParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get code interpreter sessions usage details for the organization. + */ + readonly "usageCodeInterpreterSessions": ( + options: { + readonly params: typeof UsageCodeInterpreterSessionsParams.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get completions usage details for the organization. + */ + readonly "usageCompletions": ( + options: { readonly params: typeof UsageCompletionsParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get embeddings usage details for the organization. + */ + readonly "usageEmbeddings": ( + options: { readonly params: typeof UsageEmbeddingsParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get file search calls usage details for the organization. + */ + readonly "usageFileSearchCalls": ( + options: { readonly params: typeof UsageFileSearchCallsParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get images usage details for the organization. + */ + readonly "usageImages": ( + options: { readonly params: typeof UsageImagesParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get moderations usage details for the organization. + */ + readonly "usageModerations": ( + options: { readonly params: typeof UsageModerationsParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get vector stores usage details for the organization. + */ + readonly "usageVectorStores": ( + options: { readonly params: typeof UsageVectorStoresParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get web search calls usage details for the organization. + */ + readonly "usageWebSearchCalls": ( + options: { readonly params: typeof UsageWebSearchCallsParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists all of the users in the organization. + */ + readonly "listUsers": ( + options: + | { readonly params?: typeof ListUsersParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a user by their identifier. + */ + readonly "retrieveUser": ( + userId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modifies a user's role in the organization. + */ + readonly "modifyUser": ( + userId: string, + options: { readonly payload: typeof ModifyUserRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes a user from the organization. + */ + readonly "deleteUser": ( + userId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the organization roles assigned to a user within the organization. + */ + readonly "listUserRoleAssignments": ( + userId: string, + options: { + readonly params?: typeof ListUserRoleAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Assigns an organization role to a user within the organization. + */ + readonly "assignUserRole": ( + userId: string, + options: { readonly payload: typeof AssignUserRoleRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Unassigns an organization role from a user within the organization. + */ + readonly "unassignUserRole": ( + userId: string, + roleId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the project roles assigned to a group within a project. + */ + readonly "listProjectGroupRoleAssignments": ( + projectId: string, + groupId: string, + options: { + readonly params?: typeof ListProjectGroupRoleAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Assigns a project role to a group within a project. + */ + readonly "assignProjectGroupRole": ( + projectId: string, + groupId: string, + options: { + readonly payload: typeof AssignProjectGroupRoleRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Unassigns a project role from a group within a project. + */ + readonly "unassignProjectGroupRole": ( + projectId: string, + groupId: string, + roleId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the roles configured for a project. + */ + readonly "listProjectRoles": ( + projectId: string, + options: { + readonly params?: typeof ListProjectRolesParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a custom role for a project. + */ + readonly "createProjectRole": ( + projectId: string, + options: { readonly payload: typeof CreateProjectRoleRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Updates an existing project role. + */ + readonly "updateProjectRole": ( + projectId: string, + roleId: string, + options: { readonly payload: typeof UpdateProjectRoleRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes a custom role from a project. + */ + readonly "deleteProjectRole": ( + projectId: string, + roleId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Lists the project roles assigned to a user within a project. + */ + readonly "listProjectUserRoleAssignments": ( + projectId: string, + userId: string, + options: { + readonly params?: typeof ListProjectUserRoleAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Assigns a project role to a user within a project. + */ + readonly "assignProjectUserRole": ( + projectId: string, + userId: string, + options: { readonly payload: typeof AssignProjectUserRoleRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Unassigns a project role from a user within a project. + */ + readonly "unassignProjectUserRole": ( + projectId: string, + userId: string, + roleId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a new Realtime API call over WebRTC and receive the SDP answer needed + * to complete the peer connection. + */ + readonly "createRealtimeCall": ( + options: { + readonly payload: typeof CreateRealtimeCallRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Accept an incoming SIP call and configure the realtime session that will + * handle it. + */ + readonly "acceptRealtimeCall": ( + callId: string, + options: { readonly payload: typeof AcceptRealtimeCallRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * End an active Realtime API call, whether it was initiated over SIP or + * WebRTC. + */ + readonly "hangupRealtimeCall": ( + callId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Transfer an active SIP call to a new destination using the SIP REFER verb. + */ + readonly "referRealtimeCall": ( + callId: string, + options: { readonly payload: typeof ReferRealtimeCallRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Decline an incoming SIP call by returning a SIP status code to the caller. + */ + readonly "rejectRealtimeCall": ( + callId: string, + options: { readonly payload: typeof RejectRealtimeCallRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Create a Realtime client secret with an associated session configuration. + * + * Client secrets are short-lived tokens that can be passed to a client app, + * such as a web frontend or mobile client, which grants access to the Realtime API without + * leaking your main API key. You can configure a custom TTL for each client secret. + * + * You can also attach session configuration options to the client secret, which will be + * applied to any sessions created using that client secret, but these can also be overridden + * by the client connection. + * + * [Learn more about authentication with client secrets over WebRTC](/docs/guides/realtime-webrtc). + * + * Returns the created client secret and the effective session object. The client secret is a string that looks like `ek_1234`. + */ + readonly "createRealtimeClientSecret": ( + options: { + readonly payload: typeof CreateRealtimeClientSecretRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create an ephemeral API token for use in client-side applications with the + * Realtime API. Can be configured with the same session parameters as the + * `session.update` client event. + * + * It responds with a session object, plus a `client_secret` key which contains + * a usable ephemeral API token that can be used to authenticate browser clients + * for the Realtime API. + * + * Returns the created Realtime session object, plus an ephemeral key. + */ + readonly "createRealtimeSession": ( + options: { readonly payload: typeof CreateRealtimeSessionRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create an ephemeral API token for use in client-side applications with the + * Realtime API specifically for realtime transcriptions. + * Can be configured with the same session parameters as the `transcription_session.update` client event. + * + * It responds with a session object, plus a `client_secret` key which contains + * a usable ephemeral API token that can be used to authenticate browser clients + * for the Realtime API. + * + * Returns the created Realtime transcription session object, plus an ephemeral key. + */ + readonly "createRealtimeTranscriptionSession": ( + options: { + readonly payload: typeof CreateRealtimeTranscriptionSessionRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a Realtime translation client secret with an associated translation session configuration. + * + * Client secrets are short-lived tokens that can be passed to a client app, + * such as a web frontend or mobile client, which grants access to the Realtime + * Translation API without leaking your main API key. You can configure a custom + * TTL for each client secret. + * + * Returns the created client secret and the effective translation session object. + * The client secret is a string that looks like `ek_1234`. + */ + readonly "createRealtimeTranslationClientSecret": ( + options: { + readonly payload: typeof CreateRealtimeTranslationClientSecretRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a model response. Provide [text](/docs/guides/text) or + * [image](/docs/guides/images) inputs to generate [text](/docs/guides/text) + * or [JSON](/docs/guides/structured-outputs) outputs. Have the model call + * your own [custom code](/docs/guides/function-calling) or use built-in + * [tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search) + * or [file search](/docs/guides/tools-file-search) to use your own data + * as input for the model's response. + */ + readonly "createResponse": ( + options: { readonly payload: typeof CreateResponseRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates a model response. Provide [text](/docs/guides/text) or + * [image](/docs/guides/images) inputs to generate [text](/docs/guides/text) + * or [JSON](/docs/guides/structured-outputs) outputs. Have the model call + * your own [custom code](/docs/guides/function-calling) or use built-in + * [tools](/docs/guides/tools) like [web search](/docs/guides/tools-web-search) + * or [file search](/docs/guides/tools-file-search) to use your own data + * as input for the model's response. + */ + readonly "createResponseSse": ( + 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 | Sse.SseError, + typeof CreateResponse200Sse.DecodingServices + > + /** + * Retrieves a model response with the given ID. + */ + readonly "getResponse": ( + responseId: string, + options: + | { readonly params?: typeof GetResponseParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes a model response with the given ID. + */ + readonly "deleteResponse": ( + responseId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"DeleteResponse404", typeof DeleteResponse404.Type> + > + /** + * Cancels a model response with the given ID. Only responses created with + * the `background` parameter set to `true` can be cancelled. + * [Learn more](/docs/guides/background). + */ + readonly "cancelResponse": ( + responseId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenAiClientError<"CancelResponse404", typeof CancelResponse404.Type> + > + /** + * Returns a list of input items for a given response. + */ + readonly "listInputItems": ( + responseId: string, + options: + | { readonly params?: typeof ListInputItemsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a thread. + */ + readonly "createThread": ( + options: { readonly payload: typeof CreateThreadRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a thread and run it in one request. + */ + readonly "createThreadAndRun": ( + options: { readonly payload: typeof CreateThreadAndRunRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a thread. + */ + readonly "getThread": ( + threadId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modifies a thread. + */ + readonly "modifyThread": ( + threadId: string, + options: { readonly payload: typeof ModifyThreadRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a thread. + */ + readonly "deleteThread": ( + threadId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of messages for a given thread. + */ + readonly "listMessages": ( + threadId: string, + options: + | { readonly params?: typeof ListMessagesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a message. + */ + readonly "createMessage": ( + threadId: string, + options: { readonly payload: typeof CreateMessageRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieve a message. + */ + readonly "getMessage": ( + threadId: string, + messageId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modifies a message. + */ + readonly "modifyMessage": ( + threadId: string, + messageId: string, + options: { readonly payload: typeof ModifyMessageRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Deletes a message. + */ + readonly "deleteMessage": ( + threadId: string, + messageId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of runs belonging to a thread. + */ + readonly "listRuns": ( + threadId: string, + options: + | { readonly params?: typeof ListRunsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a run. + */ + readonly "createRun": ( + threadId: string, + options: { + readonly params?: typeof CreateRunParams.Encoded | undefined + readonly payload: typeof CreateRunRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a run. + */ + readonly "getRun": ( + threadId: string, + runId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + /** + * Modifies a run. + */ + readonly "modifyRun": ( + threadId: string, + runId: string, + options: { readonly payload: typeof ModifyRunRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Cancels a run that is `in_progress`. + */ + readonly "cancelRun": ( + threadId: string, + runId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of run steps belonging to a run. + */ + readonly "listRunSteps": ( + threadId: string, + runId: string, + options: + | { readonly params?: typeof ListRunStepsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a run step. + */ + readonly "getRunStep": ( + threadId: string, + runId: string, + stepId: string, + options: + | { readonly params?: typeof GetRunStepParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * When a run has the `status: "requires_action"` and `required_action.type` is `submit_tool_outputs`, this endpoint can be used to submit the outputs from the tool calls once they're all completed. All outputs must be submitted in a single request. + */ + readonly "submitToolOuputsToRun": ( + threadId: string, + runId: string, + options: { readonly payload: typeof SubmitToolOuputsToRunRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Creates an intermediate [Upload](/docs/api-reference/uploads/object) object + * that you can add [Parts](/docs/api-reference/uploads/part-object) to. + * Currently, an Upload can accept at most 8 GB in total and expires after an + * hour after you create it. + * + * Once you complete the Upload, we will create a + * [File](/docs/api-reference/files/object) object that contains all the parts + * you uploaded. This File is usable in the rest of our platform as a regular + * File object. + * + * For certain `purpose` values, the correct `mime_type` must be specified. + * Please refer to documentation for the + * [supported MIME types for your use case](/docs/assistants/tools/file-search#supported-files). + * + * For guidance on the proper filename extensions for each purpose, please + * follow the documentation on [creating a + * File](/docs/api-reference/files/create). + * + * Returns the Upload object with status `pending`. + */ + readonly "createUpload": ( + options: { readonly payload: typeof CreateUploadRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Cancels the Upload. No Parts may be added after an Upload is cancelled. + * + * Returns the Upload object with status `cancelled`. + */ + readonly "cancelUpload": ( + uploadId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Completes the [Upload](/docs/api-reference/uploads/object). + * + * Within the returned Upload object, there is a nested [File](/docs/api-reference/files/object) object that is ready to use in the rest of the platform. + * + * You can specify the order of the Parts by passing in an ordered list of the Part IDs. + * + * The number of bytes uploaded upon completion must match the number of bytes initially specified when creating the Upload object. No Parts may be added after an Upload is completed. + * Returns the Upload object with status `completed`, including an additional `file` property containing the created usable File object. + */ + readonly "completeUpload": ( + uploadId: string, + options: { readonly payload: typeof CompleteUploadRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Adds a [Part](/docs/api-reference/uploads/part-object) to an [Upload](/docs/api-reference/uploads/object) object. A Part represents a chunk of bytes from the file you are trying to upload. + * + * Each Part can be at most 64 MB, and you can add Parts until you hit the Upload maximum of 8 GB. + * + * It is possible to add multiple Parts in parallel. You can decide the intended order of the Parts when you [complete the Upload](/docs/api-reference/uploads/complete). + */ + readonly "addUploadPart": ( + uploadId: string, + options: { readonly payload: typeof AddUploadPartRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of vector stores. + */ + readonly "listVectorStores": ( + options: { + readonly params?: typeof ListVectorStoresParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a vector store. + */ + readonly "createVectorStore": ( + options: { readonly payload: typeof CreateVectorStoreRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a vector store. + */ + readonly "getVectorStore": ( + vectorStoreId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Modifies a vector store. + */ + readonly "modifyVectorStore": ( + vectorStoreId: string, + options: { readonly payload: typeof ModifyVectorStoreRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a vector store. + */ + readonly "deleteVectorStore": ( + vectorStoreId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * The maximum number of files in a single batch request is 2000. + * Vector store file attach requests are rate limited per vector store (300 requests per minute across both this endpoint and `/vector_stores/{vector_store_id}/files`). + * For ingesting multiple files into the same vector store, this batch endpoint is recommended. + */ + readonly "createVectorStoreFileBatch": ( + vectorStoreId: string, + options: { + readonly payload: typeof CreateVectorStoreFileBatchRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a vector store file batch. + */ + readonly "getVectorStoreFileBatch": ( + vectorStoreId: string, + batchId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Cancel a vector store file batch. This attempts to cancel the processing of files in this batch as soon as possible. + */ + readonly "cancelVectorStoreFileBatch": ( + vectorStoreId: string, + batchId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of vector store files in a batch. + */ + readonly "listFilesInVectorStoreBatch": ( + vectorStoreId: string, + batchId: string, + options: { + readonly params?: typeof ListFilesInVectorStoreBatchParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns a list of vector store files. + */ + readonly "listVectorStoreFiles": ( + vectorStoreId: string, + options: { + readonly params?: typeof ListVectorStoreFilesParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * This endpoint is subject to a per-vector-store write rate limit of 300 requests per minute, shared with `/vector_stores/{vector_store_id}/file_batches`. + * For uploading multiple files to the same vector store, use the file batches endpoint to reduce request volume. + */ + readonly "createVectorStoreFile": ( + vectorStoreId: string, + options: { readonly payload: typeof CreateVectorStoreFileRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieves a vector store file. + */ + readonly "getVectorStoreFile": ( + vectorStoreId: string, + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Update attributes on a vector store file. + */ + readonly "updateVectorStoreFileAttributes": ( + vectorStoreId: string, + fileId: string, + options: { + readonly payload: typeof UpdateVectorStoreFileAttributesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a vector store file. This will remove the file from the vector store but the file itself will not be deleted. To delete the file, use the [delete file](/docs/api-reference/files/delete) endpoint. + */ + readonly "deleteVectorStoreFile": ( + vectorStoreId: string, + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieve the parsed contents of a vector store file. + */ + readonly "retrieveVectorStoreFileContent": ( + vectorStoreId: string, + fileId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Search a vector store for relevant chunks based on a query and file attributes filter. + */ + readonly "searchVectorStore": ( + vectorStoreId: string, + options: { readonly payload: typeof SearchVectorStoreRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a conversation. + */ + readonly "createConversation": ( + options: { readonly payload: typeof CreateConversationRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get a conversation + */ + readonly "getConversation": ( + conversationId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Update a conversation + */ + readonly "updateConversation": ( + conversationId: string, + options: { readonly payload: typeof UpdateConversationRequestJson.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a conversation. Items in the conversation will not be deleted. + */ + readonly "deleteConversation": ( + conversationId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List recently generated videos for the current project. + */ + readonly "ListVideos": ( + options: + | { readonly params?: typeof ListVideosParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a new video generation job from a prompt and optional reference assets. + */ + readonly "createVideo": ( + options: { readonly payload: typeof CreateVideoRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a character from an uploaded video. + */ + readonly "CreateVideoCharacter": ( + options: { + readonly payload: typeof CreateVideoCharacterRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Fetch a character. + */ + readonly "GetVideoCharacter": ( + characterId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a new video generation job by editing a source video or existing generated video. + */ + readonly "CreateVideoEdit": ( + options: { readonly payload: typeof CreateVideoEditRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create an extension of a completed video. + */ + readonly "CreateVideoExtend": ( + options: { readonly payload: typeof CreateVideoExtendRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Fetch the latest metadata for a generated video. + */ + readonly "GetVideo": ( + videoId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Permanently delete a completed or failed video and its stored assets. + */ + readonly "DeleteVideo": ( + videoId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Download the generated video bytes or a derived preview asset. + * + * Streams the rendered video content for the specified video job. + */ + readonly "RetrieveVideoContent": ( + videoId: string, + options: { + readonly params?: typeof RetrieveVideoContentParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a remix of a completed video using a refreshed prompt. + */ + readonly "CreateVideoRemix": ( + videoId: string, + options: { readonly payload: typeof CreateVideoRemixRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Returns input token counts of the request. + * + * Returns an object with `object` set to `response.input_tokens` and an `input_tokens` count. + */ + readonly "Getinputtokencounts": ( + options: { + readonly payload: typeof GetinputtokencountsRequestFormUrlEncoded.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Compact a conversation. Returns a compacted response object. + * + * Learn when and how to compact long-running conversations in the [conversation state guide](/docs/guides/conversation-state#managing-the-context-window). For ZDR-compatible compaction details, see [Compaction (advanced)](/docs/guides/conversation-state#compaction-advanced). + */ + readonly "Compactconversation": ( + options: { + readonly payload: typeof CompactconversationRequestFormUrlEncoded.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List all skills for the current project. + */ + readonly "ListSkills": ( + options: + | { readonly params?: typeof ListSkillsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a new skill. + */ + readonly "CreateSkill": ( + options: { readonly payload: typeof CreateSkillRequestFormData.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get a skill by its ID. + */ + readonly "GetSkill": ( + skillId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Update the default version pointer for a skill. + */ + readonly "UpdateSkillDefaultVersion": ( + skillId: string, + options: { + readonly payload: typeof UpdateSkillDefaultVersionRequestFormUrlEncoded.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a skill by its ID. + */ + readonly "DeleteSkill": ( + skillId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Download a skill zip bundle by its ID. + */ + readonly "GetSkillContent": ( + skillId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List skill versions for a skill. + */ + readonly "ListSkillVersions": ( + skillId: string, + options: { + readonly params?: typeof ListSkillVersionsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a new immutable skill version. + */ + readonly "CreateSkillVersion": ( + skillId: string, + options: { + readonly payload: typeof CreateSkillVersionRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Get a specific skill version. + */ + readonly "GetSkillVersion": ( + skillId: string, + version: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a skill version. + */ + readonly "DeleteSkillVersion": ( + skillId: string, + version: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Download a skill version zip bundle. + */ + readonly "GetSkillVersionContent": ( + skillId: string, + version: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Cancel an active ChatKit session and return its most recent metadata. + * + * Cancelling prevents new requests from using the issued client secret. + */ + readonly "CancelChatSessionMethod": ( + sessionId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Create a ChatKit session. + */ + readonly "CreateChatSessionMethod": ( + options: { + readonly payload: typeof CreateChatSessionMethodRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List items that belong to a ChatKit thread. + */ + readonly "ListThreadItemsMethod": ( + threadId: string, + options: { + readonly params?: typeof ListThreadItemsMethodParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Retrieve a ChatKit thread by its identifier. + */ + readonly "GetThreadMethod": ( + threadId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * Delete a ChatKit thread along with its items and stored attachments. + */ + readonly "DeleteThreadMethod": ( + threadId: string, + options: { readonly config?: Config | undefined } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > + /** + * List ChatKit threads with optional pagination and user filters. + */ + readonly "ListThreadsMethod": ( + options: { + readonly params?: typeof ListThreadsMethodParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + HttpClientError.HttpClientError | SchemaError + > +} + +export interface OpenAiClientError { + readonly _tag: Tag + readonly request: HttpClientRequest.HttpClientRequest + readonly response: HttpClientResponse.HttpClientResponse + readonly cause: E +} + +class OpenAiClientErrorImpl extends Data.Error<{ + _tag: string + cause: any + request: HttpClientRequest.HttpClientRequest + response: HttpClientResponse.HttpClientResponse +}> {} + +export const OpenAiClientError = ( + tag: Tag, + cause: E, + response: HttpClientResponse.HttpClientResponse +): OpenAiClientError => + new OpenAiClientErrorImpl({ + _tag: tag, + cause, + response, + request: response.request + }) as any diff --git a/.repos/effect/packages/ai/openai/src/OpenAiClient.ts b/.repos/effect/packages/ai/openai/src/OpenAiClient.ts new file mode 100644 index 000000000..43753b63b --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiClient.ts @@ -0,0 +1,744 @@ +/** + * The `OpenAiClient` module defines the low-level Effect service used by the + * OpenAI integration for Responses API and embedding requests. It builds a + * configured HTTP client with authentication and OpenAI organization or project + * headers, exposes helpers for non-streaming responses, SSE response streams, + * WebSocket response streams, and embeddings, and maps transport or decoding + * failures into `AiError`. + * + * @since 4.0.0 + */ +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 } from "effect/Function" +import * as Function from "effect/Function" +import * as Layer from "effect/Layer" +import * as Predicate from "effect/Predicate" +import * as Queue from "effect/Queue" +import * as RcRef from "effect/RcRef" +import * as Redacted from "effect/Redacted" +import * as Schema from "effect/Schema" +import * as Scope from "effect/Scope" +import * as Semaphore from "effect/Semaphore" +import * as Stream from "effect/Stream" +import * as AiError from "effect/unstable/ai/AiError" +import * as ResponseIdTracker from "effect/unstable/ai/ResponseIdTracker" +import * as Sse from "effect/unstable/encoding/Sse" +import * as Headers from "effect/unstable/http/Headers" +import * as HttpBody from "effect/unstable/http/HttpBody" +import * as HttpClient from "effect/unstable/http/HttpClient" +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import * as Socket from "effect/unstable/socket/Socket" +import * as Errors from "./internal/errors.ts" +import { OpenAiConfig } from "./OpenAiConfig.ts" +import * as OpenAiSchema from "./OpenAiSchema.ts" + +// ============================================================================= +// Service Interface +// ============================================================================= + +/** + * Effect service interface for the handwritten OpenAI client. + * + * **Details** + * + * 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 services + * @since 4.0.0 + */ +export interface Service { + /** + * The transformed HTTP client used by this service. + */ + readonly client: HttpClient.HttpClient + + /** + * Create a response using the OpenAI responses endpoint. + */ + readonly createResponse: ( + options: typeof OpenAiSchema.CreateResponse.Encoded + ) => Effect.Effect< + readonly [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse], + AiError.AiError + > + + /** + * Create a streaming response using the OpenAI responses endpoint. + */ + readonly createResponseStream: ( + options: Omit + ) => Effect.Effect< + readonly [ + response: HttpClientResponse.HttpClientResponse, + stream: Stream.Stream + ], + AiError.AiError + > + + /** + * Create embeddings using the OpenAI embeddings endpoint. + */ + readonly createEmbedding: ( + options: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded + ) => Effect.Effect +} + +// ============================================================================= +// Service Identifier +// ============================================================================= + +/** + * Service tag for the OpenAI client. + * + * **When to use** + * + * Use when accessing or providing the OpenAI client service through Effect's + * context. + * + * @see {@link make} for constructing an OpenAI client effectfully + * @see {@link layer} for providing a client from explicit options + * @see {@link layerConfig} for providing a client from `Config` + * + * @category services + * @since 4.0.0 + */ +export class OpenAiClient extends Context.Service()( + "@effect/ai-openai/OpenAiClient" +) {} + +// ============================================================================= +// Options +// ============================================================================= + +/** + * Options for configuring the OpenAI client. + * + * @category options + * @since 4.0.0 + */ +export type Options = { + /** + * The OpenAI API key. + */ + readonly apiKey?: Redacted.Redacted | undefined + + /** + * The base URL for the OpenAI API. + * + * @default "https://api.openai.com/v1" + */ + readonly apiUrl?: string | undefined + + /** + * Optional organization ID for multi-org accounts. + */ + readonly organizationId?: Redacted.Redacted | undefined + + /** + * Optional project ID for project-scoped requests. + */ + readonly projectId?: Redacted.Redacted | undefined + + /** + * Optional transformer for the HTTP client. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +} + +// ============================================================================= +// Constructor +// ============================================================================= + +const RedactedOpenAiHeaders = { + OpenAiOrganization: "OpenAI-Organization", + OpenAiProject: "OpenAI-Project" +} + +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + +/** + * Creates an OpenAI client service with the given options. + * + * **When to use** + * + * Use when you need the OpenAI client service value inside an effect. + * + * **Details** + * + * The returned service uses the current `HttpClient`, prepends `apiUrl` or + * `https://api.openai.com/v1`, adds the bearer token and optional OpenAI + * organization/project headers, accepts JSON responses, filters for successful + * HTTP statuses, and applies `transformClient` when provided. + * + * **Gotchas** + * + * A scoped `OpenAiConfig.withClientTransform` is applied when request helpers + * run, after the `transformClient` option supplied to `make`. + * + * @see {@link layer} for providing this client from explicit options + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced( + function*( + options: Options + ): Effect.fn.Return { + const baseClient = yield* HttpClient.HttpClient + const apiUrl = options.apiUrl ?? "https://api.openai.com/v1" + + const httpClient = baseClient.pipe( + HttpClient.mapRequest(Function.flow( + HttpClientRequest.prependUrl(apiUrl), + options.apiKey + ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey)) + : identity, + options.organizationId + ? HttpClientRequest.setHeader( + RedactedOpenAiHeaders.OpenAiOrganization, + Redacted.value(options.organizationId) + ) + : identity, + options.projectId + ? HttpClientRequest.setHeader( + RedactedOpenAiHeaders.OpenAiProject, + Redacted.value(options.projectId) + ) + : identity, + HttpClientRequest.acceptJson + )), + HttpClient.filterStatusOk, + options.transformClient + ? options.transformClient + : identity + ) + + const resolveHttpClient = Effect.map( + OpenAiConfig.getOrUndefined, + (config) => + Predicate.isNotUndefined(config?.transformClient) + ? config.transformClient(httpClient) + : httpClient + ) + + const decodeResponse = HttpClientResponse.schemaBodyJson(OpenAiSchema.Response) + + const createResponse = ( + payload: typeof OpenAiSchema.CreateResponse.Encoded + ): Effect.Effect< + [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse], + AiError.AiError + > => + 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")) + }) + ) + ), + withRedactedHeaders + ) + + const buildResponseStream = ( + response: HttpClientResponse.HttpClientResponse + ): [ + HttpClientResponse.HttpClientResponse, + Stream.Stream + ] => { + const stream = response.stream.pipe( + Stream.decodeText(), + Stream.pipeThroughChannel(Sse.decodeDataSchema(OpenAiSchema.ResponseStreamEvent)), + Stream.takeUntil((event) => + event.data.type === "response.completed" || + 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")) + }) + ) + return [response, stream] + } + + const createResponseStream: Service["createResponseStream"] = (payload) => + Effect.contextWith((services) => { + const socket = Context.getOrUndefined(services, OpenAiSocket) + if (socket) return socket.createResponseStream(payload) + 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") + ) + ) + ), + withRedactedHeaders + ) + }) + + const decodeEmbedding = HttpClientResponse.schemaBodyJson(OpenAiSchema.CreateEmbeddingResponse) + + const createEmbedding = ( + payload: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded + ): Effect.Effect => + 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")) + }) + ) + ), + withRedactedHeaders + ) + + return OpenAiClient.of({ + client: httpClient, + createResponse, + createResponseStream, + createEmbedding + }) + }, + withRedactedHeaders +) + +// ============================================================================= +// Layers +// ============================================================================= + +/** + * Creates a layer for the OpenAI client with the given options. + * + * **When to use** + * + * Use when you already have explicit `Options` values, such as an API key or + * custom API URL, and want to provide `OpenAiClient` as a `Layer`. + * + * @see {@link make} for constructing the client service effectfully + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: Options): Layer.Layer => + Layer.effect(OpenAiClient, make(options)) + +/** + * Creates a layer for the OpenAI client from provided `Config` values. + * + * **When to use** + * + * Use when you need client settings for OpenAI-compatible APIs to be read from + * Effect `Config` values while providing `OpenAiClient` as a `Layer`. + * + * **Details** + * + * Only config values supplied in `options` are loaded. Omitted fields are + * passed to `make` as `undefined`, and `transformClient` is forwarded as a + * plain option. + * + * @see {@link make} for constructing the client service effectfully + * @see {@link layer} for providing the client from already-resolved options + * + * @category layers + * @since 4.0.0 + */ +export const layerConfig = (options?: { + /** + * The config value to load for the API key. + */ + readonly apiKey?: Config.Config | undefined> | undefined + + /** + * The config value to load for the API URL. + */ + readonly apiUrl?: Config.Config | undefined + + /** + * The config value to load for the organization ID. + */ + readonly organizationId?: Config.Config | undefined> | undefined + + /** + * The config value to load for the project ID. + */ + readonly projectId?: Config.Config | undefined> | undefined + + /** + * Optional transformer for the HTTP client. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +}): Layer.Layer => + Layer.effect( + OpenAiClient, + Effect.gen(function*() { + const apiKey = Predicate.isNotUndefined(options?.apiKey) + ? yield* options.apiKey : + undefined + const apiUrl = Predicate.isNotUndefined(options?.apiUrl) + ? yield* options.apiUrl : + undefined + const organizationId = Predicate.isNotUndefined(options?.organizationId) + ? yield* options.organizationId + : undefined + const projectId = Predicate.isNotUndefined(options?.projectId) + ? yield* options.projectId : + undefined + return yield* make({ + apiKey, + apiUrl, + organizationId, + projectId, + transformClient: options?.transformClient + }) + }) + ) + +// ============================================================================= +// Websocket mode +// ============================================================================= + +/** + * Response stream event emitted by the OpenAI Responses API. + * + * @category models + * @since 4.0.0 + */ +export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type + +/** + * Service for creating OpenAI response streams over a WebSocket connection. + * + * **When to use** + * + * Use when you need direct access to the WebSocket-backed response streaming + * service rather than wrapping an effect with WebSocket mode. + * + * **Details** + * + * `createResponseStream` sends a `response.create` message over the WebSocket + * connection and returns an HTTP response together with a stream of + * `ResponseStreamEvent` values. + * + * **Gotchas** + * + * WebSocket response streams are serialized to one request at a time by the + * shared socket service. + * + * @see {@link withWebSocketMode} for enabling WebSocket mode for one effect + * @see {@link layerWebSocketMode} for providing WebSocket mode through a layer + * + * @category services + * @since 4.0.0 + */ +export class OpenAiSocket extends Context.Service + ) => Effect.Effect< + readonly [ + response: HttpClientResponse.HttpClientResponse, + stream: Stream.Stream + ], + AiError.AiError + > +}>()("@effect/ai-openai/OpenAiClient/OpenAiSocket") {} + +const makeSocket = Effect.gen(function*() { + const client = yield* OpenAiClient + const tracker = yield* ResponseIdTracker.make + const socketScope = yield* Effect.scope + const makeRequest = Effect.flatMap( + OpenAiConfig.getOrUndefined, + (config) => { + const httpClient = Predicate.isNotUndefined(config?.transformClient) + ? config.transformClient(client.client) + : client.client + return Effect.orDie(httpClient.preprocess(HttpClientRequest.post("/responses"))) + } + ) + const makeWebSocket = yield* Socket.WebSocketConstructor + + const decoder = new TextDecoder() + + const queueRef: RcRef.RcRef< + { + readonly send: (message: typeof OpenAiSchema.CreateResponse.Encoded) => Effect.Effect + readonly incoming: Queue.Dequeue + } + > = yield* RcRef.make({ + idleTimeToLive: 60_000, + acquire: Effect.gen(function*() { + const scope = yield* Effect.scope + const request = yield* makeRequest + const socket = yield* Socket.makeWebSocket(request.url.replace(/^http/, "ws")).pipe( + Effect.provideService(Socket.WebSocketConstructor, (url) => + makeWebSocket(url, { + headers: request.headers + } as any)) + ) + const write = yield* socket.writer + + yield* Scope.addFinalizerExit(scope, () => { + tracker.clearUnsafe() + return Effect.void + }) + + const incoming = yield* Queue.unbounded() + const send = (message: typeof OpenAiSchema.CreateResponse.Encoded) => + write(JSON.stringify({ + type: "response.create", + ...message + })).pipe( + Effect.mapError((_error) => + AiError.make({ + module: "OpenAiClient", + method: "createResponseStream", + reason: new AiError.NetworkError({ + reason: "TransportError", + request: { + method: "POST", + url: request.url, + urlParams: [], + hash: undefined, + headers: request.headers + }, + description: "Failed to send message over WebSocket" + }) + }) + ) + ) + + yield* socket.runRaw((msg) => { + const text = typeof msg === "string" ? msg : decoder.decode(msg) + try { + const event = decodeEvent(text) + if (event.type === "error" && "status" in event) { + const status = Number(event.status) + const error = "error" in event ? event.error as typeof ErrorEvent.Type.error : event + const json = JSON.stringify(error) + return Effect.fail( + AiError.make({ + module: "OpenAiClient", + method: "createResponseStream", + reason: AiError.reasonFromHttpStatus({ + description: json, + status: isNaN(status) ? + Object.hasOwn(errorTypeToStatus, error.type) + ? errorTypeToStatus[error.type] + : 500 : + status, + metadata: error as any, + http: { + body: json, + request: { + method: "POST", + url: request.url, + urlParams: [], + hash: undefined, + headers: request.headers + } + } + }) + }) + ) + } + Queue.offerUnsafe(incoming, event) + } catch {} + }).pipe( + Effect.catchTag("SocketError", (error) => + AiError.make({ + module: "OpenAiClient", + method: "createResponseStream", + reason: new AiError.NetworkError({ + reason: "TransportError", + request: { + method: "POST", + url: request.url, + urlParams: [], + hash: undefined, + headers: request.headers + }, + description: error.message + }) + })), + Effect.catchCause((cause) => Queue.failCause(incoming, cause)), + Effect.ensuring(Effect.forkIn(RcRef.invalidate(queueRef), socketScope, { + startImmediately: true + })), + Effect.forkScoped({ startImmediately: true }) + ) + + return { send, incoming } as const + }) + }) + + // Prime the websocket + yield* Effect.scoped(RcRef.get(queueRef)) + + // Websocket mode only allows one request at a time + const semaphore = Semaphore.makeUnsafe(1) + const request = yield* makeRequest + + return OpenAiSocket.context({ + createResponseStream(options) { + const stream = Stream.unwrap(Effect.gen(function*() { + const scope = yield* Effect.scope + yield* Effect.acquireRelease( + semaphore.take(1), + () => semaphore.release(1), + { interruptible: true } + ) + const { send, incoming } = yield* RcRef.get(queueRef) + let done = false + + yield* Scope.addFinalizerExit( + scope, + () => done ? Effect.void : RcRef.invalidate(queueRef) + ) + + yield* send(options).pipe( + Effect.forkScoped({ startImmediately: true }) + ) + + return Stream.fromQueue(incoming).pipe( + Stream.takeUntil((e) => { + done = e.type === "response.completed" || e.type === "response.incomplete" || e.type === "response.failed" + return done + }) + ) + })) + + return Effect.succeed([ + HttpClientResponse.fromWeb(request, new Response()), + stream + ]) + } + }).pipe( + Context.add(ResponseIdTracker.ResponseIdTracker, tracker) + ) +}) + +const ErrorEvent = Schema.Struct({ + type: Schema.Literal("error"), + status: Schema.Int.pipe( + Schema.withDecodingDefault(Effect.succeed(500)) + ), + error: Schema.Struct({ + type: Schema.String, + message: Schema.String + }) +}) + +const errorTypeToStatus: Record = { + invalid_request_error: 400, + invalid_api_key_error: 401, + insufficient_quota_error: 429, + rate_limit_error: 429, + service_unavailable_error: 503 +} + +const AllEvents = Schema.Union([ErrorEvent, OpenAiSchema.ResponseStreamEvent]) +const decodeEvent = Schema.decodeUnknownSync(Schema.fromJsonString(AllEvents)) + +/** + * Uses OpenAI's WebSocket mode for response streams within the provided effect. + * + * **When to use** + * + * Use to enable WebSocket mode around one effect that creates OpenAI response + * streams. + * + * **Gotchas** + * + * This only works with the following WebSocket constructor layers: + * + * - `NodeSocket.layerWebSocketConstructorWS` + * - `BunSocket.layerWebSocketConstructor` + * + * These constructor layers support the non-standard options needed to set the + * Authorization header. + * + * @see {@link layerWebSocketMode} for providing WebSocket mode through a layer + * @see {@link OpenAiSocket} for direct access to the WebSocket-backed streaming service + * + * @category providing services + * @since 4.0.0 + */ +export const withWebSocketMode = ( + effect: Effect.Effect +): Effect.Effect< + A, + E, + Exclude | OpenAiClient | Socket.WebSocketConstructor +> => + Effect.scopedWith((scope) => + Effect.flatMap( + Scope.provide(makeSocket, scope), + (services) => Effect.provideContext(effect, services) + ) + ) + +/** + * Uses OpenAI's websocket mode for all responses that use the Layer. + * + * **When to use** + * + * Use to provide WebSocket mode through layer composition for effects that use + * OpenAI response streaming. + * + * **Gotchas** + * + * This only works with the following WebSocket constructor layers: + * + * - `NodeSocket.layerWebSocketConstructorWS` + * - `BunSocket.layerWebSocketConstructor` + * + * These constructor layers support the non-standard options needed to set the + * Authorization header. + * + * @see {@link withWebSocketMode} for enabling WebSocket mode around a single effect + * + * @category layers + * @since 4.0.0 + */ +export const layerWebSocketMode: Layer.Layer< + OpenAiSocket | ResponseIdTracker.ResponseIdTracker, + never, + OpenAiClient | Socket.WebSocketConstructor +> = Layer.effectContext(makeSocket) diff --git a/.repos/effect/packages/ai/openai/src/OpenAiClientGenerated.ts b/.repos/effect/packages/ai/openai/src/OpenAiClientGenerated.ts new file mode 100644 index 000000000..6df83c43a --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiClientGenerated.ts @@ -0,0 +1,204 @@ +/** + * @since 4.0.0 + */ +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 } from "effect/Function" +import * as Function from "effect/Function" +import * as Layer from "effect/Layer" +import * as Predicate from "effect/Predicate" +import * as Redacted from "effect/Redacted" +import * as Headers from "effect/unstable/http/Headers" +import * as HttpClient from "effect/unstable/http/HttpClient" +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import * as Generated from "./Generated.ts" +import { OpenAiConfig } from "./OpenAiConfig.ts" + +// ============================================================================= +// Service Identifier +// ============================================================================= + +/** + * Service identifier for the generated OpenAI client. + * + * @since 4.0.0 + * @category service + */ +export class OpenAiClientGenerated extends Context.Service()( + "@effect/ai-openai/OpenAiClientGenerated" +) {} + +// ============================================================================= +// Options +// ============================================================================= + +/** + * Options for configuring the generated OpenAI client. + * + * @since 4.0.0 + * @category options + */ +export type Options = { + /** + * The OpenAI API key. + */ + readonly apiKey?: Redacted.Redacted | undefined + + /** + * The base URL for the OpenAI API. + * + * @default "https://api.openai.com/v1" + */ + readonly apiUrl?: string | undefined + + /** + * Optional organization ID for multi-org accounts. + */ + readonly organizationId?: Redacted.Redacted | undefined + + /** + * Optional project ID for project-scoped requests. + */ + readonly projectId?: Redacted.Redacted | undefined + + /** + * Optional transformer for the HTTP client. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +} + +const RedactedOpenAiHeaders = { + OpenAiOrganization: "OpenAI-Organization", + OpenAiProject: "OpenAI-Project" +} + +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + +// ============================================================================= +// Constructor +// ============================================================================= + +/** + * Creates a generated OpenAI client service with the given options. + * + * @since 4.0.0 + * @category constructors + */ +export const make = Effect.fnUntraced( + function*(options: Options): Effect.fn.Return { + const baseClient = yield* HttpClient.HttpClient + const apiUrl = options.apiUrl ?? "https://api.openai.com/v1" + + const httpClient = baseClient.pipe( + HttpClient.mapRequest(Function.flow( + HttpClientRequest.prependUrl(apiUrl), + options.apiKey + ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey)) + : identity, + options.organizationId + ? HttpClientRequest.setHeader( + RedactedOpenAiHeaders.OpenAiOrganization, + Redacted.value(options.organizationId) + ) + : identity, + options.projectId + ? HttpClientRequest.setHeader( + RedactedOpenAiHeaders.OpenAiProject, + Redacted.value(options.projectId) + ) + : identity, + HttpClientRequest.acceptJson + )), + options.transformClient + ? options.transformClient + : identity + ) + + return Generated.make(httpClient, { + transformClient: Effect.fnUntraced(function*(client) { + const config = yield* OpenAiConfig.getOrUndefined + if (Predicate.isNotUndefined(config?.transformClient)) { + return config.transformClient(client) + } + return client + }) + }) + }, + withRedactedHeaders +) + +// ============================================================================= +// Layers +// ============================================================================= + +/** + * Creates a layer for the generated OpenAI client with the given options. + * + * @since 4.0.0 + * @category layers + */ +export const layer = (options: Options): Layer.Layer => + Layer.effect(OpenAiClientGenerated, make(options)) + +/** + * Creates a layer for the generated OpenAI client, loading the requisite + * configuration via Effect's `Config` module. + * + * @since 4.0.0 + * @category layers + */ +export const layerConfig = (options?: { + /** + * The config value to load for the API key. + */ + readonly apiKey?: Config.Config | undefined> | undefined + + /** + * The config value to load for the API URL. + */ + readonly apiUrl?: Config.Config | undefined + + /** + * The config value to load for the organization ID. + */ + readonly organizationId?: Config.Config | undefined> | undefined + + /** + * The config value to load for the project ID. + */ + readonly projectId?: Config.Config | undefined> | undefined + + /** + * Optional transformer for the HTTP client. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +}): Layer.Layer => + Layer.effect( + OpenAiClientGenerated, + Effect.gen(function*() { + const apiKey = Predicate.isNotUndefined(options?.apiKey) + ? yield* options.apiKey : + undefined + const apiUrl = Predicate.isNotUndefined(options?.apiUrl) + ? yield* options.apiUrl : + undefined + const organizationId = Predicate.isNotUndefined(options?.organizationId) + ? yield* options.organizationId + : undefined + const projectId = Predicate.isNotUndefined(options?.projectId) + ? yield* options.projectId : + undefined + return yield* make({ + apiKey, + apiUrl, + organizationId, + projectId, + transformClient: options?.transformClient + }) + }) + ) diff --git a/.repos/effect/packages/ai/openai/src/OpenAiConfig.ts b/.repos/effect/packages/ai/openai/src/OpenAiConfig.ts new file mode 100644 index 000000000..7d723974e --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiConfig.ts @@ -0,0 +1,93 @@ +/** + * The `OpenAiConfig` module lets a workflow temporarily customize the HTTP + * client used by `@effect/ai-openai` request helpers. OpenAI client, language + * model, and embedding code read this scoped transform when they execute + * provider calls. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import type { HttpClient } from "effect/unstable/http/HttpClient" + +/** + * Context service for scoped OpenAI configuration used by provider operations. + * + * **When to use** + * + * Use to provide scoped OpenAI client configuration, such as an HTTP client + * transform, to OpenAI provider operations without passing it through each call. + * + * @see {@link withClientTransform} for scoping an HTTP client transformation + * + * @category services + * @since 4.0.0 + */ +export class OpenAiConfig extends Context.Service< + OpenAiConfig, + OpenAiConfig.Service +>()("@effect/ai-openai/OpenAiConfig") { + /** + * Gets the configured OpenAI service from the current context when present. + * + * @since 4.0.0 + */ + static readonly getOrUndefined: Effect.Effect = Effect.map( + Effect.context(), + Context.getOrUndefined(OpenAiConfig) + ) +} + +/** + * Types used by the `OpenAiConfig` context service. + * + * @since 4.0.0 + */ +export declare namespace OpenAiConfig { + /** + * Configuration values read by OpenAI provider operations when executing + * requests. + * + * @category services + * @since 4.0.0 + */ + export interface Service { + readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined + } +} + +/** + * Provides a scoped transform for the OpenAI HTTP client used by provider + * operations. + * + * **When to use** + * + * Use when you need temporary OpenAI HTTP client customization for a single + * effect or workflow without rebuilding the client layer. + * + * **Details** + * + * Supports both data-first and data-last forms. The transform is stored in the + * scoped `OpenAiConfig` service and read by OpenAI provider operations while + * running the supplied effect. + * + * **Gotchas** + * + * If a transform is already present in the scoped config, this helper replaces + * it. Compose transforms manually when both should apply. + * + * @category configuration + * @since 4.0.0 + */ +export const withClientTransform: { + (transform: (client: HttpClient) => HttpClient): (self: Effect.Effect) => Effect.Effect + (self: Effect.Effect, transform: (client: HttpClient) => HttpClient): Effect.Effect +} = dual(2, ( + self: Effect.Effect, + transformClient: (client: HttpClient) => HttpClient +) => + Effect.flatMap( + OpenAiConfig.getOrUndefined, + (config) => Effect.provideService(self, OpenAiConfig, { ...config, transformClient }) + )) diff --git a/.repos/effect/packages/ai/openai/src/OpenAiEmbeddingModel.ts b/.repos/effect/packages/ai/openai/src/OpenAiEmbeddingModel.ts new file mode 100644 index 000000000..fa466ca79 --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiEmbeddingModel.ts @@ -0,0 +1,257 @@ +/** + * The `OpenAiEmbeddingModel` module provides the OpenAI implementation of + * Effect AI's `EmbeddingModel` service. It sends embedding requests through + * `OpenAiClient`, exposes constructors for layers and `AiModel` values, + * supports scoped request configuration overrides, and checks that OpenAI + * returns one numeric vector for each requested input. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import * as Layer from "effect/Layer" +import type { Simplify } from "effect/Types" +import * as AiError from "effect/unstable/ai/AiError" +import * as EmbeddingModel from "effect/unstable/ai/EmbeddingModel" +import * as AiModel from "effect/unstable/ai/Model" +import { OpenAiClient } from "./OpenAiClient.ts" +import type * as OpenAiSchema from "./OpenAiSchema.ts" + +/** + * Model identifiers supported by OpenAI's embeddings API. + * + * @category models + * @since 4.0.0 + */ +export type Model = "text-embedding-ada-002" | "text-embedding-3-small" | "text-embedding-3-large" + +/** + * Context service for OpenAI embedding model configuration. + * + * **When to use** + * + * Use when you need scoped OpenAI request defaults or overrides for embedding + * requests from Effect context. + * + * **Details** + * + * The service stores the OpenAI create-embedding request payload without + * `input`, carrying options such as `model`, `dimensions`, `encoding_format`, + * and `user`. + * + * @see {@link withConfigOverride} for scoping embedding request overrides + * + * @category services + * @since 4.0.0 + */ +export class Config extends Context.Service< + Config, + Simplify< + & Partial< + Omit< + typeof OpenAiSchema.CreateEmbeddingRequest.Encoded, + "input" + > + > + & { + readonly [x: string]: unknown + } + > +>()("@effect/ai-openai/OpenAiEmbeddingModel/Config") {} + +/** + * Creates an `AiModel` for an OpenAI embedding model with its configured vector dimensions. + * + * **When to use** + * + * Use to provide an OpenAI `EmbeddingModel` and its `Dimensions` service to an + * Effect program. + * + * @see {@link layer} for providing only the embedding model service + * @see {@link withConfigOverride} for scoped request configuration overrides + * + * @category constructors + * @since 4.0.0 + */ +export const model = ( + model: (string & {}) | Model, + options: { + readonly dimensions: number + readonly config?: Omit + } +): AiModel.Model<"openai", EmbeddingModel.EmbeddingModel | EmbeddingModel.Dimensions, OpenAiClient> => + AiModel.make( + "openai", + model, + Layer.merge( + layer({ + model, + config: { + ...options.config, + dimensions: options.dimensions + } + }), + Layer.succeed(EmbeddingModel.Dimensions, options.dimensions) + ) + ) + +/** + * Creates an OpenAI embedding model service. + * + * **When to use** + * + * Use to construct the `EmbeddingModel.Service` effectfully when + * `OpenAiClient` is already available in the environment. + * + * **Details** + * + * The `model` option is sent with each embedding request. Constructor `config` + * supplies create-embedding request fields other than `model` and `input`, and + * scoped overrides from `withConfigOverride` are merged last for each request. + * + * **Gotchas** + * + * The service expects numeric embedding vectors. It fails with + * `InvalidOutputError` when the provider returns base64 embeddings, + * out-of-range indexes, duplicate indexes, or an unexpected number of + * embeddings. + * + * @see {@link layer} for providing the embedding model service as a layer + * @see {@link model} for creating an `AiModel` that also provides dimensions + * @see {@link withConfigOverride} for scoped request configuration overrides + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { + readonly model: (string & {}) | Model + readonly config?: Omit | undefined +}): Effect.fn.Return { + const client = yield* OpenAiClient + + const makeConfig = Effect.contextWith((services: Context.Context) => + Effect.succeed({ model, ...providerConfig, ...Context.getOrUndefined(services, Config) }) + ) + + return yield* EmbeddingModel.make({ + embedMany: Effect.fnUntraced(function*({ inputs }) { + const config = yield* makeConfig + const response = yield* client.createEmbedding({ ...config, input: inputs }) + return yield* mapProviderResponse(inputs.length, response) + }) + }) +}) + +/** + * Creates a layer for the OpenAI embedding model. + * + * **When to use** + * + * Use when composing application layers and you want OpenAI to satisfy + * `EmbeddingModel.EmbeddingModel` while supplying `OpenAiClient` from another + * layer. + * + * **Gotchas** + * + * Use the default floating-point embedding format. The service expects numeric + * vectors and fails with `InvalidOutputError` if OpenAI returns base64 + * embeddings. + * + * @see {@link make} for constructing the embedding model service effectfully + * @see {@link model} for creating an `AiModel` that also provides embedding dimensions + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: { + readonly model: (string & {}) | Model + readonly config?: Omit | undefined +}): Layer.Layer => + Layer.effect(EmbeddingModel.EmbeddingModel, make(options)) + +/** + * Provides config overrides for OpenAI embedding model operations. + * + * **When to use** + * + * Use when you need scoped OpenAI embedding request defaults for a single + * effect or workflow without rebuilding the embedding model service. + * + * **Details** + * + * Supports both data-first and data-last forms. Existing scoped config is read + * first, then the provided overrides are applied so override fields take + * precedence. + * + * @see {@link Config} for the scoped embedding request configuration service + * + * @category configuration + * @since 4.0.0 + */ +export const withConfigOverride: { + (overrides: typeof Config.Service): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, overrides: typeof Config.Service): Effect.Effect> +} = dual< + ( + overrides: typeof Config.Service + ) => (self: Effect.Effect) => Effect.Effect>, + (self: Effect.Effect, overrides: typeof Config.Service) => Effect.Effect> +>(2, (self, overrides) => + Effect.flatMap( + Effect.serviceOption(Config), + (config) => + Effect.provideService(self, Config, { + ...(config._tag === "Some" ? config.value : {}), + ...overrides + }) + )) + +const mapProviderResponse = ( + inputLength: number, + response: typeof OpenAiSchema.CreateEmbeddingResponse.Type +): Effect.Effect => { + if (response.data.length !== inputLength) { + return Effect.fail( + invalidOutput("Provider returned " + response.data.length + " embeddings but expected " + inputLength) + ) + } + + const results = new Array>(inputLength) + const seen = new Set() + + for (const entry of response.data) { + if (!Number.isInteger(entry.index) || entry.index < 0 || entry.index >= inputLength) { + return Effect.fail(invalidOutput("Provider returned invalid embedding index: " + entry.index)) + } + if (seen.has(entry.index)) { + return Effect.fail(invalidOutput("Provider returned duplicate embedding index: " + entry.index)) + } + if (!Array.isArray(entry.embedding)) { + return Effect.fail(invalidOutput("Provider returned non-vector embedding at index " + entry.index)) + } + + seen.add(entry.index) + results[entry.index] = [...entry.embedding] + } + + if (seen.size !== inputLength) { + return Effect.fail( + invalidOutput("Provider returned embeddings for " + seen.size + " inputs but expected " + inputLength) + ) + } + + return Effect.succeed({ + results, + usage: { + inputTokens: response.usage?.prompt_tokens + } + }) +} + +const invalidOutput = (description: string): AiError.AiError => + AiError.make({ + module: "OpenAiEmbeddingModel", + method: "embedMany", + reason: new AiError.InvalidOutputError({ description }) + }) diff --git a/.repos/effect/packages/ai/openai/src/OpenAiError.ts b/.repos/effect/packages/ai/openai/src/OpenAiError.ts new file mode 100644 index 000000000..5e2a96d60 --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiError.ts @@ -0,0 +1,242 @@ +/** + * OpenAI error metadata augmentation. + * + * Provides OpenAI-specific metadata fields for AI error types through module + * augmentation, enabling typed access to OpenAI error details. + * + * @since 4.0.0 + */ + +/** + * OpenAI-specific error metadata fields. + * + * @category models + * @since 4.0.0 + */ +export type OpenAiErrorMetadata = { + /** + * The OpenAI error code returned by the API. + */ + readonly errorCode: string | null + /** + * The OpenAI error type returned by the API. + */ + readonly errorType: string | null + /** + * The unique request ID for debugging with OpenAI support. + */ + readonly requestId: string | null +} + +/** + * OpenAI-specific rate limit metadata fields. + * + * **Details** + * + * Extends base error metadata with rate limit specific information from + * OpenAI's rate limit headers. + * + * @category models + * @since 4.0.0 + */ +export type OpenAiRateLimitMetadata = OpenAiErrorMetadata & { + /** + * The rate limit type (e.g. "requests", "tokens"). + */ + readonly limit: string | null + /** + * Number of remaining requests in the current window. + */ + readonly remaining: number | null + /** + * Time until the request rate limit resets. + */ + readonly resetRequests: string | null + /** + * Time until the token rate limit resets. + */ + readonly resetTokens: string | null +} + +declare module "effect/unstable/ai/AiError" { + /** + * OpenAI metadata attached to `RateLimitError` values. + * + * **Details** + * + * Captures OpenAI error details together with rate limit header information + * from responses where the provider rejected the request because a limit was + * reached. + * + * @category models + * @since 4.0.0 + */ + export interface RateLimitErrorMetadata { + /** + * OpenAI-specific details for the rate limit response. + */ + readonly openai?: OpenAiRateLimitMetadata | null + } + + /** + * OpenAI metadata attached to `QuotaExhaustedError` values. + * + * **Details** + * + * Preserves provider error details for failures caused by exhausted account, + * billing, or usage quota. + * + * @category models + * @since 4.0.0 + */ + export interface QuotaExhaustedErrorMetadata { + /** + * OpenAI-specific details for the quota exhaustion response. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `AuthenticationError` values. + * + * **Details** + * + * Preserves provider error details for failed API key, authorization, or + * permission checks. + * + * @category models + * @since 4.0.0 + */ + export interface AuthenticationErrorMetadata { + /** + * OpenAI-specific details for the authentication failure. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `ContentPolicyError` values. + * + * **Details** + * + * Preserves provider error details when OpenAI rejects input or output because + * it violates a content policy. + * + * @category models + * @since 4.0.0 + */ + export interface ContentPolicyErrorMetadata { + /** + * OpenAI-specific details for the content policy response. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `InvalidRequestError` values. + * + * **Details** + * + * Preserves provider error details for malformed requests, unsupported + * parameters, or other request validation failures reported by OpenAI. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidRequestErrorMetadata { + /** + * OpenAI-specific details for the invalid request response. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `InternalProviderError` values. + * + * **Details** + * + * Preserves provider error details for OpenAI-side failures such as transient + * server errors. + * + * @category models + * @since 4.0.0 + */ + export interface InternalProviderErrorMetadata { + /** + * OpenAI-specific details for the internal provider response. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `InvalidOutputError` values. + * + * **Details** + * + * Preserves provider error details when an OpenAI response cannot be parsed or + * validated as the expected output. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidOutputErrorMetadata { + /** + * OpenAI-specific details for the invalid output response. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `StructuredOutputError` values. + * + * **Details** + * + * Preserves provider error details when OpenAI returns content that does not + * satisfy the requested structured output schema. + * + * @category models + * @since 4.0.0 + */ + export interface StructuredOutputErrorMetadata { + /** + * OpenAI-specific details for the structured output failure. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `UnsupportedSchemaError` values. + * + * **Details** + * + * Preserves provider error details when an unsupported schema failure is + * associated with an OpenAI response. + * + * @category models + * @since 4.0.0 + */ + export interface UnsupportedSchemaErrorMetadata { + /** + * OpenAI-specific details for the unsupported schema failure. + */ + readonly openai?: OpenAiErrorMetadata | null + } + + /** + * OpenAI metadata attached to `UnknownError` values. + * + * **Details** + * + * Preserves provider error details for OpenAI failures that do not map cleanly + * to a more specific AI error category. + * + * @category models + * @since 4.0.0 + */ + export interface UnknownErrorMetadata { + /** + * OpenAI-specific details for the unclassified provider failure. + */ + readonly openai?: OpenAiErrorMetadata | null + } +} diff --git a/.repos/effect/packages/ai/openai/src/OpenAiLanguageModel.ts b/.repos/effect/packages/ai/openai/src/OpenAiLanguageModel.ts new file mode 100644 index 000000000..4addf9f5b --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiLanguageModel.ts @@ -0,0 +1,3213 @@ +/** + * The `OpenAiLanguageModel` module provides the OpenAI Responses API + * implementation of Effect AI's `LanguageModel` service. It translates Effect + * AI prompts, files, tools, structured output requests, reasoning metadata, and + * provider options into OpenAI response requests, then converts OpenAI + * non-streaming or streaming response results back into Effect AI response + * content and metadata. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as DateTime from "effect/DateTime" +import * as Effect from "effect/Effect" +import * as Encoding from "effect/Encoding" +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 Redactable from "effect/Redactable" +import * as Schema from "effect/Schema" +import * as AST from "effect/SchemaAST" +import * as SchemaIssue from "effect/SchemaIssue" +import * as Stream from "effect/Stream" +import type { Span } from "effect/Tracer" +import type { DeepMutable, Mutable, Simplify } from "effect/Types" +import * as AiError from "effect/unstable/ai/AiError" +import * as IdGenerator from "effect/unstable/ai/IdGenerator" +import * as LanguageModel from "effect/unstable/ai/LanguageModel" +import * as AiModel from "effect/unstable/ai/Model" +import { toCodecOpenAI } from "effect/unstable/ai/OpenAiStructuredOutput" +import type * as Prompt from "effect/unstable/ai/Prompt" +import type * as Response from "effect/unstable/ai/Response" +import * as Tool from "effect/unstable/ai/Tool" +import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import * as Generated from "./Generated.ts" +import * as InternalUtilities from "./internal/utilities.ts" +import { OpenAiClient } from "./OpenAiClient.ts" +import type * as OpenAiSchema from "./OpenAiSchema.ts" +import { addGenAIAnnotations } from "./OpenAiTelemetry.ts" +import type * as OpenAiTool from "./OpenAiTool.ts" + +const formatIssue = SchemaIssue.makeFormatterDefault() + +const ResponseModelIds = Generated.ModelIdsResponses.members[1] +const SharedModelIds = Generated.ModelIdsShared.members[1] + +/** + * OpenAI model identifiers supported by the Responses API language model. + * + * @category models + * @since 4.0.0 + */ +export type Model = typeof ResponseModelIds.Encoded | typeof SharedModelIds.Encoded + +/** + * Image detail level for vision requests. + */ +type ImageDetail = "auto" | "low" | "high" + +type PromptCacheBreakpoint = { readonly mode: "explicit" } + +// ============================================================================= +// Configuration +// ============================================================================= + +/** + * Context service for OpenAI language model configuration. + * + * **When to use** + * + * Use when you need to provide OpenAI Responses API request defaults through + * Effect context for language model operations. + * + * **Details** + * + * Config values are merged with the config object passed to `model`, `make`, or + * `layer`, with scoped context values taking precedence. + * + * @see {@link withConfigOverride} for scoping language model request overrides + * + * @category services + * @since 4.0.0 + */ +export class Config extends Context.Service< + Config, + Simplify< + & Partial< + Omit< + typeof OpenAiSchema.CreateResponse.Encoded, + "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 + } + > +>()("@effect/ai-openai/OpenAiLanguageModel/Config") {} + +// ============================================================================= +// Provider Options / Metadata +// ============================================================================= + +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 models + * @since 4.0.0 + */ + export interface FilePartOptions extends ProviderOptions { + /** + * Provider-specific file options for the OpenAI Responses API. + */ + readonly openai?: { + /** + * The detail level of the image to be sent to the model. One of `high`, `low`, or `auto`. Defaults to `auto`. + */ + readonly imageDetail?: ImageDetail | null + } | null + } + + /** + * OpenAI-specific options for reasoning prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartOptions extends ProviderOptions { + /** + * Provider-specific reasoning options for the OpenAI Responses API. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The encrypted content of the reasoning item - populated when a response + * is generated with `reasoning.encrypted_content` in the `include` + * parameter. + */ + readonly encryptedContent?: string | null + } | null + } + + /** + * OpenAI-specific options for assistant tool-call prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartOptions extends ProviderOptions { + /** + * Provider-specific tool-call options for the OpenAI Responses API. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The status of item. + */ + readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null + /** + * The ID of the approval request. + */ + readonly approvalRequestId?: string | null + } | null + } + + /** + * OpenAI-specific options for tool-result prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface ToolResultPartOptions extends ProviderOptions { + /** + * Provider-specific tool-result options for the OpenAI Responses API. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The status of item. + */ + readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null + /** + * The ID of the approval request. + */ + readonly approvalId?: string | null + } | null + } + + /** + * OpenAI-specific options for text prompt parts. + * + * @category models + * @since 4.0.0 + */ + export interface TextPartOptions extends ProviderOptions { + /** + * Provider-specific text options for the OpenAI Responses API. + */ + readonly openai?: { + /** + * The ID of the item to reference. + */ + readonly itemId?: string | null + /** + * The status of item. + */ + readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null + /** + * 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 + } +} + +declare module "effect/unstable/ai/Response" { + /** + * OpenAI metadata attached to a complete text response part. + * + * @category models + * @since 4.0.0 + */ + export interface TextPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the text part. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the text part. + */ + readonly itemId?: string | null + /** + * If the model emits a refusal content part, the refusal explanation + * from the model will be contained in the metadata of an empty text + * part. + */ + readonly refusal?: string | null + /** + * The status of item. + */ + readonly status?: typeof OpenAiSchema.MessageStatus.Encoded | null + /** + * The text content part annotations. + */ + readonly annotations?: ReadonlyArray | null + } + } + + /** + * OpenAI metadata emitted when a streamed text part starts. + * + * @category models + * @since 4.0.0 + */ + export interface TextStartPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed text start. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the streamed text part. + */ + readonly itemId?: string | null + } | null + } + + /** + * OpenAI metadata emitted when a streamed text part ends. + * + * @category models + * @since 4.0.0 + */ + export interface TextEndPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed text end. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the streamed text part. + */ + readonly itemId?: string | null + /** + * The annotations collected for the completed streamed text part. + */ + readonly annotations?: ReadonlyArray | null + } | null + } + + /** + * OpenAI metadata attached to a complete reasoning response part. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the reasoning part. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + /** + * Encrypted reasoning content that can be sent back in later requests. + */ + readonly encryptedContent?: string | null + } | null + } + + /** + * OpenAI metadata emitted when a streamed reasoning part starts. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningStartPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning start. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + /** + * Encrypted reasoning content that can be sent back in later requests. + */ + readonly encryptedContent?: string | null + } | null + } + + /** + * OpenAI metadata emitted for a streamed reasoning delta. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningDeltaPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning delta. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + } | null + } + + /** + * OpenAI metadata emitted when a streamed reasoning part ends. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningEndPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning end. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the reasoning part. + */ + readonly itemId?: string | null + /** + * Encrypted reasoning content that can be sent back in later requests. + */ + readonly encryptedContent?: string + } | null + } + + /** + * OpenAI metadata attached to tool-call response parts. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the tool call. + */ + readonly openai?: { + /** + * The OpenAI item ID associated with the tool call. + */ + readonly itemId?: string | null + } | null + } + + /** + * OpenAI metadata attached to document source citations. + * + * @category models + * @since 4.0.0 + */ + export interface DocumentSourcePartMetadata extends ProviderMetadata { + /** + * Provider-specific citation metadata for the OpenAI Responses API. + */ + readonly openai?: + | { + /** + * Identifies a citation to an uploaded file. + */ + readonly type: "file_citation" + /** + * The index of the file in the list of files. + */ + readonly index: number + /** + * The ID of the file. + */ + readonly fileId: string + } + | { + /** + * Identifies a citation to a generated file path. + */ + readonly type: "file_path" + /** + * The index of the file in the list of files. + */ + readonly index: number + /** + * The ID of the file. + */ + readonly fileId: string + } + | { + /** + * Identifies a citation to a file inside a container. + */ + readonly type: "container_file_citation" + /** + * The ID of the file. + */ + readonly fileId: string + /** + * The ID of the container file. + */ + readonly containerId: string + } + | null + } + + /** + * OpenAI metadata attached to URL source citations. + * + * @category models + * @since 4.0.0 + */ + export interface UrlSourcePartMetadata extends ProviderMetadata { + /** + * Provider-specific URL citation metadata for the OpenAI Responses API. + */ + readonly openai?: { + /** + * Identifies a citation to a URL. + */ + readonly type: "url_citation" + /** + * The index of the first character of the URL citation in the message. + */ + readonly startIndex: number + /** + * The index of the last character of the URL citation in the message. + */ + readonly endIndex: number + } | null + } + + /** + * OpenAI metadata attached to finish response parts. + * + * @category models + * @since 4.0.0 + */ + export interface FinishPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned when generation finishes. + */ + readonly openai?: { + /** + * The service tier reported by OpenAI for the response. + */ + readonly serviceTier?: "default" | "auto" | "flex" | "scale" | "priority" | null + } | null + } +} + +// ============================================================================= +// Language Model +// ============================================================================= + +/** + * Creates an OpenAI model descriptor that can be provided with + * `Effect.provide`. + * + * **When to use** + * + * Use when you want an OpenAI language model value that carries provider and + * model metadata and can be supplied directly to an Effect program. + * + * @see {@link layer} for creating a `LanguageModel.LanguageModel` layer directly + * @see {@link make} for constructing the language model service effectfully + * + * @category constructors + * @since 4.0.0 + */ +export const model = ( + model: (string & {}) | Model, + config?: Omit +): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> => + AiModel.make("openai", model, layer({ model, config })) + +// TODO +// /** +// * @since 4.0.0 +// * @category constructors +// */ +// export const modelWithTokenizer = ( +// model: (string & {}) | Model, +// config?: Omit +// ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> => +// AiModel.make("openai", model, layerWithTokenizer({ model, config })) + +/** + * Creates an OpenAI `LanguageModel` service from a model identifier and + * optional request defaults. + * + * **When to use** + * + * Use to construct an OpenAI Responses API language model service backed by + * `OpenAiClient`. + * + * **Details** + * + * The returned effect requires `OpenAiClient`. Request defaults from the + * `config` option are merged with any `Config` service in the context, with + * context values taking precedence. The service supports both `generateText` + * and `streamText`. + * + * @see {@link layer} for providing the service as a `Layer` + * @see {@link model} for creating a model descriptor for `Effect.provide` + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { + readonly model: (string & {}) | Model + readonly config?: Omit | undefined +}): Effect.fn.Return { + const client = yield* OpenAiClient + + const makeConfig = Effect.gen(function*() { + const services = yield* Effect.context() + return { model, ...providerConfig, ...Context.getOrUndefined(services, Config) } + }) + + const makeRequest = Effect.fnUntraced( + function*>({ config, options, toolNameMapper }: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return { + const include = new Set() + const capabilities = getModelCapabilities(config.model as string) + const messages = yield* prepareMessages({ + config, + options, + capabilities, + include, + toolNameMapper + }) + const { toolChoice, tools } = yield* prepareTools({ + config, + options, + toolNameMapper + }) + const responseFormat = yield* prepareResponseFormat({ + config, + options + }) + const { fileIdPrefixes: _fip, strictJsonSchema: _sjs, ...apiConfig } = config + const request: Mutable = { + ...apiConfig, + input: messages, + include: include.size > 0 ? Array.from(include) : undefined, + text: { + verbosity: config.text?.verbosity ?? undefined, + format: responseFormat + } + } + if (tools) request.tools = tools + if (toolChoice) request.tool_choice = toolChoice + if (options.previousResponseId) request.previous_response_id = options.previousResponseId + return request + } + ) + + return yield* LanguageModel.make({ + codecTransformer: toCodecOpenAI, + generateText: Effect.fnUntraced( + function*(options) { + const config = yield* makeConfig + const toolNameMapper = new Tool.NameMapper(options.tools) + const request = yield* makeRequest({ config, options, toolNameMapper }) + annotateRequest(options.span, request) + const [rawResponse, response] = yield* client.createResponse(request) + annotateResponse(options.span, rawResponse) + return yield* makeResponse({ + options, + rawResponse, + response, + toolNameMapper + }) + } + ), + streamText: Effect.fnUntraced( + function*(options) { + const config = yield* makeConfig + const toolNameMapper = new Tool.NameMapper(options.tools) + const request = yield* makeRequest({ config, options, toolNameMapper }) + annotateRequest(options.span, request) + const [response, stream] = yield* client.createResponseStream(request) + return yield* makeStreamResponse({ + stream, + response, + config, + options, + toolNameMapper + }) + }, + (effect, options) => + effect.pipe( + Stream.unwrap, + Stream.map((response) => { + annotateStreamResponse(options.span, response) + return response + }) + ) + ) + }) +}) + +/** + * Creates a layer that provides the OpenAI `LanguageModel.LanguageModel` + * service. + * + * **When to use** + * + * Use when composing application layers and you want OpenAI to satisfy + * `LanguageModel.LanguageModel` while supplying `OpenAiClient` from another + * layer. + * + * **Details** + * + * The `config` option supplies request defaults for the selected model. Scoped + * values from `withConfigOverride` are merged when each request is built and + * take precedence over these defaults. + * + * @see {@link make} for constructing the language model service effectfully + * @see {@link model} for creating a model descriptor for `Effect.provide` + * @see {@link withConfigOverride} for scoped request configuration overrides + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: { + readonly model: (string & {}) | Model + readonly config?: Omit | undefined +}): Layer.Layer => + Layer.effect(LanguageModel.LanguageModel, make(options)) + +/** + * Provides scoped config overrides for OpenAI language model operations. + * + * **When to use** + * + * Use to apply OpenAI Responses API config overrides around one or more + * language model operations without changing the defaults passed to `model`, + * `make`, or `layer`. + * + * **Details** + * + * The override is dual, so it can be used in pipe form or as + * `withConfigOverride(effect, overrides)`. Overrides are merged with any + * existing `Config` service in the current context, and the override values take + * precedence. + * + * @see {@link Config} for the scoped configuration service consumed by this function + * + * @category configuration + * @since 4.0.0 + */ +export const withConfigOverride: { + (overrides: typeof Config.Service): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, overrides: typeof Config.Service): Effect.Effect> +} = dual< + ( + overrides: typeof Config.Service + ) => (self: Effect.Effect) => Effect.Effect>, + (self: Effect.Effect, overrides: typeof Config.Service) => Effect.Effect> +>(2, (self, overrides) => + Effect.flatMap( + Effect.serviceOption(Config), + (config) => + Effect.provideService(self, Config, { + ...(config._tag === "Some" ? config.value : {}), + ...overrides + }) + )) + +// ============================================================================= +// Prompt Conversion +// ============================================================================= + +const getSystemMessageMode = (model: string): "system" | "developer" => + model.startsWith("o") || + model.startsWith("gpt-5") || + model.startsWith("codex-") || + model.startsWith("computer-use") + ? "developer" + : "system" + +const prepareMessages = Effect.fnUntraced( + function*>({ + config, + options, + capabilities, + include, + toolNameMapper + }: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions + readonly include: Set + readonly capabilities: ModelCapabilities + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return, AiError.AiError> { + const processedApprovalIds = new Set() + + const hasConversation = Predicate.isNotNullish(config.conversation) + + // Provider-Defined Tools + const applyPatchTool = options.tools.find((tool): tool is ReturnType => + Tool.isProviderDefined(tool) && tool.name === "OpenAiApplyPatch" + ) + const codeInterpreterTool = options.tools.find((tool): tool is ReturnType => + Tool.isProviderDefined(tool) && tool.name === "OpenAiCodeInterpreter" + ) + const shellTool = options.tools.find((tool): tool is ReturnType => + Tool.isProviderDefined(tool) && tool.name === "OpenAiShell" + ) + const localShellTool = options.tools.find((tool): tool is ReturnType => + Tool.isProviderDefined(tool) && tool.name === "OpenAiLocalShell" + ) + const webSearchTool = options.tools.find((tool): tool is ReturnType => + Tool.isProviderDefined(tool) && tool.name === "OpenAiWebSearch" + ) + const webSearchPreviewTool = options.tools.find((tool): tool is ReturnType => + Tool.isProviderDefined(tool) && tool.name === "OpenAiWebSearchPreview" + ) + + // Handle Included Features + if (Predicate.isNotUndefined(config.top_logprobs)) { + include.add("message.output_text.logprobs") + } + if (config.store === false && capabilities.isReasoningModel) { + include.add("reasoning.encrypted_content") + } + if (codeInterpreterTool) { + include.add("code_interpreter_call.outputs") + } + if (webSearchTool || webSearchPreviewTool) { + include.add("web_search_call.action.sources") + } + + const messages: Array = [] + const prompt = options.incrementalPrompt ?? options.prompt + + for (const message of prompt.content) { + switch (message.role) { + case "system": { + messages.push({ + role: getSystemMessageMode(config.model as string), + content: [{ + type: "input_text", + text: message.content, + ...getPromptCacheBreakpoint(message) + }] + }) + break + } + + case "user": { + const content: Array = [] + + for (let index = 0; index < message.content.length; index++) { + const part = message.content[index] + + switch (part.type) { + case "text": { + content.push({ + type: "input_text", + text: part.text, + ...getPromptCacheBreakpoint(part) + }) + break + } + + case "file": { + if (part.mediaType.startsWith("image/")) { + const detail = getImageDetail(part) + const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType + + 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}` + content.push({ type: "input_image", image_url: imageUrl, detail }) + } + } else if (part.mediaType === "application/pdf") { + if (typeof part.data === "string" && isFileId(part.data, config)) { + content.push({ type: "input_file", file_id: part.data }) + } + + if (part.data instanceof URL) { + content.push({ type: "input_file", file_url: part.data.toString() }) + } + + if (part.data instanceof Uint8Array) { + const base64 = Encoding.encodeBase64(part.data) + const fileName = part.fileName ?? `part-${index}.pdf` + const fileData = `data:application/pdf;base64,${base64}` + content.push({ type: "input_file", filename: fileName, file_data: fileData }) + } + } else { + return yield* AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareMessages", + reason: new AiError.InvalidRequestError({ + description: `Detected unsupported media type for file: '${part.mediaType}'` + }) + }) + } + } + } + } + + messages.push({ role: "user", content }) + + break + } + + case "assistant": { + const reasoningMessages: Record> = Object + .create(null) + + for (const part of message.content) { + switch (part.type) { + case "text": { + const id = getItemId(part) + + // When in conversation mode, skip items that already exist in the + // conversation context to avoid "Duplicate item found" errors + if (hasConversation && Predicate.isNotNull(id)) { + break + } + + if (config.store === true && Predicate.isNotNull(id)) { + messages.push({ type: "item_reference", id }) + break + } + + messages.push({ + id: id!, + type: "message", + role: "assistant", + status: part.options.openai?.status ?? "completed", + content: [{ + type: "output_text", + text: part.text, + annotations: part.options.openai?.annotations ?? [], + logprobs: [] + }] + }) + + break + } + + case "reasoning": { + const id = getItemId(part) + const encryptedContent = getEncryptedContent(part) + + if (hasConversation && Predicate.isNotNull(id)) { + break + } + + if (Predicate.isNotNull(id)) { + const message = reasoningMessages[id] + + if (config.store === true) { + // Use item references to refer to reasoning (single reference) + // when the first part is encountered + if (Predicate.isUndefined(message)) { + messages.push({ type: "item_reference", id }) + + // Store unused reasoning message to mark its id as used + reasoningMessages[id] = { + type: "reasoning", + id, + summary: [] + } + } + } else { + const summaryParts: Array = [] + + if (part.text.length > 0) { + summaryParts.push({ type: "summary_text", text: part.text }) + } + + if (Predicate.isUndefined(message)) { + reasoningMessages[id] = { + type: "reasoning", + id, + summary: summaryParts, + ...(Predicate.isNotNull(encryptedContent) + ? { encrypted_content: encryptedContent } + : undefined) + } + + messages.push(reasoningMessages[id]) + } else { + message.summary.push(...summaryParts) + + // Update encrypted content to enable setting it in the + // last summary part + if (Predicate.isNotNull(encryptedContent)) { + message.encrypted_content = encryptedContent + } + } + } + } + + break + } + + case "tool-call": { + const id = getItemId(part) + const status = getStatus(part) + + if (hasConversation && Predicate.isNotNull(id)) { + break + } + + if (config.store && Predicate.isNotNull(id)) { + messages.push({ type: "item_reference", id }) + break + } + + if (part.providerExecuted) { + break + } + + const toolName = toolNameMapper.getProviderName(part.name) + + if (Predicate.isNotUndefined(localShellTool) && toolName === "local_shell") { + const params = yield* Schema.decodeUnknownEffect(localShellTool.parametersSchema)(part.params).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareMessages", + reason: new AiError.ToolParameterValidationError({ + toolName: "local_shell", + toolParams: part.params as Schema.Json, + description: error.message + }) + }) + ) + ) + + messages.push({ + id: id!, + type: "local_shell_call", + call_id: part.id, + status: status ?? "completed", + action: params.action + }) + + break + } + + if (Predicate.isNotUndefined(shellTool) && toolName === "shell") { + const params = yield* Schema.decodeUnknownEffect(shellTool.parametersSchema)(part.params).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareMessages", + reason: new AiError.ToolParameterValidationError({ + toolName: "shell", + toolParams: part.params as Schema.Json, + description: error.message + }) + }) + ) + ) + + messages.push({ + id: id!, + type: "shell_call", + call_id: part.id, + status: status ?? "completed", + action: params.action + }) + + break + } + + messages.push({ + type: "function_call", + name: toolName, + call_id: part.id, + arguments: JSON.stringify(part.params), + ...(Predicate.isNotNull(id) ? { id } : {}), + ...(Predicate.isNotNull(status) ? { status } : {}) + }) + + break + } + + // Assistant tool-result parts are always provider executed + case "tool-result": { + // Skip execution denied results - these have no corresponding + // item in OpenAI's store + if ( + Predicate.hasProperty(part.result, "type") && + part.result.type === "execution-denied" + ) { + break + } + + if (hasConversation) { + break + } + + if (config.store === true) { + const id = getItemId(part) ?? part.id + messages.push({ type: "item_reference", id }) + } + } + } + } + + break + } + + case "tool": { + for (const part of message.content) { + if (part.type === "tool-approval-response") { + if (processedApprovalIds.has(part.approvalId)) { + continue + } + + processedApprovalIds.add(part.approvalId) + + if (config.store === true) { + messages.push({ type: "item_reference", id: part.approvalId }) + } + + messages.push({ + type: "mcp_approval_response", + approval_request_id: part.approvalId, + approve: part.approved + } as any) + + continue + } + + // Skip execution-denied results that already have an approvalId - + // this indicates that the part was already handled via tool-approval-response + if ( + Predicate.hasProperty(part.result, "type") && + part.result.type === "execution-denied" + ) { + if (Predicate.isNotNullish(part.options.openai?.approvalId)) { + continue + } + } + + const id = getItemId(part) ?? part.id + const status = getStatus(part) + const toolName = toolNameMapper.getProviderName(part.name) + + if (Predicate.isNotUndefined(applyPatchTool) && toolName === "apply_patch") { + messages.push({ + id, + type: "apply_patch_call_output", + call_id: part.id, + ...(part.result as any) + }) + continue + } + + if (Predicate.isNotUndefined(shellTool) && toolName === "shell") { + messages.push({ + id, + type: "shell_call_output", + call_id: part.id, + output: part.result as any, + ...(Predicate.isNotNull(status) ? { status } : {}) + }) + continue + } + + if (Predicate.isNotUndefined(localShellTool) && toolName === "local_shell") { + messages.push({ + id, + type: "local_shell_call_output", + call_id: part.id, + 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), + ...(Predicate.isNotNull(status) ? { status } : {}) + }) + } + + break + } + } + } + + return messages + } +) + +// ============================================================================= +// HTTP Details +// ============================================================================= + +const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +const buildHttpResponseDetails = ( + response: HttpClientResponse.HttpClientResponse +): typeof Response.HttpResponseDetails.Type => ({ + status: response.status, + headers: Redactable.redact(response.headers) as Record +}) + +// ============================================================================= +// Response Conversion +// ============================================================================= + +type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type + +type KnownResponseStreamEventType = + | "response.created" + | "response.completed" + | "response.incomplete" + | "response.failed" + | "response.output_item.added" + | "response.output_item.done" + | "response.output_text.delta" + | "response.output_text.annotation.added" + | "response.reasoning_summary_part.added" + | "response.reasoning_summary_part.done" + | "response.reasoning_summary_text.delta" + | "response.function_call_arguments.delta" + | "response.function_call_arguments.done" + | "response.code_interpreter_call_code.delta" + | "response.code_interpreter_call_code.done" + | "response.apply_patch_call_operation_diff.delta" + | "response.apply_patch_call_operation_diff.done" + | "response.image_generation_call.partial_image" + | "error" + +type KnownResponseStreamEvent = Extract + +const knownResponseStreamEventTypes = new Set([ + "response.created", + "response.completed", + "response.incomplete", + "response.failed", + "response.output_item.added", + "response.output_item.done", + "response.output_text.delta", + "response.output_text.annotation.added", + "response.reasoning_summary_part.added", + "response.reasoning_summary_part.done", + "response.reasoning_summary_text.delta", + "response.function_call_arguments.delta", + "response.function_call_arguments.done", + "response.code_interpreter_call_code.delta", + "response.code_interpreter_call_code.done", + "response.apply_patch_call_operation_diff.delta", + "response.apply_patch_call_operation_diff.done", + "response.image_generation_call.partial_image", + "error" +]) + +const isKnownResponseStreamEvent = ( + event: ResponseStreamEvent +): event is KnownResponseStreamEvent => knownResponseStreamEventTypes.has(event.type as KnownResponseStreamEventType) + +const makeResponse = Effect.fnUntraced( + function*>({ + options, + rawResponse, + response, + toolNameMapper + }: { + readonly options: LanguageModel.ProviderOptions + readonly rawResponse: OpenAiSchema.Response + readonly response: HttpClientResponse.HttpClientResponse + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return< + Array, + AiError.AiError, + IdGenerator.IdGenerator + > { + const idGenerator = yield* IdGenerator.IdGenerator + + const approvalRequests = getApprovalRequestIdMapping(options.prompt) + + const webSearchTool = options.tools.find((tool) => + Tool.isProviderDefined(tool) && + (tool.name === "OpenAiWebSearch" || + tool.name === "OpenAiWebSearchPreview") + ) as Tool.AnyProviderDefined | undefined + + let hasToolCalls = false + const parts: Array = [] + + const createdAt = new Date(rawResponse.created_at * 1000) + parts.push({ + type: "response-metadata", + id: rawResponse.id, + modelId: rawResponse.model as string, + timestamp: DateTime.formatIso(DateTime.fromDateUnsafe(createdAt)), + request: buildHttpRequestDetails(response.request) + }) + + for (const part of rawResponse.output) { + switch (part.type) { + case "apply_patch_call": { + const toolName = toolNameMapper.getCustomName("apply_patch") + parts.push({ + type: "tool-call", + id: part.call_id, + name: toolName, + params: { call_id: part.call_id, operation: part.operation }, + metadata: { openai: makeItemIdMetadata(part.id) } + }) + break + } + + case "code_interpreter_call": { + const toolName = toolNameMapper.getCustomName("code_interpreter") + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params: { code: part.code, container_id: part.container_id }, + providerExecuted: true + }) + parts.push({ + type: "tool-result", + id: part.id, + name: toolName, + isFailure: false, + result: { outputs: part.outputs }, + providerExecuted: true + }) + break + } + + case "file_search_call": { + const toolName = toolNameMapper.getCustomName("file_search") + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params: {}, + providerExecuted: true + }) + parts.push({ + type: "tool-result", + id: part.id, + name: toolName, + isFailure: false, + result: { + status: part.status, + queries: part.queries, + results: part.results ?? null + }, + providerExecuted: true + }) + break + } + + case "function_call": { + hasToolCalls = true + + const toolName = part.name + + const toolParams = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(part.arguments), + catch: (cause) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "makeResponse", + reason: new AiError.ToolParameterValidationError({ + toolName, + toolParams: {}, + description: `Faled to securely JSON parse tool parameters: ${cause}` + }) + }) + }) + + const params = yield* transformToolCallParams(options.tools, part.name, toolParams) + + parts.push({ + type: "tool-call", + id: part.call_id, + name: toolName, + params, + metadata: { openai: makeItemIdMetadata(part.id) } + }) + break + } + + case "image_generation_call": { + const toolName = toolNameMapper.getCustomName("image_generation") + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params: {}, + providerExecuted: true + }) + parts.push({ + type: "tool-result", + id: part.id, + name: toolName, + isFailure: false, + result: { result: part.result } + }) + break + } + + case "local_shell_call": { + const toolName = toolNameMapper.getCustomName("local_shell") + parts.push({ + type: "tool-call", + id: part.call_id, + name: toolName, + params: { action: part.action }, + metadata: { openai: makeItemIdMetadata(part.id) } + }) + break + } + + case "mcp_call": { + const toolId = Predicate.isNotNullish(part.approval_request_id) + ? (approvalRequests.get(part.approval_request_id) ?? part.id) + : part.id + + const { toolName, params } = yield* normalizeMcpToolCall({ + toolNameMapper, + toolParams: part.arguments, + method: "makeResponse" + }) + + parts.push({ + type: "tool-call", + id: toolId, + name: toolName, + params, + providerExecuted: true + }) + + parts.push({ + type: "tool-result", + id: toolId, + name: toolName, + isFailure: false, + providerExecuted: true, + result: { + type: "mcp_call", + name: part.name, + arguments: part.arguments, + server_label: part.server_label, + ...(Predicate.isNotNullish(part.output) ? { output: part.output } : undefined), + ...(Predicate.isNotNullish(part.error) ? { error: part.error } : undefined) + }, + metadata: { openai: makeItemIdMetadata(part.id) } + }) + + break + } + + case "mcp_list_tools": { + // Skip + break + } + + case "mcp_approval_request": { + const approvalRequestId = (part as any).approval_request_id ?? part.id + const toolId = yield* idGenerator.generateId() + + const { toolName, params } = yield* normalizeMcpToolCall({ + toolNameMapper, + toolParams: part.arguments, + method: "makeResponse" + }) + + parts.push({ + type: "tool-call", + id: toolId, + name: toolName, + params, + providerExecuted: true + }) + + parts.push({ + type: "tool-approval-request", + toolCallId: toolId, + approvalId: approvalRequestId + }) + + break + } + + case "message": { + for (const contentPart of part.content) { + switch (contentPart.type) { + case "output_text": { + const annotations = contentPart.annotations.length > 0 + ? { annotations: contentPart.annotations as any } + : undefined + + parts.push({ + type: "text", + text: contentPart.text, + metadata: { + openai: { + ...makeItemIdMetadata(part.id), + ...annotations + } + } + }) + for (const annotation of contentPart.annotations) { + if (annotation.type === "container_file_citation") { + parts.push({ + type: "source", + sourceType: "document", + id: yield* idGenerator.generateId(), + mediaType: "text/plain", + title: annotation.filename, + fileName: annotation.filename, + metadata: { + openai: { + type: annotation.type, + fileId: annotation.file_id, + containerId: annotation.container_id + } + } + }) + } + if (annotation.type === "file_citation") { + parts.push({ + type: "source", + sourceType: "document", + id: yield* idGenerator.generateId(), + mediaType: "text/plain", + title: annotation.filename, + fileName: annotation.filename, + metadata: { + openai: { + type: annotation.type, + fileId: annotation.file_id, + index: annotation.index + } + } + }) + } + if (annotation.type === "file_path") { + parts.push({ + type: "source", + sourceType: "document", + id: yield* idGenerator.generateId(), + mediaType: "application/octet-stream", + title: annotation.file_id, + fileName: annotation.file_id, + metadata: { + openai: { + type: annotation.type, + fileId: annotation.file_id, + index: annotation.index + } + } + }) + } + if (annotation.type === "url_citation") { + parts.push({ + type: "source", + sourceType: "url", + id: yield* idGenerator.generateId(), + url: annotation.url, + title: annotation.title, + metadata: { + openai: { + type: annotation.type, + startIndex: annotation.start_index, + endIndex: annotation.end_index + } + } + }) + } + } + break + } + case "refusal": { + parts.push({ + type: "text", + text: "", + metadata: { openai: { refusal: contentPart.refusal } } + }) + break + } + } + } + break + } + + case "reasoning": { + const metadata = { + openai: { + ...makeItemIdMetadata(part.id), + ...makeEncryptedContentMetadata(part.encrypted_content) + } + } + // If there are no summary parts, we have to add an empty one to + // propagate the part identifier and encrypted content + if (part.summary.length === 0) { + parts.push({ type: "reasoning", text: "", metadata }) + } else { + for (const summary of part.summary) { + parts.push({ type: "reasoning", text: summary.text, metadata }) + } + } + break + } + + case "shell_call": { + const toolName = toolNameMapper.getCustomName("shell") + parts.push({ + type: "tool-call", + id: part.call_id, + name: toolName, + params: { action: part.action }, + metadata: { openai: makeItemIdMetadata(part.id) } + }) + break + } + + case "web_search_call": { + const toolName = toolNameMapper.getCustomName( + webSearchTool?.name ?? "web_search" + ) + parts.push({ + type: "tool-call", + id: part.id, + name: toolName, + params: webSearchTool?.name === "OpenAiWebSearchPreview" + ? {} + : { action: part.action }, + providerExecuted: true + }) + parts.push({ + type: "tool-result", + id: part.id, + name: toolName, + isFailure: false, + result: { action: part.action, status: part.status }, + providerExecuted: true + }) + break + } + } + } + + const finishReason = InternalUtilities.resolveFinishReason( + rawResponse.incomplete_details?.reason, + hasToolCalls + ) + + parts.push({ + type: "finish", + reason: finishReason, + usage: getUsage(rawResponse.usage), + response: buildHttpResponseDetails(response), + ...toServiceTier(rawResponse.service_tier) + }) + + return parts + } +) + +const makeStreamResponse = Effect.fnUntraced( + function*>({ + stream, + response, + config, + options, + toolNameMapper + }: { + readonly config: typeof Config.Service + readonly stream: Stream.Stream + readonly response: HttpClientResponse.HttpClientResponse + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper + }): Effect.fn.Return< + Stream.Stream, + AiError.AiError, + IdGenerator.IdGenerator + > { + const idGenerator = yield* IdGenerator.IdGenerator + + const approvalRequests = getApprovalRequestIdMapping(options.prompt) + const streamApprovalRequests = new Map() + + let hasToolCalls = false + + // Track annotations for current message to include in text-end metadata + const activeAnnotations: Array = [] + + type ReasoningSummaryPartStatus = "active" | "can-conclude" | "concluded" + type ReasoningPart = { + encryptedContent: string | undefined + summaryParts: Record + } + + // Track active reasoning items with state machine for proper concluding logic + const activeReasoning: Record = Object.create(null) + + const getOrCreateReasoningPart = ( + itemId: string, + encryptedContent?: string | null + ): ReasoningPart => { + const activePart = activeReasoning[itemId] + if (Predicate.isNotUndefined(activePart)) { + if (Predicate.isNotNullish(encryptedContent)) { + activePart.encryptedContent = encryptedContent + } + return activePart + } + + const reasoningPart: ReasoningPart = { + encryptedContent: Predicate.isNotNullish(encryptedContent) ? encryptedContent : undefined, + summaryParts: {} + } + activeReasoning[itemId] = reasoningPart + return reasoningPart + } + + // Track active tool calls with optional provider-specific state + const activeToolCalls: Record = {} + + const webSearchTool = options.tools.find((tool) => + Tool.isProviderDefined(tool) && + (tool.name === "OpenAiWebSearch" || + tool.name === "OpenAiWebSearchPreview") + ) as ReturnType | ReturnType | undefined + + return stream.pipe( + Stream.mapEffect(Effect.fnUntraced(function*(event) { + const parts: Array = [] + + if (!isKnownResponseStreamEvent(event)) { + return parts + } + + switch (event.type) { + case "response.created": { + const createdAt = new Date(event.response.created_at * 1000) + parts.push({ + type: "response-metadata", + id: event.response.id, + modelId: event.response.model, + timestamp: DateTime.formatIso(DateTime.fromDateUnsafe(createdAt)), + request: buildHttpRequestDetails(response.request) + }) + break + } + + case "error": { + parts.push({ type: "error", error: event }) + break + } + + case "response.completed": + case "response.incomplete": { + parts.push({ + type: "finish", + reason: InternalUtilities.resolveFinishReason( + event.response.incomplete_details?.reason, + hasToolCalls + ), + usage: getUsage(event.response.usage), + response: buildHttpResponseDetails(response), + ...toServiceTier(event.response.service_tier) + }) + 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": { + const toolId = event.item.call_id + const toolName = toolNameMapper.getCustomName("apply_patch") + const operation = event.item.operation + activeToolCalls[event.output_index] = { + id: toolId, + name: toolName, + applyPatch: { + hasDiff: operation.type !== "delete_file", + endEmitted: operation.type === "delete_file" + } + } + parts.push({ + type: "tool-params-start", + id: toolId, + name: toolName + }) + + if (operation.type === "delete_file") { + parts.push({ + type: "tool-params-delta", + id: toolId, + delta: JSON.stringify({ + call_id: toolId, + operation: operation + }) + }) + parts.push({ + type: "tool-params-end", + id: toolId + }) + } else { + parts.push({ + type: "tool-params-delta", + id: toolId, + delta: `{"call_id":"${InternalUtilities.escapeJSONDelta(toolId)}",` + + `"operation":{"type":"${InternalUtilities.escapeJSONDelta(operation.type)}",` + + `"path":"${InternalUtilities.escapeJSONDelta(operation.path)}","diff":"` + }) + } + break + } + + case "code_interpreter_call": { + const toolName = toolNameMapper.getCustomName("code_interpreter") + activeToolCalls[event.output_index] = { + id: event.item.id, + name: toolName, + codeInterpreter: { containerId: event.item.container_id } + } + parts.push({ + type: "tool-params-start", + id: event.item.id, + name: toolName, + providerExecuted: true + }) + parts.push({ + type: "tool-params-delta", + id: event.item.id, + delta: `{"containerId":"${event.item.container_id}","code":"` + }) + break + } + + case "computer_call": { + const toolName = toolNameMapper.getCustomName("computer_use") + activeToolCalls[event.output_index] = { + id: event.item.id, + name: toolName + } + parts.push({ + type: "tool-params-start", + id: event.item.id, + name: toolName, + providerExecuted: true + }) + break + } + + case "file_search_call": { + const toolName = toolNameMapper.getCustomName("file_search") + parts.push({ + type: "tool-call", + id: event.item.id, + name: toolName, + params: {}, + providerExecuted: true + }) + break + } + + case "function_call": { + activeToolCalls[event.output_index] = { + id: event.item.call_id, + name: event.item.name, + functionCall: { emitted: false } + } + parts.push({ + type: "tool-params-start", + id: event.item.call_id, + name: event.item.name + }) + break + } + + case "image_generation_call": { + const toolName = toolNameMapper.getCustomName("image_generation") + parts.push({ + type: "tool-call", + id: event.item.id, + name: toolName, + params: {}, + providerExecuted: true + }) + break + } + + case "mcp_call": + case "mcp_list_tools": + case "mcp_approval_request": { + // We emit MCP tool call / approvals on `output_item.done` to facilitate: + // - Aliasing tool call identifiers when an approval request id exists + // - Emit a proper tool-approval-request part for MCP approvals + break + } + + case "message": { + // Clear annotations for new message + activeAnnotations.length = 0 + parts.push({ + type: "text-start", + id: event.item.id, + metadata: { openai: makeItemIdMetadata(event.item.id) } + }) + break + } + + case "reasoning": { + const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content) + if (Predicate.isUndefined(reasoningPart.summaryParts[0])) { + reasoningPart.summaryParts[0] = "active" + parts.push({ + type: "reasoning-start", + id: `${event.item.id}:0`, + metadata: { + openai: { + ...makeItemIdMetadata(event.item.id), + ...makeEncryptedContentMetadata(reasoningPart.encryptedContent) + } + } + }) + } + break + } + + case "shell_call": { + const toolName = toolNameMapper.getCustomName("shell") + activeToolCalls[event.output_index] = { + id: event.item.id ?? event.item.call_id, + name: toolName + } + break + } + + case "web_search_call": { + const toolName = toolNameMapper.getCustomName( + webSearchTool?.providerName ?? "web_search" + ) + activeToolCalls[event.output_index] = { + id: event.item.id, + name: toolName + } + if (webSearchTool?.name === "OpenAiWebSearch") { + break + } + parts.push({ + type: "tool-params-start", + id: event.item.id, + name: webSearchTool?.name ?? "OpenAiWebSearch", + providerExecuted: true + }) + parts.push({ + type: "tool-params-end", + id: event.item.id + }) + parts.push({ + type: "tool-call", + id: event.item.id, + name: toolName, + params: {}, + providerExecuted: true + }) + break + } + } + + break + } + + case "response.output_item.done": { + switch (event.item.type) { + case "apply_patch_call": { + const toolCall = activeToolCalls[event.output_index] + if ( + Predicate.isNotUndefined(toolCall.applyPatch) && + !toolCall.applyPatch.endEmitted && + event.item.operation.type !== "delete_file" + ) { + if (!toolCall.applyPatch.hasDiff) { + parts.push({ + type: "tool-params-delta", + id: toolCall.id, + delta: InternalUtilities.escapeJSONDelta(event.item.operation.diff ?? "") + }) + } + parts.push({ + type: "tool-params-delta", + id: toolCall.id, + delta: `"}}` + }) + parts.push({ + type: "tool-params-end", + id: toolCall.id + }) + toolCall.applyPatch.endEmitted = true + } + // Emit the final tool call with the complete diff when the status is completed + if (Predicate.isNotUndefined(toolCall) && event.item.status === "completed") { + const toolName = toolNameMapper.getCustomName("apply_patch") + parts.push({ + type: "tool-call", + id: toolCall.id, + name: toolName, + params: { call_id: event.item.call_id, operation: event.item.operation }, + metadata: { openai: makeItemIdMetadata(event.item.id) } + }) + } + delete activeToolCalls[event.output_index] + break + } + + case "code_interpreter_call": { + delete activeToolCalls[event.output_index] + const toolName = toolNameMapper.getCustomName("code_interpreter") + parts.push({ + type: "tool-result", + id: event.item.id, + name: toolName, + isFailure: false, + result: { outputs: event.item.outputs }, + providerExecuted: true + }) + break + } + + case "computer_call": { + delete activeToolCalls[event.output_index] + const toolName = toolNameMapper.getCustomName("computer_use") + parts.push({ + type: "tool-params-end", + id: event.item.id + }) + parts.push({ + type: "tool-call", + id: event.item.id, + name: toolName, + params: {}, + providerExecuted: true + }) + parts.push({ + type: "tool-result", + id: event.item.id, + name: toolName, + isFailure: false, + result: { status: event.item.status ?? "completed" } + }) + break + } + + case "file_search_call": { + delete activeToolCalls[event.output_index] + const toolName = toolNameMapper.getCustomName("file_search") + const results = Predicate.isNotNullish(event.item.results) + ? { results: event.item.results } + : undefined + parts.push({ + type: "tool-result", + id: event.item.id, + name: toolName, + isFailure: false, + result: { ...results, status: event.item.status, queries: event.item.queries }, + providerExecuted: true + }) + break + } + + case "function_call": { + const toolCall = activeToolCalls[event.output_index] + if (Predicate.isNotUndefined(toolCall?.functionCall?.emitted) && toolCall.functionCall.emitted) { + delete activeToolCalls[event.output_index] + break + } + delete activeToolCalls[event.output_index] + + hasToolCalls = true + + const toolName = event.item.name + const toolArgs = event.item.arguments + + const toolParams = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(toolArgs), + catch: (cause) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "makeStreamResponse", + reason: new AiError.ToolParameterValidationError({ + toolName, + toolParams: {}, + description: `Failed securely JSON parse tool parameters: ${cause}` + }) + }) + }) + + const params = yield* transformToolCallParams(options.tools, toolName, toolParams) + + parts.push({ + type: "tool-params-end", + id: event.item.call_id + }) + + parts.push({ + type: "tool-call", + id: event.item.call_id, + name: toolName, + params, + metadata: { openai: makeItemIdMetadata(event.item.id) } + }) + + break + } + + case "image_generation_call": { + const toolName = toolNameMapper.getCustomName("image_generation") + parts.push({ + type: "tool-result", + id: event.item.id, + name: toolName, + isFailure: false, + result: { result: event.item.result }, + providerExecuted: true + }) + break + } + + case "local_shell_call": { + const toolName = toolNameMapper.getCustomName("local_shell") + parts.push({ + type: "tool-call", + id: event.item.call_id, + name: toolName, + params: { action: event.item.action }, + metadata: { openai: makeItemIdMetadata(event.item.id) } + }) + break + } + + case "mcp_call": { + const approvalRequestId = event.item.approval_request_id + + // Track approval with our own tool call identifiers + const toolId = Predicate.isNotNullish(approvalRequestId) + ? (streamApprovalRequests.get(approvalRequestId) ?? approvalRequests.get(approvalRequestId) ?? + event.item.id) + : event.item.id + + const { toolName, params } = yield* normalizeMcpToolCall({ + toolNameMapper, + toolParams: event.item.arguments, + method: "makeStreamResponse" + }) + + parts.push({ + type: "tool-call", + id: toolId, + name: toolName, + params, + providerExecuted: true + }) + + parts.push({ + type: "tool-result", + id: toolId, + name: toolName, + isFailure: false, + providerExecuted: true, + result: { + type: "mcp_call", + name: event.item.name, + arguments: event.item.arguments, + server_label: event.item.server_label, + ...(Predicate.isNotNullish(event.item.output) ? { output: event.item.output } : undefined), + ...(Predicate.isNotNullish(event.item.error) ? { error: event.item.error } : undefined) + }, + metadata: { openai: makeItemIdMetadata(event.item.id) } + }) + + break + } + + case "mcp_list_tools": { + // Skip + break + } + + case "mcp_approval_request": { + const toolId = yield* idGenerator.generateId() + const approvalRequestId = (event.item as any).approval_request_id ?? event.item.id + streamApprovalRequests.set(approvalRequestId, toolId) + const { toolName, params } = yield* normalizeMcpToolCall({ + toolNameMapper, + toolParams: event.item.arguments, + method: "makeStreamResponse" + }) + parts.push({ + type: "tool-call", + id: toolId, + name: toolName, + params, + providerExecuted: true + }) + parts.push({ + type: "tool-approval-request", + approvalId: approvalRequestId, + toolCallId: toolId + }) + break + } + + case "message": { + const annotations = activeAnnotations.length > 0 + ? { annotations: activeAnnotations.slice() } + : undefined + parts.push({ + type: "text-end", + id: event.item.id, + metadata: { openai: { ...annotations, ...makeItemIdMetadata(event.item.id) } } + }) + break + } + + case "reasoning": { + const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content) + for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) { + if (status === "active" || status === "can-conclude") { + parts.push({ + type: "reasoning-end", + id: `${event.item.id}:${summaryIndex}`, + metadata: { + openai: { + ...makeItemIdMetadata(event.item.id), + ...makeEncryptedContentMetadata(reasoningPart.encryptedContent) + } + } + }) + } + } + delete activeReasoning[event.item.id] + break + } + + case "shell_call": { + delete activeToolCalls[event.output_index] + const toolName = toolNameMapper.getCustomName("shell") + parts.push({ + type: "tool-call", + id: event.item.id ?? event.item.call_id, + name: toolName, + params: { action: event.item.action }, + metadata: { openai: makeItemIdMetadata(event.item.id) } + }) + break + } + + case "web_search_call": { + delete activeToolCalls[event.output_index] + 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, + name: toolName, + isFailure: false, + result: { action: event.item.action, status: event.item.status }, + providerExecuted: true + }) + break + } + } + + break + } + + case "response.output_text.delta": { + parts.push({ + type: "text-delta", + id: event.item_id, + delta: event.delta + }) + break + } + + case "response.output_text.annotation.added": { + const annotation = event.annotation as typeof OpenAiSchema.Annotation.Encoded + // Track annotation for text-end metadata + activeAnnotations.push(annotation) + if (annotation.type === "container_file_citation") { + parts.push({ + type: "source", + sourceType: "document", + id: yield* idGenerator.generateId(), + mediaType: "text/plain", + title: annotation.filename, + fileName: annotation.filename, + metadata: { + openai: { + type: annotation.type, + fileId: annotation.file_id, + containerId: annotation.container_id + } + } + }) + } else if (annotation.type === "file_citation") { + parts.push({ + type: "source", + sourceType: "document", + id: yield* idGenerator.generateId(), + mediaType: "text/plain", + title: annotation.filename, + fileName: annotation.filename, + metadata: { + openai: { + type: annotation.type, + fileId: annotation.file_id, + index: annotation.index + } + } + }) + } else if (annotation.type === "file_path") { + parts.push({ + type: "source", + sourceType: "document", + id: yield* idGenerator.generateId(), + mediaType: "application/octet-stream", + title: annotation.file_id, + fileName: annotation.file_id, + metadata: { + openai: { + type: annotation.type, + fileId: annotation.file_id, + index: annotation.index + } + } + }) + } else if (annotation.type === "url_citation") { + parts.push({ + type: "source", + sourceType: "url", + id: yield* idGenerator.generateId(), + url: annotation.url, + title: annotation.title, + metadata: { + openai: { + type: annotation.type, + startIndex: annotation.start_index, + endIndex: annotation.end_index + } + } + }) + } + break + } + + case "response.function_call_arguments.delta": { + const toolCallPart = activeToolCalls[event.output_index] + if (Predicate.isNotUndefined(toolCallPart)) { + parts.push({ + type: "tool-params-delta", + id: toolCallPart.id, + delta: event.delta + }) + } + break + } + + case "response.function_call_arguments.done": { + const toolCall = activeToolCalls[event.output_index] + if ( + Predicate.isNotUndefined(toolCall?.functionCall) && + !toolCall.functionCall.emitted + ) { + hasToolCalls = true + + const toolParams = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(event.arguments), + catch: (cause) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "makeStreamResponse", + reason: new AiError.ToolParameterValidationError({ + toolName: toolCall.name, + toolParams: {}, + description: `Failed securely JSON parse tool parameters: ${cause}` + }) + }) + }) + + const params = yield* transformToolCallParams(options.tools, toolCall.name, toolParams) + + parts.push({ + type: "tool-params-end", + id: toolCall.id + }) + + parts.push({ + type: "tool-call", + id: toolCall.id, + name: toolCall.name, + params, + metadata: { openai: makeItemIdMetadata(event.item_id) } + }) + + toolCall.functionCall.emitted = true + } + break + } + + case "response.apply_patch_call_operation_diff.delta": { + const toolCall = activeToolCalls[event.output_index] + if (Predicate.isNotUndefined(toolCall?.applyPatch)) { + parts.push({ + type: "tool-params-delta", + id: toolCall.id, + delta: InternalUtilities.escapeJSONDelta(event.delta) + }) + toolCall.applyPatch.hasDiff = true + } + break + } + + case "response.apply_patch_call_operation_diff.done": { + const toolCall = activeToolCalls[event.output_index] + if (Predicate.isNotUndefined(toolCall?.applyPatch) && !toolCall.applyPatch.endEmitted) { + if (!toolCall.applyPatch.hasDiff && Predicate.isNotUndefined(event.delta)) { + parts.push({ + type: "tool-params-delta", + id: toolCall.id, + delta: InternalUtilities.escapeJSONDelta(event.delta) + }) + toolCall.applyPatch.hasDiff = true + } + parts.push({ + type: "tool-params-delta", + id: toolCall.id, + delta: `"}}` + }) + parts.push({ + type: "tool-params-end", + id: toolCall.id + }) + toolCall.applyPatch.endEmitted = true + } + break + } + + case "response.code_interpreter_call_code.delta": { + const toolCall = activeToolCalls[event.output_index] + if (Predicate.isNotUndefined(toolCall)) { + parts.push({ + type: "tool-params-delta", + id: toolCall.id, + delta: InternalUtilities.escapeJSONDelta(event.delta) + }) + } + break + } + + case "response.code_interpreter_call_code.done": { + const toolCall = activeToolCalls[event.output_index] + if (Predicate.isNotUndefined(toolCall) && Predicate.isNotUndefined(toolCall.codeInterpreter)) { + const toolName = toolNameMapper.getCustomName("code_interpreter") + parts.push({ + type: "tool-params-delta", + id: toolCall.id, + delta: "\"}" + }) + parts.push({ type: "tool-params-end", id: toolCall.id }) + parts.push({ + type: "tool-call", + id: toolCall.id, + name: toolName, + params: { + code: event.code, + container_id: toolCall.codeInterpreter.containerId + }, + providerExecuted: true + }) + } + break + } + + case "response.image_generation_call.partial_image": { + const toolName = toolNameMapper.getCustomName("image_generation") + parts.push({ + type: "tool-result", + id: event.item_id, + name: toolName, + isFailure: false, + providerExecuted: false, + result: { result: event.partial_image_b64 }, + preliminary: true + }) + break + } + + case "response.reasoning_summary_part.added": { + const reasoningPart = getOrCreateReasoningPart(event.item_id) + if (event.summary_index > 0) { + // Conclude all can-conclude parts before starting new one + for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) { + if (status === "can-conclude") { + parts.push({ + type: "reasoning-end", + id: `${event.item_id}:${summaryIndex}`, + metadata: { + openai: { + ...makeItemIdMetadata(event.item_id), + ...makeEncryptedContentMetadata(reasoningPart.encryptedContent) + } + } + }) + reasoningPart.summaryParts[Number(summaryIndex)] = "concluded" + } + } + } + + if (Predicate.isUndefined(reasoningPart.summaryParts[event.summary_index])) { + reasoningPart.summaryParts[event.summary_index] = "active" + parts.push({ + type: "reasoning-start", + id: `${event.item_id}:${event.summary_index}`, + metadata: { + openai: { + ...makeItemIdMetadata(event.item_id), + ...makeEncryptedContentMetadata(reasoningPart.encryptedContent) + } + } + }) + } + break + } + + case "response.reasoning_summary_text.delta": { + parts.push({ + type: "reasoning-delta", + id: `${event.item_id}:${event.summary_index}`, + delta: event.delta, + metadata: { openai: makeItemIdMetadata(event.item_id) } + }) + break + } + + case "response.reasoning_summary_part.done": { + const reasoningPart = getOrCreateReasoningPart(event.item_id) + // When OpenAI stores message data, we can immediately conclude the + // reasoning part given that we do not need the encrypted content + if (config.store === true) { + parts.push({ + type: "reasoning-end", + id: `${event.item_id}:${event.summary_index}`, + metadata: { openai: makeItemIdMetadata(event.item_id) } + }) + // Mark the summary part concluded + reasoningPart.summaryParts[event.summary_index] = "concluded" + } else { + // Mark the summary part as can-conclude given we still need a + // final summary part with the encrypted content + reasoningPart.summaryParts[event.summary_index] = "can-conclude" + } + break + } + } + + return parts + })), + Stream.flattenIterable + ) + } +) + +// ============================================================================= +// Telemetry +// ============================================================================= + +const annotateRequest = ( + span: Span, + request: typeof OpenAiSchema.CreateResponse.Encoded +): void => { + addGenAIAnnotations(span, { + system: "openai", + operation: { name: "chat" }, + request: { + model: request.model as string, + temperature: request.temperature as number | undefined, + topP: request.top_p as number | undefined, + maxTokens: request.max_output_tokens as number | undefined + }, + openai: { + request: { + responseFormat: (request.text as any)?.format?.type, + serviceTier: request.service_tier as string | undefined + } + } + }) +} + +const annotateResponse = (span: Span, response: OpenAiSchema.Response): void => { + const finishReason = response.incomplete_details?.reason as string | undefined + addGenAIAnnotations(span, { + response: { + id: response.id, + model: response.model as string, + finishReasons: Predicate.isNotUndefined(finishReason) ? [finishReason] : undefined + }, + usage: { + inputTokens: response.usage?.input_tokens as number | undefined, + outputTokens: response.usage?.output_tokens as number | undefined + }, + openai: { + response: { + serviceTier: response.service_tier as string | undefined + } + } + }) +} + +const annotateStreamResponse = (span: Span, part: Response.StreamPartEncoded) => { + if (part.type === "response-metadata") { + addGenAIAnnotations(span, { + response: { + id: part.id, + model: part.modelId + } + }) + } + if (part.type === "finish") { + const serviceTier = (part.metadata as any)?.openai?.serviceTier as string | undefined + addGenAIAnnotations(span, { + response: { + finishReasons: [part.reason] + }, + usage: { + inputTokens: part.usage.inputTokens.total, + outputTokens: part.usage.outputTokens.total + }, + openai: { + response: { serviceTier } + } + }) + } +} + +// ============================================================================= +// Tool Conversion +// ============================================================================= + +type OpenAiToolChoice = typeof OpenAiSchema.CreateResponse.Encoded["tool_choice"] + +const prepareTools = Effect.fnUntraced(function*>({ + config, + options, + toolNameMapper +}: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions + readonly toolNameMapper: Tool.NameMapper +}): Effect.fn.Return<{ + readonly tools: ReadonlyArray | undefined + readonly toolChoice: OpenAiToolChoice | undefined +}, AiError.AiError> { + // Return immediately if no tools are in the toolkit + if (options.tools.length === 0) { + return { tools: undefined, toolChoice: undefined } + } + + const tools: Array = [] + let toolChoice: OpenAiToolChoice | undefined = undefined + + // Filter the incoming tools down to the set of allowed tools as indicated by + // the tool choice. This must be done here given that there is no tool name + // in OpenAI's provider-defined tools, so there would be no way to perform + // this filter otherwise + let allowedTools = options.tools + if (typeof options.toolChoice === "object" && "oneOf" in options.toolChoice) { + const allowedToolNames = new Set(options.toolChoice.oneOf) + allowedTools = options.tools.filter((tool) => allowedToolNames.has(tool.name)) + toolChoice = options.toolChoice.mode === "required" ? "required" : "auto" + } + + // Convert the tools in the toolkit to the provider-defined format + for (const tool of allowedTools) { + if (Tool.isUserDefined(tool) || Tool.isDynamic(tool)) { + const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true + const description = Tool.getDescription(tool) + const parameters = yield* tryToolJsonSchema(tool, "prepareTools") + tools.push({ + type: "function", + name: tool.name, + parameters, + strict, + ...(Predicate.isNotUndefined(description) ? { description } : undefined) + }) + } + + if (Tool.isProviderDefined(tool)) { + const openAiTool = tool as OpenAiTool.OpenAiTool + switch (openAiTool.name) { + case "OpenAiApplyPatch": { + tools.push({ type: "apply_patch" }) + break + } + case "OpenAiCodeInterpreter": { + const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareTools", + reason: new AiError.ToolConfigurationError({ + toolName: openAiTool.name, + description: error.message + }) + }) + ) + ) + tools.push({ + ...args, + type: "code_interpreter" + }) + break + } + case "OpenAiFileSearch": { + const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareTools", + reason: new AiError.ToolConfigurationError({ + toolName: openAiTool.name, + description: error.message + }) + }) + ) + ) + tools.push({ + ...args, + type: "file_search" + }) + break + } + case "OpenAiShell": { + tools.push({ type: "shell" }) + break + } + case "OpenAiImageGeneration": { + const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareTools", + reason: new AiError.ToolConfigurationError({ + toolName: openAiTool.name, + description: error.message + }) + }) + ) + ) + tools.push({ + ...args, + type: "image_generation" + }) + break + } + case "OpenAiLocalShell": { + tools.push({ type: "local_shell" }) + break + } + case "OpenAiMcp": { + const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareTools", + reason: new AiError.ToolConfigurationError({ + toolName: openAiTool.name, + description: error.message + }) + }) + ) + ) + tools.push({ + ...args, + type: "mcp" + }) + break + } + case "OpenAiWebSearch": { + const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareTools", + reason: new AiError.ToolConfigurationError({ + toolName: openAiTool.name, + description: error.message + }) + }) + ) + ) + tools.push({ + ...args, + type: "web_search" + }) + break + } + case "OpenAiWebSearchPreview": { + const args = yield* Schema.decodeUnknownEffect(openAiTool.argsSchema)(tool.args).pipe( + Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareTools", + reason: new AiError.ToolConfigurationError({ + toolName: openAiTool.name, + description: error.message + }) + }) + ) + ) + tools.push({ + ...args, + type: "web_search_preview" + }) + break + } + default: { + return yield* AiError.make({ + module: "OpenAiLanguageModel", + method: "prepareTools", + reason: new AiError.InvalidRequestError({ + description: `Unknown provider-defined tool '${tool.name}'` + }) + }) + } + } + } + } + + if (options.toolChoice === "auto" || options.toolChoice === "none" || options.toolChoice === "required") { + toolChoice = options.toolChoice + } + + if (typeof options.toolChoice === "object" && "tool" in options.toolChoice) { + const toolName = toolNameMapper.getProviderName(options.toolChoice.tool) + const providerNames = toolNameMapper.providerNames + if (providerNames.includes(toolName)) { + toolChoice = { type: toolName as any } + } else { + toolChoice = { type: "function", name: options.toolChoice.tool } + } + } + + return { tools, toolChoice } +}) + +// ============================================================================= +// Utilities +// ============================================================================= + +const isFileId = (data: string, config: typeof Config.Service): boolean => + config.fileIdPrefixes != null && config.fileIdPrefixes.some((prefix) => data.startsWith(prefix)) + +const getItemId = ( + part: + | Prompt.TextPart + | Prompt.ReasoningPart + | Prompt.ToolCallPart + | Prompt.ToolResultPart +): string | null => part.options.openai?.itemId ?? null +const getStatus = ( + part: + | Prompt.TextPart + | Prompt.ToolCallPart + | Prompt.ToolResultPart +): typeof OpenAiSchema.MessageStatus.Encoded | null => part.options.openai?.status ?? null +const getEncryptedContent = ( + part: Prompt.ReasoningPart +): string | null => part.options.openai?.encryptedContent ?? null + +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) => + Predicate.isNotNullish(encryptedContent) ? { encryptedContent } : undefined + +const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError => + AiError.make({ + module: "OpenAiLanguageModel", + method, + reason: new AiError.UnsupportedSchemaError({ + description: error instanceof Error ? error.message : String(error) + }) + }) + +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 }), + catch: (error) => unsupportedSchemaError(error, method) + }) + +const tryToolJsonSchema = (tool: T, method: string) => + Effect.try({ + try: () => Tool.getJsonSchema(tool, { transformer: toCodecOpenAI }), + catch: (error) => unsupportedSchemaError(error, method) + }) + +const prepareResponseFormat = Effect.fnUntraced(function*({ config, options }: { + readonly config: typeof Config.Service + readonly options: LanguageModel.ProviderOptions +}): Effect.fn.Return { + if (options.responseFormat.type === "json") { + const name = options.responseFormat.objectName + const schema = options.responseFormat.schema + const jsonSchema = yield* tryJsonSchema(schema, "prepareResponseFormat") + return { + type: "json_schema", + name, + description: AST.resolveDescription(schema.ast) ?? "Response with a JSON object", + schema: jsonSchema, + strict: config.strictJsonSchema ?? true + } + } + return { type: "text" } +}) + +interface ModelCapabilities { + readonly isReasoningModel: boolean + readonly systemMessageMode: "remove" | "system" | "developer" + readonly supportsFlexProcessing: boolean + readonly supportsPriorityProcessing: boolean + /** + * Allow temperature, topP, logProbs when reasoningEffort is none. + */ + readonly supportsNonReasoningParameters: boolean +} + +const getModelCapabilities = (modelId: string): ModelCapabilities => { + const supportsFlexProcessing = modelId.startsWith("o3") || + modelId.startsWith("o4-mini") || + (modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat")) + + const supportsPriorityProcessing = modelId.startsWith("gpt-4") || + modelId.startsWith("gpt-5-mini") || + (modelId.startsWith("gpt-5") && + !modelId.startsWith("gpt-5-nano") && + !modelId.startsWith("gpt-5-chat")) || + modelId.startsWith("o3") || + modelId.startsWith("o4-mini") + + // Use allowlist approach: only known reasoning models should use 'developer' role + // This prevents issues with fine-tuned models, third-party models, and custom models + const isReasoningModel = modelId.startsWith("o1") || + modelId.startsWith("o3") || + modelId.startsWith("o4-mini") || + modelId.startsWith("codex-mini") || + modelId.startsWith("computer-use-preview") || + (modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat")) + + // https://platform.openai.com/docs/guides/latest-model#gpt-5-1-parameter-compatibility + // GPT-5.1 and GPT-5.2 support temperature, topP, logProbs when reasoningEffort is none + const supportsNonReasoningParameters = modelId.startsWith("gpt-5.1") || modelId.startsWith("gpt-5.2") + + const systemMessageMode = isReasoningModel ? "developer" : "system" + + return { + supportsFlexProcessing, + supportsPriorityProcessing, + isReasoningModel, + systemMessageMode, + supportsNonReasoningParameters + } +} + +const getApprovalRequestIdMapping = (prompt: Prompt.Prompt): ReadonlyMap => { + const mapping = new Map() + + for (const message of prompt.content) { + if (message.role !== "assistant") { + continue + } + + for (const part of message.content) { + if (part.type !== "tool-call") { + continue + } + + const approvalRequestId = part.options.openai?.approvalRequestId + + if (Predicate.isNotNullish(approvalRequestId)) { + mapping.set(approvalRequestId, part.id) + } + } + } + + return mapping +} + +const normalizeMcpToolCall = Effect.fnUntraced(function*>({ + toolNameMapper, + toolParams, + method +}: { + readonly toolNameMapper: Tool.NameMapper + readonly toolParams: unknown + readonly method: string +}): Effect.fn.Return<{ + readonly toolName: string + readonly params: unknown +}, AiError.AiError> { + const toolName = toolNameMapper.getCustomName("mcp") + + if (typeof toolParams !== "string") { + return { toolName, params: toolParams } + } + + const params = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(toolParams), + catch: (cause) => + AiError.make({ + module: "OpenAiLanguageModel", + method, + reason: new AiError.ToolParameterValidationError({ + toolName, + toolParams, + description: `Failed to securely JSON parse tool parameters: ${cause}` + }) + }) + }) + + return { toolName, params } +}) + +const getUsage = (usage: OpenAiSchema.ResponseUsage | null | undefined): Response.Usage => { + if (Predicate.isNullish(usage)) { + return { + inputTokens: { + uncached: undefined, + total: undefined, + cacheRead: undefined, + cacheWrite: undefined + }, + outputTokens: { + total: undefined, + text: undefined, + reasoning: undefined + } + } + } + + const inputTokens = usage.input_tokens + const outputTokens = usage.output_tokens + const cachedTokens = getUsageTokenDetail(usage.input_tokens_details, "cached_tokens") ?? 0 + const cacheWriteTokens = getUsageTokenDetail(usage.input_tokens_details, "cache_write_tokens") + const reasoningTokens = getUsageTokenDetail(usage.output_tokens_details, "reasoning_tokens") ?? 0 + + return { + inputTokens: { + uncached: inputTokens - cachedTokens, + total: inputTokens, + cacheRead: cachedTokens, + cacheWrite: cacheWriteTokens + }, + outputTokens: { + total: outputTokens, + text: outputTokens - reasoningTokens, + reasoning: reasoningTokens + } + } +} + +type ServiceTier = "default" | "auto" | "flex" | "scale" | "priority" | null + +const toServiceTier = (value: string | undefined): { + readonly metadata: { + readonly openai: { + readonly serviceTier: ServiceTier + } + } +} | undefined => { + switch (value) { + case "default": + case "auto": + case "flex": + case "scale": + case "priority": + return { metadata: { openai: { serviceTier: value } } } + default: + return undefined + } +} + +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, + 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") + + const transform = Schema.decodeEffect(codec) + + return yield* ( + transform(toolParams) as Effect.Effect + ).pipe(Effect.mapError((error) => + AiError.make({ + module: "OpenAiLanguageModel", + method: "makeResponse", + reason: new AiError.ToolParameterValidationError({ + toolName, + toolParams, + description: formatIssue(error.issue) + }) + }) + )) +}) diff --git a/.repos/effect/packages/ai/openai/src/OpenAiSchema.ts b/.repos/effect/packages/ai/openai/src/OpenAiSchema.ts new file mode 100644 index 000000000..2714c893a --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiSchema.ts @@ -0,0 +1,1324 @@ +/** + * The `OpenAiSchema` module defines the request, response, streaming, and + * embedding schemas used by the handwritten OpenAI client. These schemas are + * the transport boundary for JSON sent to and decoded from the Responses and + * embeddings endpoints. + * + * @since 4.0.0 + */ +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) + +const JsonObject = Schema.Record(Schema.String, Schema.Unknown) + +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. + * + * **Details** + * + * These values request additional response fields such as image URLs, encrypted + * reasoning content, output logprobs, code interpreter outputs, or web search + * sources. This schema enumerates the include values supported by this client + * path. + * + * @category schemas + * @since 4.0.0 + */ +export const IncludeEnum = Schema.Literals([ + "message.input_image.image_url", + "reasoning.encrypted_content", + "message.output_text.logprobs", + "code_interpreter_call.outputs", + "web_search_call.action.sources" +]) + +/** + * Type of optional `include` values accepted by OpenAI Responses requests. + * + * @category models + * @since 4.0.0 + */ +export type IncludeEnum = typeof IncludeEnum.Type + +/** + * Schema for lifecycle statuses shared by messages, reasoning items, and tool calls. + * + * **Details** + * + * Accepted values are `"in_progress"`, `"completed"`, and `"incomplete"`. + * This item-level status is used by message, reasoning, and tool-call shapes. + * + * @category schemas + * @since 4.0.0 + */ +export const MessageStatus = Schema.Literals(["in_progress", "completed", "incomplete"]) + +/** + * Lifecycle status shared by messages, reasoning items, and tool calls. + * + * **Details** + * + * Accepted values are `"in_progress"`, `"completed"`, and `"incomplete"`. + * + * @category models + * @since 4.0.0 + */ +export type MessageStatus = typeof MessageStatus.Type + +const InputTextContent = Schema.Struct({ + type: Schema.Literal("input_text"), + text: Schema.String, + prompt_cache_breakpoint: Schema.optional(PromptCacheBreakpoint) +}) + +const InputImageContent = Schema.Struct({ + type: Schema.Literal("input_image"), + image_url: Schema.optionalKey(Schema.NullOr(Schema.String)), + file_id: Schema.optionalKey(Schema.NullOr(Schema.String)), + detail: Schema.optionalKey(Schema.NullOr(ImageDetail)) +}) + +const InputFileContent = Schema.Struct({ + type: Schema.Literal("input_file"), + file_id: Schema.optionalKey(Schema.NullOr(Schema.String)), + filename: Schema.optionalKey(Schema.String), + file_url: Schema.optionalKey(Schema.String), + file_data: Schema.optionalKey(Schema.String) +}) + +/** + * Schema for content blocks accepted in OpenAI Responses input messages. + * + * **Details** + * + * Accepted block variants are `input_text`, `input_image`, and `input_file`. + * + * @see {@link InputItem} for request input item shapes that can contain these content blocks + * + * @category schemas + * @since 4.0.0 + */ +export const InputContent = Schema.Union([ + InputTextContent, + InputImageContent, + InputFileContent +]) + +/** + * Content block accepted in OpenAI Responses input messages. + * + * **Details** + * + * Accepted block variants are `input_text`, `input_image`, and `input_file`. + * + * @category models + * @since 4.0.0 + */ +export type InputContent = typeof InputContent.Type + +/** + * Schema for a text block containing a model-provided reasoning summary. + * + * **Details** + * + * The decoded shape is `type: "summary_text"` plus `text` containing the + * reasoning summary text. + * + * @see {@link ReasoningItem} for reasoning output items that contain summary text blocks + * + * @category schemas + * @since 4.0.0 + */ +export const SummaryTextContent = Schema.Struct({ + type: Schema.Literal("summary_text"), + text: Schema.String +}) + +/** + * Text content block used for model-provided reasoning summaries. + * + * @category models + * @since 4.0.0 + */ +export type SummaryTextContent = typeof SummaryTextContent.Type + +const ReasoningTextContent = Schema.Struct({ + type: Schema.Literal("reasoning_text"), + text: Schema.String +}) + +const RefusalContent = Schema.Struct({ + type: Schema.Literal("refusal"), + refusal: Schema.String +}) + +const TextContent = Schema.Struct({ + type: Schema.Literal("text"), + text: Schema.String +}) + +const ComputerScreenshotContent = Schema.Struct({ + type: Schema.Literal("computer_screenshot"), + image_url: Schema.NullOr(Schema.String), + file_id: Schema.NullOr(Schema.String) +}) + +const FileCitationAnnotation = Schema.Struct({ + type: Schema.Literal("file_citation"), + file_id: Schema.String, + index: Schema.Int, + filename: Schema.String +}) + +const UrlCitationAnnotation = Schema.Struct({ + type: Schema.Literal("url_citation"), + url: Schema.String, + start_index: Schema.Int, + end_index: Schema.Int, + title: Schema.String +}) + +const ContainerFileCitationAnnotation = Schema.Struct({ + type: Schema.Literal("container_file_citation"), + container_id: Schema.String, + file_id: Schema.String, + 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.Int +}) + +/** + * Schema for citation and file-path annotations attached to output text content. + * + * **Details** + * + * Accepts annotation objects discriminated by `type`: `file_citation`, + * `url_citation`, `container_file_citation`, or `file_path`. + * + * @category schemas + * @since 4.0.0 + */ +export const Annotation = Schema.Union([ + FileCitationAnnotation, + UrlCitationAnnotation, + ContainerFileCitationAnnotation, + FilePathAnnotation +]) + +/** + * Citation or file-path annotation attached to output text content. + * + * **Details** + * + * Accepted annotation variants are `file_citation`, `url_citation`, + * `container_file_citation`, and `file_path`. + * + * @category models + * @since 4.0.0 + */ +export type Annotation = typeof Annotation.Type + +const OutputTextContent = Schema.Struct({ + type: Schema.Literal("output_text"), + text: Schema.String, + annotations: Schema.Array(Annotation), + logprobs: Schema.optionalKey(Schema.Array(Schema.Unknown)) +}) + +const OutputMessageContent = Schema.Union([ + InputTextContent, + OutputTextContent, + TextContent, + SummaryTextContent, + ReasoningTextContent, + RefusalContent, + InputImageContent, + ComputerScreenshotContent, + InputFileContent +]) + +const OutputMessage = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("message"), + role: Schema.Literal("assistant"), + content: Schema.Array(OutputMessageContent), + status: MessageStatus +}) + +/** + * Schema for a reasoning output item containing encrypted content, summaries, and optional reasoning text. + * + * **When to use** + * + * Use when decoding or encoding OpenAI Responses reasoning items that may be + * carried into later request input. + * + * **Details** + * + * Reasoning items represent model reasoning content. `summary` is required, + * while `content` and `status` are optional. + * + * **Gotchas** + * + * `encrypted_content` is populated only when `reasoning.encrypted_content` is + * requested through `include`. + * + * @see {@link InputItem} for request input items that can carry reasoning items + * @see {@link IncludeEnum} for requesting encrypted reasoning content + * + * @category schemas + * @since 4.0.0 + */ +export const ReasoningItem = Schema.Struct({ + type: Schema.Literal("reasoning"), + id: Schema.String, + encrypted_content: Schema.optionalKey(Schema.NullOr(Schema.String)), + summary: Schema.Array(SummaryTextContent), + content: Schema.optionalKey(Schema.Array(ReasoningTextContent)), + status: Schema.optionalKey(MessageStatus) +}) + +/** + * Reasoning output item containing encrypted content, summaries, and optional reasoning text. + * + * **When to use** + * + * Use when typing OpenAI Responses reasoning items that may be carried into + * later request input. + * + * **Details** + * + * Reasoning items represent model reasoning content. `summary` is required, + * while `content` and `status` are optional. + * + * **Gotchas** + * + * `encrypted_content` is populated only when `reasoning.encrypted_content` is + * requested through `include`. + * + * @category models + * @since 4.0.0 + */ +export type ReasoningItem = typeof ReasoningItem.Type + +const FunctionCall = Schema.Struct({ + id: Schema.optionalKey(Schema.String), + type: Schema.Literal("function_call"), + call_id: Schema.String, + name: Schema.String, + arguments: Schema.String, + status: Schema.optionalKey(MessageStatus) +}) + +const FunctionCallOutput = Schema.Struct({ + id: Schema.optionalKey(Schema.NullOr(Schema.String)), + type: Schema.Literal("function_call_output"), + call_id: Schema.String, + output: Schema.Union([ + Schema.String, + Schema.Array(InputContent) + ]), + status: Schema.optionalKey(Schema.NullOr(MessageStatus)) +}) + +const ItemReference = Schema.Struct({ + type: Schema.Literal("item_reference"), + id: Schema.String +}) + +const LocalShellCall = Schema.Struct({ + id: Schema.optionalKey(Schema.String), + type: Schema.Literal("local_shell_call"), + call_id: Schema.String, + action: Schema.Unknown, + status: Schema.optionalKey(MessageStatus) +}) + +const LocalShellCallOutput = Schema.Struct({ + id: Schema.optionalKey(Schema.String), + type: Schema.Literal("local_shell_call_output"), + call_id: Schema.String, + output: Schema.Unknown, + status: Schema.optionalKey(MessageStatus) +}) + +const ShellCall = Schema.Struct({ + id: Schema.optionalKey(Schema.String), + type: Schema.Literal("shell_call"), + call_id: Schema.String, + action: Schema.Unknown, + status: Schema.optionalKey(MessageStatus) +}) + +const ShellCallOutput = Schema.Struct({ + id: Schema.optionalKey(Schema.String), + type: Schema.Literal("shell_call_output"), + call_id: Schema.String, + output: Schema.Unknown, + status: Schema.optionalKey(MessageStatus) +}) + +const ApplyPatchCallOutput = Schema.Struct({ + id: Schema.optionalKey(Schema.String), + type: Schema.Literal("apply_patch_call_output"), + call_id: Schema.String, + status: Schema.optionalKey(MessageStatus), + output: Schema.optionalKey(Schema.Unknown) +}) + +const McpApprovalResponse = Schema.Struct({ + type: Schema.Literal("mcp_approval_response"), + approval_request_id: Schema.String, + approve: Schema.Boolean +}) + +const RequestMessageItem = Schema.Struct({ + type: Schema.optionalKey(Schema.Literal("message")), + role: MessageRole, + status: Schema.optionalKey(MessageStatus), + content: Schema.Union([ + Schema.String, + Schema.Array(InputContent) + ]) +}) + +/** + * Schema for item shapes accepted by an OpenAI Responses request `input` field. + * + * **When to use** + * + * Use when validating structured `CreateResponse.input` array items. + * + * **Details** + * + * Accepted item families include request/output messages, function call and + * function call output, reasoning items, item references, shell and local shell + * calls and outputs, apply-patch output, and MCP approval responses. + * + * @see {@link CreateResponse} for the request schema that consumes input items + * @see {@link InputContent} for content blocks inside message items + * + * @category schemas + * @since 4.0.0 + */ +export const InputItem = Schema.Union([ + RequestMessageItem, + OutputMessage, + FunctionCall, + FunctionCallOutput, + ReasoningItem, + ItemReference, + LocalShellCall, + LocalShellCallOutput, + ShellCall, + ShellCallOutput, + ApplyPatchCallOutput, + McpApprovalResponse +]) + +/** + * Item shape accepted by an OpenAI Responses request `input` field. + * + * **When to use** + * + * Use when typing structured `CreateResponse.input` array items. + * + * **Details** + * + * Accepted item families include request/output messages, function call and + * function call output, reasoning items, item references, shell and local shell + * calls and outputs, apply-patch output, and MCP approval responses. + * + * @category models + * @since 4.0.0 + */ +export type InputItem = typeof InputItem.Type + +const FunctionTool = Schema.Struct({ + type: Schema.Literal("function"), + name: Schema.String, + description: Schema.optionalKey(Schema.NullOr(Schema.String)), + parameters: Schema.optionalKey(Schema.NullOr(JsonObject)), + strict: Schema.optionalKey(Schema.NullOr(Schema.Boolean)) +}) + +const CustomTool = Schema.Struct({ + type: Schema.Literal("custom"), + name: Schema.String, + description: Schema.optionalKey(Schema.String), + format: Schema.optionalKey(Schema.Unknown) +}) + +const ProviderDefinedTool = Schema.StructWithRest( + Schema.Struct({ + type: Schema.Literals([ + "apply_patch", + "code_interpreter", + "file_search", + "image_generation", + "local_shell", + "mcp", + "shell", + "web_search", + "web_search_preview" + ]) + }), + [UnknownRecord] +) + +/** + * Schema for tool definitions that can be supplied to an OpenAI Responses request. + * + * **When to use** + * + * Use when validating or encoding the `tools` array for a Responses request, + * including provider-defined tool records with provider-specific fields. + * + * **Details** + * + * Accepted variants are function tools, custom tools, and provider-defined + * OpenAI tools. Provider-defined `type` literals include `apply_patch`, + * `code_interpreter`, `file_search`, `image_generation`, `local_shell`, `mcp`, + * `shell`, `web_search`, and `web_search_preview`. + * + * **Gotchas** + * + * Provider-defined tools use `Schema.StructWithRest`, so this schema checks the + * provider tool `type` and permits additional provider fields rather than fully + * validating every provider-specific tool payload. + * + * @see {@link ToolChoice} for selecting whether and which tools the model may call + * @see {@link CreateResponse} for the request schema that consumes tools + * + * @category schemas + * @since 4.0.0 + */ +export const Tool = Schema.Union([ + FunctionTool, + CustomTool, + ProviderDefinedTool +]) + +/** + * Tool definition that can be supplied to an OpenAI Responses request. + * + * @category models + * @since 4.0.0 + */ +export type Tool = typeof Tool.Type + +/** + * Schema for selecting whether and which tools the model may call in a Responses request. + * + * **When to use** + * + * Use when validating or encoding the `tool_choice` field that constrains model + * tool use separately from the tool definitions themselves. + * + * **Details** + * + * Accepted forms are `"none"`, `"auto"`, `"required"`, an allowed-tools set, + * a named function or custom tool, or a provider-defined tool choice. + * + * @see {@link Tool} for tool definitions referenced by tool choices + * @see {@link CreateResponse} for the request schema that consumes `tool_choice` + * + * @category schemas + * @since 4.0.0 + */ +export const ToolChoice = Schema.Union([ + Schema.Literals(["none", "auto", "required"]), + Schema.Struct({ + type: Schema.Literal("allowed_tools"), + mode: Schema.Literals(["auto", "required"]), + tools: Schema.Array(JsonObject) + }), + Schema.Struct({ + type: Schema.Literal("function"), + name: Schema.String + }), + Schema.Struct({ + type: Schema.Literal("custom"), + name: Schema.String + }), + Schema.StructWithRest( + Schema.Struct({ + type: Schema.Literals([ + "apply_patch", + "code_interpreter", + "file_search", + "image_generation", + "local_shell", + "mcp", + "shell", + "web_search", + "web_search_preview" + ]) + }), + [UnknownRecord] + ) +]) + +/** + * Tool selection mode or named tool choice for a Responses request. + * + * **Details** + * + * Accepted forms are `"none"`, `"auto"`, `"required"`, an allowed-tools set, + * a named function or custom tool, or a provider-defined tool choice. + * + * @category models + * @since 4.0.0 + */ +export type ToolChoice = typeof ToolChoice.Type + +/** + * Schema for text output format configuration, including plain text, JSON object, and JSON Schema responses. + * + * **When to use** + * + * Use when validating or encoding the `text.format` setting for a Responses + * request, especially when choosing structured JSON Schema output. + * + * **Details** + * + * Accepted variants are `text`, `json_schema`, and `json_object`. + * + * **Gotchas** + * + * `json_object` is the older JSON mode. Prefer `json_schema` for models that + * support it. + * + * @see {@link CreateResponse} for the request schema that consumes text format configuration + * + * @category schemas + * @since 4.0.0 + */ +export const TextResponseFormatConfiguration = Schema.Union([ + Schema.Struct({ type: Schema.Literal("text") }), + Schema.Struct({ + type: Schema.Literal("json_schema"), + description: Schema.optionalKey(Schema.String), + name: Schema.String, + schema: JsonObject, + strict: Schema.optionalKey(Schema.NullOr(Schema.Boolean)) + }), + Schema.Struct({ type: Schema.Literal("json_object") }) +]) + +/** + * Text output format configuration for plain text, JSON object, or JSON Schema responses. + * + * @category models + * @since 4.0.0 + */ +export type TextResponseFormatConfiguration = typeof TextResponseFormatConfiguration.Type + +/** + * Schema for request options used to create an OpenAI Responses API response. + * + * **When to use** + * + * Use to validate or encode payloads sent to the OpenAI Responses API. + * + * **Details** + * + * 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, + * prompt caching, and optional response fields requested through `include`. + * + * **Gotchas** + * + * When `stream` is `true`, the API returns stream events instead of a single + * response object. + * + * @see {@link Response} for decoded non-streaming response objects + * @see {@link ResponseStreamEvent} for decoded streaming event objects + * + * @category schemas + * @since 4.0.0 + */ +export const CreateResponse = Schema.Struct({ + metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), + 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", "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.Int), + max_tool_calls: Schema.optional(Schema.Int), + text: Schema.optional( + Schema.Struct({ + format: Schema.optional(TextResponseFormatConfiguration), + verbosity: Schema.optional(Schema.Literals(["low", "medium", "high"])) + }) + ), + tools: Schema.optional(Schema.Array(Tool)), + tool_choice: Schema.optional(ToolChoice), + truncation: Schema.optional(Schema.Literals(["auto", "disabled"])), + input: Schema.optional( + Schema.Union([ + Schema.String, + Schema.Array(InputItem) + ]) + ), + include: Schema.optional(Schema.Array(IncludeEnum)), + store: Schema.optional(Schema.Boolean), + instructions: Schema.optional(Schema.String), + stream: Schema.optional(Schema.Boolean), + conversation: Schema.optional(Schema.String), + modalities: Schema.optional(Schema.Array(Schema.Literals(["text", "audio"]))), + seed: Schema.optional(Schema.Int) +}) + +/** + * Request options used to create an OpenAI Responses API response. + * + * @category options + * @since 4.0.0 + */ +export type CreateResponse = typeof CreateResponse.Type + +/** + * Schema for token accounting reported on OpenAI Responses API response objects. + * + * **Details** + * + * The required counters are `input_tokens`, `output_tokens`, and + * `total_tokens`. Provider-specific token detail objects are preserved through + * `input_tokens_details`, `output_tokens_details`, and additional fields. + * + * @category schemas + * @since 4.0.0 + */ +export const ResponseUsage = Schema.StructWithRest( + Schema.Struct({ + 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) + }), + [UnknownRecord] +) + +/** + * Token accounting reported on OpenAI Responses API response objects. + * + * **Details** + * + * Includes total input, output, and combined token counts, with provider-specific + * token detail fields preserved when present. + * + * @category models + * @since 4.0.0 + */ +export type ResponseUsage = typeof ResponseUsage.Type + +const ApplyPatchOperation = Schema.Struct({ + type: Schema.String, + path: Schema.String, + diff: Schema.optionalKey(Schema.String) +}) + +const ApplyPatchCall = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("apply_patch_call"), + call_id: Schema.String, + operation: ApplyPatchOperation, + status: Schema.optionalKey(MessageStatus) +}) + +const CodeInterpreterCall = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("code_interpreter_call"), + code: Schema.optionalKey(Schema.String), + container_id: Schema.String, + outputs: Schema.optionalKey(Schema.Array(Schema.Unknown)), + status: Schema.optionalKey(MessageStatus) +}) + +const ComputerCall = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("computer_call"), + status: Schema.optionalKey(MessageStatus) +}) + +const FileSearchCall = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("file_search_call"), + status: Schema.optionalKey(Schema.String), + queries: Schema.optionalKey(Schema.Array(Schema.String)), + results: Schema.optionalKey(Schema.NullOr(Schema.Unknown)) +}) + +const ImageGenerationCall = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("image_generation_call"), + result: Schema.optionalKey(Schema.NullOr(Schema.String)), + status: Schema.optionalKey(Schema.Literals(["in_progress", "completed", "generating", "failed"])) +}) + +const McpCall = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("mcp_call"), + approval_request_id: Schema.optionalKey(Schema.NullOr(Schema.String)), + name: Schema.String, + arguments: Schema.Unknown, + output: Schema.optionalKey(Schema.Unknown), + error: Schema.optionalKey(Schema.Unknown), + server_label: Schema.optionalKey(Schema.NullOr(Schema.String)) +}) + +const McpListTools = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("mcp_list_tools") +}) + +const McpApprovalRequest = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("mcp_approval_request"), + approval_request_id: Schema.optionalKey(Schema.String), + name: Schema.String, + arguments: Schema.Unknown +}) + +const WebSearchCall = Schema.Struct({ + id: Schema.String, + type: Schema.Literal("web_search_call"), + action: Schema.optionalKey(Schema.Unknown), + status: Schema.optionalKey(Schema.String) +}) + +const OutputItem = Schema.Union([ + ApplyPatchCall, + CodeInterpreterCall, + ComputerCall, + FileSearchCall, + FunctionCall, + ImageGenerationCall, + LocalShellCall, + McpCall, + McpListTools, + McpApprovalRequest, + OutputMessage, + ReasoningItem, + ShellCall, + WebSearchCall +]) + +const ResponseError = Schema.Struct({ + code: Schema.String, + message: Schema.String +}) + +/** + * Schema for an OpenAI Responses API response object. + * + * **When to use** + * + * Use to decode non-streaming OpenAI Responses API responses. + * + * **Details** + * + * Response objects include the response id, model, creation time, output items, + * optional token usage, optional incomplete details, and optional service tier. + * + * @see {@link CreateResponse} for the request schema that creates responses + * @see {@link ResponseUsage} for token accounting on responses + * @see {@link ResponseStreamEvent} for streaming response events + * + * @category schemas + * @since 4.0.0 + */ +export const Response = Schema.Struct({ + id: Schema.String, + object: Schema.optionalKey(Schema.Literal("response")), + model: Schema.String, + 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({ + reason: Schema.optionalKey(Schema.Literals(["max_output_tokens", "content_filter"])) + }) + ) + ), + service_tier: Schema.optionalKey(Schema.String) +}) + +/** + * OpenAI Responses API response object. + * + * **When to use** + * + * Use when typing non-streaming OpenAI Responses API responses. + * + * **Details** + * + * Response objects include metadata, output items, optional token usage, and + * optional incomplete details. + * + * @category models + * @since 4.0.0 + */ +export type Response = typeof Response.Type + +const ResponseCreatedEvent = Schema.Struct({ + type: Schema.Literal("response.created"), + response: Response, + sequence_number: Schema.Int +}) + +const ResponseCompletedEvent = Schema.Struct({ + type: Schema.Literal("response.completed"), + response: Response, + sequence_number: Schema.Int +}) + +const ResponseIncompleteEvent = Schema.Struct({ + type: Schema.Literal("response.incomplete"), + response: Response, + sequence_number: Schema.Int +}) + +const ResponseFailedEvent = Schema.Struct({ + type: Schema.Literal("response.failed"), + response: Response, + sequence_number: Schema.Int +}) + +const ResponseOutputItemAddedEvent = Schema.Struct({ + type: Schema.Literal("response.output_item.added"), + output_index: Schema.Int, + sequence_number: Schema.Int, + item: OutputItem +}) + +const ResponseOutputItemDoneEvent = Schema.Struct({ + type: Schema.Literal("response.output_item.done"), + output_index: Schema.Int, + sequence_number: Schema.Int, + item: OutputItem +}) + +const ResponseOutputTextDeltaEvent = Schema.Struct({ + type: Schema.Literal("response.output_text.delta"), + item_id: Schema.String, + output_index: Schema.Int, + content_index: Schema.Int, + delta: Schema.String, + sequence_number: 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.Int, + content_index: Schema.Int, + annotation_index: Schema.Int, + sequence_number: Schema.Int, + annotation: Annotation +}) + +const ResponseReasoningSummaryPartAddedEvent = Schema.Struct({ + type: Schema.Literal("response.reasoning_summary_part.added"), + item_id: Schema.String, + output_index: Schema.Int, + summary_index: Schema.Int, + sequence_number: Schema.Int, + part: SummaryTextContent +}) + +const ResponseReasoningSummaryPartDoneEvent = Schema.Struct({ + type: Schema.Literal("response.reasoning_summary_part.done"), + item_id: Schema.String, + output_index: Schema.Int, + summary_index: Schema.Int, + sequence_number: Schema.Int, + part: SummaryTextContent +}) + +const ResponseReasoningSummaryTextDeltaEvent = Schema.Struct({ + type: Schema.Literal("response.reasoning_summary_text.delta"), + item_id: Schema.String, + output_index: Schema.Int, + summary_index: Schema.Int, + delta: Schema.String, + sequence_number: Schema.Int +}) + +const ResponseFunctionCallArgumentsDeltaEvent = Schema.Struct({ + type: Schema.Literal("response.function_call_arguments.delta"), + item_id: Schema.String, + output_index: Schema.Int, + sequence_number: Schema.Int, + delta: Schema.String +}) + +const ResponseFunctionCallArgumentsDoneEvent = Schema.Struct({ + type: Schema.Literal("response.function_call_arguments.done"), + item_id: Schema.String, + output_index: Schema.Int, + sequence_number: 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.Int, + sequence_number: 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.Int, + sequence_number: 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.Int, + sequence_number: 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.Int, + sequence_number: 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.Int, + sequence_number: Schema.Int, + partial_image_b64: Schema.String +}) + +const ResponseErrorEvent = Schema.Struct({ + type: Schema.Literal("error"), + code: Schema.NullOr(Schema.String), + message: Schema.String, + param: Schema.NullOr(Schema.String), + sequence_number: 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.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", + "response.incomplete", + "response.failed", + "response.output_item.added", + "response.output_item.done", + "response.output_text.delta", + "response.output_text.annotation.added", + "response.reasoning_summary_part.added", + "response.reasoning_summary_part.done", + "response.reasoning_summary_text.delta", + "response.function_call_arguments.delta", + "response.function_call_arguments.done", + "response.code_interpreter_call_code.delta", + "response.code_interpreter_call_code.done", + "response.apply_patch_call_operation_diff.delta", + "response.apply_patch_call_operation_diff.done", + "response.image_generation_call.partial_image", + "error" +]) + +/** + * Fallback event shape for future or provider-specific response stream events. + * + * @category models + * @since 4.0.0 + */ +export type UnknownResponseStreamEvent = { + readonly type: string + readonly [key: string]: unknown +} + +const UnknownResponseStreamEvent = Schema.declare( + (value): value is UnknownResponseStreamEvent => + Predicate.hasProperty(value, "type") && + typeof value.type === "string" && + !knownResponseStreamEventTypes.has(value.type), + { + identifier: "UnknownResponseStreamEvent", + description: "Fallback for unknown future stream events" + } +) + +/** + * Schema for server-sent event shapes emitted by OpenAI Responses API streams. + * + * **When to use** + * + * Use to decode events from a streaming OpenAI Responses API request. + * + * **Details** + * + * Known event variants include response lifecycle events, output item events, + * text and reasoning deltas, tool-call deltas, partial image events, and error + * events. + * + * **Gotchas** + * + * Future event types decode through the fallback only when their `type` is not + * one of the known event types. Malformed known events still fail to decode. + * + * @see {@link Response} for complete response objects carried by lifecycle events + * @see {@link UnknownResponseStreamEvent} for the fallback shape for future event types + * + * @category schemas + * @since 4.0.0 + */ +export const ResponseStreamEvent = Schema.Union([ + ResponseCreatedEvent, + ResponseCompletedEvent, + ResponseIncompleteEvent, + ResponseFailedEvent, + ResponseOutputItemAddedEvent, + ResponseOutputItemDoneEvent, + ResponseOutputTextDeltaEvent, + ResponseOutputTextAnnotationAddedEvent, + ResponseReasoningSummaryPartAddedEvent, + ResponseReasoningSummaryPartDoneEvent, + ResponseReasoningSummaryTextDeltaEvent, + ResponseFunctionCallArgumentsDeltaEvent, + ResponseFunctionCallArgumentsDoneEvent, + ResponseCodeInterpreterCallCodeDeltaEvent, + ResponseCodeInterpreterCallCodeDoneEvent, + ResponseApplyPatchCallOperationDiffDeltaEvent, + ResponseApplyPatchCallOperationDiffDoneEvent, + ResponseImageGenerationCallPartialImageEvent, + ResponseErrorEvent, + NestedResponseErrorEvent, + UnknownResponseStreamEvent +]) + +/** + * Server-sent event shape emitted by OpenAI Responses API streams. + * + * **When to use** + * + * Use when typing events from a streaming OpenAI Responses API request. + * + * **Details** + * + * Includes known response stream events plus a fallback shape for unknown future + * event types. + * + * @category models + * @since 4.0.0 + */ +export type ResponseStreamEvent = typeof ResponseStreamEvent.Type + +/** + * Schema for one embedding item returned by the OpenAI embeddings API. + * + * **When to use** + * + * Use when validating individual embedding entries at the OpenAI client boundary + * before assuming the embedding payload is a numeric vector. + * + * **Details** + * + * An embedding item contains its `index`, optional `object` marker, and an + * `embedding` represented either as a numeric vector or as a string. + * + * **Gotchas** + * + * Callers that need numeric vectors must account for string embeddings, such as + * base64-encoded embeddings returned for string encoding formats. + * + * @category schemas + * @since 4.0.0 + */ +export const Embedding = Schema.Struct({ + embedding: Schema.Union([ + Schema.Array(Schema.Finite), + Schema.String + ]), + index: Schema.Int, + object: Schema.optionalKey(Schema.String) +}) + +/** + * One embedding item returned by the OpenAI embeddings API. + * + * **Details** + * + * Contains the item index and embedding payload. The embedding payload may be a + * numeric vector or a string. + * + * @category models + * @since 4.0.0 + */ +export type Embedding = typeof Embedding.Type + +/** + * Schema for the request payload sent to the OpenAI embeddings endpoint. + * + * **When to use** + * + * Use when validating or encoding embeddings requests before sending them to + * OpenAI, while leaving model-specific limits to the provider. + * + * **Details** + * + * Requires `input` and `model`. `input` may be a string, an array of strings, + * a token array, or an array of token arrays. Optional fields configure the + * embedding encoding format, requested dimensions, and user identifier. + * + * **Gotchas** + * + * This schema validates the transport shape, but OpenAI still enforces + * provider-side constraints such as non-empty input, integer token ids, input + * size limits, positive dimensions, and model-specific dimension support. + * + * @category schemas + * @since 4.0.0 + */ +export const CreateEmbeddingRequest = Schema.Struct({ + input: Schema.Union([ + Schema.String, + Schema.Array(Schema.String), + 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.Int), + user: Schema.optionalKey(Schema.String) +}) + +/** + * Request payload sent to the OpenAI embeddings endpoint. + * + * @category models + * @since 4.0.0 + */ +export type CreateEmbeddingRequest = typeof CreateEmbeddingRequest.Type + +/** + * Schema for a successful response payload returned by the OpenAI embeddings endpoint. + * + * **When to use** + * + * Use when you need to validate embeddings responses at an OpenAI client + * boundary before trusting item shapes, especially when numeric and string + * embeddings are both allowed. + * + * **Details** + * + * The response contains an array of `Embedding` items, the model name, an + * optional `object: "list"` marker, and optional token usage counts for prompt + * and total tokens. + * + * **Gotchas** + * + * Each `Embedding` may contain either a numeric vector or a string embedding. + * Callers that require numeric vectors must account for string embeddings. + * + * @see {@link CreateEmbeddingRequest} for the request schema sent to the embeddings endpoint + * @see {@link Embedding} for individual embedding items in the response + * + * @category schemas + * @since 4.0.0 + */ +export const CreateEmbeddingResponse = Schema.Struct({ + data: Schema.Array(Embedding), + model: Schema.String, + object: Schema.optionalKey(Schema.Literal("list")), + usage: Schema.optionalKey( + Schema.Struct({ + prompt_tokens: Schema.Int, + total_tokens: Schema.Int + }) + ) +}) + +/** + * Successful response payload returned by the OpenAI embeddings endpoint. + * + * **When to use** + * + * Use when typing successful OpenAI embeddings responses. + * + * **Details** + * + * Contains embedding items, the model name, optional list marker, and optional + * token usage counts. + * + * @category models + * @since 4.0.0 + */ +export type CreateEmbeddingResponse = typeof CreateEmbeddingResponse.Type diff --git a/.repos/effect/packages/ai/openai/src/OpenAiTelemetry.ts b/.repos/effect/packages/ai/openai/src/OpenAiTelemetry.ts new file mode 100644 index 000000000..6af5c9b4f --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiTelemetry.ts @@ -0,0 +1,162 @@ +/** + * The `OpenAiTelemetry` module defines OpenAI-specific telemetry attributes + * and a helper for adding them to a tracing span. It keeps the standard GenAI + * telemetry attributes and adds request and response metadata, such as response + * format, service tier, and system fingerprint, under the `gen_ai.openai.*` + * OpenTelemetry namespaces. + * + * @since 4.0.0 + */ +import { dual } from "effect/Function" +import * as String from "effect/String" +import type { Span } from "effect/Tracer" +import type { Simplify } from "effect/Types" +import * as Telemetry from "effect/unstable/ai/Telemetry" + +/** + * The attributes used to describe telemetry in the context of Generative + * Artificial Intelligence (GenAI) Models requests and responses. + * + * **Details** + * + * These attributes follow the OpenTelemetry generative AI semantic + * conventions: + * https://opentelemetry.io/docs/specs/semconv/attributes-registry/gen-ai/ + * + * @category models + * @since 4.0.0 + */ +export type OpenAiTelemetryAttributes = Simplify< + & Telemetry.GenAITelemetryAttributes + & Telemetry.AttributesWithPrefix + & Telemetry.AttributesWithPrefix +> + +/** + * All telemetry attributes which are part of the GenAI specification, + * including the OpenAI-specific attributes. + * + * @category models + * @since 4.0.0 + */ +export type AllAttributes = Telemetry.AllAttributes & RequestAttributes & ResponseAttributes + +/** + * Telemetry attributes which are part of the GenAI specification and are + * namespaced by `gen_ai.openai.request`. + * + * @category models + * @since 4.0.0 + */ +export interface RequestAttributes { + /** + * The response format that is requested. + */ + readonly responseFormat?: (string & {}) | WellKnownResponseFormat | null | undefined + /** + * The service tier requested. May be a specific tier, `default`, or `auto`. + */ + readonly serviceTier?: (string & {}) | WellKnownServiceTier | null | undefined +} + +/** + * Telemetry attributes which are part of the GenAI specification and are + * namespaced by `gen_ai.openai.response`. + * + * @category models + * @since 4.0.0 + */ +export interface ResponseAttributes { + /** + * The service tier used for the response. + */ + readonly serviceTier?: string | null | undefined + /** + * A fingerprint to track any eventual change in the Generative AI + * environment. + */ + readonly systemFingerprint?: string | null | undefined +} + +/** + * The `gen_ai.openai.request.response_format` attribute has the following + * list of well-known values. + * + * **Details** + * + * If one of them applies, then the respective value **MUST** be used; + * otherwise, a custom value **MAY** be used. + * + * @category models + * @since 4.0.0 + */ +export type WellKnownResponseFormat = "json_object" | "json_schema" | "text" + +/** + * The `gen_ai.openai.request.service_tier` attribute has the following + * list of well-known values. + * + * **Details** + * + * If one of them applies, then the respective value **MUST** be used; + * otherwise, a custom value **MAY** be used. + * + * @category models + * @since 4.0.0 + */ +export type WellKnownServiceTier = "auto" | "default" + +/** + * Options accepted by `addGenAIAnnotations`, combining standard GenAI + * telemetry attributes with optional OpenAI request and response attributes. + * + * @category options + * @since 4.0.0 + */ +export type OpenAiTelemetryAttributeOptions = Telemetry.GenAITelemetryAttributeOptions & { + openai?: { + request?: RequestAttributes | undefined + response?: ResponseAttributes | undefined + } | undefined +} + +const addOpenAiRequestAttributes = Telemetry.addSpanAttributes("gen_ai.openai.request", String.camelToSnake)< + RequestAttributes +> +const addOpenAiResponseAttributes = Telemetry.addSpanAttributes("gen_ai.openai.response", String.camelToSnake)< + ResponseAttributes +> + +/** + * Applies the specified OpenAI GenAI telemetry attributes to the provided + * `Span`. + * + * **When to use** + * + * Use to annotate an existing OpenTelemetry span with standard GenAI attributes + * plus OpenAI-specific request and response metadata. + * + * **Gotchas** + * + * Mutates the supplied `Span` in place. + * + * @see {@link OpenAiTelemetryAttributeOptions} for the accepted telemetry attributes + * @see {@link Telemetry.addGenAIAnnotations} for the provider-neutral annotation helper + * + * @category tracing + * @since 4.0.0 + */ +export const addGenAIAnnotations: { + (options: OpenAiTelemetryAttributeOptions): (span: Span) => void + (span: Span, options: OpenAiTelemetryAttributeOptions): void +} = dual(2, (span: Span, options: OpenAiTelemetryAttributeOptions) => { + Telemetry.addGenAIAnnotations(span, options) + if (options.openai != null) { + if (options.openai.request != null) { + addOpenAiRequestAttributes(span, options.openai.request) + } + if (options.openai.response != null) { + addOpenAiResponseAttributes(span, options.openai.response) + } + } +}) diff --git a/.repos/effect/packages/ai/openai/src/OpenAiTool.ts b/.repos/effect/packages/ai/openai/src/OpenAiTool.ts new file mode 100644 index 000000000..7d80c57d3 --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/OpenAiTool.ts @@ -0,0 +1,342 @@ +/** + * The `OpenAiTool` module defines OpenAI provider tools for Effect AI language + * model requests. It exposes typed descriptors for tools such as Apply Patch, + * Code Interpreter, File Search, Image Generation, MCP, Web Search, and + * shell-like local tools, including their provider names, configuration + * arguments, call parameters, success schemas, and handler requirements. + * + * @since 4.0.0 + */ +import * as Schema from "effect/Schema" +import * as Tool from "effect/unstable/ai/Tool" +import * as Generated from "./Generated.ts" + +/** + * Union of all OpenAI provider-defined tools. + * + * @category models + * @since 4.0.0 + */ +export type OpenAiTool = + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + +/** + * Defines the OpenAI Apply Patch tool that allows the model to apply diffs by creating, + * deleting, or updating files. This local tool runs in your environment and + * requires a handler to execute file operations. + * + * **When to use** + * + * Use when you want an OpenAI model to request structured file edits as create, + * delete, or update operations that your application executes through a local + * handler. + * + * @category tools + * @since 4.0.0 + */ +export const ApplyPatch = Tool.providerDefined({ + id: "openai.apply_patch", + customName: "OpenAiApplyPatch", + providerName: "apply_patch", + requiresHandler: true, + parameters: Schema.Struct({ + call_id: Generated.ApplyPatchToolCall.fields.call_id, + operation: Generated.ApplyPatchToolCall.fields.operation + }), + success: Schema.Struct({ + status: Generated.ApplyPatchToolCallOutput.fields.status, + output: Generated.ApplyPatchToolCallOutput.fields.output + }) +}) + +/** + * Defines the OpenAI Code Interpreter tool that allows the model to execute Python code in + * a sandboxed environment. + * + * **When to use** + * + * Use to enable OpenAI-hosted Python execution for a model response. + * + * **Details** + * + * The tool is configured with a `container` argument. Successful tool calls + * expose `outputs`, which may contain logs or generated images, or `null` when + * no outputs are available. + * + * @category tools + * @since 4.0.0 + */ +export const CodeInterpreter = Tool.providerDefined({ + id: "openai.code_interpreter", + customName: "OpenAiCodeInterpreter", + providerName: "code_interpreter", + args: Schema.Struct({ + container: Generated.CodeInterpreterTool.fields.container + }), + parameters: Schema.Struct({ + code: Generated.CodeInterpreterToolCall.fields.code, + container_id: Generated.CodeInterpreterToolCall.fields.container_id + }), + success: Schema.Struct({ + outputs: Generated.CodeInterpreterToolCall.fields.outputs + }) +}) + +/** + * Defines the OpenAI File Search tool that enables the model to search through uploaded + * files and vector stores. + * + * **When to use** + * + * Use to let an OpenAI model search uploaded files through one or more vector + * stores. + * + * **Details** + * + * The tool requires `vector_store_ids` and accepts optional `filters`, + * `max_num_results`, and `ranking_options`. Successful tool calls expose the + * search `status`, generated `queries`, and optional `results`. + * + * @category tools + * @since 4.0.0 + */ +export const FileSearch = Tool.providerDefined({ + id: "openai.file_search", + customName: "OpenAiFileSearch", + providerName: "file_search", + args: Schema.Struct({ + filters: Generated.FileSearchTool.fields.filters, + max_num_results: Generated.FileSearchTool.fields.max_num_results, + ranking_options: Generated.FileSearchTool.fields.ranking_options, + vector_store_ids: Generated.FileSearchTool.fields.vector_store_ids + }), + success: Schema.Struct({ + status: Generated.FileSearchToolCall.fields.status, + queries: Generated.FileSearchToolCall.fields.queries, + results: Generated.FileSearchToolCall.fields.results + }) +}) + +/** + * Defines the OpenAI Image Generation tool that enables the model to generate images using + * the GPT image models. + * + * **When to use** + * + * Use to enable OpenAI provider-defined image generation through a language + * model response. + * + * **Details** + * + * The tool configures the `image_generation` provider tool, including model, + * size, quality, output format, moderation, background, input-image options, + * and partial image settings. Successful tool calls expose `result` as base64 + * image data or `null`. + * + * @category tools + * @since 4.0.0 + */ +export const ImageGeneration = Tool.providerDefined({ + id: "openai.image_generation", + customName: "OpenAiImageGeneration", + providerName: "image_generation", + args: Schema.Struct({ + background: Generated.ImageGenTool.fields.background, + input_fidelity: Generated.ImageGenTool.fields.input_fidelity, + input_image_mask: Generated.ImageGenTool.fields.input_image_mask, + model: Generated.ImageGenTool.fields.model, + moderation: Generated.ImageGenTool.fields.moderation, + output_compression: Generated.ImageGenTool.fields.output_compression, + output_format: Generated.ImageGenTool.fields.output_format, + partial_images: Generated.ImageGenTool.fields.partial_images, + quality: Generated.ImageGenTool.fields.quality, + size: Generated.ImageGenTool.fields.size + }), + success: Schema.Struct({ + result: Generated.ImageGenToolCall.fields.result + }) +}) + +/** + * Defines the OpenAI Local Shell tool that enables the model to run a command with a local + * shell. This local tool runs in your environment and requires a handler to + * execute commands. + * + * **When to use** + * + * Use to let an OpenAI model request local shell commands that your application + * executes through a handler. + * + * **Details** + * + * The tool exposes a provider-defined `local_shell` call. It is marked as + * handler-required, so applications must provide the command execution policy + * and implementation. + * + * @category tools + * @since 4.0.0 + */ +export const LocalShell = Tool.providerDefined({ + id: "openai.local_shell", + customName: "OpenAiLocalShell", + providerName: "local_shell", + requiresHandler: true, + parameters: Schema.Struct({ + action: Generated.LocalShellToolCall.fields.action + }), + success: Schema.Struct({ + output: Generated.LocalShellToolCallOutput.fields.output + }) +}) + +/** + * Defines the OpenAI MCP tool that gives the model access to additional tools via remote + * Model Context Protocol (MCP) servers. + * + * **When to use** + * + * Use to let an OpenAI model call tools exposed by a remote MCP server. + * + * **Details** + * + * The tool accepts MCP server configuration such as allowed tools, + * authorization, connector id, approval requirements, server metadata, and + * server URL. Tool call results include the called tool name, arguments, output, + * error, and server label. + * + * **Gotchas** + * + * This schema leaves both `server_url` and `connector_id` optional, but OpenAI + * may require a server URL or connector id for a usable MCP tool configuration. + * + * @category tools + * @since 4.0.0 + */ +export const Mcp = Tool.providerDefined({ + id: "openai.mcp", + customName: "OpenAiMcp", + providerName: "mcp", + args: Schema.Struct({ + allowed_tools: Generated.MCPTool.fields.allowed_tools, + authorization: Generated.MCPTool.fields.authorization, + connector_id: Generated.MCPTool.fields.connector_id, + require_approval: Generated.MCPTool.fields.require_approval, + server_description: Generated.MCPTool.fields.server_description, + server_label: Generated.MCPTool.fields.server_label, + server_url: Generated.MCPTool.fields.server_url + }), + parameters: Schema.Unknown, + success: Schema.Struct({ + type: Generated.MCPToolCall.fields.type, + name: Generated.MCPToolCall.fields.name, + arguments: Generated.MCPToolCall.fields.arguments, + output: Generated.MCPToolCall.fields.output, + error: Generated.MCPToolCall.fields.error, + server_label: Generated.MCPToolCall.fields.server_label + }) +}) + +/** + * Defines the OpenAI shell tool for model-requested command execution. + * + * **When to use** + * + * Use to let an OpenAI model request shell commands that your application + * executes through a handler. + * + * **Details** + * + * The tool exposes a provider-defined `shell` call. It is marked as + * handler-required, so applications must provide the command execution policy + * and implementation. + * + * @category tools + * @since 4.0.0 + */ +export const Shell = Tool.providerDefined({ + id: "openai.shell", + customName: "OpenAiShell", + providerName: "shell", + requiresHandler: true, + parameters: Schema.Struct({ + action: Generated.FunctionShellCall.fields.action + }), + success: Schema.Struct({ + output: Generated.FunctionShellCallOutput.fields.output + }) +}) + +/** + * Defines the OpenAI Web Search tool that enables the model to search the web for + * information. + * + * **When to use** + * + * Use to enable OpenAI provider-defined web search for a model response. + * + * **Details** + * + * The tool accepts optional filters, user location, and search context size. + * Successful calls expose the performed search action and status. + * + * @see {@link WebSearchPreview} for the preview web search provider tool + * + * @category tools + * @since 4.0.0 + */ +export const WebSearch = Tool.providerDefined({ + id: "openai.web_search", + customName: "OpenAiWebSearch", + providerName: "web_search", + args: Schema.Struct({ + filters: Generated.WebSearchTool.fields.filters, + user_location: Generated.WebSearchTool.fields.user_location, + search_context_size: Generated.WebSearchTool.fields.search_context_size + }), + parameters: Schema.Struct({ + action: Generated.WebSearchToolCall.fields.action + }), + success: Schema.Struct({ + action: Generated.WebSearchToolCall.fields.action, + status: Generated.WebSearchToolCall.fields.status + }) +}) + +/** + * Defines the OpenAI preview Web Search tool for model responses. + * + * **When to use** + * + * Use to enable the preview OpenAI web search provider tool. + * + * **Details** + * + * The preview tool accepts optional user location and search context size, then + * exposes the performed search action and status in successful calls. + * + * @see {@link WebSearch} for the stable web search provider tool + * + * @category tools + * @since 4.0.0 + */ +export const WebSearchPreview = Tool.providerDefined({ + id: "openai.web_search_preview", + customName: "OpenAiWebSearchPreview", + providerName: "web_search_preview", + args: Schema.Struct({ + user_location: Generated.WebSearchPreviewTool.fields.user_location, + search_context_size: Generated.WebSearchPreviewTool.fields.search_context_size + }), + success: Schema.Struct({ + action: Generated.WebSearchToolCall.fields.action, + status: Generated.WebSearchToolCall.fields.status + }) +}) diff --git a/.repos/effect/packages/ai/openai/src/index.ts b/.repos/effect/packages/ai/openai/src/index.ts new file mode 100644 index 000000000..b1758b617 --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/index.ts @@ -0,0 +1,55 @@ +/** + * @since 4.0.0 + */ + +// @barrel: Auto-generated exports. Do not edit manually. + +/** + * @since 1.0.0 + */ +export * as Generated from "./Generated.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiClient from "./OpenAiClient.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiClientGenerated from "./OpenAiClientGenerated.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiConfig from "./OpenAiConfig.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiEmbeddingModel from "./OpenAiEmbeddingModel.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiError from "./OpenAiError.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiLanguageModel from "./OpenAiLanguageModel.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiSchema from "./OpenAiSchema.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiTelemetry from "./OpenAiTelemetry.ts" + +/** + * @since 4.0.0 + */ +export * as OpenAiTool from "./OpenAiTool.ts" diff --git a/.repos/effect/packages/ai/openai/src/internal/errors.ts b/.repos/effect/packages/ai/openai/src/internal/errors.ts new file mode 100644 index 000000000..fc93181b2 --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/internal/errors.ts @@ -0,0 +1,341 @@ +import * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import * as Number from "effect/Number" +import * as Option from "effect/Option" +import * as Predicate from "effect/Predicate" +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" +import type { OpenAiErrorMetadata } from "../OpenAiError.ts" + +// ============================================================================= +// OpenAI Error Body Schema +// ============================================================================= + +/** @internal */ +export const OpenAiErrorBody = Schema.Struct({ + error: Schema.Struct({ + message: Schema.String, + type: Schema.optional(Schema.NullOr(Schema.String)), + param: Schema.optional(Schema.NullOr(Schema.String)), + code: Schema.optional(Schema.NullOr(Schema.String)) + }) +}) + +const OpenAiCompatibleErrorBody = Schema.Struct({ + error: Schema.String, + code: Schema.optional(Schema.String) +}) + +// ============================================================================= +// Error Mappers +// ============================================================================= + +/** @internal */ +export const mapSchemaError = dual< + (method: string) => (error: Schema.SchemaError) => AiError.AiError, + (error: Schema.SchemaError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenAiClient", + method, + 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, + (error: HttpClientError.HttpClientError, method: string) => Effect.Effect +>(2, (error, method) => { + const reason = error.reason + switch (reason._tag) { + case "TransportError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.NetworkError({ + reason: "TransportError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "EncodeError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.NetworkError({ + reason: "EncodeError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "InvalidUrlError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.NetworkError({ + reason: "InvalidUrlError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "StatusCodeError": { + return mapStatusCodeError(reason, method) + } + case "DecodeError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Failed to decode response" + }) + })) + } + case "EmptyBodyError": { + return Effect.fail(AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Response body was empty" + }) + })) + } + } +}) + +/** @internal */ +const mapStatusCodeError = Effect.fnUntraced(function*( + error: HttpClientError.StatusCodeError, + method: string +) { + const { request, response, description } = error + const status = response.status + const headers = response.headers as Record + const requestId = headers["x-request-id"] + + // Try to get the actual response body. The description from filterStatusOk + // is often just "non 2xx status code", so try reading from response.text + let body: string | undefined = description + if (!description || !description.startsWith("{")) { + const responseBody = yield* Effect.option(response.text) + if (Option.isSome(responseBody) && responseBody.value) { + body = responseBody.value + } + } + + // Try to parse the body as JSON to extract error details + let json: unknown = undefined + // @effect-diagnostics effect/tryCatchInEffectGen:off + try { + json = Predicate.isNotUndefined(body) ? JSON.parse(body) : undefined + } catch { + 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, + http: buildHttpContext({ request, response, body }), + metadata: { + errorCode, + errorType: Option.isSome(decoded) ? decoded.value.error.type ?? null : null, + requestId: requestId ?? null + } + }) + + return yield* AiError.make({ module: "OpenAiClient", method, reason }) +}) + +// ============================================================================= +// Rate Limits +// ============================================================================= + +/** @internal */ +export const parseRateLimitHeaders = (headers: Record) => { + const retryAfterRaw = headers["retry-after"] + let retryAfter: Duration.Duration | undefined + if (Predicate.isNotUndefined(retryAfterRaw)) { + const parsed = Number.parse(retryAfterRaw) + if (Option.isSome(parsed)) { + retryAfter = Duration.seconds(parsed.value) + } + } + const remainingRaw = headers["x-ratelimit-remaining-requests"] + const remaining = Predicate.isNotUndefined(remainingRaw) + ? Option.getOrNull(Number.parse(remainingRaw)) + : null + return { + retryAfter, + limit: headers["x-ratelimit-limit-requests"] ?? null, + remaining, + resetRequests: headers["x-ratelimit-reset-requests"] ?? null, + resetTokens: headers["x-ratelimit-reset-tokens"] ?? null + } +} + +// ============================================================================= +// HTTP Context +// ============================================================================= + +/** @internal */ +export const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +/** @internal */ +export const buildHttpContext = (params: { + readonly request: HttpClientRequest.HttpClientRequest + readonly response?: HttpClientResponse.HttpClientResponse + readonly body?: string | undefined +}): typeof AiError.HttpContext.Type => ({ + request: buildHttpRequestDetails(params.request), + response: Predicate.isNotUndefined(params.response) + ? { + status: params.response.status, + headers: Redactable.redact(params.response.headers) as Record + } + : undefined, + body: params.body +}) + +// ============================================================================= +// HTTP Status Code +// ============================================================================= + +/** @internal */ +export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { + readonly status: number + readonly headers: Record + readonly message: string | undefined + readonly metadata: OpenAiErrorMetadata + readonly http: typeof AiError.HttpContext.Type +}): AiError.AiErrorReason => { + const errorDescription = AiError.buildErrorDescription({ + status, + message, + method: http.request.method, + url: http.request.url, + errorCode: metadata.errorCode, + errorType: metadata.errorType, + requestId: metadata.requestId, + body: http.body + }) + + switch (status) { + case 400: + return new AiError.InvalidRequestError({ + 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: errorDescription, + metadata: { openai: metadata }, + http + }) + case 409: + case 422: + return new AiError.InvalidRequestError({ + description: errorDescription, + metadata: { openai: metadata }, + http + }) + case 402: + return new AiError.QuotaExhaustedError({ + metadata: { openai: metadata }, + http + }) + case 429: { + // Best-effort detection: OpenAI returns insufficient_quota for billing/quota issues + if ( + metadata.errorCode === "insufficient_quota" || + metadata.errorType === "insufficient_quota" || + metadata.errorCode === "billing_insufficient_balance" || + metadata.errorType === "billing_insufficient_balance" + ) { + return new AiError.QuotaExhaustedError({ + metadata: { openai: metadata }, + http + }) + } + const { retryAfter, ...rateLimitMetadata } = parseRateLimitHeaders(headers) + return new AiError.RateLimitError({ + retryAfter, + metadata: { + openai: { + ...metadata, + ...rateLimitMetadata + } + }, + http + }) + } + default: + if (status >= 500) { + return new AiError.InternalProviderError({ + description: message ?? "Server error", + metadata, + http + }) + } + return new AiError.UnknownError({ + description: message, + metadata, + http + }) + } +} diff --git a/.repos/effect/packages/ai/openai/src/internal/utilities.ts b/.repos/effect/packages/ai/openai/src/internal/utilities.ts new file mode 100644 index 000000000..26cf670c3 --- /dev/null +++ b/.repos/effect/packages/ai/openai/src/internal/utilities.ts @@ -0,0 +1,27 @@ +import type * as Response from "effect/unstable/ai/Response" + +const finishReasonMap: Record = { + content_filter: "content-filter", + function_call: "tool-calls", + length: "length", + stop: "stop", + tool_calls: "tool-calls" +} + +/** @internal */ +export const escapeJSONDelta = (delta: string): string => JSON.stringify(delta).slice(1, -1) + +/** @internal */ +export const resolveFinishReason = ( + finishReason: string | null | undefined, + hasToolCalls: boolean +): Response.FinishReason => { + if (finishReason == null) { + return hasToolCalls ? "tool-calls" : "stop" + } + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined + if (reason == null) { + return hasToolCalls ? "tool-calls" : "unknown" + } + return reason +} diff --git a/.repos/effect/packages/ai/openai/test/OpenAiClient.test.ts b/.repos/effect/packages/ai/openai/test/OpenAiClient.test.ts new file mode 100644 index 000000000..d027d86d0 --- /dev/null +++ b/.repos/effect/packages/ai/openai/test/OpenAiClient.test.ts @@ -0,0 +1,810 @@ +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, 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" +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*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) + + 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*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) + + 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*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) + + 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*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) + + 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*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) + + 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") + })))) + + 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*() { + 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" + }).pipe( + OpenAiConfig.withClientTransform((client) => { + configTransformApplied = true + return client.pipe( + HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "config")) + ) + }) + ) + + const requests = yield* MockHttpClient.requests + assert.isTrue(optionsTransformApplied) + assert.isTrue(configTransformApplied) + 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*() { + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated + yield* client.createResponse({ + payload: { model: "gpt-4o", input: "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*() { + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated + yield* client.createResponse({ + payload: { model: "gpt-4o", input: "test" } + }) + + 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" + })))) + + 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" } + }).pipe( + OpenAiConfig.withClientTransform((client) => { + configTransformApplied = true + return client.pipe( + HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "config")) + ) + }) + ) + + const requests = yield* MockHttpClient.requests + assert.isTrue(optionsTransformApplied) + assert.isTrue(configTransformApplied) + 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", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + assert.isNotNull(client.client) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("layerConfig loads from Config", () => { + const configProvider = ConfigProvider.fromEnv({ + env: { + MY_API_KEY: "sk-config-key", + MY_API_URL: "https://config.api.com/v1" + } + }) + + return Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) + + 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 client = yield* OpenAiClient.make({ + apiKey: Redacted.make("test-key") + }) + 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, "NetworkError") + }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, makeTransportErrorHttpClient())))) + + it.effect("maps 400 status to InvalidRequestError reason", () => + Effect.gen(function*() { + 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 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") + 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 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") + 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 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 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, "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" + } + } + })))) + + 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 = { + request: { + method: "POST" as const, + url: "https://api.openai.com", + urlParams: [], + hash: undefined, + headers: {} + } + } + const reason = Errors.mapStatusCodeToReason({ + status: 429, + headers: {}, + message: "You exceeded your current quota", + metadata: { + errorCode: "insufficient_quota", + errorType: "insufficient_quota", + requestId: null + }, + http + }) + assert.strictEqual(reason._tag, "QuotaExhaustedError") + }) + + it("OpenAiErrorBody decodes error with type and code", () => { + const json = { + error: { + message: "You exceeded your current quota", + type: "insufficient_quota", + code: "insufficient_quota" + } + } + const decoded = Schema.decodeUnknownSync(Errors.OpenAiErrorBody)(json) + assert.strictEqual(decoded.error.message, "You exceeded your current quota") + assert.strictEqual(decoded.error.type, "insufficient_quota") + assert.strictEqual(decoded.error.code, "insufficient_quota") + }) + + it.effect("maps 5xx status to InternalProviderError reason", () => + Effect.gen(function*() { + 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 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 client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createEmbedding({ + model: "invalid-model", + input: "test" + }).pipe(Effect.flip) + + 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 client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createEmbedding({ + model: "text-embedding-ada-002", + input: "test" + }).pipe(Effect.flip) + + 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.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") + ) + + assert.strictEqual(result._tag, "Some") + })) + + it.effect("accepts keepalive stream events", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const [_, stream] = yield* client.createResponseStream({ + model: "gpt-4o", + input: "test" + }) + + const events = yield* Stream.runCollect(stream) + const parts = globalThis.Array.from(events) + + assert.strictEqual(parts.length, 3) + const keepAlive = parts[1] + assert.strictEqual(keepAlive.type, "keepalive") + if (keepAlive.type === "keepalive" && "sequence_number" in keepAlive) { + assert.strictEqual(keepAlive.sequence_number, 2) + } + + const completed = parts[2] + assert.isTrue(typeof completed === "object" && completed !== null && "type" in completed) + if (typeof completed === "object" && completed !== null && "type" in completed) { + assert.strictEqual(completed.type, "response.completed") + if ( + completed.type === "response.completed" && + "response" in completed && + typeof completed.response === "object" && + completed.response !== null && + "id" in completed.response + ) { + assert.strictEqual(completed.response.id, "resp_stream") + } + } + }).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" }) + } + ] + })))) + + 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" + }).pipe( + Effect.andThen(([_, stream]) => Stream.runDrain(stream)), + Effect.flip + ) + + assert.strictEqual(result._tag, "AiError") + assert.strictEqual(result.reason._tag, "InternalProviderError") + }).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/.repos/effect/packages/ai/openai/test/OpenAiEmbeddingModel.test.ts b/.repos/effect/packages/ai/openai/test/OpenAiEmbeddingModel.test.ts new file mode 100644 index 000000000..5bf08890f --- /dev/null +++ b/.repos/effect/packages/ai/openai/test/OpenAiEmbeddingModel.test.ts @@ -0,0 +1,289 @@ +import { OpenAiClient, OpenAiEmbeddingModel } from "@effect/ai-openai" +import { assert, describe, it } from "@effect/vitest" +import { Effect, Layer, Redacted } from "effect" +import { EmbeddingModel } from "effect/unstable/ai" +import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" + +describe("OpenAiEmbeddingModel", () => { + it.effect("model provides dimensions service", () => + Effect.gen(function*() { + const dimensions = yield* EmbeddingModel.Dimensions + assert.strictEqual(dimensions, 1536) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.model("text-embedding-3-small", { dimensions: 1536 })), + Effect.provideService(OpenAiClient.OpenAiClient, noopOpenAiClient) + )) + + it.effect("reorders embeddings by provider index", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + data: [ + { index: 1, embedding: [20, 21], object: "embedding" }, + { index: 0, embedding: [10, 11], object: "embedding" } + ], + model: "text-embedding-3-small", + object: "list", + usage: { + prompt_tokens: 7, + total_tokens: 7 + } + })) + }) + )) + ) + + const response = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embedMany(["first", "second"]) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.deepStrictEqual(response.embeddings.map((embedding) => embedding.vector), [[10, 11], [20, 21]]) + assert.strictEqual(response.usage.inputTokens, 7) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.model, "text-embedding-3-small") + assert.deepStrictEqual(requestBody.input, ["first", "second"]) + })) + + it.effect("merges config and applies withConfigOverride precedence", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + data: [{ index: 0, embedding: [1, 2, 3], object: "embedding" }], + model: "override-model", + object: "list", + usage: { + prompt_tokens: 3, + total_tokens: 3 + } + })) + }) + )) + ) + + yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + yield* model.embed("hello") + }).pipe( + OpenAiEmbeddingModel.withConfigOverride({ + model: "override-model", + dimensions: 1024, + user: "request-user" + }), + Effect.provide(OpenAiEmbeddingModel.layer({ + model: "base-model", + config: { + dimensions: 256, + user: "provider-user" + } + })), + Effect.provide(clientLayer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.strictEqual(requestBody.model, "override-model") + assert.strictEqual(requestBody.dimensions, 1024) + assert.strictEqual(requestBody.user, "request-user") + assert.deepStrictEqual(requestBody.input, ["hello"]) + })) + + it.effect("fails with InvalidOutputError when provider returns duplicate indices", () => + Effect.gen(function*() { + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse(request, { + data: [ + { index: 0, embedding: [1], object: "embedding" }, + { index: 0, embedding: [2], object: "embedding" } + ], + model: "text-embedding-3-small", + object: "list", + usage: { + prompt_tokens: 2, + total_tokens: 2 + } + })) + ) + )) + ) + + const error = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embedMany(["a", "b"]).pipe(Effect.flip) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.strictEqual(error._tag, "AiError") + assert.strictEqual(error.reason._tag, "InvalidOutputError") + })) + + it.effect("fails with InvalidOutputError when provider returns out-of-range indices", () => + Effect.gen(function*() { + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse(request, { + data: [ + { index: 0, embedding: [1], object: "embedding" }, + { index: 2, embedding: [2], object: "embedding" } + ], + model: "text-embedding-3-small", + object: "list", + usage: { + prompt_tokens: 2, + total_tokens: 2 + } + })) + ) + )) + ) + + const error = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embedMany(["a", "b"]).pipe(Effect.flip) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.strictEqual(error._tag, "AiError") + assert.strictEqual(error.reason._tag, "InvalidOutputError") + })) + + it.effect("fails with InvalidOutputError when provider returns wrong embedding count", () => + Effect.gen(function*() { + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse(request, { + data: [{ index: 0, embedding: [1], object: "embedding" }], + model: "text-embedding-3-small", + object: "list", + usage: { + prompt_tokens: 1, + total_tokens: 1 + } + })) + ) + )) + ) + + const error = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embedMany(["a", "b"]).pipe(Effect.flip) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.strictEqual(error._tag, "AiError") + assert.strictEqual(error.reason._tag, "InvalidOutputError") + })) + + it.effect("fails with InvalidOutputError when provider returns base64 embeddings", () => + Effect.gen(function*() { + const clientLayer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse(request, { + data: [{ index: 0, embedding: "AQID", object: "embedding" }], + model: "text-embedding-3-small", + object: "list", + usage: { + prompt_tokens: 1, + total_tokens: 1 + } + })) + ) + )) + ) + + const error = yield* Effect.gen(function*() { + const model = yield* EmbeddingModel.EmbeddingModel + return yield* model.embed("a").pipe(Effect.flip) + }).pipe( + Effect.provide(OpenAiEmbeddingModel.layer({ model: "text-embedding-3-small" })), + Effect.provide(clientLayer) + ) + + assert.strictEqual(error._tag, "AiError") + assert.strictEqual(error.reason._tag, "InvalidOutputError") + })) +}) + +const makeHttpClient = ( + handler: ( + request: HttpClientRequest.HttpClientRequest + ) => Effect.Effect +) => + HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + return yield* handler(request) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + +const jsonResponse = ( + request: HttpClientRequest.HttpClientRequest, + body: unknown +): HttpClientResponse.HttpClientResponse => + HttpClientResponse.fromWeb( + request, + new Response(JSON.stringify(body), { + status: 200, + headers: { + "content-type": "application/json" + } + }) + ) + +const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => + Effect.gen(function*() { + const body = request.body + if (body._tag === "Uint8Array") { + const text = new TextDecoder().decode(body.body) + return JSON.parse(text) + } + return yield* Effect.die(new Error("Expected Uint8Array body")) + }) + +const noopOpenAiClient: OpenAiClient.Service = { + client: undefined as unknown as OpenAiClient.Service["client"], + createResponse: () => Effect.die(new Error("noop")), + createResponseStream: () => Effect.die(new Error("noop")), + createEmbedding: () => Effect.die(new Error("noop")) +} diff --git a/.repos/effect/packages/ai/openai/test/OpenAiLanguageModel.test.ts b/.repos/effect/packages/ai/openai/test/OpenAiLanguageModel.test.ts new file mode 100644 index 000000000..19b1e4527 --- /dev/null +++ b/.repos/effect/packages/ai/openai/test/OpenAiLanguageModel.test.ts @@ -0,0 +1,1925 @@ +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, Response as AiResponse, Tool, Toolkit } from "effect/unstable/ai" +import { HttpClient, type HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" + +describe("OpenAiLanguageModel", () => { + describe("make", () => { + it.effect("sends correct model in request", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ prompt: "test" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")) + ) + + const metadata = result.content.find((part) => part.type === "response-metadata") + + strictEqual(metadata?.modelId, "gpt-4o-mini") + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("sends custom model string in request", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ prompt: "test" }).pipe( + Effect.provide(OpenAiLanguageModel.model("ft:gpt-4o-mini:custom")) + ) + + const metadata = result.content.find((part) => part.type === "response-metadata") + strictEqual(metadata?.modelId, "ft:gpt-4o-mini:custom") + }).pipe(Effect.provide(makeTestLayer({ body: { model: "ft:gpt-4o-mini:custom" as any } })))) + }) + + 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*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "system", content: "You are a helpful assistant" }, + { role: "user", content: "Hello" } + ]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const systemMessage = body.input.find((m: any) => m.role === "system") + assert.isDefined(systemMessage) + deepStrictEqual(systemMessage.content, [{ + type: "input_text", + text: "You are a helpful assistant" + }]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("uses developer role for reasoning models", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "system", content: "You are a helpful assistant" }, + { role: "user", content: "Hello" } + ]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("o1"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const devMessage = body.input.find((m: any) => m.role === "developer") + assert.isDefined(devMessage) + 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", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "system", content: "You are a helpful assistant" }, + { role: "user", content: "Hello" } + ]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-5"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const devMessage = body.input.find((m: any) => m.role === "developer") + assert.isDefined(devMessage) + }).pipe(Effect.provide(makeTestLayer({ body: { model: "gpt-5" } })))) + + it.effect("uses developer role for o3 models", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "system", content: "You are a helpful assistant" }, + { role: "user", content: "Hello" } + ]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("o3-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const devMessage = body.input.find((m: any) => m.role === "developer") + assert.isDefined(devMessage) + }).pipe(Effect.provide(makeTestLayer({ body: { model: "o3-mini" } })))) + }) + + describe("user messages", () => { + it.effect("converts text parts to input_text", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: "Hello world" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.input.find((m: any) => m.role === "user") + assert.isDefined(userMessage) + deepStrictEqual(userMessage.content, [{ type: "input_text", text: "Hello world" }]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("handles image URLs", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "image/png", + data: new URL("https://example.com/image.png") + }) + ] + }]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.input.find((m: any) => m.role === "user") + deepStrictEqual(userMessage.content, [{ + type: "input_image", + image_url: "https://example.com/image.png", + detail: "auto" + }]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("handles image with custom detail level", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "image/png", + data: new URL("https://example.com/image.png"), + options: { openai: { imageDetail: "high" } } + }) + ] + }]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.input.find((m: any) => m.role === "user") + strictEqual(userMessage.content[0].detail, "high") + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("handles image file IDs with configured prefixes", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "image/png", + data: "file-abc123" + }) + ] + }]) + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini", { + fileIdPrefixes: ["file-"] + })) + ) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.input.find((m: any) => m.role === "user") + deepStrictEqual(userMessage.content, [{ + type: "input_image", + file_id: "file-abc123", + detail: "auto" + }]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("handles image base64 data", () => + Effect.gen(function*() { + const imageData = new Uint8Array([137, 80, 78, 71]) // PNG magic bytes + + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "image/png", + data: imageData + }) + ] + }]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.input.find((m: any) => m.role === "user") + assert.isTrue(userMessage.content[0].image_url.startsWith("data:image/png;base64,")) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("handles PDF URLs", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "application/pdf", + data: new URL("https://example.com/document.pdf") + }) + ] + }]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.input.find((m: any) => m.role === "user") + deepStrictEqual(userMessage.content, [{ + type: "input_file", + file_url: "https://example.com/document.pdf" + }]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("handles PDF base64 data with filename", () => + Effect.gen(function*() { + const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]) // %PDF + + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "application/pdf", + data: pdfData, + fileName: "document.pdf" + }) + ] + }]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.input.find((m: any) => m.role === "user") + strictEqual(userMessage.content[0].type, "input_file") + strictEqual(userMessage.content[0].filename, "document.pdf") + assert.isTrue(userMessage.content[0].file_data.startsWith("data:application/pdf;base64,")) + }).pipe(Effect.provide(makeTestLayer()))) + }) + + describe("assistant messages", () => { + it.effect("converts text parts to message output", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Hello" }, + { + role: "assistant", + content: [Prompt.textPart({ text: "Hi there!" })] + }, + { role: "user", content: "How are you?" } + ]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const assistantMessage = body.input.find((m: any) => m.type === "message" && m.role === "assistant") + assert.isDefined(assistantMessage) + strictEqual(assistantMessage.content[0].type, "output_text") + strictEqual(assistantMessage.content[0].text, "Hi there!") + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("converts reasoning parts", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Think step by step" }, + { + role: "assistant", + content: [ + Prompt.reasoningPart({ + text: "Let me think...", + options: { openai: { itemId: "reasoning_123" } } + }) + ] + }, + { role: "user", content: "Continue" } + ]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("o1"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const reasoningItem = body.input.find((m: any) => m.type === "reasoning") + assert.isDefined(reasoningItem) + 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({ + prompt: Prompt.make([ + { role: "user", content: "Use the tool" }, + { + role: "assistant", + content: [ + Prompt.toolCallPart({ + id: "call_abc", + name: "TestTool", + params: { input: "test" }, + providerExecuted: false + }) + ] + }, + { + role: "tool", + content: [ + Prompt.toolResultPart({ + id: "call_abc", + name: "TestTool", + isFailure: false, + result: { output: "result" }, + providerExecuted: false + }) + ] + } + ]), + toolkit: TestToolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const functionCall = body.input.find((m: any) => m.type === "function_call") + assert.isDefined(functionCall) + strictEqual(functionCall.name, "TestTool") + strictEqual(functionCall.call_id, "call_abc") + }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + }) + + describe("tool messages", () => { + it.effect("converts tool results to function_call_output", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Use the tool" }, + { + role: "assistant", + content: [ + Prompt.toolCallPart({ + id: "call_abc", + name: "TestTool", + params: { input: "test" }, + providerExecuted: false + }) + ] + }, + { + role: "tool", + content: [ + Prompt.toolResultPart({ + id: "call_abc", + name: "TestTool", + isFailure: false, + result: { output: "result" }, + 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((m: any) => m.type === "function_call_output") + assert.isDefined(toolOutput) + strictEqual(toolOutput.call_id, "call_abc") + strictEqual(toolOutput.output, JSON.stringify({ output: "result" })) + }).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()))) + }) + }) + + describe("tool preparation", () => { + it.effect("converts user-defined tools to function type", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit: TestToolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((t: any) => t.type === "function") + assert.isDefined(tool) + strictEqual(tool.name, "TestTool") + strictEqual(tool.description, "A test tool") + strictEqual(tool.strict, true) + }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + + it.effect("empty object on properties for empty parameters", () => + Effect.gen(function*() { + const EmptyTool = Tool.make("EmptyParamsTool", { + description: "Empty params tool", + parameters: Tool.EmptyParams, + success: Schema.String + }) + const toolkit = Toolkit.make(EmptyTool) + const toolkitLayer = toolkit.toLayer({ + EmptyParamsTool: () => Effect.succeed("ok") + }) + + yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit + }).pipe(Effect.provide([OpenAiLanguageModel.model("gpt-4o-mini"), toolkitLayer])) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((t: any) => t.type === "function" && t.name === "EmptyParamsTool") + assert.isDefined(tool) + deepStrictEqual(tool.parameters, { + type: "object", + properties: {}, + additionalProperties: false + }) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("converts dynamic tools to function type", () => + 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(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((entry: any) => entry.type === "function" && entry.name === "DynamicTool") + assert.isDefined(tool) + strictEqual(tool.description, "A dynamic tool") + deepStrictEqual(tool.parameters, inputSchema) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("empty object on properties for empty parameters", () => + Effect.gen(function*() { + const EmptyTool = Tool.make("EmptyParamsTool", { + description: "Empty params tool", + parameters: Tool.EmptyParams, + success: Schema.String + }) + const toolkit = Toolkit.make(EmptyTool) + const toolkitLayer = toolkit.toLayer({ + EmptyParamsTool: () => Effect.succeed("ok") + }) + + yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit + }).pipe(Effect.provide([OpenAiLanguageModel.model("gpt-4o-mini"), toolkitLayer])) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((t: any) => t.type === "function" && t.name === "EmptyParamsTool") + assert.isDefined(tool) + deepStrictEqual(tool.parameters, { + type: "object", + properties: {}, + additionalProperties: false + }) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("handles tool choice auto", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit: TestToolkit, + toolChoice: "auto" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.tool_choice, "auto") + }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + + it.effect("handles tool choice none", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit: TestToolkit, + toolChoice: "none" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.tool_choice, "none") + }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + + it.effect("handles tool choice required", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit: TestToolkit, + toolChoice: "required" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.tool_choice, "required") + }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + + it.effect("handles specific tool choice", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit: TestToolkit, + toolChoice: { tool: "TestTool" } + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + deepStrictEqual(body.tool_choice, { type: "function", name: "TestTool" }) + }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + + it.effect("adds code_interpreter tool", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.CodeInterpreter({ container: { type: "auto" } })) + + yield* LanguageModel.generateText({ + prompt: "Run some code", + toolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((t: any) => t.type === "code_interpreter") + assert.isDefined(tool) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("adds web_search tool", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.WebSearch({})) + + yield* LanguageModel.generateText({ + prompt: "Search the web", + toolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((t: any) => t.type === "web_search") + assert.isDefined(tool) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("adds file_search tool with vector store IDs", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.FileSearch({ + vector_store_ids: ["vs_123"] + })) + + yield* LanguageModel.generateText({ + prompt: "Search files", + toolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((t: any) => t.type === "file_search") + assert.isDefined(tool) + deepStrictEqual(tool.vector_store_ids, ["vs_123"]) + }).pipe(Effect.provide(makeTestLayer()))) + }) + + describe("response format", () => { + it.effect("uses text format by default", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: "Hello" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.text?.format?.type, "text") + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("uses json_schema format for structured output", () => + Effect.gen(function*() { + yield* LanguageModel.generateObject({ + prompt: "Give me a person", + schema: Schema.Struct({ + name: Schema.String, + age: Schema.Number + }) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.text?.format?.type, "json_schema") + strictEqual(body.text?.format?.strict, true) + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [makeTextOutput(JSON.stringify({ name: "John", age: 30 }))] + } + })))) + }) + + describe("response handling", () => { + it.effect("extracts text from output_text", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Hello" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + strictEqual(result.text, "Hello, world!") + }).pipe(Effect.provide(makeTestLayer({ + body: { output: [makeTextOutput("Hello, world!")] } + })))) + + it.effect("extracts multiple text parts", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Hello" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const textParts = result.content.filter((p) => p.type === "text") + strictEqual(textParts.length, 2) + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [ + makeTextOutput("First"), + makeTextOutput("Second", { id: "msg_456" }) + ] + } + })))) + + it.effect("handles refusal content", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Do something bad" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const textPart = result.content.find((p) => p.type === "text") + strictEqual(textPart?.text, "") + strictEqual(textPart?.metadata?.openai?.refusal, "I cannot do that") + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [{ + type: "message", + id: "msg_123", + role: "assistant", + status: "completed", + content: [{ type: "refusal", refusal: "I cannot do that" }] + }] + } + })))) + + it.effect("parses function call arguments", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit: TestToolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const toolCall = result.content.find((p) => p.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type === "tool-call") { + strictEqual(toolCall.name, "TestTool") + deepStrictEqual(toolCall.params, { input: "hello" }) + } + }).pipe( + Effect.provide([ + makeTestLayer({ + body: { output: [makeFunctionCall("TestTool", { input: "hello" })] } + }), + TestToolkitLayer + ]) + )) + + it.effect("uses canonical OpenAiMcp name for mcp_call", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Use MCP", + toolkit: McpToolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type === "tool-call") { + strictEqual(toolCall.name, "OpenAiMcp") + deepStrictEqual(toolCall.params, { packageName: "effect" }) + } + + const toolResult = result.content.find((part) => part.type === "tool-result") + assert.isDefined(toolResult) + if (toolResult?.type === "tool-result") { + strictEqual(toolResult.name, "OpenAiMcp") + strictEqual(toolResult.result.name, "CheckPackage") + } + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [makeMcpCall("CheckPackage", { packageName: "effect" })] + } + })))) + + 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({ + prompt: "Use MCP", + toolkit: McpToolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type === "tool-call") { + strictEqual(toolCall.name, "OpenAiMcp") + deepStrictEqual(toolCall.params, { packageName: "effect" }) + } + + const approvalRequest = result.content.find((part) => part.type === "tool-approval-request") + assert.isDefined(approvalRequest) + if (toolCall?.type === "tool-call" && approvalRequest?.type === "tool-approval-request") { + strictEqual(approvalRequest.toolCallId, toolCall.id) + } + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [makeMcpApprovalRequest("CheckPackage", { packageName: "effect" })] + } + })))) + + it.effect("extracts reasoning parts", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Think about this" + }).pipe(Effect.provide(OpenAiLanguageModel.model("o1"))) + + const reasoningParts = result.content.filter((p) => p.type === "reasoning") + strictEqual(reasoningParts.length, 2) + if (reasoningParts[0]?.type === "reasoning") { + strictEqual(reasoningParts[0].text, "First thought") + } + }).pipe(Effect.provide(makeTestLayer({ + body: { + model: "o1", + output: [makeReasoningOutput(["First thought", "Second thought"])] + } + })))) + + it.effect("extracts usage information", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Hello" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const finishPart = result.content.find((p) => p.type === "finish") + assert.isDefined(finishPart) + if (finishPart?.type === "finish") { + deepStrictEqual(finishPart.usage.inputTokens, { + uncached: 7, + total: 10, + cacheRead: 3, + cacheWrite: 4 + }) + deepStrictEqual(finishPart.usage.outputTokens, { total: 20, text: 20, reasoning: 0 }) + } + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [makeTextOutput("Hello")], + usage: makeUsage({ + input_tokens_details: { + cached_tokens: 3, + cache_write_tokens: 4 + } + }) + } + })))) + + it.effect("determines finish reason from incomplete_details", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Hello" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const finishPart = result.content.find((p) => p.type === "finish") + if (finishPart?.type === "finish") { + strictEqual(finishPart.reason, "content-filter") + } + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [makeTextOutput("Hello")], + incomplete_details: { reason: "content_filter" } + } + })))) + + it.effect("defaults finish reason to stop", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Hello" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const finishPart = result.content.find((p) => p.type === "finish") + if (finishPart?.type === "finish") { + strictEqual(finishPart.reason, "stop") + } + }).pipe(Effect.provide(makeTestLayer({ + body: { output: [makeTextOutput("Hello")] } + })))) + + it.effect("sets finish reason to tool-calls when has tool calls", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit: TestToolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const finishPart = result.content.find((p) => p.type === "finish") + if (finishPart?.type === "finish") { + strictEqual(finishPart.reason, "tool-calls") + } + }).pipe( + Effect.provide([ + makeTestLayer({ + body: { output: [makeFunctionCall("TestTool", { input: "test" })] } + }), + TestToolkitLayer + ]) + )) + + it.effect("extracts url citations as source parts", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: "Hello" + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const sourcePart = result.content.find((p) => p.type === "source") + assert.isDefined(sourcePart) + if (sourcePart?.type === "source" && sourcePart.sourceType === "url") { + strictEqual(sourcePart.url.href, "https://example.com/") + strictEqual(sourcePart.title, "Example") + } + }).pipe(Effect.provide(makeTestLayer({ + body: { + output: [{ + type: "message", + id: "msg_123", + role: "assistant", + status: "completed", + content: [{ + type: "output_text", + text: "Check this out", + annotations: [{ + type: "url_citation", + url: "https://example.com", + title: "Example", + start_index: 0, + end_index: 14 + }], + logprobs: [] + }] + }] + } + })))) + }) + }) + + 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" + const outputItem = { + type: "apply_patch_call", + id: "patch_item_1", + call_id: "patch_call_1", + status: "in_progress", + operation: { + type: "update_file", + path: "src/example.ts", + diff + } + } as const + + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ + id: "resp_patch_stream", + status: "in_progress", + output: [] + }) + }, + { + type: "response.output_item.added", + output_index: 0, + sequence_number: 2, + item: outputItem + }, + { + type: "response.apply_patch_call_operation_diff.delta", + sequence_number: 3, + output_index: 0, + item_id: outputItem.id, + delta: diff + }, + { + type: "response.apply_patch_call_operation_diff.done", + sequence_number: 4, + output_index: 0, + item_id: outputItem.id + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Update src/example.ts", + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)) + ) + + const parts = globalThis.Array.from(partsChunk) + const params = decodeToolParamsFromStream(parts, outputItem.call_id) + + deepStrictEqual(params, { + call_id: outputItem.call_id, + operation: { + type: "update_file", + path: "src/example.ts", + diff + } + }) + })) + + it.effect("emits tool call from function_call_arguments.done when output_item.done is missing", () => + Effect.gen(function*() { + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ + id: "resp_function_call_done", + 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: "TestTool", + arguments: "", + status: "in_progress" + } + }, + { + type: "response.function_call_arguments.delta", + sequence_number: 3, + output_index: 0, + item_id: "fc_1", + delta: "{\"input\":\"hel" + }, + { + type: "response.function_call_arguments.done", + sequence_number: 4, + output_index: 0, + item_id: "fc_1", + name: "TestTool", + arguments: "{\"input\":\"hello\"}" + }, + { + type: "response.completed", + sequence_number: 5, + response: makeDefaultResponse({ + id: "resp_function_call_done", + status: "completed", + output: [] + }) + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Use the test tool", + toolkit: TestToolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)), + Effect.provide(TestToolkitLayer) + ) + + const parts = globalThis.Array.from(partsChunk) + const toolCalls = parts.filter((part) => part.type === "tool-call" && part.id === "call_1") + strictEqual(toolCalls.length, 1) + const toolCall = toolCalls[0] + assert.isDefined(toolCall) + if (toolCall?.type === "tool-call") { + strictEqual(toolCall.name, "TestTool") + deepStrictEqual(toolCall.params, { input: "hello" }) + } + + const toolParamsEnd = parts.find((part) => part.type === "tool-params-end" && part.id === "call_1") + assert.isDefined(toolParamsEnd) + })) + + 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 = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ + id: "resp_reasoning_missing_state", + status: "in_progress", + output: [] + }) + }, + { + type: "response.reasoning_summary_part.added", + sequence_number: 2, + output_index: 0, + item_id: "rs_missing", + summary_index: 1 + }, + { + type: "response.reasoning_summary_text.delta", + sequence_number: 3, + output_index: 0, + item_id: "rs_missing", + summary_index: 1, + delta: "thinking" + }, + { + type: "response.reasoning_summary_part.done", + sequence_number: 4, + output_index: 0, + item_id: "rs_missing", + summary_index: 1 + }, + { + type: "response.output_item.done", + sequence_number: 5, + output_index: 0, + item: makeReasoningOutput(["thinking"], { id: "rs_missing" }) + }, + { + type: "response.output_item.done", + sequence_number: 6, + output_index: 1, + item: makeReasoningOutput([], { id: "rs_done_only" }) + }, + { + type: "response.completed", + sequence_number: 7, + response: makeDefaultResponse({ + id: "resp_reasoning_missing_state", + status: "completed", + output: [] + }) + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "reason" + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)) + ) + + const parts = globalThis.Array.from(partsChunk) + assert.isDefined(parts.find((part) => part.type === "reasoning-start" && part.id === "rs_missing:1")) + assert.isDefined(parts.find((part) => part.type === "reasoning-end" && part.id === "rs_missing:1")) + assert.isDefined(parts.find((part) => part.type === "finish")) + })) + + it.effect("uses canonical OpenAiMcp name for streamed mcp_call", () => + Effect.gen(function*() { + const outputItem = makeMcpCall("CheckPackage", { packageName: "effect" }, { id: "mcp_call_1" }) + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ + id: "resp_mcp_stream", + status: "in_progress", + output: [] + }) + }, + { + type: "response.output_item.done", + output_index: 0, + sequence_number: 2, + item: outputItem + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Use MCP", + toolkit: McpToolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)) + ) + + const parts = globalThis.Array.from(partsChunk) + const toolCall = parts.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type === "tool-call") { + strictEqual(toolCall.name, "OpenAiMcp") + deepStrictEqual(toolCall.params, { packageName: "effect" }) + } + + const toolResult = parts.find((part) => part.type === "tool-result") + assert.isDefined(toolResult) + if (toolResult?.type === "tool-result") { + strictEqual(toolResult.name, "OpenAiMcp") + strictEqual(toolResult.result.name, "CheckPackage") + } + })) + + it.effect("uses canonical OpenAiMcp name for streamed mcp_approval_request", () => + Effect.gen(function*() { + const outputItem = makeMcpApprovalRequest("CheckPackage", { packageName: "effect" }, { id: "approval_1" }) + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ + id: "resp_mcp_approval_stream", + status: "in_progress", + output: [] + }) + }, + { + type: "response.output_item.done", + output_index: 0, + sequence_number: 2, + item: outputItem + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Use MCP", + toolkit: McpToolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)) + ) + + const parts = globalThis.Array.from(partsChunk) + const toolCall = parts.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type === "tool-call") { + strictEqual(toolCall.name, "OpenAiMcp") + deepStrictEqual(toolCall.params, { packageName: "effect" }) + } + + const approvalRequest = parts.find((part) => part.type === "tool-approval-request") + assert.isDefined(approvalRequest) + if (toolCall?.type === "tool-call" && approvalRequest?.type === "tool-approval-request") { + strictEqual(approvalRequest.toolCallId, toolCall.id) + } + })) + + it.effect("pre-resolves denied OpenAiMcp approvals without lookup failure", () => + Effect.gen(function*() { + const result = yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { + role: "assistant", + content: [ + Prompt.toolCallPart({ + id: "mcp_tool_call_1", + name: "OpenAiMcp", + params: { packageName: "effect" }, + providerExecuted: true + }), + Prompt.makePart("tool-approval-request", { + approvalId: "approval_1", + toolCallId: "mcp_tool_call_1" + }) + ] + }, + { + role: "tool", + content: [ + Prompt.toolApprovalResponsePart({ + approvalId: "approval_1", + approved: false, + reason: "Denied" + }) + ] + }, + { role: "user", content: "Continue" } + ]), + toolkit: McpToolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeTestLayer({ + body: { + output: [makeTextOutput("Handled denied MCP approval")] + } + })) + ) + + strictEqual(result.text, "Handled denied MCP approval") + })) + }) + + describe("withConfigOverride", () => { + it.effect("merges config overrides", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ prompt: "test" }).pipe( + OpenAiLanguageModel.withConfigOverride({ temperature: 0.5 }), + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")) + ) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.temperature, 0.5) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("override takes precedence", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ prompt: "test" }).pipe( + OpenAiLanguageModel.withConfigOverride({ temperature: 0.9 }), + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini", { temperature: 0.5 })) + ) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.temperature, 0.9) + }).pipe(Effect.provide(makeTestLayer()))) + }) + + describe("config", () => { + it.effect("does not leak library-only fields into request body", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ prompt: "test" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini", { + fileIdPrefixes: ["file-"], + strictJsonSchema: false, + temperature: 0.5 + })) + ) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.fileIdPrefixes, undefined) + strictEqual(body.strictJsonSchema, undefined) + strictEqual(body.temperature, 0.5) + }).pipe(Effect.provide(makeTestLayer()))) + }) +}) + +// ============================================================================= +// Test Infrastructure +// ============================================================================= + +class MockOpenAiResponse extends Context.Service | undefined +}>()("MockOpenAiResponse") {} + +class MockHttpClient extends Context.Service> +}>()("MockHttpClient") { + static requests = Effect.service(MockHttpClient).pipe( + Effect.flatMap((client) => client.requests) + ) +} + +const encodeResponse = Schema.encodeUnknownEffect(OpenAiSchema.Response) + +const makeHttpClient = Effect.gen(function*() { + const capturedRequests = yield* Ref.make>([]) + const response = yield* MockOpenAiResponse + const body = yield* Effect.orDie(encodeResponse(response.body)) + + 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(JSON.stringify(body), { + headers: response.headers ?? {}, + status: response.status + }) + ) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, MockHttpClient.of({ requests: Ref.get(capturedRequests) })) + ) +}) + +const HttpClientLayer = Layer.effectContext(makeHttpClient) + +const makeStreamTestLayer = (events: ReadonlyArray) => { + const response = HttpClientResponse.fromWeb( + HttpClientRequest.get("https://api.openai.com/v1/responses"), + new Response("", { + status: 200, + headers: { "content-type": "text/event-stream" } + }) + ) + + return Layer.succeed( + OpenAiClient.OpenAiClient, + OpenAiClient.OpenAiClient.of({ + client: undefined as any, + createResponse: () => Effect.die(new Error("unexpected createResponse call")), + createResponseStream: () => Effect.succeed([response, Stream.fromIterable(events)]), + createEmbedding: () => Effect.die(new Error("unexpected createEmbedding call")) + }) + ) +} + +const makeDefaultResponse = ( + overrides: Partial = {} +): Generated.Response => ({ + id: "resp_test123", + object: "response", + created_at: Math.floor(Date.now() / 1000), + 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 makeTestLayer = (options: { + readonly body?: Partial + readonly status?: number + readonly headers?: Record +} = {}) => + OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenAiResponse, { + body: makeDefaultResponse(options.body), + status: options.status ?? 200, + headers: options.headers ?? {} + })) + ) + +const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => + Effect.gen(function*() { + const body = request.body + if (body._tag === "Uint8Array") { + const text = new TextDecoder().decode(body.body) + return JSON.parse(text) + } + return yield* Effect.die(new Error("Expected Uint8Array body")) + }) + +const decodeToolParamsFromStream = ( + parts: ReadonlyArray, + toolCallId: string +): Record => { + const start = parts.find((part) => part.type === "tool-params-start" && part.id === toolCallId) + const end = parts.find((part) => part.type === "tool-params-end" && part.id === toolCallId) + assert.isDefined(start) + assert.isDefined(end) + + const deltas = parts + .filter((part) => part.type === "tool-params-delta" && part.id === toolCallId) + .map((part) => part.delta) + .join("") + + return JSON.parse(deltas) as Record +} + +const makeTextOutput = ( + text: string, + overrides: Partial = {} +): Generated.OutputMessage => ({ + type: "message", + id: "msg_123", + role: "assistant" as const, + status: "completed", + content: [{ type: "output_text", text, annotations: [], logprobs: [] }], + ...overrides +}) + +const makeFunctionCall = ( + name: string, + args: Record, + overrides: Partial = {} +): Generated.FunctionToolCall => ({ + type: "function_call", + id: "fc_123", + call_id: "call_123", + name, + arguments: JSON.stringify(args), + status: "completed", + ...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, + overrides: Partial = {} +): Generated.MCPToolCall => ({ + type: "mcp_call", + id: "mcp_call_123", + server_label: "npm", + name, + arguments: JSON.stringify(args), + output: "ok", + status: "completed", + ...overrides +}) + +const makeMcpApprovalRequest = ( + name: string, + args: Record, + overrides: Partial & { readonly approval_request_id?: string } = {} +): Generated.MCPApprovalRequest => ({ + type: "mcp_approval_request", + id: "approval_123", + server_label: "npm", + name, + arguments: JSON.stringify(args), + ...overrides +}) + +const makeReasoningOutput = ( + summaries: Array, + overrides: Partial = {} +): Generated.ReasoningItem => ({ + type: "reasoning", + id: "rs_123", + summary: summaries.map((text) => ({ type: "summary_text", text })), + encrypted_content: null, + ...overrides +}) + +type TestResponseUsage = Omit & { + readonly input_tokens_details: { + readonly cached_tokens: number + readonly cache_write_tokens?: number + } +} + +const makeUsage = ( + overrides: Partial = {} +): TestResponseUsage => ({ + input_tokens: 10, + output_tokens: 20, + total_tokens: 30, + input_tokens_details: { cached_tokens: 0 }, + output_tokens_details: { reasoning_tokens: 0 }, + ...overrides +}) + +const TestTool = Tool.make("TestTool", { + description: "A test tool", + parameters: Schema.Struct({ input: Schema.String }), + success: Schema.Struct({ output: Schema.String }) +}) + +const TestToolkit = Toolkit.make(TestTool) + +const McpToolkit = Toolkit.make(OpenAiTool.Mcp({ + server_label: "npm", + server_url: "https://example.com/mcp", + require_approval: "never" +})) + +const TestToolkitLayer = TestToolkit.toLayer({ + TestTool: ({ input }) => Effect.succeed({ output: `processed: ${input}` }) +}) diff --git a/.repos/effect/packages/ai/openai/test/OpenAiSchema.test.ts b/.repos/effect/packages/ai/openai/test/OpenAiSchema.test.ts new file mode 100644 index 000000000..8cdd06a1b --- /dev/null +++ b/.repos/effect/packages/ai/openai/test/OpenAiSchema.test.ts @@ -0,0 +1,456 @@ +import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai" +import * as OpenAiSchema from "@effect/ai-openai/OpenAiSchema" +import { assert, describe, it } from "@effect/vitest" +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", + object: "response", + model: "gpt-4o-mini", + status: "completed", + created_at: 1, + output: [], + ...overrides +}) + +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(), + output: [ + { + id: "msg_1", + type: "message", + role: "assistant", + status: "completed", + content: [ + { + type: "output_text", + text: "hello", + annotations: [ + { + type: "url_citation", + url: "https://example.com", + start_index: 0, + end_index: 5, + title: "example" + } + ] + }, + { + type: "refusal", + refusal: "cannot comply" + } + ] + }, + { + id: "fc_1", + type: "function_call", + call_id: "call_1", + name: "lookup", + arguments: "{}", + status: "completed" + }, + { + id: "reasoning_1", + type: "reasoning", + summary: [ + { + type: "summary_text", + text: "thinking" + } + ] + } + ], + usage: { + input_tokens: 10, + output_tokens: 20, + total_tokens: 30 + } + }) + + assert.strictEqual(decoded.id, "resp_123") + assert.strictEqual(decoded.output.length, 3) + assert.strictEqual(decoded.output[0].type, "message") + if (decoded.output[0].type === "message") { + assert.strictEqual(decoded.output[0].content[0].type, "output_text") + } + }) + + 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 = { + id: "ap_1", + type: "apply_patch_call", + call_id: "call_ap", + operation: { + type: "update_file", + path: "README.md", + diff: "@@" + } + } + + const events = [ + { type: "response.created", sequence_number: 1, response }, + { + type: "response.completed", + sequence_number: 2, + response: makeResponse() + }, + { type: "response.incomplete", sequence_number: 3, response: makeResponse({ status: "incomplete" }) }, + { type: "response.failed", sequence_number: 4, response: makeResponse({ status: "failed" }) }, + { type: "response.output_item.added", sequence_number: 5, output_index: 0, item: applyPatchItem }, + { type: "response.output_item.done", sequence_number: 6, output_index: 0, item: applyPatchItem }, + { + type: "response.output_text.delta", + sequence_number: 7, + item_id: "msg_1", + output_index: 0, + content_index: 0, + delta: "hel" + }, + { + type: "response.output_text.annotation.added", + sequence_number: 8, + item_id: "msg_1", + output_index: 0, + content_index: 0, + annotation_index: 0, + annotation: { + type: "file_path", + file_id: "file_1", + index: 0 + } + }, + { + type: "response.reasoning_summary_part.added", + sequence_number: 9, + item_id: "reasoning_1", + output_index: 1, + summary_index: 0, + part: { type: "summary_text", text: "thinking" } + }, + { + type: "response.reasoning_summary_part.done", + sequence_number: 10, + item_id: "reasoning_1", + output_index: 1, + summary_index: 0, + part: { type: "summary_text", text: "thinking" } + }, + { + type: "response.reasoning_summary_text.delta", + sequence_number: 11, + item_id: "reasoning_1", + output_index: 1, + summary_index: 0, + delta: "..." + }, + { + type: "response.function_call_arguments.delta", + sequence_number: 12, + item_id: "fc_1", + output_index: 2, + delta: "{" + }, + { + type: "response.function_call_arguments.done", + sequence_number: 13, + item_id: "fc_1", + output_index: 2, + arguments: "{}" + }, + { + type: "response.code_interpreter_call_code.delta", + sequence_number: 14, + item_id: "code_1", + output_index: 3, + delta: "print" + }, + { + type: "response.code_interpreter_call_code.done", + sequence_number: 15, + item_id: "code_1", + output_index: 3, + code: "print('ok')" + }, + { + type: "response.apply_patch_call_operation_diff.delta", + sequence_number: 16, + item_id: "ap_1", + output_index: 4, + delta: "@@" + }, + { + type: "response.apply_patch_call_operation_diff.done", + sequence_number: 17, + item_id: "ap_1", + output_index: 4, + delta: "@@" + }, + { + type: "response.image_generation_call.partial_image", + sequence_number: 18, + item_id: "img_1", + output_index: 5, + partial_image_b64: "AQID" + } + ] + + for (const event of events) { + const decoded = Schema.decodeUnknownSync(OpenAiSchema.ResponseStreamEvent)(event) + assert.strictEqual(decoded.type, event.type) + } + }) + + it.effect("keeps keepalive and unknown events tolerant in SSE decoding", () => + Effect.gen(function*() { + const sseBody = [ + { + type: "response.created", + sequence_number: 1, + response: makeResponse({ status: "in_progress" }) + }, + { + type: "keepalive", + sequence_number: 2, + heartbeat: true + }, + { + type: "provider.future_event", + sequence_number: 3, + nested: { ok: true } + }, + { + type: "response.completed", + sequence_number: 4, + response: makeResponse({ status: "completed" }) + } + ].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, 4) + assert.strictEqual(decoded[1].type, "keepalive") + assert.strictEqual(decoded[2].type, "provider.future_event") + if (decoded[1].type === "keepalive") { + assert.strictEqual(decoded[1].sequence_number, 2) + } + })) + + it.effect("does not silently decode malformed known events as unknown", () => + Effect.gen(function*() { + const malformed = yield* Schema.decodeUnknownEffect(OpenAiSchema.ResponseStreamEvent)({ + type: "response.completed", + sequence_number: 1 + }).pipe(Effect.flip) + + 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", + model: "text-embedding-3-small", + data: [{ object: "embedding", index: 0, embedding: [0.1, 0.2] }], + usage: { prompt_tokens: 2, total_tokens: 2 } + }) + const base64 = Schema.decodeUnknownSync(OpenAiSchema.CreateEmbeddingResponse)({ + object: "list", + model: "text-embedding-3-small", + data: [{ object: "embedding", index: 0, embedding: "AQID" }], + usage: { prompt_tokens: 2, total_tokens: 2 } + }) + + 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/.repos/effect/packages/ai/openai/tsconfig.json b/.repos/effect/packages/ai/openai/tsconfig.json new file mode 100644 index 000000000..e2a8ca19a --- /dev/null +++ b/.repos/effect/packages/ai/openai/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../../effect" } + ] +} diff --git a/.repos/effect/packages/ai/openrouter/CHANGELOG.md b/.repos/effect/packages/ai/openrouter/CHANGELOG.md new file mode 100644 index 000000000..6c9f47b0a --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/CHANGELOG.md @@ -0,0 +1,842 @@ +# @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 + +- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]: + - effect@4.0.0-beta.90 + +## 4.0.0-beta.89 + +### Patch Changes + +- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]: + - effect@4.0.0-beta.89 + +## 4.0.0-beta.88 + +### Patch Changes + +- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]: + - effect@4.0.0-beta.88 + +## 4.0.0-beta.87 + +### Patch Changes + +- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]: + - effect@4.0.0-beta.87 + +## 4.0.0-beta.86 + +### Patch Changes + +- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]: + - effect@4.0.0-beta.86 + +## 4.0.0-beta.85 + +### Patch Changes + +- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]: + - effect@4.0.0-beta.85 + +## 4.0.0-beta.84 + +### Patch Changes + +- [#2379](https://github.com/Effect-TS/effect-smol/pull/2379) [`717d1c8`](https://github.com/Effect-TS/effect-smol/commit/717d1c8b160d4de631b6b7938abcad9e3472c3d7) Thanks @walln! - Convert audio file parts in prompts into OpenRouter `input_audio` content blocks. + + Previously, every non-image file part was converted into a generic `file` content block. OpenRouter only accepts audio as base64-encoded `input_audio` content parts, so audio attachments were rejected or mishandled by the upstream provider. + + Audio file parts with a recognized `mediaType` (aac, aiff, flac, m4a, mp3, ogg, pcm16, pcm24, and wav) are now converted into `input_audio` blocks. Unsupported audio media types and `URL` audio data fail with an `AiError` since OpenRouter requires base64-encoded audio data and does not fetch audio URLs. + +- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]: + - effect@4.0.0-beta.84 + +## 4.0.0-beta.83 + +### Patch Changes + +- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]: + - effect@4.0.0-beta.83 + +## 4.0.0-beta.82 + +### Patch Changes + +- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]: + - effect@4.0.0-beta.82 + +## 4.0.0-beta.81 + +### Patch Changes + +- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]: + - effect@4.0.0-beta.81 + +## 4.0.0-beta.80 + +### Patch Changes + +- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]: + - effect@4.0.0-beta.80 + +## 4.0.0-beta.79 + +### Patch Changes + +- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]: + - effect@4.0.0-beta.79 + +## 4.0.0-beta.78 + +### Patch Changes + +- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]: + - effect@4.0.0-beta.78 + +## 4.0.0-beta.77 + +### Patch Changes + +- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]: + - effect@4.0.0-beta.77 + +## 4.0.0-beta.76 + +### Patch Changes + +- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]: + - effect@4.0.0-beta.76 + +## 4.0.0-beta.75 + +### Patch Changes + +- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]: + - effect@4.0.0-beta.75 + +## 4.0.0-beta.74 + +### Patch Changes + +- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]: + - effect@4.0.0-beta.74 + +## 4.0.0-beta.73 + +### Patch Changes + +- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]: + - effect@4.0.0-beta.73 + +## 4.0.0-beta.72 + +### Patch Changes + +- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]: + - effect@4.0.0-beta.72 + +## 4.0.0-beta.71 + +### Patch Changes + +- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]: + - effect@4.0.0-beta.71 + +## 4.0.0-beta.70 + +### Patch Changes + +- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]: + - effect@4.0.0-beta.70 + +## 4.0.0-beta.69 + +### Patch Changes + +- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]: + - effect@4.0.0-beta.69 + +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + +## 4.0.0-beta.67 + +### Patch Changes + +- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]: + - effect@4.0.0-beta.67 + +## 4.0.0-beta.66 + +### Patch Changes + +- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]: + - effect@4.0.0-beta.66 + +## 4.0.0-beta.65 + +### Patch Changes + +- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]: + - effect@4.0.0-beta.65 + +## 4.0.0-beta.64 + +### Patch Changes + +- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]: + - effect@4.0.0-beta.64 + +## 4.0.0-beta.63 + +### Patch Changes + +- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]: + - effect@4.0.0-beta.63 + +## 4.0.0-beta.62 + +### Patch Changes + +- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]: + - effect@4.0.0-beta.62 + +## 4.0.0-beta.61 + +### Patch Changes + +- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]: + - effect@4.0.0-beta.61 + +## 4.0.0-beta.60 + +### Patch Changes + +- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]: + - effect@4.0.0-beta.60 + +## 4.0.0-beta.59 + +### Patch Changes + +- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]: + - effect@4.0.0-beta.59 + +## 4.0.0-beta.58 + +### Patch Changes + +- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]: + - effect@4.0.0-beta.58 + +## 4.0.0-beta.57 + +### Patch Changes + +- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]: + - effect@4.0.0-beta.57 + +## 4.0.0-beta.56 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]: + - effect@4.0.0-beta.55 + +## 4.0.0-beta.54 + +### Patch Changes + +- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]: + - effect@4.0.0-beta.54 + +## 4.0.0-beta.53 + +### Patch Changes + +- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]: + - effect@4.0.0-beta.53 + +## 4.0.0-beta.52 + +### Patch Changes + +- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]: + - effect@4.0.0-beta.52 + +## 4.0.0-beta.51 + +### Patch Changes + +- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]: + - effect@4.0.0-beta.51 + +## 4.0.0-beta.50 + +### Patch Changes + +- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]: + - effect@4.0.0-beta.50 + +## 4.0.0-beta.49 + +### Patch Changes + +- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]: + - effect@4.0.0-beta.49 + +## 4.0.0-beta.48 + +### Patch Changes + +- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]: + - effect@4.0.0-beta.48 + +## 4.0.0-beta.47 + +### Patch Changes + +- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]: + - effect@4.0.0-beta.47 + +## 4.0.0-beta.46 + +### Patch Changes + +- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]: + - effect@4.0.0-beta.46 + +## 4.0.0-beta.45 + +### Patch Changes + +- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]: + - effect@4.0.0-beta.45 + +## 4.0.0-beta.44 + +### Patch Changes + +- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename the `ServiceMap` module to `Context` across exports, docs, and tests. + +- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]: + - effect@4.0.0-beta.44 + +## 4.0.0-beta.43 + +### Patch Changes + +- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]: + - effect@4.0.0-beta.43 + +## 4.0.0-beta.42 + +### Patch Changes + +- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]: + - effect@4.0.0-beta.42 + +## 4.0.0-beta.41 + +### Patch Changes + +- [#1871](https://github.com/Effect-TS/effect-smol/pull/1871) [`977386d`](https://github.com/Effect-TS/effect-smol/commit/977386da5e2e8aac2c07e99175673e0b5771191b) Thanks @IMax153! - Fix HTTP Referer header name in the `OpenRouterClient` + +- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]: + - effect@4.0.0-beta.41 + +## 4.0.0-beta.40 + +### Patch Changes + +- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]: + - effect@4.0.0-beta.40 + +## 4.0.0-beta.39 + +### Patch Changes + +- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]: + - effect@4.0.0-beta.39 + +## 4.0.0-beta.38 + +### Patch Changes + +- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]: + - effect@4.0.0-beta.38 + +## 4.0.0-beta.37 + +### Patch Changes + +- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]: + - effect@4.0.0-beta.37 + +## 4.0.0-beta.36 + +### Patch Changes + +- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]: + - effect@4.0.0-beta.36 + +## 4.0.0-beta.35 + +### Patch Changes + +- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]: + - effect@4.0.0-beta.35 + +## 4.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]: + - effect@4.0.0-beta.34 + +## 4.0.0-beta.33 + +### Patch Changes + +- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]: + - effect@4.0.0-beta.33 + +## 4.0.0-beta.32 + +### Patch Changes + +- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]: + - effect@4.0.0-beta.32 + +## 4.0.0-beta.31 + +### Patch Changes + +- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]: + - effect@4.0.0-beta.31 + +## 4.0.0-beta.30 + +### Patch Changes + +- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]: + - effect@4.0.0-beta.30 + +## 4.0.0-beta.29 + +### Patch Changes + +- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]: + - effect@4.0.0-beta.29 + +## 4.0.0-beta.28 + +### Patch Changes + +- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]: + - effect@4.0.0-beta.28 + +## 4.0.0-beta.27 + +### Patch Changes + +- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]: + - effect@4.0.0-beta.27 + +## 4.0.0-beta.26 + +### Patch Changes + +- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]: + - effect@4.0.0-beta.26 + +## 4.0.0-beta.25 + +### Patch Changes + +- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]: + - effect@4.0.0-beta.25 + +## 4.0.0-beta.24 + +### Patch Changes + +- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]: + - effect@4.0.0-beta.24 + +## 4.0.0-beta.23 + +### Patch Changes + +- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]: + - effect@4.0.0-beta.23 + +## 4.0.0-beta.22 + +### Patch Changes + +- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]: + - effect@4.0.0-beta.22 + +## 4.0.0-beta.21 + +### Patch Changes + +- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]: + - effect@4.0.0-beta.21 + +## 4.0.0-beta.20 + +### Patch Changes + +- [#1529](https://github.com/Effect-TS/effect-smol/pull/1529) [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8) Thanks @tim-smart! - Add dedicated AiError metadata interfaces per reason so provider packages can safely augment metadata without conflicting module declarations. + +- [#1528](https://github.com/Effect-TS/effect-smol/pull/1528) [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34) Thanks @tim-smart! - Add `Model.ModelName` and provide it from AI model constructors. + +- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]: + - effect@4.0.0-beta.20 + +## 4.0.0-beta.19 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Patch Changes + +- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]: + - effect@4.0.0-beta.18 + +## 4.0.0-beta.17 + +### Patch Changes + +- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]: + - effect@4.0.0-beta.17 + +## 4.0.0-beta.16 + +### Patch Changes + +- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]: + - effect@4.0.0-beta.16 + +## 4.0.0-beta.15 + +### Patch Changes + +- [#1502](https://github.com/Effect-TS/effect-smol/pull/1502) [`285b7e6`](https://github.com/Effect-TS/effect-smol/commit/285b7e667167566d5788367d5155b19c79f1bf22) Thanks @tim-smart! - allow undefined for ai config + +- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]: + - effect@4.0.0-beta.15 + +## 4.0.0-beta.14 + +### Patch Changes + +- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]: + - effect@4.0.0-beta.14 + +## 4.0.0-beta.13 + +### Patch Changes + +- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]: + - effect@4.0.0-beta.13 + +## 4.0.0-beta.12 + +### Patch Changes + +- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]: + - effect@4.0.0-beta.12 + +## 4.0.0-beta.11 + +### Patch Changes + +- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]: + - effect@4.0.0-beta.11 + +## 4.0.0-beta.10 + +### Patch Changes + +- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]: + - effect@4.0.0-beta.10 + +## 4.0.0-beta.9 + +### Patch Changes + +- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]: + - effect@4.0.0-beta.9 + +## 4.0.0-beta.8 + +### Patch Changes + +- [#1354](https://github.com/Effect-TS/effect-smol/pull/1354) [`b94962c`](https://github.com/Effect-TS/effect-smol/commit/b94962c249d46cf96cdf2e41188dc9feda41536a) Thanks @IMax153! - Fix the generated schemas for ai providers + +- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]: + - effect@4.0.0-beta.8 + +## 4.0.0-beta.7 + +### Patch Changes + +- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]: + - effect@4.0.0-beta.7 + +## 4.0.0-beta.6 + +### Patch Changes + +- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]: + - effect@4.0.0-beta.6 + +## 4.0.0-beta.5 + +### Patch Changes + +- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]: + - effect@4.0.0-beta.5 + +## 4.0.0-beta.4 + +### Patch Changes + +- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]: + - effect@4.0.0-beta.4 + +## 4.0.0-beta.3 + +### Patch Changes + +- [#1306](https://github.com/Effect-TS/effect-smol/pull/1306) [`c9fb5a5`](https://github.com/Effect-TS/effect-smol/commit/c9fb5a5bfb1c331c91d592323f4027b72a3bc0b4) Thanks @Leka74! - Fix sparse array crash in `streamText` tool call handling. + +- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]: + - effect@4.0.0-beta.3 + +## 4.0.0-beta.2 + +### Patch Changes + +- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]: + - effect@4.0.0-beta.2 + +## 4.0.0-beta.1 + +### Patch Changes + +- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]: + - effect@4.0.0-beta.1 + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta + +### Patch Changes + +- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]: + - effect@4.0.0-beta.0 diff --git a/.repos/effect/packages/ai/openrouter/README.md b/.repos/effect/packages/ai/openrouter/README.md new file mode 100644 index 000000000..cc120b6a4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/openrouter/codegen.yml b/.repos/effect/packages/ai/openrouter/codegen.yml new file mode 100644 index 000000000..7fea7aae9 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/codegen.yml @@ -0,0 +1,34 @@ +# yaml-language-server: $schema=../../tools/ai-codegen/codegen.schema.json +spec: https://openrouter.ai/openapi.yaml +output: src/Generated.ts +name: OpenRouterClient +header: | + /** + * @since 1.0.0 + */ +excludeAnnotations: + - default +disableAdditionalProperties: true +patches: + # 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/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/ChatStreamChoice/required/1"}]' +replacements: + # 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/.repos/effect/packages/ai/openrouter/package.json b/.repos/effect/packages/ai/openrouter/package.json new file mode 100644 index 000000000..deda898f1 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/package.json @@ -0,0 +1,69 @@ +{ + "name": "@effect/ai-openrouter", + "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.git", + "directory": "packages/ai/openrouter" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "ai", + "openrouter" + ], + "keywords": [ + "typescript", + "ai", + "openrouter" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./internal/*": null, + "./index": null, + "./*/index": null + } + }, + "scripts": { + "codegen": "effect-utils codegen", + "build": "tsc -b tsconfig.json && pnpm babel", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "devDependencies": { + "effect": "workspace:^" + }, + "peerDependencies": { + "effect": "workspace:^" + } +} diff --git a/.repos/effect/packages/ai/openrouter/src/Generated.ts b/.repos/effect/packages/ai/openrouter/src/Generated.ts new file mode 100644 index 000000000..4c3859a37 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/Generated.ts @@ -0,0 +1,36330 @@ +/** + * @since 1.0.0 + */ + +import * as Data from "effect/Data" +import * as Effect from "effect/Effect" +import type { SchemaError } from "effect/Schema" +import * as Schema from "effect/Schema" +import * as Stream from "effect/Stream" +import * as Sse from "effect/unstable/encoding/Sse" +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" +// non-recursive definitions +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 "type": "bash_code_execution_output" +} +export const AnthropicBashCodeExecutionOutput = Schema.Struct({ + "file_id": Schema.String, + "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 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 +} +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, + "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 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 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 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 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 AnthropicTextEditorCodeExecutionCreateResult = { + readonly "is_file_update": boolean + readonly "type": "text_editor_code_execution_create_result" +} +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 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 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 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 AnthropicUrlPdfSource = { readonly "type": "url"; readonly "url": string } +export const AnthropicUrlPdfSource = Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }).annotate({ + "identifier": "AnthropicUrlPdfSource" +}) +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 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 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 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 "output_index": number + readonly "sequence_number": number + readonly "type": "response.apply_patch_call_operation_diff.delta" +} +export const ApplyPatchCallOperationDiffDeltaEvent = 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.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 "sequence_number": number + readonly "type": "response.apply_patch_call_operation_diff.done" +} +export const ApplyPatchCallOperationDiffDoneEvent = Schema.Struct({ + "diff": 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.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 "sequence_number": number + readonly "type": "response.custom_tool_call_input.delta" +} +export const BaseCustomToolCallInputDeltaEvent = 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": "BaseCustomToolCallInputDeltaEvent" +}) +export type BaseCustomToolCallInputDoneEvent = { + 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 BaseCustomToolCallInputDoneEvent = 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": "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 "output_index": number + readonly "sequence_number": number + readonly "type": "response.function_call_arguments.delta" +} +export const BaseFunctionCallArgsDeltaEvent = 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": "BaseFunctionCallArgsDeltaEvent" +}) +export type BaseFunctionCallArgsDoneEvent = { + 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 BaseFunctionCallArgsDoneEvent = 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": "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, + "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 "sequence_number": number + readonly "text": string + readonly "type": "response.reasoning_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.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": "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 BaseReasoningSummaryTextDeltaEvent = 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": "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 BaseReasoningSummaryTextDoneEvent = 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" })), + "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 "type": "response.refusal.delta" +} +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.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 BaseRefusalDoneEvent = 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" })), + "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 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 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 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 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 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 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 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 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 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 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 [x: string]: Schema.Json } | null +} +export const ConflictResponseErrorData = 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 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 [x: string]: Schema.Json } | null +} +export const EdgeNetworkTimeoutResponseErrorData = 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 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 [x: string]: Schema.Json } | null +} +export const ForbiddenResponseErrorData = 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 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({ "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 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 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": "Event emitted when function call arguments streaming is complete", + "identifier": "FunctionCallArgsDoneEvent" +}) +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 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": "Emitted when the fusion judge starts producing the structured analysis.", + "identifier": "FusionCallAnalysisInProgressEvent" +}) +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 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 FusionCallPanelAddedEvent = { + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.added" +} +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." + }) + ), + "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({ + "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.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({ + "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({ + "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" }))] + ), + 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": "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." + }), + "text": Schema.String.annotate({ + "description": "A text fragment of the image being generated (e.g. partial SVG markup)" + }), + "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({ + "read_only": Schema.optionalKey(Schema.Boolean), + "tool_names": Schema.optionalKey(Schema.Array(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({ + "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.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.Null + ]) + ), + "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": + "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 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 ModelGroup = + | "Router" + | "Media" + | "Other" + | "GPT" + | "Claude" + | "Gemini" + | "Gemma" + | "Grok" + | "Cohere" + | "Nova" + | "Qwen" + | "Yi" + | "DeepSeek" + | "Mistral" + | "Llama2" + | "Llama3" + | "Llama4" + | "PaLM" + | "RWKV" + | "Qwen3" +export const ModelGroup = Schema.Literals([ + "Router", + "Media", + "Other", + "GPT", + "Claude", + "Gemini", + "Gemma", + "Grok", + "Cohere", + "Nova", + "Qwen", + "Yi", + "DeepSeek", + "Mistral", + "Llama2", + "Llama3", + "Llama4", + "PaLM", + "RWKV", + "Qwen3" +]).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 NamespaceFunctionTool = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Literals(["direct", "programmatic"])), Schema.Null]) + ), + "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]) + ), + "parameters": Schema.optionalKey( + 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": "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": "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" + | "top_k" + | "min_p" + | "top_a" + | "frequency_penalty" + | "presence_penalty" + | "repetition_penalty" + | "max_tokens" + | "max_completion_tokens" + | "logit_bias" + | "logprobs" + | "top_logprobs" + | "prediction" + | "seed" + | "response_format" + | "structured_outputs" + | "stop" + | "tools" + | "tool_choice" + | "parallel_tool_calls" + | "include_reasoning" + | "reasoning" + | "reasoning_effort" + | "web_search_options" + | "verbosity" +export const Parameter = Schema.Literals([ + "temperature", + "top_p", + "top_k", + "min_p", + "top_a", + "frequency_penalty", + "presence_penalty", + "repetition_penalty", + "max_tokens", + "max_completion_tokens", + "logit_bias", + "logprobs", + "top_logprobs", + "prediction", + "seed", + "response_format", + "structured_outputs", + "stop", + "tools", + "tool_choice", + "parallel_tool_calls", + "include_reasoning", + "reasoning", + "reasoning_effort", + "web_search_options", + "verbosity" +]).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 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." + }) + ), + "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" }) + ) + ), + "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({ "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 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": + "The engine to use for parsing PDF files. \"pdf-text\" is deprecated and automatically redirected to \"cloudflare-ai\".", + "identifier": "PDFParserEngine" +}) +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" + | "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" +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", + "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({ "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 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 ProviderSortConfig = { + readonly "by"?: "price" | "throughput" | "latency" | "exacto" | null + readonly "partition"?: "model" | "none" | 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 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 RankingsDailyItem = { + readonly "date": string + readonly "model_permaslug": string + readonly "total_tokens": string +} +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 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 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 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 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 "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 ReasoningSummaryTextDoneEvent = 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" })), + "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 "output_index": number + readonly "refusal": string + readonly "sequence_number": number + readonly "type": "response.refusal.done" +} +export const RefusalDoneEvent = 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" })), + "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": "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 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 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 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 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 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 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 ShellServerTool = { readonly "type": "shell" } +export const ShellServerTool = Schema.Struct({ "type": Schema.Literal("shell") }).annotate({ + "description": "Shell tool configuration", + "identifier": "ShellServerTool" +}) +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 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 "bytes"?: ReadonlyArray; readonly "logprob"?: number; readonly "token"?: string } + > +} +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( + 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 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 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 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 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 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 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 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 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 SupportedParameters = {} +export const SupportedParameters = Schema.Struct({}).annotate({ + "description": + "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 TaskClassificationMacroCategory = { + readonly "key": string + readonly "label": string + readonly "token_share": number + readonly "usage_share": 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 TaskClassificationModel = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Model identifier (permaslug)." }), + "tag_token_share": Schema.Number.annotate({ + "description": + "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 "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 "type": "response.output_text.delta" +} +export const TextDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.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 "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 "text": string + readonly "type": "response.output_text.done" +} +export const TextDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "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" })), + "text": Schema.String, + "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 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" + }) + ), + "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" }))] + ), + 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." + }) + ), + "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 is disabled." })), + "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.optionalKey( + Schema.String.annotate({ + "description": + "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)" + }) + ), + "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": "New description for 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.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" })) + ), + "slug": Schema.optionalKey( + Schema.String.annotate({ + "description": + "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" }) + ), + "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 "created": number + readonly "description"?: 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 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" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the model" })), + "generate_audio": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the model supports generating audio alongside video" + }), + "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" }), + "name": Schema.String.annotate({ "description": "Display name of the model" }), + "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" + }) + ), + "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" + ]) + ), + Schema.Null + ]).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 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 "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.completed" +} +export const WebSearchCallCompletedEvent = 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({ "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 "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.searching" +} +export const WebSearchCallSearchingEvent = 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({ "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 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 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 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 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 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 WorkspaceMember = { + readonly "created_at": string + readonly "id": string + readonly "role": "admin" | "member" + readonly "user_id": string + readonly "workspace_id": 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 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 "citation": + | AnthropicCitationCharLocation + | AnthropicCitationPageLocation + | AnthropicCitationContentBlockLocation + | AnthropicCitationWebSearchResultLocation + | AnthropicCitationSearchResultLocation + readonly "type": "citations_delta" + } + | { 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({ + "citation": Schema.Union([ + AnthropicCitationCharLocation, + AnthropicCitationPageLocation, + AnthropicCitationContentBlockLocation, + AnthropicCitationWebSearchResultLocation, + AnthropicCitationSearchResultLocation + ], { mode: "oneOf" }), + "type": Schema.Literal("citations_delta") + }), + Schema.Struct({ "content": Schema.Union([Schema.String, Schema.Null]), "type": Schema.Literal("compaction_delta") }) + ], { mode: "oneOf" }), + "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 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 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 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 OutputItemApplyPatchCall = { + readonly "call_id": string + readonly "created_by"?: string + readonly "id": string + readonly "operation": ApplyPatchCreateFileOperation | ApplyPatchUpdateFileOperation | ApplyPatchDeleteFileOperation + readonly "status": "in_progress" | "completed" + readonly "type": "apply_patch_call" +} +export const OutputItemApplyPatchCall = Schema.Struct({ + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "id": Schema.String, + "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_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]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Bad Request - Invalid request parameters or malformed input", + "identifier": "BadRequestResponse" +}) +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 "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 "key": string + readonly "type": "eq" | "ne" | "gt" | "gte" | "lt" | "lte" + readonly "value": string | number | boolean | ReadonlyArray + } + | 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 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" }) + ), + Schema.Boolean, + Schema.Array( + Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ]) + ) + ]) + }), + 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" }) + ) + ) + }) + ), + "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 ConflictResponse = Schema.Struct({ + "error": ConflictResponseErrorData, + "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": "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" }) + ), + "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." + }) + ), + "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]) + ), + "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_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]) + ), + "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]) + ), + "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({ "identifier": "ListKeyAssignmentsResponse" }) +export type Legacy_ChatContentVideo = { + readonly "type": "input_video" + readonly "video_url": Legacy_ChatContentVideoInput +} +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.Literal("low"), + Schema.Literal("medium"), + Schema.Literal("high"), + Schema.Literal("xhigh"), + Schema.Literal("max"), + Schema.Null + ]).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`, `xhigh`, or `max`." + }) + ), + "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]) + ), + "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" }))] + ), + 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" }) + ), + "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]) + ), + "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]) + ), + "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" }) + ), + "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" })) + ), + "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" }) + ), + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached input token (read)" }) + ), + "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." + }) + ), + "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." + }) + ), + "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." + }) + ), + "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 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 ContentFilterBuiltinEntry = { + readonly "action": ContentFilterBuiltinAction + readonly "label"?: string + readonly "scan_scope"?: PromptInjectionScanScope + readonly "slug": ContentFilterBuiltinSlug +} +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 ContentFilterBuiltinEntryInput = { + readonly "action": ContentFilterBuiltinAction + readonly "label"?: string + readonly "scan_scope"?: PromptInjectionScanScope + readonly "slug": ContentFilterBuiltinSlug +} +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 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 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 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 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 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 ProviderOverloadedResponse = { + readonly "error": ProviderOverloadedResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +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 GenerationResponse = { + readonly "data": { + 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 "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 "is_byok": boolean + readonly "latency": number | null + readonly "model": string + 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 "user_agent": string | null + readonly "web_search_engine": string | null + } +} +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" }), + "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" + }) + ), + "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." + }) + ), + "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" + }) + ), + "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" }) + ), + "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" }) + ), + "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" })) + ), + "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" }) + ), + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached input token (read)" }) + ), + "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." + }) + ), + "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." + }) + ), + "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." + }) + ), + "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 AppRankingsResponse = { + readonly "data": ReadonlyArray + readonly "meta": RankingsDailyMeta +} +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 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 ReasoningDetailSummary = { + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "summary": string + readonly "type": "reasoning.summary" +} +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 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 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") }))]) + ), + "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 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 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 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 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 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 RequestTimeoutResponse = { + readonly "error": RequestTimeoutResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +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]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Request Timeout - Operation exceeded time limit", + "identifier": "RequestTimeoutResponse" +}) +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 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 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 SearchModelsServerTool_OpenRouter = { + readonly "parameters"?: SearchModelsServerToolConfig + readonly "type": "openrouter:experimental__search_models" +} +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 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 ServiceUnavailableResponse = Schema.Struct({ + "error": ServiceUnavailableResponseErrorData, + "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": "Service Unavailable - Service temporarily unavailable", + "identifier": "ServiceUnavailableResponse" +}) +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 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 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 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 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 STTResponse = { + readonly "duration"?: number + readonly "language"?: string + readonly "segments"?: ReadonlyArray + readonly "task"?: string + readonly "text": string + readonly "usage"?: STTUsage + readonly "words"?: ReadonlyArray +} +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" })) + ), + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Detected or forced language, present when response_format is verbose_json" + }) + ), + "segments": Schema.optionalKey( + Schema.Array(STTSegment).annotate({ + "description": "Timestamped transcript segments, present when response_format is verbose_json" + }) + ), + "task": Schema.optionalKey( + Schema.String.annotate({ "description": "The task performed, present when response_format is verbose_json" }) + ), + "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 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 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") }) + ]) + ), + 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]) + ), + "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 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 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 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 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 OutputDatetimeItem = { + readonly "datetime": string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "timezone": string + readonly "type": "openrouter:datetime" +} +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 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 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 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 OutputMcpServerToolItem = { + readonly "id"?: string + readonly "serverLabel"?: string + readonly "status": ToolCallStatus + readonly "toolName"?: string + readonly "type": "openrouter:mcp" +} +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 OutputSearchModelsServerToolItem = { + readonly "arguments"?: string + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:experimental__search_models" +} +export const OutputSearchModelsServerToolItem = 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.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "An openrouter:experimental__search_models server tool output item", + "identifier": "OutputSearchModelsServerToolItem" +}) +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 OutputSubagentServerToolItem = 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.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": 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 OutputTextEditorServerToolItem = { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:text_editor" +} +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 OutputToolSearchServerToolItem = { + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:tool_search" +} +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 +} +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" }) + ) + ), + "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" + } + 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 })) + ), + "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]) + ), + "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") + }), + [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" }) + ), + 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 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> + > + /** + * List all API keys for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "list": ( + options: + | { readonly params?: typeof ListParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"List400", typeof List400.Type> + | OpenRouterClientError<"List401", typeof List401.Type> + | OpenRouterClientError<"List429", typeof List429.Type> + | OpenRouterClientError<"List500", typeof List500.Type> + > + /** + * 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 "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 params?: typeof CreateMessagesParams.Encoded | undefined + readonly payload: typeof CreateMessagesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateMessages400", typeof CreateMessages400.Type> + | OpenRouterClientError<"CreateMessages401", typeof CreateMessages401.Type> + | OpenRouterClientError<"CreateMessages403", typeof CreateMessages403.Type> + | OpenRouterClientError<"CreateMessages404", typeof CreateMessages404.Type> + | OpenRouterClientError<"CreateMessages429", typeof CreateMessages429.Type> + | OpenRouterClientError<"CreateMessages500", typeof CreateMessages500.Type> + | OpenRouterClientError<"CreateMessages503", typeof CreateMessages503.Type> + | OpenRouterClientError<"CreateMessages529", typeof CreateMessages529.Type> + > + /** + * Creates a message using the Anthropic Messages API format. Supports text, images, PDFs, tools, and extended thinking. + */ + readonly "createMessagesSse": ( + 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 | Sse.SseError, + typeof CreateMessages200Sse.DecodingServices + > + /** + * 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 "getModel": ( + author: string, + slug: string, + options: + | { readonly params?: typeof GetModelParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetModel403", typeof GetModel403.Type> + | OpenRouterClientError<"GetModel404", typeof GetModel404.Type> + | OpenRouterClientError<"GetModel500", typeof GetModel500.Type> + > + /** + * List all models and their properties + */ + readonly "getModels": ( + options: + | { readonly params?: typeof GetModelsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetModels400", typeof GetModels400.Type> + | OpenRouterClientError<"GetModels403", typeof GetModels403.Type> + | OpenRouterClientError<"GetModels500", typeof GetModels500.Type> + > + /** + * List all endpoints for a model + */ + readonly "listEndpoints": ( + author: string, + slug: string, + options: + | { readonly params?: typeof ListEndpointsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListEndpoints403", typeof ListEndpoints403.Type> + | OpenRouterClientError<"ListEndpoints404", typeof ListEndpoints404.Type> + | OpenRouterClientError<"ListEndpoints500", typeof ListEndpoints500.Type> + > + /** + * Get total count of available models + */ + readonly "listModelsCount": ( + options: { + readonly params?: typeof ListModelsCountParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListModelsCount400", typeof ListModelsCount400.Type> + | OpenRouterClientError<"ListModelsCount403", typeof ListModelsCount403.Type> + | OpenRouterClientError<"ListModelsCount500", typeof ListModelsCount500.Type> + > + /** + * 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 "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> + > + /** + * 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 "listObservabilityDestinations": ( + options: { + readonly params?: typeof ListObservabilityDestinationsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListObservabilityDestinations400", typeof ListObservabilityDestinations400.Type> + | OpenRouterClientError<"ListObservabilityDestinations401", typeof ListObservabilityDestinations401.Type> + | OpenRouterClientError<"ListObservabilityDestinations500", typeof ListObservabilityDestinations500.Type> + > + /** + * 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 "createObservabilityDestination": ( + options: { + readonly params?: typeof CreateObservabilityDestinationParams.Encoded | undefined + readonly payload: typeof CreateObservabilityDestinationRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * Fetch a single observability destination by its UUID. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof GetObservabilityDestinationParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetObservabilityDestination401", typeof GetObservabilityDestination401.Type> + | OpenRouterClientError<"GetObservabilityDestination404", typeof GetObservabilityDestination404.Type> + | OpenRouterClientError<"GetObservabilityDestination500", typeof GetObservabilityDestination500.Type> + > + /** + * Delete an existing observability destination. This performs a soft delete. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof DeleteObservabilityDestinationParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteObservabilityDestination401", typeof DeleteObservabilityDestination401.Type> + | OpenRouterClientError<"DeleteObservabilityDestination404", typeof DeleteObservabilityDestination404.Type> + | OpenRouterClientError<"DeleteObservabilityDestination500", typeof DeleteObservabilityDestination500.Type> + > + /** + * 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 "updateObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof UpdateObservabilityDestinationParams.Encoded | undefined + readonly payload: typeof UpdateObservabilityDestinationRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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 members of the organization associated with the authenticated management key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listOrganizationMembers": ( + options: { + readonly params?: typeof ListOrganizationMembersParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListOrganizationMembers401", typeof ListOrganizationMembers401.Type> + | OpenRouterClientError<"ListOrganizationMembers404", typeof ListOrganizationMembers404.Type> + | OpenRouterClientError<"ListOrganizationMembers500", typeof ListOrganizationMembers500.Type> + > + /** + * Lists all presets for the authenticated user, ordered by most recently updated first. + */ + readonly "listPresets": ( + options: + | { readonly params?: typeof ListPresetsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListPresets400", typeof ListPresets400.Type> + | OpenRouterClientError<"ListPresets401", typeof ListPresets401.Type> + | OpenRouterClientError<"ListPresets500", typeof ListPresets500.Type> + > + /** + * Retrieves a preset by its slug with its currently designated version inline. + */ + readonly "getPreset": ( + slug: string, + options: + | { readonly params?: typeof GetPresetParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetPreset400", typeof GetPreset400.Type> + | OpenRouterClientError<"GetPreset401", typeof GetPreset401.Type> + | OpenRouterClientError<"GetPreset404", typeof GetPreset404.Type> + | OpenRouterClientError<"GetPreset500", typeof GetPreset500.Type> + > + /** + * 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 "createPresetsChatCompletions": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsChatCompletionsParams.Encoded | undefined + readonly payload: typeof CreatePresetsChatCompletionsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * 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 "createPresetsMessages": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsMessagesParams.Encoded | undefined + readonly payload: typeof CreatePresetsMessagesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * 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 "createPresetsResponses": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsResponsesParams.Encoded | undefined + readonly payload: typeof CreatePresetsResponsesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * Lists all versions of a preset, ordered by version number ascending (oldest first). + */ + readonly "listPresetVersions": ( + slug: string, + options: { + readonly params?: typeof ListPresetVersionsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListPresetVersions400", typeof ListPresetVersions400.Type> + | OpenRouterClientError<"ListPresetVersions401", typeof ListPresetVersions401.Type> + | OpenRouterClientError<"ListPresetVersions404", typeof ListPresetVersions404.Type> + | OpenRouterClientError<"ListPresetVersions500", typeof ListPresetVersions500.Type> + > + /** + * Retrieves a specific version of a preset by its slug and version number. + */ + readonly "getPresetVersion": ( + slug: string, + version: string, + options: { + readonly params?: typeof GetPresetVersionParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetPresetVersion400", typeof GetPresetVersion400.Type> + | OpenRouterClientError<"GetPresetVersion401", typeof GetPresetVersion401.Type> + | OpenRouterClientError<"GetPresetVersion404", typeof GetPresetVersion404.Type> + | OpenRouterClientError<"GetPresetVersion500", typeof GetPresetVersion500.Type> + > + /** + * List all providers + */ + readonly "listProviders": ( + options: + | { readonly params?: typeof ListProvidersParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListProviders500", typeof ListProviders500.Type> + > + /** + * Submits a rerank request to the rerank router + */ + readonly "createRerank": ( + options: { + readonly params?: typeof CreateRerankParams.Encoded | undefined + readonly payload: typeof CreateRerankRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * Submits a rerank request to the rerank router + */ + 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, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * Creates a streaming or non-streaming response using OpenResponses API format + */ + 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, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * Returns job status and content URLs when completed + */ + readonly "getVideos": ( + jobId: string, + options: + | { readonly params?: typeof GetVideosParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetVideos401", typeof GetVideos401.Type> + | OpenRouterClientError<"GetVideos404", typeof GetVideos404.Type> + | OpenRouterClientError<"GetVideos500", typeof GetVideos500.Type> + > + /** + * Streams the generated video content from the upstream provider + */ + readonly "listVideosContent": ( + jobId: string, + options: { + readonly params?: typeof ListVideosContentParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * Returns a list of all available video generation models and their properties + */ + readonly "listVideosModels": ( + options: { + readonly params?: typeof ListVideosModelsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListVideosModels400", typeof ListVideosModels400.Type> + | OpenRouterClientError<"ListVideosModels500", typeof ListVideosModels500.Type> + > + /** + * List all workspaces for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listWorkspaces": ( + options: + | { readonly params?: typeof ListWorkspacesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListWorkspaces401", typeof ListWorkspaces401.Type> + | OpenRouterClientError<"ListWorkspaces500", typeof ListWorkspaces500.Type> + > + /** + * Create a new workspace for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "createWorkspace": ( + options: { + readonly params?: typeof CreateWorkspaceParams.Encoded | undefined + readonly payload: typeof CreateWorkspaceRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateWorkspace400", typeof CreateWorkspace400.Type> + | OpenRouterClientError<"CreateWorkspace401", typeof CreateWorkspace401.Type> + | OpenRouterClientError<"CreateWorkspace403", typeof CreateWorkspace403.Type> + | OpenRouterClientError<"CreateWorkspace500", typeof CreateWorkspace500.Type> + > + /** + * Get a single workspace by ID or slug. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + 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 DeleteWorkspaceParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * Update an existing workspace by ID or slug. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "updateWorkspace": ( + id: string, + options: { + readonly params?: typeof UpdateWorkspaceParams.Encoded | undefined + readonly payload: typeof UpdateWorkspaceRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * List all budgets configured for a workspace. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listWorkspaceBudgets": ( + id: string, + options: { + readonly params?: typeof ListWorkspaceBudgetsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListWorkspaceBudgets401", typeof ListWorkspaceBudgets401.Type> + | OpenRouterClientError<"ListWorkspaceBudgets404", typeof ListWorkspaceBudgets404.Type> + | OpenRouterClientError<"ListWorkspaceBudgets500", typeof ListWorkspaceBudgets500.Type> + > + /** + * 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 "upsertWorkspaceBudget": ( + id: string, + interval: string, + options: { + readonly params?: typeof UpsertWorkspaceBudgetParams.Encoded | undefined + readonly payload: typeof UpsertWorkspaceBudgetRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UpsertWorkspaceBudget400", typeof UpsertWorkspaceBudget400.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget401", typeof UpsertWorkspaceBudget401.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget404", typeof UpsertWorkspaceBudget404.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget500", typeof UpsertWorkspaceBudget500.Type> + > + /** + * Remove the budget for a given interval. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteWorkspaceBudget": ( + id: string, + interval: string, + options: { + readonly params?: typeof DeleteWorkspaceBudgetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteWorkspaceBudget401", typeof DeleteWorkspaceBudget401.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget404", typeof DeleteWorkspaceBudget404.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget500", typeof DeleteWorkspaceBudget500.Type> + > + /** + * 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 "listWorkspaceMembers": ( + id: string, + options: { + readonly params?: typeof ListWorkspaceMembersParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListWorkspaceMembers401", typeof ListWorkspaceMembers401.Type> + | OpenRouterClientError<"ListWorkspaceMembers403", typeof ListWorkspaceMembers403.Type> + | OpenRouterClientError<"ListWorkspaceMembers404", typeof ListWorkspaceMembers404.Type> + | OpenRouterClientError<"ListWorkspaceMembers500", typeof ListWorkspaceMembers500.Type> + > + /** + * 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 "bulkAddWorkspaceMembers": ( + id: string, + options: { + readonly params?: typeof BulkAddWorkspaceMembersParams.Encoded | undefined + readonly payload: typeof BulkAddWorkspaceMembersRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > + /** + * 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 "bulkRemoveWorkspaceMembers": ( + id: string, + options: { + readonly params?: typeof BulkRemoveWorkspaceMembersParams.Encoded | undefined + readonly payload: typeof BulkRemoveWorkspaceMembersRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | 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> + > +} + +export interface OpenRouterClientError { + readonly _tag: Tag + readonly request: HttpClientRequest.HttpClientRequest + readonly response: HttpClientResponse.HttpClientResponse + readonly cause: E +} + +class OpenRouterClientErrorImpl extends Data.Error<{ + _tag: string + cause: any + request: HttpClientRequest.HttpClientRequest + response: HttpClientResponse.HttpClientResponse +}> {} + +export const OpenRouterClientError = ( + tag: Tag, + cause: E, + response: HttpClientResponse.HttpClientResponse +): OpenRouterClientError => + new OpenRouterClientErrorImpl({ + _tag: tag, + cause, + response, + request: response.request + }) as any diff --git a/.repos/effect/packages/ai/openrouter/src/OpenRouterClient.ts b/.repos/effect/packages/ai/openrouter/src/OpenRouterClient.ts new file mode 100644 index 000000000..8a2e66429 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/OpenRouterClient.ts @@ -0,0 +1,368 @@ +/** + * The `OpenRouterClient` module provides an Effect service for calling + * OpenRouter's chat completions API. It wraps the generated OpenRouter HTTP + * client with Effect-native constructors, layers, authentication and optional + * site ranking headers, typed errors, and streaming support. + * + * @since 4.0.0 + */ +import type * as Config from "effect/Config" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { identity } from "effect/Function" +import * as Layer from "effect/Layer" +import * as Predicate from "effect/Predicate" +import type * as Redacted from "effect/Redacted" +import * as Schema from "effect/Schema" +import * as Stream from "effect/Stream" +import type * as AiError from "effect/unstable/ai/AiError" +import * as Sse from "effect/unstable/encoding/Sse" +import * as HttpBody from "effect/unstable/http/HttpBody" +import * as HttpClient from "effect/unstable/http/HttpClient" +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import * as Generated from "./Generated.ts" +import * as Errors from "./internal/errors.ts" +import { OpenRouterConfig } from "./OpenRouterConfig.ts" + +// ============================================================================= +// Service Interface +// ============================================================================= + +/** + * The OpenRouter client service interface. + * + * **Details** + * + * Provides methods for interacting with OpenRouter's Chat Completions API, + * including both synchronous and streaming message creation. + * + * @category services + * @since 4.0.0 + */ +export interface Service { + readonly client: Generated.OpenRouterClient + + readonly createChatCompletion: ( + options: typeof Generated.ChatRequest.Encoded + ) => Effect.Effect< + [body: typeof Generated.SendChatCompletionRequest200.Type, response: HttpClientResponse.HttpClientResponse], + AiError.AiError + > + + readonly createChatCompletionStream: ( + options: Omit + ) => Effect.Effect< + [ + response: HttpClientResponse.HttpClientResponse, + stream: Stream.Stream + ], + AiError.AiError + > +} + +/** + * Decoded `data` payload from an OpenRouter chat completion streaming chunk. + * + * **Details** + * + * The payload contains streamed choices, model metadata, optional usage, and may + * include an OpenRouter error object for a streamed response. + * + * @category models + * @since 4.0.0 + */ +export type ChatStreamingResponseChunkData = typeof Generated.ChatStreamingResponse.fields.data.Type + +// ============================================================================= +// Service Identifier +// ============================================================================= + +/** + * Service tag for the OpenRouter client. + * + * **When to use** + * + * Use when accessing or providing the OpenRouter client service through + * Effect's context. + * + * @see {@link make} for constructing an OpenRouter client effectfully + * @see {@link layer} for providing a client from explicit options + * @see {@link layerConfig} for providing a client from `Config` + * + * @category services + * @since 4.0.0 + */ +export class OpenRouterClient extends Context.Service< + OpenRouterClient, + Service +>()("@effect/ai-openrouter/OpenRouterClient") {} + +// ============================================================================= +// Options +// ============================================================================= + +/** + * Configuration for creating an OpenRouter client. + * + * @category options + * @since 4.0.0 + */ +export type Options = { + readonly apiKey?: Redacted.Redacted | undefined + + readonly apiUrl?: string | undefined + + /** + * Optional URL of your site for rankings on `openrouter.ai`. + */ + readonly siteReferrer?: string | undefined + + /** + * Optional title of your site for rankings on `openrouter.ai`. + */ + readonly siteTitle?: string | undefined + + /** + * Optional transformer for the underlying HTTP client. + * + * **When to use** + * + * Use to add middleware, logging, or custom request/response handling. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +} + +// ============================================================================= +// Constructor +// ============================================================================= + +/** + * Creates an OpenRouter client service from explicit options. + * + * **When to use** + * + * Use when you need the OpenRouter client service value inside an effect. + * + * **Details** + * + * The returned service uses the current `HttpClient`, prepends `apiUrl` or + * `https://openrouter.ai/api/v1`, adds the bearer token and optional + * `HTTP-Referer` and `X-Title` headers, accepts JSON responses, and applies + * `transformClient` when provided. + * + * **Gotchas** + * + * Scoped `OpenRouterConfig.withClientTransform` applies to generated client + * request methods. Streaming chat completion requests are sent directly by this + * module and do not read that scoped transform. + * + * @see {@link layer} for providing this client from explicit options + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced( + function*(options: Options): Effect.fn.Return { + const baseClient = yield* HttpClient.HttpClient + + const httpClient = baseClient.pipe( + HttpClient.mapRequest((request) => + request.pipe( + HttpClientRequest.prependUrl(options.apiUrl ?? "https://openrouter.ai/api/v1"), + options.apiKey ? HttpClientRequest.bearerToken(options.apiKey) : identity, + options.siteReferrer ? HttpClientRequest.setHeader("HTTP-Referer", options.siteReferrer) : identity, + options.siteTitle ? HttpClientRequest.setHeader("X-Title", options.siteTitle) : identity, + HttpClientRequest.acceptJson + ) + ), + options.transformClient ?? identity + ) + + const httpClientOk = HttpClient.filterStatusOk(httpClient) + + const client = Generated.make(httpClient, { + transformClient: Effect.fnUntraced(function*(client) { + const config = yield* OpenRouterConfig.getOrUndefined + if (Predicate.isNotUndefined(config?.transformClient)) { + return config.transformClient(client) + } + return client + }) + }) + + const createChatCompletion: Service["createChatCompletion"] = (payload) => + client.sendChatCompletionRequest({ payload, config: { includeResponse: true } }).pipe( + 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")) + }) + ) + + const buildChatCompletionStream = ( + response: HttpClientResponse.HttpClientResponse + ): [ + HttpClientResponse.HttpClientResponse, + Stream.Stream + ] => { + const stream = response.stream.pipe( + Stream.decodeText(), + Stream.pipeThroughChannel(Sse.decode()), + Stream.mapEffect((event) => decodeChatCompletionSseData(event.data)), + Stream.takeWhile((data) => data !== "[DONE]"), + 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")) + }) + ) as any + return [response, stream] + } + + const createChatCompletionStream: Service["createChatCompletionStream"] = (payload) => + httpClientOk.execute( + HttpClientRequest.post("/chat/completions", { + body: HttpBody.jsonUnsafe({ + ...payload, + stream: true, + stream_options: { include_usage: true } + }) + }) + ).pipe( + Effect.map(buildChatCompletionStream), + Effect.catchTag( + "HttpClientError", + (error) => Errors.mapHttpClientError(error, "createChatCompletionStream") + ) + ) + + return OpenRouterClient.of({ + client, + createChatCompletion, + createChatCompletionStream + }) + } +) + +// ============================================================================= +// Layers +// ============================================================================= + +/** + * Creates a layer for the OpenRouter client with the given options. + * + * **When to use** + * + * Use when you already have the OpenRouter client options in code and want to + * provide `OpenRouterClient` as a layer. + * + * @see {@link make} for constructing the client service effectfully + * @see {@link layerConfig} for loading client settings from `Config` + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: Options): Layer.Layer => + Layer.effect(OpenRouterClient, make(options)) + +/** + * Creates a layer for the OpenRouter client from provided `Config` values. + * + * **When to use** + * + * Use when you need client settings for OpenRouter to be read from Effect + * `Config` values while providing `OpenRouterClient` as a layer. + * + * **Details** + * + * Only config values supplied in `options` are loaded. Omitted fields are + * passed to `make` as `undefined`, and `transformClient` is forwarded as a + * plain option. + * + * @see {@link make} for constructing the client service effectfully + * @see {@link layer} for providing the client from already-resolved options + * + * @category layers + * @since 4.0.0 + */ +export const layerConfig = (options?: { + /** + * The config value to load for the API key. + */ + readonly apiKey?: Config.Config | undefined> | undefined + + /** + * The config value to load for the API URL. + */ + readonly apiUrl?: Config.Config | undefined + + /** + * The config value to load for the site referrer URL. + */ + readonly siteReferrer?: Config.Config | undefined + + /** + * The config value to load for the site title. + */ + readonly siteTitle?: Config.Config | undefined + + /** + * Optional transformer for the HTTP client. + */ + readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined +}): Layer.Layer => + Layer.effect( + OpenRouterClient, + Effect.gen(function*() { + const apiKey = Predicate.isNotUndefined(options?.apiKey) + ? yield* options.apiKey + : undefined + const apiUrl = Predicate.isNotUndefined(options?.apiUrl) + ? yield* options.apiUrl + : undefined + const siteReferrer = Predicate.isNotUndefined(options?.siteReferrer) + ? yield* options.siteReferrer + : undefined + const siteTitle = Predicate.isNotUndefined(options?.siteTitle) + ? yield* options.siteTitle + : undefined + return yield* make({ + apiKey, + apiUrl, + siteReferrer, + siteTitle, + transformClient: options?.transformClient + }) + }) + ) + +// ============================================================================= +// Internal Utilities +// ============================================================================= + +const ChatStreamingResponseChunkDataFromString = Schema.fromJsonString(Generated.ChatStreamingResponse.fields.data) +const decodeChatStreamingResponseChunkData = Schema.decodeUnknownEffect(ChatStreamingResponseChunkDataFromString) + +const decodeChatCompletionSseData = ( + data: string +): Effect.Effect => + data === "[DONE]" + ? Effect.succeed(data) + : decodeChatStreamingResponseChunkData(data) diff --git a/.repos/effect/packages/ai/openrouter/src/OpenRouterConfig.ts b/.repos/effect/packages/ai/openrouter/src/OpenRouterConfig.ts new file mode 100644 index 000000000..e58445718 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/OpenRouterConfig.ts @@ -0,0 +1,99 @@ +/** + * The `OpenRouterConfig` module lets a workflow temporarily customize the HTTP + * client used by generated OpenRouter request methods. `OpenRouterClient` reads + * this scoped transform when generated client operations execute, so callers can + * add middleware or instrumentation without rebuilding the client layer. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import type { HttpClient } from "effect/unstable/http/HttpClient" + +/** + * Context service for scoped OpenRouter provider configuration used by client + * operations. + * + * **When to use** + * + * Use as the context service tag when manually providing or reading scoped + * OpenRouter provider configuration in an Effect context. + * + * @see {@link withClientTransform} for scoping an HTTP client transformation + * + * @category services + * @since 4.0.0 + */ +export class OpenRouterConfig extends Context.Service< + OpenRouterConfig, + OpenRouterConfig.Service +>()("@effect/ai-openrouter/OpenRouterConfig") { + /** + * Gets the configured OpenRouter service from the current context when present. + * + * @since 4.0.0 + */ + static readonly getOrUndefined: Effect.Effect = Effect.map( + Effect.context(), + Context.getOrUndefined(OpenRouterConfig) + ) +} + +/** + * Types associated with the `OpenRouterConfig` context service. + * + * @since 4.0.0 + */ +export declare namespace OpenRouterConfig { + /** + * Configuration values read by OpenRouter provider operations when resolving + * the generated HTTP client. + * + * @category services + * @since 4.0.0 + */ + export interface Service { + readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined + } +} + +/** + * Provides a scoped transform for the OpenRouter HTTP client used by provider + * operations. + * + * **When to use** + * + * Use when you need temporary OpenRouter HTTP client customization for a + * single effect or workflow without rebuilding the client layer. + * + * **Details** + * + * Supports both data-first and data-last forms. The transform is stored in the + * scoped `OpenRouterConfig` service and read by generated OpenRouter request + * operations while running the supplied effect. + * + * **Gotchas** + * + * If a transform is already present in the scoped config, this helper replaces + * it. Compose transforms manually when both should apply. Streaming chat + * completion requests are sent directly by `OpenRouterClient.make` and do not + * read this scoped transform. + * + * @category configuration + * @since 4.0.0 + */ +export const withClientTransform: { + (transform: (client: HttpClient) => HttpClient): (self: Effect.Effect) => Effect.Effect + (self: Effect.Effect, transform: (client: HttpClient) => HttpClient): Effect.Effect +} = dual< + (transform: (client: HttpClient) => HttpClient) => (self: Effect.Effect) => Effect.Effect, + (self: Effect.Effect, transform: (client: HttpClient) => HttpClient) => Effect.Effect +>( + 2, + (self, transformClient) => + Effect.flatMap( + OpenRouterConfig.getOrUndefined, + (config) => Effect.provideService(self, OpenRouterConfig, { ...config, transformClient }) + ) +) diff --git a/.repos/effect/packages/ai/openrouter/src/OpenRouterError.ts b/.repos/effect/packages/ai/openrouter/src/OpenRouterError.ts new file mode 100644 index 000000000..63d0555e0 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/OpenRouterError.ts @@ -0,0 +1,225 @@ +/** + * OpenRouter error metadata augmentation. + * + * Provides OpenRouter-specific metadata fields for AI error types through + * module augmentation, enabling typed access to OpenRouter error details. + * + * @since 4.0.0 + */ + +/** + * OpenRouter-specific error metadata fields. + * + * @category models + * @since 4.0.0 + */ +export type OpenRouterErrorMetadata = { + /** + * The error code returned by the API. + */ + readonly errorCode: string | number | null + /** + * The error type returned by the API. + */ + readonly errorType: string | null + /** + * The unique request ID for debugging. + */ + readonly requestId: string | null +} + +/** + * OpenRouter-specific rate limit metadata fields. + * + * @category models + * @since 4.0.0 + */ +export type OpenRouterRateLimitMetadata = OpenRouterErrorMetadata & { + readonly limit: string | null + readonly remaining: number | null + readonly resetRequests: string | null + readonly resetTokens: string | null +} + +declare module "effect/unstable/ai/AiError" { + /** + * OpenRouter metadata attached to `RateLimitError` values. + * + * **Details** + * + * Captures OpenRouter error details together with rate limit header + * information from responses where the provider rejected the request because + * a limit was reached. + * + * @category models + * @since 4.0.0 + */ + export interface RateLimitErrorMetadata { + /** + * OpenRouter-specific details for the rate limit response. + */ + readonly openrouter?: OpenRouterRateLimitMetadata | null + } + + /** + * OpenRouter metadata attached to `QuotaExhaustedError` values. + * + * **Details** + * + * Preserves provider error details for failures caused by exhausted account, + * billing, or usage quota. + * + * @category models + * @since 4.0.0 + */ + export interface QuotaExhaustedErrorMetadata { + /** + * OpenRouter-specific details for the quota exhaustion response. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `AuthenticationError` values. + * + * **Details** + * + * Preserves provider error details for failed API key, authorization, or + * permission checks. + * + * @category models + * @since 4.0.0 + */ + export interface AuthenticationErrorMetadata { + /** + * OpenRouter-specific details for the authentication failure. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `ContentPolicyError` values. + * + * **Details** + * + * Preserves provider error details when OpenRouter rejects input or output + * because it violates a content policy. + * + * @category models + * @since 4.0.0 + */ + export interface ContentPolicyErrorMetadata { + /** + * OpenRouter-specific details for the content policy response. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `InvalidRequestError` values. + * + * **Details** + * + * Preserves provider error details for malformed requests, unsupported + * parameters, or other request validation failures reported by OpenRouter. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidRequestErrorMetadata { + /** + * OpenRouter-specific details for the invalid request response. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `InternalProviderError` values. + * + * **Details** + * + * Preserves provider error details for OpenRouter-side failures such as + * transient server errors or overload responses. + * + * @category models + * @since 4.0.0 + */ + export interface InternalProviderErrorMetadata { + /** + * OpenRouter-specific details for the internal provider response. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `InvalidOutputError` values. + * + * **Details** + * + * Preserves provider error details when an OpenRouter response cannot be + * parsed or validated as the expected output. + * + * @category models + * @since 4.0.0 + */ + export interface InvalidOutputErrorMetadata { + /** + * OpenRouter-specific details for the invalid output response. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `StructuredOutputError` values. + * + * **Details** + * + * Preserves provider error details when OpenRouter returns content that does + * not satisfy the requested structured output schema. + * + * @category models + * @since 4.0.0 + */ + export interface StructuredOutputErrorMetadata { + /** + * OpenRouter-specific details for the structured output failure. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `UnsupportedSchemaError` values. + * + * **Details** + * + * Preserves provider error details when an unsupported schema failure is + * associated with an OpenRouter response. + * + * @category models + * @since 4.0.0 + */ + export interface UnsupportedSchemaErrorMetadata { + /** + * OpenRouter-specific details for the unsupported schema failure. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } + + /** + * OpenRouter metadata attached to `UnknownError` values. + * + * **Details** + * + * Preserves provider error details for OpenRouter failures that do not map + * cleanly to a more specific AI error category. + * + * @category models + * @since 4.0.0 + */ + export interface UnknownErrorMetadata { + /** + * OpenRouter-specific details for the unclassified provider failure. + */ + readonly openrouter?: OpenRouterErrorMetadata | null + } +} diff --git a/.repos/effect/packages/ai/openrouter/src/OpenRouterLanguageModel.ts b/.repos/effect/packages/ai/openrouter/src/OpenRouterLanguageModel.ts new file mode 100644 index 000000000..b32664cd9 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/OpenRouterLanguageModel.ts @@ -0,0 +1,1860 @@ +/** + * The `OpenRouterLanguageModel` module provides the OpenRouter implementation + * of Effect AI's `LanguageModel` service. It translates provider-neutral + * prompts, tools, files, structured output requests, reasoning metadata, + * cache-control hints, and provider options into OpenRouter chat completion + * requests, records GenAI telemetry around those calls, and converts normal or + * streaming results back into Effect AI response content and metadata. + * + * @since 4.0.0 + */ +/** @effect-diagnostics preferSchemaOverJson:skip-file */ +import * as Arr from "effect/Array" +import * as Context from "effect/Context" +import * as DateTime from "effect/DateTime" +import * as Effect from "effect/Effect" +import * as Encoding from "effect/Encoding" +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 Redactable from "effect/Redactable" +import type * as Schema from "effect/Schema" +import * as SchemaAST from "effect/SchemaAST" +import * as Stream from "effect/Stream" +import type { Span } from "effect/Tracer" +import type { DeepMutable, Mutable, Simplify } from "effect/Types" +import * as AiError from "effect/unstable/ai/AiError" +import { toCodecAnthropic } from "effect/unstable/ai/AnthropicStructuredOutput" +import * as IdGenerator from "effect/unstable/ai/IdGenerator" +import * as LanguageModel from "effect/unstable/ai/LanguageModel" +import * as AiModel from "effect/unstable/ai/Model" +import { toCodecOpenAI } from "effect/unstable/ai/OpenAiStructuredOutput" +import type * as Prompt from "effect/unstable/ai/Prompt" +import type * as Response from "effect/unstable/ai/Response" +import { addGenAIAnnotations } from "effect/unstable/ai/Telemetry" +import * as Tool from "effect/unstable/ai/Tool" +import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" +import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import type * as Generated from "./Generated.ts" +import { ReasoningDetailsDuplicateTracker, resolveFinishReason } from "./internal/utilities.ts" +import { type ChatStreamingResponseChunkData, OpenRouterClient } from "./OpenRouterClient.ts" + +// ============================================================================= +// Configuration +// ============================================================================= + +/** + * Context service for OpenRouter language model configuration. + * + * **When to use** + * + * Use to provide scoped OpenRouter chat completion defaults or per-operation + * overrides for an OpenRouter language model service. + * + * @see {@link withConfigOverride} for scoping language model request overrides + * + * @category services + * @since 4.0.0 + */ +export class Config extends Context.Service< + Config, + Simplify< + & Partial< + Omit< + typeof Generated.ChatRequest.Encoded, + "messages" | "response_format" | "tools" | "tool_choice" | "stream" | "stream_options" + > + > + & { + /** + * Whether to use strict JSON schema validation for structured outputs. + * + * Only applies to models that support structured outputs. Defaults to + * `true` when structured outputs are supported. + */ + readonly strictJsonSchema?: boolean | undefined + } + > +>()("@effect/ai-openrouter/OpenRouterLanguageModel/Config") {} + +// ============================================================================= +// Provider Options / Metadata +// ============================================================================= + +/** + * OpenRouter assistant reasoning detail blocks preserved for multi-turn + * conversations. + * + * @category models + * @since 4.0.0 + */ +export type ReasoningDetails = Exclude + +/** + * File annotations emitted on OpenRouter assistant messages and exposed in + * finish metadata. + * + * @category models + * @since 4.0.0 + */ +export type FileAnnotation = Extract< + NonNullable[number], + { type: "file" } +> + +declare module "effect/unstable/ai/Prompt" { + /** + * OpenRouter-specific options for system messages. + * + * **Details** + * + * These options are used when translating system instructions into + * OpenRouter chat messages. + * + * @category models + * @since 4.0.0 + */ + export interface SystemMessageOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the system message. + */ + readonly openrouter?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + } | null + } + + /** + * OpenRouter-specific options for user messages. + * + * **Details** + * + * These options are used when translating user content into OpenRouter chat + * messages. + * + * @category models + * @since 4.0.0 + */ + export interface UserMessageOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the user message. + */ + readonly openrouter?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + } | null + } + + /** + * OpenRouter-specific options for assistant messages. + * + * **Details** + * + * Preserves reasoning metadata when assistant messages are replayed in later + * OpenRouter requests. + * + * @category models + * @since 4.0.0 + */ + export interface AssistantMessageOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the assistant message. + */ + readonly openrouter?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + /** + * Reasoning details associated with the assistant message. + */ + readonly reasoningDetails?: ReasoningDetails | null + } | null + } + + /** + * OpenRouter-specific options for tool messages. + * + * **Details** + * + * These options are used when converting tool results into OpenRouter chat + * messages. + * + * @category models + * @since 4.0.0 + */ + export interface ToolMessageOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the tool message. + */ + readonly openrouter?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + } | null + } + + /** + * OpenRouter-specific options for text prompt parts. + * + * **When to use** + * + * Use when you use these options to control how text content is sent to OpenRouter. + * + * @category models + * @since 4.0.0 + */ + export interface TextPartOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the text part. + */ + readonly openrouter?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + } | null + } + + /** + * OpenRouter-specific options for reasoning prompt parts. + * + * **Details** + * + * Preserves provider reasoning blocks so reasoning-aware conversations can + * continue across OpenRouter requests. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the reasoning part. + */ + readonly openrouter?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + /** + * Reasoning details associated with the reasoning part. + */ + readonly reasoningDetails?: ReasoningDetails | null + } | null + } + + /** + * OpenRouter-specific options for file prompt parts. + * + * **Details** + * + * Controls file naming and prompt caching for files sent to OpenRouter. + * + * @category models + * @since 4.0.0 + */ + export interface FilePartOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the file part. + */ + readonly openrouter?: { + /** + * The name to give to the file. Will be prioritized over the file name + * associated with the file part, if present. + */ + readonly fileName?: string | null + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + } | null + } + + /** + * OpenRouter-specific options for tool call prompt parts. + * + * **Details** + * + * Preserves reasoning details associated with tool calls when a conversation + * is sent back to OpenRouter. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the tool call part. + */ + readonly openrouter?: { + /** + * Reasoning details associated with the tool call part. + */ + readonly reasoningDetails?: ReasoningDetails | null + } | null + } + + /** + * OpenRouter-specific options for tool result prompt parts. + * + * **Details** + * + * Controls prompt caching for tool results sent to OpenRouter. + * + * @category models + * @since 4.0.0 + */ + export interface ToolResultPartOptions extends ProviderOptions { + /** + * Provider-specific options sent to OpenRouter for the tool result part. + */ + readonly openrouter?: { + /** + * A breakpoint which marks the end of reusable content eligible for caching. + */ + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null + } | null + } +} + +declare module "effect/unstable/ai/Response" { + /** + * OpenRouter metadata attached to completed reasoning response parts. + * + * **Details** + * + * Preserves provider reasoning details that can be sent back in later turns. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the reasoning part. + */ + readonly openrouter?: { + /** + * Reasoning details emitted by the underlying provider for this part. + */ + readonly reasoningDetails?: ReasoningDetails | null + } | null + } + + /** + * OpenRouter metadata emitted when a streamed reasoning part starts. + * + * **Details** + * + * Carries the first reasoning detail chunk when OpenRouter exposes one. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningStartPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning start. + */ + readonly openrouter?: { + /** + * Reasoning details emitted by the underlying provider for this part. + */ + readonly reasoningDetails?: ReasoningDetails | null + } | null + } + + /** + * OpenRouter metadata emitted for streamed reasoning deltas. + * + * **Details** + * + * Carries provider reasoning detail chunks as they arrive from OpenRouter. + * + * @category models + * @since 4.0.0 + */ + export interface ReasoningDeltaPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the streamed reasoning delta. + */ + readonly openrouter?: { + /** + * Reasoning details emitted by the underlying provider for this delta. + */ + readonly reasoningDetails?: ReasoningDetails | null + } | null + } + + /** + * OpenRouter metadata attached to tool-call response parts. + * + * **Details** + * + * Associates tool calls with provider reasoning details when the model emits + * reasoning and tool calls together. + * + * @category models + * @since 4.0.0 + */ + export interface ToolCallPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned for the tool call. + */ + readonly openrouter?: { + /** + * Reasoning details associated with this tool call. + */ + readonly reasoningDetails?: ReasoningDetails | null + } | null + } + + /** + * OpenRouter metadata attached to URL source citations. + * + * **Details** + * + * Includes citation text and offsets returned by providers that support URL + * annotations. + * + * @category models + * @since 4.0.0 + */ + export interface UrlSourcePartMetadata extends ProviderMetadata { + /** + * Provider-specific citation metadata returned for the URL source. + */ + readonly openrouter?: { + /** + * The cited source content returned by the provider. + */ + readonly content?: string | null + /** + * The zero-based start index of the citation in the generated text. + */ + readonly startIndex?: number | null + /** + * The zero-based end index of the citation in the generated text. + */ + readonly endIndex?: number | null + } | null + } + + /** + * OpenRouter metadata attached to finish response parts. + * + * **Details** + * + * Exposes provider response details that are not represented by the common + * Effect AI finish part fields. + * + * @category models + * @since 4.0.0 + */ + export interface FinishPartMetadata extends ProviderMetadata { + /** + * Provider-specific metadata returned when the OpenRouter response finishes. + */ + readonly openrouter?: { + /** + * Provider fingerprint for the backend configuration that served the request. + */ + readonly systemFingerprint?: string | null + /** + * Raw token usage reported by OpenRouter. + */ + readonly usage?: typeof Generated.ChatUsage.Encoded | null + /** + * File annotations returned by the provider. + */ + readonly annotations?: ReadonlyArray | null + /** + * The OpenRouter provider that served the request, when reported. + */ + readonly provider?: string | null + } | null + } +} + +// ============================================================================= +// Language Model +// ============================================================================= + +/** + * Creates an OpenRouter model descriptor that can be provided with + * `Effect.provide`. + * + * **When to use** + * + * Use when you want an OpenRouter language model value that carries provider + * and model metadata and can be supplied directly to an Effect program. + * + * **Details** + * + * The returned model requires `OpenRouterClient` and provides + * `LanguageModel.LanguageModel`. + * + * @see {@link layer} for creating a `LanguageModel.LanguageModel` layer directly + * @see {@link make} for constructing the language model service effectfully + * @see {@link withConfigOverride} for scoping OpenRouter request overrides + * + * @category constructors + * @since 4.0.0 + */ +export const model = ( + model: string, + config?: Omit +): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenRouterClient> => + AiModel.make("openai", model, layer({ model, config })) + +/** + * Creates an OpenRouter `LanguageModel` service from a model identifier and + * optional request defaults. + * + * **When to use** + * + * Use when you need to construct a `LanguageModel.Service` value backed by + * `OpenRouterClient` inside an Effect. + * + * **Details** + * + * The returned effect requires `OpenRouterClient`. Request defaults from the + * `config` option are merged with any `Config` service in the context, with + * context values taking precedence. The service supports both `generateText` + * and `streamText`. + * + * **Gotchas** + * + * Provider-defined tools are not supported by this provider integration; + * requests that include them fail with an `InvalidUserInputError`. + * + * @see {@link layer} for providing the service as a `Layer` + * @see {@link model} for creating a model descriptor for `Effect.provide` + * @see {@link withConfigOverride} for scoping request defaults around operations + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { + readonly model: string + readonly config?: Omit | undefined +}): Effect.fn.Return { + const client = yield* OpenRouterClient + const codecTransformer = getCodecTransformer(model) + + 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 { + 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.ChatRequest.Encoded = { + ...config, + messages, + ...(Predicate.isNotUndefined(responseFormat) ? { response_format: responseFormat } : undefined), + ...(Predicate.isNotUndefined(tools) ? { tools } : undefined), + ...(Predicate.isNotUndefined(toolChoice) ? { tool_choice: toolChoice } : undefined) + } + return request + } + ) + + return yield* LanguageModel.make({ + codecTransformer: toCodecOpenAI, + generateText: Effect.fnUntraced( + function*(options) { + const config = yield* makeConfig + const request = yield* makeRequest({ config, options }) + annotateRequest(options.span, request) + const [rawResponse, response] = yield* client.createChatCompletion(request) + annotateResponse(options.span, rawResponse) + return yield* makeResponse({ rawResponse, response }) + } + ), + streamText: Effect.fnUntraced( + function*(options) { + const config = yield* makeConfig + const request = yield* makeRequest({ config, options }) + annotateRequest(options.span, request) + const [response, stream] = yield* client.createChatCompletionStream(request) + return yield* makeStreamResponse({ response, stream }) + }, + (effect, options) => + effect.pipe( + Stream.unwrap, + Stream.map((response) => { + annotateStreamResponse(options.span, response) + return response + }) + ) + ) + }) +}) + +/** + * Creates a layer for the OpenRouter language model. + * + * **When to use** + * + * Use when composing application layers and you want OpenRouter to satisfy + * `LanguageModel.LanguageModel` while supplying `OpenRouterClient` from another + * layer. + * + * @see {@link make} for constructing the language model service effectfully + * @see {@link model} for creating a model descriptor for `Effect.provide` + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: { + readonly model: string + readonly config?: Omit | undefined +}): Layer.Layer => + Layer.effect(LanguageModel.LanguageModel, make(options)) + +/** + * Provides config overrides for OpenRouter language model operations. + * + * **When to use** + * + * Use to apply OpenRouter request configuration to one effect without changing + * the model's default configuration. + * + * **Details** + * + * The overrides are merged with any existing `Config` service for the duration + * of the supplied effect. Fields in `overrides` take precedence over existing + * config, and the helper supports both pipe form and + * `withConfigOverride(effect, overrides)`. + * + * @see {@link Config} for available OpenRouter request configuration fields + * + * @category configuration + * @since 4.0.0 + */ +export const withConfigOverride: { + (overrides: typeof Config.Service): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, overrides: typeof Config.Service): Effect.Effect> +} = dual< + ( + overrides: typeof Config.Service + ) => (self: Effect.Effect) => Effect.Effect>, + (self: Effect.Effect, overrides: typeof Config.Service) => Effect.Effect> +>(2, (self, overrides) => + Effect.flatMap( + Effect.serviceOption(Config), + (config) => + Effect.provideService(self, Config, { + ...(config._tag === "Some" ? config.value : {}), + ...overrides + }) + )) + +// ============================================================================= +// Prompt Conversion +// ============================================================================= + +const prepareMessages = Effect.fnUntraced( + function*({ options }: { + readonly options: LanguageModel.ProviderOptions + }): Effect.fn.Return, AiError.AiError> { + const messages: Array = [] + + const reasoningDetailsTracker = new ReasoningDetailsDuplicateTracker() + + for (const message of options.prompt.content) { + switch (message.role) { + case "system": { + const cache_control = getCacheControl(message) + + messages.push({ + role: "system", + content: [{ + type: "text", + text: message.content, + ...(Predicate.isNotNull(cache_control) ? { cache_control } : undefined) + }] + }) + + break + } + + case "user": { + const content: Array = [] + + // Get the message-level cache control + const messageCacheControl = getCacheControl(message) + + if (message.content.length === 1 && message.content[0].type === "text") { + messages.push({ + role: "user", + content: Predicate.isNotNull(messageCacheControl) + ? [{ type: "text", text: message.content[0].text, cache_control: messageCacheControl }] + : message.content[0].text + }) + + break + } + + // Find the index of the last text part in the message content + let lastTextPartIndex = -1 + for (let i = message.content.length - 1; i >= 0; i--) { + if (message.content[i].type === "text") { + lastTextPartIndex = i + break + } + } + + for (let index = 0; index < message.content.length; index++) { + const part = message.content[index] + const isLastTextPart = part.type === "text" && index === lastTextPartIndex + const partCacheControl = getCacheControl(part) + + switch (part.type) { + case "text": { + const cache_control = Predicate.isNotNull(partCacheControl) + ? partCacheControl + : isLastTextPart + ? messageCacheControl + : null + + content.push({ + type: "text", + text: part.text, + ...(Predicate.isNotNull(cache_control) ? { cache_control } : undefined) + }) + + break + } + + case "file": { + if (part.mediaType.startsWith("image/")) { + const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType + + content.push({ + type: "image_url", + image_url: { + url: part.data instanceof URL + ? part.data.toString() + : part.data instanceof Uint8Array + ? `data:${mediaType};base64,${Encoding.encodeBase64(part.data)}` + : part.data + }, + ...(Predicate.isNotNull(partCacheControl) ? { cache_control: partCacheControl } : undefined) + }) + + break + } + + if (part.mediaType.startsWith("audio/")) { + const format = audioFormats[part.mediaType.toLowerCase()] + + if (Predicate.isUndefined(format)) { + return yield* AiError.make({ + module: "OpenRouterLanguageModel", + method: "prepareMessages", + reason: new AiError.InvalidUserInputError({ + description: `Detected unsupported media type for audio file: '${part.mediaType}' ` + + `- OpenRouter supports ${supportedAudioFormats} audio` + }) + }) + } + + if (part.data instanceof URL) { + return yield* AiError.make({ + module: "OpenRouterLanguageModel", + method: "prepareMessages", + reason: new AiError.InvalidUserInputError({ + description: "Detected URL data for audio file - OpenRouter requires " + + "audio to be provided as base64-encoded data" + }) + }) + } + + content.push({ + type: "input_audio", + input_audio: { + data: part.data instanceof Uint8Array + ? Encoding.encodeBase64(part.data) + : getBase64FromDataUrl(part.data), + format + }, + ...(Predicate.isNotNull(partCacheControl) ? { cache_control: partCacheControl } : undefined) + }) + + break + } + + const options = part.options.openrouter + const fileName = options?.fileName ?? part.fileName ?? "" + + content.push({ + type: "file", + file: { + filename: fileName, + file_data: part.data instanceof URL + ? part.data.toString() + : part.data instanceof Uint8Array + ? `data:${part.mediaType};base64,${Encoding.encodeBase64(part.data)}` + : part.data + }, + ...(Predicate.isNotNull(partCacheControl) ? { cache_control: partCacheControl } : undefined) + } as any) + + break + } + } + } + + messages.push({ role: "user", content }) + + break + } + + case "assistant": { + let text = "" + let reasoning = "" + const toolCalls: Array = [] + + for (const part of message.content) { + switch (part.type) { + case "text": { + text += part.text + break + } + + case "reasoning": { + reasoning += part.text + break + } + + case "tool-call": { + toolCalls.push({ + type: "function", + id: part.id, + function: { name: part.name, arguments: JSON.stringify(part.params) } + }) + break + } + + default: { + break + } + } + } + + const messageReasoningDetails = message.options.openrouter?.reasoningDetails + + // Use message-level reasoning details if available, otherwise find from parts + // Priority: message-level > first tool call > first reasoning part + // This prevents duplicate thinking blocks when Claude makes parallel tool calls + const candidateReasoningDetails: ReasoningDetails | null = Predicate.isNotNullish(messageReasoningDetails) + && Array.isArray(messageReasoningDetails) + && messageReasoningDetails.length > 0 + ? messageReasoningDetails + : findFirstReasoningDetails(message.content) + + // Deduplicate reasoning details across all messages to prevent "Duplicate + // item found with id" errors in multi-turn conversations. + let reasoningDetails: ReasoningDetails | null = null + if (Predicate.isNotNull(candidateReasoningDetails) && candidateReasoningDetails.length > 0) { + const uniqueReasoningDetails: Mutable = [] + for (const detail of candidateReasoningDetails) { + if (reasoningDetailsTracker.upsert(detail)) { + uniqueReasoningDetails.push(detail) + } + } + if (uniqueReasoningDetails.length > 0) { + reasoningDetails = uniqueReasoningDetails + } + } + + messages.push({ + role: "assistant", + content: text, + reasoning: reasoning.length > 0 ? reasoning : null, + ...(Predicate.isNotNull(reasoningDetails) ? { reasoning_details: reasoningDetails } : undefined), + ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : undefined) + }) + + break + } + + case "tool": { + for (const part of message.content) { + // Skip tool approval parts + if (part.type === "tool-approval-response") { + continue + } + + messages.push({ + role: "tool", + tool_call_id: part.id, + content: JSON.stringify(part.result) + }) + } + + break + } + } + } + + return messages + } +) + +// ============================================================================= +// HTTP Details +// ============================================================================= + +const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +const buildHttpResponseDetails = ( + response: HttpClientResponse.HttpClientResponse +): typeof Response.HttpResponseDetails.Type => ({ + status: response.status, + headers: Redactable.redact(response.headers) as Record +}) + +// ============================================================================= +// Response Conversion +// ============================================================================= + +const makeResponse = Effect.fnUntraced( + function*({ rawResponse, response }: { + readonly rawResponse: Generated.SendChatCompletionRequest200 + readonly response: HttpClientResponse.HttpClientResponse + }): Effect.fn.Return, AiError.AiError, IdGenerator.IdGenerator> { + const idGenerator = yield* IdGenerator.IdGenerator + + const parts: Array = [] + let hasToolCalls = false + let hasEncryptedReasoning = false + + const createdAt = new Date(rawResponse.created * 1000) + parts.push({ + type: "response-metadata", + id: rawResponse.id, + modelId: rawResponse.model, + timestamp: DateTime.formatIso(DateTime.fromDateUnsafe(createdAt)), + request: buildHttpRequestDetails(response.request) + }) + + const choice = rawResponse.choices[0] + if (Predicate.isUndefined(choice)) { + return yield* AiError.make({ + module: "OpenRouterLanguageModel", + method: "makeResponse", + reason: new AiError.InvalidOutputError({ + description: "Received response with empty choices" + }) + }) + } + + const message = choice.message + let finishReason = choice.finish_reason + + const reasoningDetails = message.reasoning_details + if (Predicate.isNotNullish(reasoningDetails) && reasoningDetails.length > 0) { + for (const detail of reasoningDetails) { + switch (detail.type) { + case "reasoning.text": { + if (Predicate.isNotNullish(detail.text) && detail.text.length > 0) { + parts.push({ + type: "reasoning", + text: detail.text, + metadata: { openrouter: { reasoningDetails: [detail] } } + }) + } + break + } + case "reasoning.summary": { + if (detail.summary.length > 0) { + parts.push({ + type: "reasoning", + text: detail.summary, + metadata: { openrouter: { reasoningDetails: [detail] } } + }) + } + break + } + case "reasoning.encrypted": { + if (detail.data.length > 0) { + hasEncryptedReasoning = true + parts.push({ + type: "reasoning", + text: "[REDACTED]", + metadata: { openrouter: { reasoningDetails: [detail] } } + }) + } + break + } + } + } + } else if (Predicate.isNotNullish(message.reasoning) && message.reasoning.length > 0) { + // message.reasoning fallback only when reasoning_details absent/empty + parts.push({ + type: "reasoning", + text: message.reasoning + }) + } + + const content = message.content + if (Predicate.isNotNullish(content)) { + if (typeof content === "string") { + if (content.length > 0) { + parts.push({ type: "text", text: content }) + } + } else { + for (const item of content) { + if (item.type === "text") { + parts.push({ type: "text", text: item.text }) + } + } + } + } + + const toolCalls = message.tool_calls + if (Predicate.isNotNullish(toolCalls) && toolCalls.length > 0) { + hasToolCalls = true + for (let index = 0; index < toolCalls.length; index++) { + const toolCall = toolCalls[index] + const toolName = toolCall.function.name + const toolParams = toolCall.function.arguments ?? "{}" + const params = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(toolParams), + catch: (cause) => + AiError.make({ + module: "OpenRouterLanguageModel", + method: "makeResponse", + reason: new AiError.ToolParameterValidationError({ + toolName, + toolParams: {}, + description: `Failed to securely JSON parse tool parameters: ${cause}` + }) + }) + }) + parts.push({ + type: "tool-call", + id: toolCall.id, + name: toolName, + params, + // Only attach reasoning_details to the first tool call to avoid + // duplicating thinking blocks for parallel tool calls (Claude) + ...(index === 0 && Predicate.isNotNullish(reasoningDetails) && reasoningDetails.length > 0 + ? { metadata: { openrouter: { reasoningDetails } } } + : undefined) + }) + } + } + + const images = message.images + if (Predicate.isNotNullish(images)) { + for (const image of images) { + const url = image.image_url.url + if (url.startsWith("data:")) { + const mediaType = getMediaType(url, "image/jpeg") + const data = getBase64FromDataUrl(url) + parts.push({ type: "file", mediaType, data }) + } else { + const id = yield* idGenerator.generateId() + parts.push({ type: "source", sourceType: "url", id, url, title: "" }) + } + } + } + + const annotations = choice.message.annotations + if (Predicate.isNotNullish(annotations)) { + for (const annotation of annotations) { + if (annotation.type === "url_citation") { + parts.push({ + type: "source", + sourceType: "url", + id: annotation.url_citation.url, + url: annotation.url_citation.url, + title: annotation.url_citation.title ?? "", + metadata: { + openrouter: { + ...(Predicate.isNotUndefined(annotation.url_citation.content) + ? { content: annotation.url_citation.content } + : undefined), + ...(Predicate.isNotUndefined(annotation.url_citation.start_index) + ? { startIndex: annotation.url_citation.start_index } + : undefined), + ...(Predicate.isNotUndefined(annotation.url_citation.end_index) + ? { endIndex: annotation.url_citation.end_index } + : undefined) + } + } + }) + } + } + } + + // Extract file annotations to expose in provider metadata + const fileAnnotations = annotations?.filter((annotation) => { + return annotation.type === "file" + }) + + // Fix for Gemini 3 thoughtSignature: when there are tool calls with encrypted + // reasoning (thoughtSignature), the model returns 'stop' but expects continuation. + // Override to 'tool-calls' so the SDK knows to continue the conversation. + if (hasEncryptedReasoning && hasToolCalls && finishReason === "stop") { + finishReason = "tool_calls" + } + + parts.push({ + type: "finish", + reason: resolveFinishReason(finishReason), + usage: getUsage(rawResponse.usage), + response: buildHttpResponseDetails(response), + metadata: { + openrouter: { + systemFingerprint: rawResponse.system_fingerprint ?? null, + usage: rawResponse.usage ?? null, + ...(Predicate.isNotUndefined(fileAnnotations) && fileAnnotations.length > 0 + ? { annotations: fileAnnotations } + : undefined), + ...(Predicate.hasProperty(rawResponse, "provider") && Predicate.isString(rawResponse.provider) + ? { provider: rawResponse.provider } + : undefined) + } + } + }) + + return parts + } +) + +const makeStreamResponse = Effect.fnUntraced( + function*({ response, stream }: { + readonly response: HttpClientResponse.HttpClientResponse + readonly stream: Stream.Stream + }): Effect.fn.Return< + Stream.Stream, + AiError.AiError, + IdGenerator.IdGenerator + > { + const idGenerator = yield* IdGenerator.IdGenerator + + let textStarted = false + let reasoningStarted = false + let responseMetadataEmitted = false + let reasoningDetailsAttachedToToolCall = false + let finishReason: Response.FinishReason = "other" + let openRouterResponseId: string | undefined = undefined + let activeReasoningId: string | undefined = undefined + let activeTextId: string | undefined = undefined + + let totalToolCalls = 0 + const activeToolCalls: Record = {} + + // Track reasoning details to preserve for multi-turn conversations + const accumulatedReasoningDetails: DeepMutable = [] + + // Track file annotations to expose in provider metadata + const accumulatedFileAnnotations: Array = [] + + const usage: DeepMutable = { + inputTokens: { + total: undefined, + uncached: undefined, + cacheRead: undefined, + cacheWrite: undefined + }, + outputTokens: { + total: undefined, + text: undefined, + reasoning: undefined + } + } + + return stream.pipe( + Stream.mapEffect(Effect.fnUntraced(function*(event) { + const parts: Array = [] + + if (Predicate.isNotUndefined(event.error)) { + finishReason = "error" + parts.push({ type: "error", error: event.error }) + } + + if (Predicate.isNotUndefined(event.id) && !responseMetadataEmitted) { + const timestamp = yield* DateTime.now + parts.push({ + type: "response-metadata", + id: event.id, + modelId: event.model, + timestamp: DateTime.formatIso(timestamp), + request: buildHttpRequestDetails(response.request) + }) + responseMetadataEmitted = true + } + + if (Predicate.isNotUndefined(event.usage)) { + const computed = getUsage(event.usage) + usage.inputTokens = computed.inputTokens + usage.outputTokens = computed.outputTokens + } + + const choice = event.choices[0] + if (Predicate.isNotUndefined(choice)) { + if (Predicate.isNotNullish(choice.finish_reason)) { + finishReason = resolveFinishReason(choice.finish_reason) + } + + const delta = choice.delta + if (Predicate.isNullish(delta)) { + return parts + } + + const emitReasoning = Effect.fnUntraced( + function*(delta: string, metadata?: Response.ReasoningDeltaPart["metadata"] | undefined) { + if (!reasoningStarted) { + activeReasoningId = openRouterResponseId ?? (yield* idGenerator.generateId()) + parts.push({ + type: "reasoning-start", + id: activeReasoningId, + metadata + }) + reasoningStarted = true + } + parts.push({ + type: "reasoning-delta", + id: activeReasoningId!, + delta, + metadata + }) + } + ) + + const reasoningDetails = delta.reasoning_details + if (Predicate.isNotUndefined(reasoningDetails) && reasoningDetails.length > 0) { + // Accumulate reasoning_details to preserve for multi-turn conversations + // Merge consecutive reasoning.text items into a single entry + for (const detail of reasoningDetails) { + if (detail.type === "reasoning.text") { + const lastDetail = accumulatedReasoningDetails[accumulatedReasoningDetails.length - 1] + if (Predicate.isNotUndefined(lastDetail) && lastDetail.type === "reasoning.text") { + // Merge with the previous text detail + lastDetail.text = (lastDetail.text ?? "") + (detail.text ?? "") + lastDetail.signature = lastDetail.signature ?? detail.signature ?? null + lastDetail.format = lastDetail.format ?? detail.format ?? null + } else { + // Start a new text detail + accumulatedReasoningDetails.push({ ...detail }) + } + } else { + // Non-text details (encrypted, summary) are pushed as-is + accumulatedReasoningDetails.push(detail) + } + } + + // Emit reasoning_details in providerMetadata for each delta chunk + // so users can accumulate them on their end before sending back + const metadata: Response.ReasoningDeltaPart["metadata"] = { + openrouter: { + reasoningDetails + } + } + for (const detail of reasoningDetails) { + switch (detail.type) { + case "reasoning.text": { + if (Predicate.isNotNullish(detail.text)) { + yield* emitReasoning(detail.text, metadata) + } + break + } + + case "reasoning.summary": { + if (Predicate.isNotNullish(detail.summary)) { + yield* emitReasoning(detail.summary, metadata) + } + break + } + + case "reasoning.encrypted": { + if (Predicate.isNotNullish(detail.data)) { + yield* emitReasoning("[REDACTED]", metadata) + } + break + } + } + } + } else if (Predicate.isNotNullish(delta.reasoning)) { + yield* emitReasoning(delta.reasoning) + } + + const content = delta.content + if (Predicate.isNotNullish(content)) { + // If reasoning was previously active and now we're starting text content, + // we should end the reasoning first to maintain proper order + if (reasoningStarted && !textStarted) { + parts.push({ + type: "reasoning-end", + id: activeReasoningId!, + // Include accumulated reasoning_details so the we can update the + // reasoning part's provider metadata with the correct signature. + // 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 } } + : undefined + }) + reasoningStarted = false + } + + if (!textStarted) { + activeTextId = openRouterResponseId ?? (yield* idGenerator.generateId()) + parts.push({ + type: "text-start", + id: activeTextId + }) + textStarted = true + } + + parts.push({ + type: "text-delta", + id: activeTextId!, + delta: content + }) + } + + const annotations = delta.annotations + if (Predicate.isNotNullish(annotations)) { + for (const annotation of annotations) { + if (annotation.type === "url_citation") { + parts.push({ + type: "source", + sourceType: "url", + id: annotation.url_citation.url, + url: annotation.url_citation.url, + title: annotation.url_citation.title ?? "", + metadata: { + openrouter: { + ...(Predicate.isNotUndefined(annotation.url_citation.content) + ? { content: annotation.url_citation.content } + : undefined), + ...(Predicate.isNotUndefined(annotation.url_citation.start_index) + ? { startIndex: annotation.url_citation.start_index } + : undefined), + ...(Predicate.isNotUndefined(annotation.url_citation.end_index) + ? { endIndex: annotation.url_citation.end_index } + : undefined) + } + } + }) + } else if (annotation.type === "file") { + accumulatedFileAnnotations.push(annotation) + } + } + } + + const toolCalls = delta.tool_calls + if (Predicate.isNotNullish(toolCalls)) { + 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 + if (Predicate.isUndefined(activeToolCall)) { + if (toolCall.type !== "function") { + return yield* AiError.make({ + module: "OpenRouterLanguageModel", + method: "makeStreamResponse", + reason: new AiError.InvalidOutputError({ + description: "Received tool call delta that was not of type: 'function'" + }) + }) + } + + if (Predicate.isNullish(toolCall.id)) { + return yield* AiError.make({ + module: "OpenRouterLanguageModel", + method: "makeStreamResponse", + reason: new AiError.InvalidOutputError({ + description: "Received tool call delta without a tool call identifier" + }) + }) + } + + if (Predicate.isNullish(toolCall.function?.name)) { + return yield* AiError.make({ + module: "OpenRouterLanguageModel", + method: "makeStreamResponse", + reason: new AiError.InvalidOutputError({ + description: "Received tool call delta without a tool call name" + }) + }) + } + + activeToolCall = { + id: toolCall.id, + type: "function", + name: toolCall.function.name, + params: argumentsDelta + } + + activeToolCalls[index] = activeToolCall + + parts.push({ + type: "tool-params-start", + id: activeToolCall.id, + name: activeToolCall.name + }) + } else { + 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: argumentsDelta + }) + } + + // Check if the tool call is complete + // @effect-diagnostics-next-line tryCatchInEffectGen:off + try { + const params = Tool.unsafeSecureJsonParse(activeToolCall.params) + + parts.push({ + type: "tool-params-end", + id: activeToolCall.id + }) + + parts.push({ + type: "tool-call", + id: activeToolCall.id, + name: activeToolCall.name, + params, + // Only attach reasoning_details to the first tool call to avoid + // duplicating thinking blocks for parallel tool calls (Claude) + metadata: reasoningDetailsAttachedToToolCall ? undefined : { + openrouter: { reasoningDetails: accumulatedReasoningDetails } + } + }) + + reasoningDetailsAttachedToToolCall = true + + // Increment the total tool calls emitted by the stream and + // remove the active tool call + totalToolCalls += 1 + delete activeToolCalls[toolCall.index] + } catch { + // Tool call incomplete, continue parsing + continue + } + } + } + + const images = delta.images + if (Predicate.isNotNullish(images)) { + for (const image of images) { + parts.push({ + type: "file", + mediaType: getMediaType(image.image_url.url, "image/jpeg"), + data: getBase64FromDataUrl(image.image_url.url) + }) + } + } + } + + // Usage is only emitted by the last part of the stream, so we need to + // handle flushing any remaining text / reasoning / tool calls + if (Predicate.isNotUndefined(event.usage)) { + // Fix for Gemini 3 thoughtSignature: when there are tool calls with encrypted + // reasoning (thoughtSignature), the model returns 'stop' but expects continuation. + // Override to 'tool-calls' so the SDK knows to continue the conversation. + const hasEncryptedReasoning = accumulatedReasoningDetails.some( + (detail) => detail.type === "reasoning.encrypted" && detail.data.length > 0 + ) + if (totalToolCalls > 0 && hasEncryptedReasoning && finishReason === "stop") { + finishReason = resolveFinishReason("tool-calls") + } + + // Forward any unsent tool calls if finish reason is 'tool-calls' + if (finishReason === "tool-calls") { + for (const toolCall of Object.values(activeToolCalls)) { + // Coerce invalid tool call parameters to an empty object + let params: unknown + // @effect-diagnostics-next-line tryCatchInEffectGen:off + try { + params = Tool.unsafeSecureJsonParse(toolCall.params) + } catch { + params = {} + } + + // Only attach reasoning_details to the first tool call to avoid + // duplicating thinking blocks for parallel tool calls (Claude) + parts.push({ + type: "tool-call", + id: toolCall.id, + name: toolCall.name, + params, + metadata: reasoningDetailsAttachedToToolCall ? undefined : { + openrouter: { reasoningDetails: accumulatedReasoningDetails } + } + }) + + reasoningDetailsAttachedToToolCall = true + } + } + + // End reasoning first if it was started, to maintain proper order + if (reasoningStarted) { + parts.push({ + type: "reasoning-end", + id: activeReasoningId!, + // Include accumulated reasoning_details so that we can update the + // reasoning part's provider metadata with the correct signature, + metadata: accumulatedReasoningDetails.length > 0 + ? { openrouter: { reasoningDetails: accumulatedReasoningDetails } } + : undefined + }) + } + + if (textStarted) { + parts.push({ type: "text-end", id: activeTextId! }) + } + + const metadata: Response.FinishPart["metadata"] = { + openrouter: { + ...(Predicate.isNotNullish(event.system_fingerprint) + ? { systemFingerprint: event.system_fingerprint } + : undefined), + ...(Predicate.isNotUndefined(event.usage) ? { usage: event.usage } : undefined), + ...(Predicate.hasProperty(event, "provider") && Predicate.isString(event.provider) + ? { provider: event.provider } + : undefined), + ...(accumulatedFileAnnotations.length > 0 ? { annotations: accumulatedFileAnnotations } : undefined) + } + } + + parts.push({ + type: "finish", + reason: finishReason, + usage, + response: buildHttpResponseDetails(response), + metadata + }) + } + + return parts + })), + Stream.flattenIterable + ) + } +) + +// ============================================================================= +// Tool Conversion +// ============================================================================= + +const prepareTools = Effect.fnUntraced( + function*({ options, transformer }: { + readonly options: LanguageModel.ProviderOptions + readonly transformer: LanguageModel.CodecTransformer + }): Effect.fn.Return<{ + readonly tools: ReadonlyArray | undefined + readonly toolChoice: typeof Generated.ChatToolChoice.Encoded | undefined + }, AiError.AiError> { + if (options.tools.length === 0) { + return { tools: undefined, toolChoice: undefined } + } + + const hasProviderDefinedTools = options.tools.some((tool) => Tool.isProviderDefined(tool)) + if (hasProviderDefinedTools) { + return yield* AiError.make({ + module: "OpenRouterLanguageModel", + method: "prepareTools", + reason: new AiError.InvalidUserInputError({ + description: "Provider-defined tools are unsupported by the OpenRouter " + + "provider integration at this time" + }) + }) + } + + 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* tryToolJsonSchema(tool, "prepareTools", transformer) + const strict = Tool.getStrictMode(tool) ?? null + + tools.push({ + type: "function", + function: { + name: tool.name, + parameters, + strict, + ...(Predicate.isNotUndefined(description) ? { description } : undefined) + } + }) + } + + if (options.toolChoice === "none") { + toolChoice = "none" + } else if (options.toolChoice === "auto") { + toolChoice = "auto" + } else if (options.toolChoice === "required") { + toolChoice = "required" + } else if ("tool" in options.toolChoice) { + toolChoice = { type: "function", function: { name: options.toolChoice.tool } } + } else { + const allowedTools = new Set(options.toolChoice.oneOf) + tools = tools.filter((tool) => allowedTools.has(tool.function.name)) + toolChoice = options.toolChoice.mode === "required" ? "required" : "auto" + } + + return { tools, toolChoice } + } +) + +// ============================================================================= +// Telemetry +// ============================================================================= + +const annotateRequest = ( + span: Span, + request: typeof Generated.ChatRequest.Encoded +): void => { + addGenAIAnnotations(span, { + system: "openrouter", + operation: { name: "chat" }, + request: { + model: request.model, + temperature: request.temperature, + topP: request.top_p, + maxTokens: request.max_tokens, + stopSequences: Arr.ensure(request.stop).filter( + Predicate.isNotNullish + ) + } + }) +} + +const annotateResponse = (span: Span, response: Generated.SendChatCompletionRequest200): void => { + addGenAIAnnotations(span, { + response: { + id: response.id, + model: response.model, + finishReasons: response.choices.map((choice) => choice.finish_reason).filter(Predicate.isNotNullish) + }, + usage: { + inputTokens: response.usage?.prompt_tokens, + outputTokens: response.usage?.completion_tokens + } + }) +} + +const annotateStreamResponse = (span: Span, part: Response.StreamPartEncoded) => { + if (part.type === "response-metadata") { + addGenAIAnnotations(span, { + response: { + id: part.id, + model: part.modelId + } + }) + } + if (part.type === "finish") { + addGenAIAnnotations(span, { + response: { + finishReasons: [part.reason] + }, + usage: { + inputTokens: part.usage.inputTokens.total, + outputTokens: part.usage.outputTokens.total + } + }) + } +} + +// ============================================================================= +// Internal Utilities +// ============================================================================= + +const getCacheControl = ( + part: + | Prompt.SystemMessage + | Prompt.UserMessage + | Prompt.AssistantMessage + | Prompt.ToolMessage + | Prompt.TextPart + | Prompt.ReasoningPart + | Prompt.FilePart + | Prompt.ToolResultPart +): typeof Generated.ChatContentCacheControl.Encoded | null => part.options.openrouter?.cacheControl ?? null + +const findFirstReasoningDetails = (content: ReadonlyArray): ReasoningDetails | null => { + for (const part of content) { + // First try tool calls since they have complete accumulated reasoning details + if (part.type === "tool-call") { + const details = part.options.openrouter?.reasoningDetails + if (Predicate.isNotNullish(details) && Array.isArray(details) && details.length > 0) { + return details as ReasoningDetails + } + } + + // Fallback to reasoning parts which have delta reasoning details + if (part.type === "reasoning") { + const details = part.options.openrouter?.reasoningDetails + if (Predicate.isNotNullish(details) && Array.isArray(details) && details.length > 0) { + return details as ReasoningDetails + } + } + } + + return null +} + +const getCodecTransformer = (model: string): LanguageModel.CodecTransformer => { + if (model.startsWith("anthropic/") || model.startsWith("claude-")) { + return toCodecAnthropic + } + if ( + model.startsWith("openai/") || + model.startsWith("gpt-") || + model.startsWith("o1-") || + model.startsWith("o3-") || + model.startsWith("o4-") + ) { + return toCodecOpenAI + } + return LanguageModel.defaultCodecTransformer +} + +const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError => + AiError.make({ + module: "OpenRouterLanguageModel", + method, + reason: new AiError.UnsupportedSchemaError({ + description: error instanceof Error ? error.message : String(error) + }) + }) + +const tryJsonSchema = ( + schema: S, + method: string, + transformer: LanguageModel.CodecTransformer +) => + Effect.try({ + try: () => Tool.getJsonSchemaFromSchema(schema, { transformer }), + 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 { + if (options.responseFormat.type === "json") { + const description = SchemaAST.resolveDescription(options.responseFormat.schema.ast) + const jsonSchema = yield* tryJsonSchema(options.responseFormat.schema, "getResponseFormat", transformer) + return { + type: "json_schema", + json_schema: { + name: options.responseFormat.objectName, + schema: jsonSchema, + strict: config.strictJsonSchema ?? null, + ...(Predicate.isNotUndefined(description) ? { description } : undefined) + } + } + } + return undefined +}) + +/** + * Maps audio media types to the formats supported by OpenRouter. + * + * @see https://openrouter.ai/docs/guides/overview/multimodal/audio + */ +const audioFormats: Record = { + "audio/aac": "aac", + "audio/aiff": "aiff", + "audio/x-aiff": "aiff", + "audio/flac": "flac", + "audio/x-flac": "flac", + "audio/l16": "pcm16", + "audio/l24": "pcm24", + "audio/m4a": "m4a", + "audio/x-m4a": "m4a", + "audio/mp4": "m4a", + "audio/mp3": "mp3", + "audio/mpeg": "mp3", + "audio/ogg": "ogg", + "audio/wav": "wav", + "audio/wave": "wav", + "audio/x-wav": "wav" +} + +const supportedAudioFormats = Array.from(new Set(Object.values(audioFormats))).join(", ") + +const getMediaType = (dataUrl: string, defaultMediaType: string): string => { + const match = dataUrl.match(/^data:([^;]+)/) + return match ? (match[1] ?? defaultMediaType) : defaultMediaType +} + +const getBase64FromDataUrl = (dataUrl: string): string => { + const match = dataUrl.match(/^data:[^;]*;base64,(.+)$/) + return match ? match[1]! : dataUrl +} + +const getUsage = (usage: Generated.ChatUsage | undefined): Response.Usage => { + if (Predicate.isUndefined(usage)) { + return { + inputTokens: { uncached: undefined, total: 0, cacheRead: undefined, cacheWrite: undefined }, + outputTokens: { total: 0, text: undefined, reasoning: undefined } + } + } + const promptTokens = usage.prompt_tokens + const completionTokens = usage.completion_tokens + const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0 + const cacheWriteTokens = usage.prompt_tokens_details?.cache_write_tokens ?? 0 + const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0 + return { + inputTokens: { + uncached: promptTokens - cacheReadTokens, + total: promptTokens, + cacheRead: cacheReadTokens, + cacheWrite: cacheWriteTokens + }, + outputTokens: { + total: completionTokens, + text: completionTokens - reasoningTokens, + reasoning: reasoningTokens + } + } +} diff --git a/.repos/effect/packages/ai/openrouter/src/index.ts b/.repos/effect/packages/ai/openrouter/src/index.ts new file mode 100644 index 000000000..2ea005eb1 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/index.ts @@ -0,0 +1,30 @@ +/** + * @since 4.0.0 + */ + +// @barrel: Auto-generated exports. Do not edit manually. + +/** + * @since 4.0.0 + */ +export * as Generated from "./Generated.ts" + +/** + * @since 4.0.0 + */ +export * as OpenRouterClient from "./OpenRouterClient.ts" + +/** + * @since 4.0.0 + */ +export * as OpenRouterConfig from "./OpenRouterConfig.ts" + +/** + * @since 4.0.0 + */ +export * as OpenRouterError from "./OpenRouterError.ts" + +/** + * @since 4.0.0 + */ +export * as OpenRouterLanguageModel from "./OpenRouterLanguageModel.ts" diff --git a/.repos/effect/packages/ai/openrouter/src/internal/errors.ts b/.repos/effect/packages/ai/openrouter/src/internal/errors.ts new file mode 100644 index 000000000..27caabb28 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/internal/errors.ts @@ -0,0 +1,349 @@ +import * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import { dual } from "effect/Function" +import * as Number from "effect/Number" +import * as Option from "effect/Option" +import * as Predicate from "effect/Predicate" +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" +import type * as Generated from "../Generated.ts" +import type { OpenRouterErrorMetadata } from "../OpenRouterError.ts" + +// ============================================================================= +// OpenRouter Error Body Schema +// ============================================================================= + +/** @internal */ +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.Finite]))) + }) +}) + +/** @internal */ +export type OpenRouterClientErrorBody = { + readonly error: { + readonly code: string | number | null + readonly message: string + readonly param?: string | null + readonly type?: string | null + } +} + +// ============================================================================= +// Error Mappers +// ============================================================================= + +/** @internal */ +export const mapSchemaError = dual< + (method: string) => (error: Schema.SchemaError) => AiError.AiError, + (error: Schema.SchemaError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenRouterClient", + method, + 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, + (error: Generated.OpenRouterClientError, method: string) => AiError.AiError +>(2, (error, method) => { + const { request, response, cause } = error + const status = response.status + const headers = response.headers as Record + const metadata: OpenRouterErrorMetadata = { + errorCode: cause.error.code ?? null, + errorType: cause.error.type ?? null, + requestId: headers["x-request-id"] ?? null + } + const http = buildHttpContext({ request, response, body: JSON.stringify(cause) }) + const reason = mapStatusCodeToReason({ + status, + headers, + message: cause.error.message, + metadata, + http + }) + return AiError.make({ module: "OpenRouterClient", method, reason }) +}) + +/** @internal */ +export const mapHttpClientError = dual< + (method: string) => (error: HttpClientError.HttpClientError) => Effect.Effect, + (error: HttpClientError.HttpClientError, method: string) => Effect.Effect +>(2, (error, method) => { + const reason = error.reason + switch (reason._tag) { + case "TransportError": { + return Effect.fail(AiError.make({ + module: "OpenRouterClient", + method, + reason: new AiError.NetworkError({ + reason: "TransportError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "EncodeError": { + return Effect.fail(AiError.make({ + module: "OpenRouterClient", + method, + reason: new AiError.NetworkError({ + reason: "EncodeError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "InvalidUrlError": { + return Effect.fail(AiError.make({ + module: "OpenRouterClient", + method, + reason: new AiError.NetworkError({ + reason: "InvalidUrlError", + description: reason.description, + request: buildHttpRequestDetails(reason.request) + }) + })) + } + case "StatusCodeError": { + return mapStatusCodeError(reason, method) + } + case "DecodeError": { + return Effect.fail(AiError.make({ + module: "OpenRouterClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Failed to decode response" + }) + })) + } + case "EmptyBodyError": { + return Effect.fail(AiError.make({ + module: "OpenRouterClient", + method, + reason: new AiError.InvalidOutputError({ + description: reason.description ?? "Response body was empty" + }) + })) + } + } +}) + +/** @internal */ +const mapStatusCodeError = Effect.fnUntraced(function*( + error: HttpClientError.StatusCodeError, + method: string +) { + const { request, response, description } = error + const status = response.status + const headers = response.headers as Record + const requestId = headers["x-request-id"] + + let body: string | undefined = description + if (!description || !description.startsWith("{")) { + const responseBody = yield* Effect.option(response.text) + if (Option.isSome(responseBody) && responseBody.value) { + body = responseBody.value + } + } + + let json: unknown = undefined + // @effect-diagnostics effect/tryCatchInEffectGen:off + try { + json = Predicate.isNotUndefined(body) ? JSON.parse(body) : undefined + } catch { + json = undefined + } + const decoded = Schema.decodeUnknownOption(OpenRouterErrorBody)(json) + + const reason = mapStatusCodeToReason({ + status, + headers, + message: Option.isSome(decoded) ? decoded.value.error.message : undefined, + http: buildHttpContext({ request, response, body }), + metadata: { + errorCode: Option.isSome(decoded) ? decoded.value.error.code ?? null : null, + errorType: Option.isSome(decoded) ? decoded.value.error.type ?? null : null, + requestId: requestId ?? null + } + }) + + return yield* AiError.make({ module: "OpenRouterClient", method, reason }) +}) + +// ============================================================================= +// Rate Limits +// ============================================================================= + +/** @internal */ +export const parseRateLimitHeaders = (headers: Record) => { + const retryAfterRaw = headers["retry-after"] + let retryAfter: Duration.Duration | undefined + if (Predicate.isNotUndefined(retryAfterRaw)) { + const parsed = Number.parse(retryAfterRaw) + if (Option.isSome(parsed)) { + retryAfter = Duration.seconds(parsed.value) + } + } + const remainingRaw = headers["x-ratelimit-remaining-requests"] + const remaining = Predicate.isNotUndefined(remainingRaw) + ? Option.getOrNull(Number.parse(remainingRaw)) + : null + return { + retryAfter, + limit: headers["x-ratelimit-limit-requests"] ?? null, + remaining, + resetRequests: headers["x-ratelimit-reset-requests"] ?? null, + resetTokens: headers["x-ratelimit-reset-tokens"] ?? null + } +} + +// ============================================================================= +// HTTP Context +// ============================================================================= + +/** @internal */ +export const buildHttpRequestDetails = ( + request: HttpClientRequest.HttpClientRequest +): typeof Response.HttpRequestDetails.Type => ({ + method: request.method, + url: request.url, + urlParams: Array.from(request.urlParams), + hash: Option.getOrUndefined(request.hash), + headers: Redactable.redact(request.headers) as Record +}) + +/** @internal */ +export const buildHttpContext = (params: { + readonly request: HttpClientRequest.HttpClientRequest + readonly response?: HttpClientResponse.HttpClientResponse + readonly body?: string | undefined +}): typeof AiError.HttpContext.Type => ({ + request: buildHttpRequestDetails(params.request), + response: Predicate.isNotUndefined(params.response) + ? { + status: params.response.status, + headers: Redactable.redact(params.response.headers) as Record + } + : undefined, + body: params.body +}) + +// ============================================================================= +// HTTP Status Code +// ============================================================================= + +/** @internal */ +export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { + readonly status: number + readonly headers: Record + readonly message: string | undefined + readonly metadata: OpenRouterErrorMetadata + readonly http: typeof AiError.HttpContext.Type +}): AiError.AiErrorReason => { + const errorDescription = AiError.buildErrorDescription({ + status, + message, + method: http.request.method, + url: http.request.url, + errorCode: metadata.errorCode, + errorType: metadata.errorType, + requestId: metadata.requestId, + body: http.body + }) + + switch (status) { + case 400: + return new AiError.InvalidRequestError({ + 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 + }) + case 404: + case 409: + case 422: + return new AiError.InvalidRequestError({ + description: errorDescription, + metadata: { openrouter: metadata }, + http + }) + case 429: { + if ( + metadata.errorCode === "insufficient_quota" || + metadata.errorType === "insufficient_quota" + ) { + return new AiError.QuotaExhaustedError({ + metadata: { openrouter: metadata }, + http + }) + } + const { retryAfter, ...rateLimitMetadata } = parseRateLimitHeaders(headers) + return new AiError.RateLimitError({ + retryAfter, + metadata: { + openrouter: { + ...metadata, + ...rateLimitMetadata + } + }, + http + }) + } + case 529: + return new AiError.InternalProviderError({ + description: message ?? "OpenRouter API is overloaded", + metadata: { openrouter: metadata }, + http + }) + default: + if (status >= 500) { + return new AiError.InternalProviderError({ + description: message ?? "Server error", + metadata: { openrouter: metadata }, + http + }) + } + return new AiError.UnknownError({ + description: message, + metadata: { openrouter: metadata }, + http + }) + } +} diff --git a/.repos/effect/packages/ai/openrouter/src/internal/utilities.ts b/.repos/effect/packages/ai/openrouter/src/internal/utilities.ts new file mode 100644 index 000000000..1d5112e94 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/src/internal/utilities.ts @@ -0,0 +1,94 @@ +import * as Predicate from "effect/Predicate" +import type * as Response from "effect/unstable/ai/Response" +import type { ReasoningDetails } from "../OpenRouterLanguageModel.ts" + +const finishReasonMap: Record = { + content_filter: "content-filter", + error: "error", + function_call: "tool-calls", + length: "length", + tool_calls: "tool-calls", + stop: "stop" +} + +/** @internal */ +export const resolveFinishReason = ( + finishReason: string | null | undefined +): Response.FinishReason => { + if (Predicate.isNullish(finishReason)) { + return "other" + } + return Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : "unknown" +} + +/** + * Tracks ReasoningDetailUnion entries and deduplicates them based + * on a derived canonical key. + * + * This is used when converting messages to ensure the API request only + * contains unique reasoning details, preventing "Duplicate item found with id" + * errors in multi-turn conversations. + * + * The canonical key logic matches the OpenRouter API's deduplication exactly + * (see openrouter-web/packages/llm-interfaces/reasonings/duplicate-tracker.ts): + * - Summary: key = summary field + * - Encrypted: key = id field (if truthy) or data field + * - Text: key = text field (if truthy) or signature field (if truthy) + * + * @internal + */ +export class ReasoningDetailsDuplicateTracker { + readonly #seenKeys = new Set() + + /** + * Attempts to track a detail. + * + * or `false` if it was skipped (no valid key) or already seen (duplicate). + */ + upsert(detail: ReasoningDetails[number]): boolean { + const key = this.getCanonicalKey(detail) + + if (Predicate.isNull(key)) { + return false + } + + if (this.#seenKeys.has(key)) { + return false + } + + this.#seenKeys.add(key) + + return true + } + + private getCanonicalKey(detail: ReasoningDetails[number]): string | null { + // This logic matches the OpenRouter API's deduplication exactly. + // See: openrouter-web/packages/llm-interfaces/reasonings/duplicate-tracker.ts + switch (detail.type) { + case "reasoning.summary": { + return detail.summary + } + + case "reasoning.encrypted": { + return Predicate.isNotNullish(detail.id) ? detail.id : detail.data + } + + case "reasoning.text": { + if (Predicate.isNotNullish(detail.text)) { + return detail.text + } + + if (Predicate.isNotNullish(detail.signature)) { + return detail.signature + } + + return null + } + + default: { + // Handle unknown types gracefully + return null + } + } + } +} diff --git a/.repos/effect/packages/ai/openrouter/test/Generated.test.ts b/.repos/effect/packages/ai/openrouter/test/Generated.test.ts new file mode 100644 index 000000000..8790fed73 --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/test/Generated.test.ts @@ -0,0 +1,75 @@ +import { Generated } from "@effect/ai-openrouter" +import { describe, it } from "@effect/vitest" +import { deepStrictEqual } from "@effect/vitest/utils" +import { Schema } from "effect" + +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) + }) +}) diff --git a/.repos/effect/packages/ai/openrouter/test/OpenRouterClient.test.ts b/.repos/effect/packages/ai/openrouter/test/OpenRouterClient.test.ts new file mode 100644 index 000000000..3e5eac5a4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts b/.repos/effect/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts new file mode 100644 index 000000000..ab6738fdd --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts @@ -0,0 +1,458 @@ +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, 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("generateText", () => { + describe("message preparation", () => { + describe("audio file parts", () => { + it.effect("converts audio bytes to input_audio", () => + Effect.gen(function*() { + const audioData = new Uint8Array([0x49, 0x44, 0x33, 0x04]) // ID3v2 magic bytes + + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "audio/mpeg", + data: audioData + }) + ] + }]) + }).pipe(Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.messages.find((message: any) => message.role === "user") + deepStrictEqual(userMessage.content, [{ + type: "input_audio", + input_audio: { + data: "SUQzBA==", + format: "mp3" + } + }]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("converts base64 data url audio to input_audio", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "audio/wav", + data: "data:audio/wav;base64,UklGRg==" + }) + ] + }]) + }).pipe(Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.messages.find((message: any) => message.role === "user") + deepStrictEqual(userMessage.content, [{ + type: "input_audio", + input_audio: { + data: "UklGRg==", + format: "wav" + } + }]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("maps audio media types to OpenRouter audio formats", () => + Effect.gen(function*() { + const mediaTypes: ReadonlyArray = [ + ["audio/aac", "aac"], + ["audio/x-aiff", "aiff"], + ["audio/flac", "flac"], + ["audio/L16", "pcm16"], + ["audio/mp4", "m4a"], + ["audio/mp3", "mp3"], + ["audio/ogg", "ogg"], + ["audio/x-wav", "wav"] + ] + + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: mediaTypes.map(([mediaType]) => + Prompt.filePart({ + mediaType, + data: new Uint8Array([0x00]) + }) + ) + }]) + }).pipe(Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.messages.find((message: any) => message.role === "user") + deepStrictEqual( + userMessage.content.map((item: any) => item.input_audio.format), + mediaTypes.map(([, format]) => format) + ) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("fails on unsupported audio media types", () => + Effect.gen(function*() { + const error = yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "audio/webm", + data: new Uint8Array([0x00]) + }) + ] + }]) + }).pipe( + Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash")), + Effect.flip + ) + + strictEqual(error.reason._tag, "InvalidUserInputError") + assert.include(error.message, "audio/webm") + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("fails on audio URLs", () => + Effect.gen(function*() { + const error = yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "audio/mpeg", + data: new URL("https://example.com/audio.mp3") + }) + ] + }]) + }).pipe( + Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash")), + Effect.flip + ) + + strictEqual(error.reason._tag, "InvalidUserInputError") + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("converts non-audio files to file blocks", () => + Effect.gen(function*() { + const pdfData = new Uint8Array([0x25, 0x50, 0x44, 0x46]) // %PDF + + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [ + Prompt.filePart({ + mediaType: "application/pdf", + fileName: "document.pdf", + data: pdfData + }) + ] + }]) + }).pipe(Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const userMessage = body.messages.find((message: any) => message.role === "user") + deepStrictEqual(userMessage.content, [{ + type: "file", + file: { + filename: "document.pdf", + file_data: "data:application/pdf;base64,JVBERg==" + } + }]) + }).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}"] + ) + })) + }) +}) + +// ============================================================================= +// Test Infrastructure +// ============================================================================= + +class MockOpenRouterResponse extends Context.Service | undefined +}>()("MockOpenRouterResponse") {} + +class MockHttpClient extends Context.Service> +}>()("MockHttpClient") { + static requests = Effect.service(MockHttpClient).pipe( + Effect.flatMap((client) => client.requests) + ) +} + +const encodeResponse = Schema.encodeEffect(Generated.SendChatCompletionRequest200) + +const makeHttpClient = Effect.gen(function*() { + const capturedRequests = yield* Ref.make>([]) + const response = yield* MockOpenRouterResponse + const body = yield* Effect.orDie(encodeResponse(response.body)) + + 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(JSON.stringify(body), { + headers: response.headers ?? {}, + status: response.status + }) + ) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, MockHttpClient.of({ requests: Ref.get(capturedRequests) })) + ) +}) + +const HttpClientLayer = Layer.effectContext(makeHttpClient) + +const makeDefaultResponse = ( + overrides: Partial = {} +): typeof Generated.SendChatCompletionRequest200.Type => ({ + id: "gen-test123", + choices: [{ + finish_reason: "stop", + index: 0, + message: { + role: "assistant", + content: "Hello!" + } + }], + created: 1234567890, + model: "google/gemini-2.5-flash", + object: "chat.completion", + system_fingerprint: null, + ...overrides +}) + +const makeTestLayer = (options: { + readonly body?: Partial + readonly status?: number + readonly headers?: Record +} = {}) => + OpenRouterClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenRouterResponse, { + body: makeDefaultResponse(options.body), + status: options.status ?? 200, + headers: options.headers ?? {} + })) + ) + +const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => + Effect.gen(function*() { + const body = request.body + if (body._tag === "Uint8Array") { + const text = new TextDecoder().decode(body.body) + return JSON.parse(text) + } + 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 httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + return HttpClientResponse.fromWeb( + request, + new Response(body, { + status: 200, + headers: { "content-type": "text/event-stream" } + }) + ) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + return OpenRouterClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed(HttpClient.HttpClient, httpClient)) + ) +} diff --git a/.repos/effect/packages/ai/openrouter/tsconfig.json b/.repos/effect/packages/ai/openrouter/tsconfig.json new file mode 100644 index 000000000..1cf21756b --- /dev/null +++ b/.repos/effect/packages/ai/openrouter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../../effect" }, + { "path": "../anthropic" }, + { "path": "../openai" } + ] +} diff --git a/.repos/effect/packages/atom/react/CHANGELOG.md b/.repos/effect/packages/atom/react/CHANGELOG.md new file mode 100644 index 000000000..ba66b0a15 --- /dev/null +++ b/.repos/effect/packages/atom/react/CHANGELOG.md @@ -0,0 +1,808 @@ +# @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 + +- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]: + - effect@4.0.0-beta.90 + +## 4.0.0-beta.89 + +### Patch Changes + +- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]: + - effect@4.0.0-beta.89 + +## 4.0.0-beta.88 + +### Patch Changes + +- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]: + - effect@4.0.0-beta.88 + +## 4.0.0-beta.87 + +### Patch Changes + +- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]: + - effect@4.0.0-beta.87 + +## 4.0.0-beta.86 + +### Patch Changes + +- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]: + - effect@4.0.0-beta.86 + +## 4.0.0-beta.85 + +### Patch Changes + +- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]: + - effect@4.0.0-beta.85 + +## 4.0.0-beta.84 + +### Patch Changes + +- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]: + - effect@4.0.0-beta.84 + +## 4.0.0-beta.83 + +### Patch Changes + +- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]: + - effect@4.0.0-beta.83 + +## 4.0.0-beta.82 + +### Patch Changes + +- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]: + - effect@4.0.0-beta.82 + +## 4.0.0-beta.81 + +### Patch Changes + +- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]: + - effect@4.0.0-beta.81 + +## 4.0.0-beta.80 + +### Patch Changes + +- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]: + - effect@4.0.0-beta.80 + +## 4.0.0-beta.79 + +### Patch Changes + +- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]: + - effect@4.0.0-beta.79 + +## 4.0.0-beta.78 + +### Patch Changes + +- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]: + - effect@4.0.0-beta.78 + +## 4.0.0-beta.77 + +### Patch Changes + +- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]: + - effect@4.0.0-beta.77 + +## 4.0.0-beta.76 + +### Patch Changes + +- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]: + - effect@4.0.0-beta.76 + +## 4.0.0-beta.75 + +### Patch Changes + +- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]: + - effect@4.0.0-beta.75 + +## 4.0.0-beta.74 + +### Patch Changes + +- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]: + - effect@4.0.0-beta.74 + +## 4.0.0-beta.73 + +### Patch Changes + +- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]: + - effect@4.0.0-beta.73 + +## 4.0.0-beta.72 + +### Patch Changes + +- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]: + - effect@4.0.0-beta.72 + +## 4.0.0-beta.71 + +### Patch Changes + +- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]: + - effect@4.0.0-beta.71 + +## 4.0.0-beta.70 + +### Patch Changes + +- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]: + - effect@4.0.0-beta.70 + +## 4.0.0-beta.69 + +### Patch Changes + +- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]: + - effect@4.0.0-beta.69 + +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + +## 4.0.0-beta.67 + +### Patch Changes + +- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]: + - effect@4.0.0-beta.67 + +## 4.0.0-beta.66 + +### Patch Changes + +- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]: + - effect@4.0.0-beta.66 + +## 4.0.0-beta.65 + +### Patch Changes + +- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]: + - effect@4.0.0-beta.65 + +## 4.0.0-beta.64 + +### Patch Changes + +- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]: + - effect@4.0.0-beta.64 + +## 4.0.0-beta.63 + +### Patch Changes + +- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]: + - effect@4.0.0-beta.63 + +## 4.0.0-beta.62 + +### Patch Changes + +- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]: + - effect@4.0.0-beta.62 + +## 4.0.0-beta.61 + +### Patch Changes + +- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]: + - effect@4.0.0-beta.61 + +## 4.0.0-beta.60 + +### Patch Changes + +- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]: + - effect@4.0.0-beta.60 + +## 4.0.0-beta.59 + +### Patch Changes + +- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]: + - effect@4.0.0-beta.59 + +## 4.0.0-beta.58 + +### Patch Changes + +- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]: + - effect@4.0.0-beta.58 + +## 4.0.0-beta.57 + +### Patch Changes + +- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]: + - effect@4.0.0-beta.57 + +## 4.0.0-beta.56 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]: + - effect@4.0.0-beta.55 + +## 4.0.0-beta.54 + +### Patch Changes + +- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]: + - effect@4.0.0-beta.54 + +## 4.0.0-beta.53 + +### Patch Changes + +- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]: + - effect@4.0.0-beta.53 + +## 4.0.0-beta.52 + +### Patch Changes + +- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]: + - effect@4.0.0-beta.52 + +## 4.0.0-beta.51 + +### Patch Changes + +- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]: + - effect@4.0.0-beta.51 + +## 4.0.0-beta.50 + +### Patch Changes + +- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]: + - effect@4.0.0-beta.50 + +## 4.0.0-beta.49 + +### Patch Changes + +- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]: + - effect@4.0.0-beta.49 + +## 4.0.0-beta.48 + +### Patch Changes + +- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]: + - effect@4.0.0-beta.48 + +## 4.0.0-beta.47 + +### Patch Changes + +- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]: + - effect@4.0.0-beta.47 + +## 4.0.0-beta.46 + +### Patch Changes + +- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]: + - effect@4.0.0-beta.46 + +## 4.0.0-beta.45 + +### Patch Changes + +- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]: + - effect@4.0.0-beta.45 + +## 4.0.0-beta.44 + +### Patch Changes + +- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename the `ServiceMap` module to `Context` across exports, docs, and tests. + +- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]: + - effect@4.0.0-beta.44 + +## 4.0.0-beta.43 + +### Patch Changes + +- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]: + - effect@4.0.0-beta.43 + +## 4.0.0-beta.42 + +### Patch Changes + +- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]: + - effect@4.0.0-beta.42 + +## 4.0.0-beta.41 + +### Patch Changes + +- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]: + - effect@4.0.0-beta.41 + +## 4.0.0-beta.40 + +### Patch Changes + +- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]: + - effect@4.0.0-beta.40 + +## 4.0.0-beta.39 + +### Patch Changes + +- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]: + - effect@4.0.0-beta.39 + +## 4.0.0-beta.38 + +### Patch Changes + +- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]: + - effect@4.0.0-beta.38 + +## 4.0.0-beta.37 + +### Patch Changes + +- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]: + - effect@4.0.0-beta.37 + +## 4.0.0-beta.36 + +### Patch Changes + +- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]: + - effect@4.0.0-beta.36 + +## 4.0.0-beta.35 + +### Patch Changes + +- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]: + - effect@4.0.0-beta.35 + +## 4.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]: + - effect@4.0.0-beta.34 + +## 4.0.0-beta.33 + +### Patch Changes + +- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]: + - effect@4.0.0-beta.33 + +## 4.0.0-beta.32 + +### Patch Changes + +- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]: + - effect@4.0.0-beta.32 + +## 4.0.0-beta.31 + +### Patch Changes + +- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]: + - effect@4.0.0-beta.31 + +## 4.0.0-beta.30 + +### Patch Changes + +- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]: + - effect@4.0.0-beta.30 + +## 4.0.0-beta.29 + +### Patch Changes + +- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]: + - effect@4.0.0-beta.29 + +## 4.0.0-beta.28 + +### Patch Changes + +- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]: + - effect@4.0.0-beta.28 + +## 4.0.0-beta.27 + +### Patch Changes + +- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]: + - effect@4.0.0-beta.27 + +## 4.0.0-beta.26 + +### Patch Changes + +- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]: + - effect@4.0.0-beta.26 + +## 4.0.0-beta.25 + +### Patch Changes + +- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]: + - effect@4.0.0-beta.25 + +## 4.0.0-beta.24 + +### Patch Changes + +- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]: + - effect@4.0.0-beta.24 + +## 4.0.0-beta.23 + +### Patch Changes + +- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]: + - effect@4.0.0-beta.23 + +## 4.0.0-beta.22 + +### Patch Changes + +- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]: + - effect@4.0.0-beta.22 + +## 4.0.0-beta.21 + +### Patch Changes + +- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]: + - effect@4.0.0-beta.21 + +## 4.0.0-beta.20 + +### Patch Changes + +- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]: + - effect@4.0.0-beta.20 + +## 4.0.0-beta.19 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Patch Changes + +- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]: + - effect@4.0.0-beta.18 + +## 4.0.0-beta.17 + +### Patch Changes + +- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]: + - effect@4.0.0-beta.17 + +## 4.0.0-beta.16 + +### Patch Changes + +- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]: + - effect@4.0.0-beta.16 + +## 4.0.0-beta.15 + +### Patch Changes + +- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]: + - effect@4.0.0-beta.15 + +## 4.0.0-beta.14 + +### Patch Changes + +- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]: + - effect@4.0.0-beta.14 + +## 4.0.0-beta.13 + +### Patch Changes + +- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]: + - effect@4.0.0-beta.13 + +## 4.0.0-beta.12 + +### Patch Changes + +- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]: + - effect@4.0.0-beta.12 + +## 4.0.0-beta.11 + +### Patch Changes + +- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]: + - effect@4.0.0-beta.11 + +## 4.0.0-beta.10 + +### Patch Changes + +- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]: + - effect@4.0.0-beta.10 + +## 4.0.0-beta.9 + +### Patch Changes + +- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]: + - effect@4.0.0-beta.9 + +## 4.0.0-beta.8 + +### Patch Changes + +- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]: + - effect@4.0.0-beta.8 + +## 4.0.0-beta.7 + +### Patch Changes + +- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]: + - effect@4.0.0-beta.7 + +## 4.0.0-beta.6 + +### Patch Changes + +- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]: + - effect@4.0.0-beta.6 + +## 4.0.0-beta.5 + +### Patch Changes + +- [#1314](https://github.com/Effect-TS/effect-smol/pull/1314) [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8) Thanks @zeyuri! - Port ReactHydration to effect-smol. + + Add `Hydration` module to `effect/unstable/reactivity` with `dehydrate`, `hydrate`, and `toValues` for SSR state serialization. Add `HydrationBoundary` React component to `@effect/atom-react` with two-phase hydration (new atoms in render, existing atoms after commit). + +- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]: + - effect@4.0.0-beta.5 + +## 4.0.0-beta.4 + +### Patch Changes + +- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]: + - effect@4.0.0-beta.4 + +## 4.0.0-beta.3 + +### Patch Changes + +- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]: + - effect@4.0.0-beta.3 + +## 4.0.0-beta.2 + +### Patch Changes + +- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]: + - effect@4.0.0-beta.2 + +## 4.0.0-beta.1 + +### Patch Changes + +- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]: + - effect@4.0.0-beta.1 + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta + +### Patch Changes + +- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]: + - effect@4.0.0-beta.0 diff --git a/.repos/effect/packages/atom/react/LICENSE b/.repos/effect/packages/atom/react/LICENSE new file mode 100644 index 000000000..7f6fe480f --- /dev/null +++ b/.repos/effect/packages/atom/react/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-present The Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.repos/effect/packages/atom/react/README.md b/.repos/effect/packages/atom/react/README.md new file mode 100644 index 000000000..f3b2b6c49 --- /dev/null +++ b/.repos/effect/packages/atom/react/README.md @@ -0,0 +1,14 @@ +# @effect/atom-react + +[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 + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/atom-react) diff --git a/.repos/effect/packages/atom/react/package.json b/.repos/effect/packages/atom/react/package.json new file mode 100644 index 000000000..f838a7d6f --- /dev/null +++ b/.repos/effect/packages/atom/react/package.json @@ -0,0 +1,81 @@ +{ + "name": "@effect/atom-react", + "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.git", + "directory": "packages/atom/react" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "react", + "database" + ], + "keywords": [ + "typescript", + "react", + "database" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./internal/*": null, + "./index": null, + "./*/index": null + } + }, + "scripts": { + "build": "tsc -b tsconfig.json && pnpm babel", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "peerDependencies": { + "effect": "workspace:^", + "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": "^7.0.1", + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@types/scheduler": "^0.26.0", + "effect": "workspace:^", + "jsdom": "^30.0.1", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-error-boundary": "^6.1.3", + "scheduler": "^0.27.0" + } +} diff --git a/.repos/effect/packages/atom/react/src/Hooks.ts b/.repos/effect/packages/atom/react/src/Hooks.ts new file mode 100644 index 000000000..4632ea333 --- /dev/null +++ b/.repos/effect/packages/atom/react/src/Hooks.ts @@ -0,0 +1,487 @@ +/** + * React hooks for working with Effect atoms from components. The hooks read, + * write, mount, refresh, and subscribe to atoms from `RegistryContext`, handle + * `AsyncResult` atoms with React Suspense, and expose helpers for reading and + * deriving `AtomRef` values. + * + * @since 4.0.0 + */ +"use client" + +import * as Cause from "effect/Cause" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult" +import * as Atom from "effect/unstable/reactivity/Atom" +import type * as AtomRef from "effect/unstable/reactivity/AtomRef" +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" +import * as React from "react" +import { RegistryContext } from "./RegistryContext.ts" + +interface AtomStore { + readonly subscribe: (f: () => void) => () => void + readonly snapshot: () => A + readonly getServerSnapshot: () => A +} + +const storeRegistry = new WeakMap, AtomStore>>() + +function makeStore(registry: AtomRegistry.AtomRegistry, atom: Atom.Atom): AtomStore { + let stores = storeRegistry.get(registry) + if (stores === undefined) { + stores = new WeakMap() + storeRegistry.set(registry, stores) + } + const store = stores.get(atom) + if (store !== undefined) { + return store + } + const newStore: AtomStore = { + subscribe(f) { + return registry.subscribe(atom, f) + }, + snapshot() { + return registry.get(atom) + }, + getServerSnapshot() { + return Atom.getServerValue(atom, registry) + } + } + stores.set(atom, newStore) + return newStore +} + +function useStore(registry: AtomRegistry.AtomRegistry, atom: Atom.Atom): A { + const store = makeStore(registry, atom) + + return React.useSyncExternalStore(store.subscribe, store.snapshot, store.getServerSnapshot) +} + +const initialValuesSet = new WeakMap>>() + +/** + * Seeds initial atom values in the current React atom registry. + * + * **When to use** + * + * Use to seed atom values from a React component after the current registry + * already exists. + * + * **Gotchas** + * + * Each atom is initialized at most once for a given registry by this hook, so + * later calls for the same atom in that registry are ignored. + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomInitialValues = (initialValues: Iterable, any]>): void => { + const registry = React.useContext(RegistryContext) + let set = initialValuesSet.get(registry) + if (set === undefined) { + set = new WeakSet() + initialValuesSet.set(registry, set) + } + for (const [atom, value] of initialValues) { + if (!set.has(atom)) { + set.add(atom) + ;(registry as any).ensureNode(atom).setValue(value) + } + } +} + +/** + * Subscribes to an atom in the current React registry and returns its current + * value, optionally mapped through a selector. + * + * **When to use** + * + * Use when a React component needs to render from an atom value without also + * returning a setter. + * + * **Details** + * + * When a selector is provided, the hook maps the atom before subscribing so the + * component reads the selected value from the current `RegistryContext`. + * + * @see {@link useAtom} for reading and updating a writable atom from one component + * @see {@link useAtomRef} for reading an `AtomRef` directly + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomValue: { + (atom: Atom.Atom): A + (atom: Atom.Atom, f: (_: A) => B): B +} = (atom: Atom.Atom, f?: (_: A) => A): A => { + const registry = React.useContext(RegistryContext) + if (f) { + const atomB = React.useMemo(() => Atom.map(atom, f), [atom, f]) + return useStore(registry, atomB) + } + return useStore(registry, atom) +} + +function mountAtom(registry: AtomRegistry.AtomRegistry, atom: Atom.Atom): void { + React.useEffect(() => registry.mount(atom), [atom, registry]) +} + +function setAtom( + registry: AtomRegistry.AtomRegistry, + atom: Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): "promise" extends Mode ? ( + (value: W) => Promise> + ) : + "promiseExit" extends Mode ? ( + (value: W) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) +{ + if (options?.mode === "promise" || options?.mode === "promiseExit") { + return React.useCallback((value: W) => { + registry.set(atom, value) + const promise = Effect.runPromiseExit( + AtomRegistry.getResult(registry, atom as Atom.Atom>, { + suspendOnWaiting: true + }) + ) + return options!.mode === "promise" ? promise.then(flattenExit) : promise + }, [registry, atom, options.mode]) as any + } + return React.useCallback((value: W | ((value: R) => W)) => { + registry.set(atom, typeof value === "function" ? (value as any)(registry.get(atom)) : value) + }, [registry, atom]) as any +} + +const flattenExit = (exit: Exit.Exit): A => { + if (Exit.isSuccess(exit)) return exit.value + throw Cause.squash(exit.cause) +} + +/** + * Mounts an atom in the current React registry for the lifetime of the + * component. + * + * **When to use** + * + * Use to keep an atom mounted from a React component without reading, writing, + * or refreshing it. + * + * **Details** + * + * The hook uses the current `RegistryContext` and releases the mount through + * React effect cleanup when the component unmounts or when the registry or atom + * dependency changes. + * + * @see {@link useAtomSet} for mounting a writable atom while returning a setter + * @see {@link useAtomRefresh} for mounting an atom while returning a refresh callback + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomMount = (atom: Atom.Atom): void => { + const registry = React.useContext(RegistryContext) + mountAtom(registry, atom) +} + +/** + * Mounts a writable atom and returns a setter without subscribing to its value. + * + * **When to use** + * + * Use when a React component needs to update a writable atom without rendering + * from that atom's value. + * + * **Details** + * + * The hook mounts the atom and returns a setter. In value mode the setter + * accepts a write value or updater function; for `AsyncResult` atoms, `promise` + * and `promiseExit` modes return a promise for the success value or full `Exit`. + * + * @see {@link useAtom} for reading and updating the same writable atom + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomSet = < + R, + W, + Mode extends "value" | "promise" | "promiseExit" = never +>( + atom: Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): "promise" extends Mode ? ( + (value: W) => Promise> + ) : + "promiseExit" extends Mode ? ( + (value: W) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) => +{ + const registry = React.useContext(RegistryContext) + mountAtom(registry, atom) + return setAtom(registry, atom, options) +} + +/** + * Mounts an atom and returns a callback that refreshes it in the current React + * registry. + * + * **When to use** + * + * Use to expose a React callback that requests a refresh for an atom without + * reading or writing its value. + * + * **Details** + * + * The hook uses the current `RegistryContext`, mounts the atom for the + * component lifetime, and returns a callback that calls `registry.refresh`. + * + * @see {@link useAtomMount} for mounting an atom without returning a refresh callback + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRefresh = (atom: Atom.Atom): () => void => { + const registry = React.useContext(RegistryContext) + mountAtom(registry, atom) + return React.useCallback(() => { + registry.refresh(atom) + }, [registry, atom]) +} + +/** + * Subscribes to a writable atom and returns its current value together with a + * setter for updating it. + * + * **When to use** + * + * Use when a React component needs both to render the current value of a + * writable atom and update it from the same component. + * + * @see {@link useAtomValue} for subscribing to an atom without a setter + * @see {@link useAtomSet} for updating a writable atom without subscribing to its value + * + * @category hooks + * @since 4.0.0 + */ +export const useAtom = ( + atom: Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): readonly [ + value: R, + write: "promise" extends Mode ? ( + (value: W) => Promise> + ) : + "promiseExit" extends Mode ? ( + (value: W) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) +] => { + const registry = React.useContext(RegistryContext) + return [ + useStore(registry, atom), + setAtom(registry, atom, options) + ] as const +} + +const atomPromiseMap = { + suspendOnWaiting: new WeakMap< + AtomRegistry.AtomRegistry, + WeakMap, Promise> + >(), + default: new WeakMap< + AtomRegistry.AtomRegistry, + WeakMap, Promise> + >() +} + +function atomToPromise( + registry: AtomRegistry.AtomRegistry, + atom: Atom.Atom>, + suspendOnWaiting: boolean +) { + 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 + } + promise = new Promise((resolve) => { + const dispose = registry.subscribe(atom, (result) => { + if (result._tag === "Initial" || (suspendOnWaiting && result.waiting)) { + return + } + setTimeout(dispose, 1000) + resolve() + map.delete(atom) + }) + }) + map.set(atom, promise) + return promise +} + +function atomResultOrSuspend( + registry: AtomRegistry.AtomRegistry, + atom: Atom.Atom>, + suspendOnWaiting: boolean +) { + const value = useStore(registry, atom) + if (value._tag === "Initial" || (suspendOnWaiting && value.waiting)) { + throw atomToPromise(registry, atom, suspendOnWaiting) + } + return value +} + +/** + * Reads an `AsyncResult` atom through React Suspense, suspending while the + * result is initial or configured as waiting. + * + * **When to use** + * + * Use when a React component should render only after an `AsyncResult` atom has + * left its initial state, with loading delegated to a Suspense boundary. + * + * **Details** + * + * `suspendOnWaiting` defaults to `false`. When `includeFailure` is `true`, a + * failure result is returned instead of being thrown. + * + * **Gotchas** + * + * Without `includeFailure`, failure results are thrown with + * `Cause.squash(result.cause)`, so callers need an error boundary for failures. + * + * @see {@link useAtomValue} for reading the raw `AsyncResult` value without Suspense + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomSuspense = ( + atom: Atom.Atom>, + options?: { + readonly suspendOnWaiting?: boolean | undefined + readonly includeFailure?: IncludeFailure | undefined + } +): AsyncResult.Success | (IncludeFailure extends true ? AsyncResult.Failure : never) => { + const registry = React.useContext(RegistryContext) + const result = atomResultOrSuspend(registry, atom, options?.suspendOnWaiting ?? false) + if (result._tag === "Failure" && !options?.includeFailure) { + throw Cause.squash(result.cause) + } + return result as any +} + +/** + * Subscribes a callback to an atom in the current React registry for the + * component lifetime. + * + * **When to use** + * + * Use when a React component needs to run a callback for atom changes without + * reading the atom value during render. + * + * **Details** + * + * The subscription is installed in a React effect and cleaned up on unmount or + * dependency change. When `options.immediate` is enabled, the callback receives + * the current value when the effect subscribes. + * + * @see {@link useAtomValue} for reading an atom value during render instead of running a callback + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomSubscribe = ( + atom: Atom.Atom, + f: (_: A) => void, + options?: { readonly immediate?: boolean } +): void => { + const registry = React.useContext(RegistryContext) + React.useEffect( + () => registry.subscribe(atom, f, options), + [registry, atom, f, options?.immediate] + ) +} + +/** + * Subscribes to an atom ref and returns its latest value. + * + * **When to use** + * + * Use when a React component should render from an `AtomRef.ReadonlyRef` + * directly instead of reading an atom through the current registry. + * + * **Details** + * + * The hook subscribes with `ref.subscribe`, triggers re-renders through React + * state, and returns the current `ref.value`. + * + * @see {@link useAtomValue} for reading an `Atom` from the current registry + * @see {@link useAtomRefPropValue} for reading a property ref value + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRef = (ref: AtomRef.ReadonlyRef): A => { + const [, setValue] = React.useState(ref.value) + React.useEffect(() => ref.subscribe(setValue), [ref]) + return ref.value +} + +/** + * Returns a memoized atom ref for a property of another atom ref. + * + * **When to use** + * + * Use to derive an `AtomRef` for one property of an object-shaped atom ref. + * + * **Details** + * + * The hook memoizes `ref.prop(prop)` for the `[ref, prop]` dependency pair and + * returns the property ref so callers can read, set, update, or subscribe to + * that nested property. + * + * @see {@link useAtomRef} for subscribing to an atom ref value + * @see {@link useAtomRefPropValue} for subscribing directly to a property value + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRefProp = (ref: AtomRef.AtomRef, prop: K): AtomRef.AtomRef => + React.useMemo(() => ref.prop(prop), [ref, prop]) + +/** + * Subscribes to a property ref derived from an atom ref and returns its current + * value. + * + * **When to use** + * + * Use when a React component needs only the current value of one property from + * an object-shaped `AtomRef`. + * + * **Details** + * + * The hook composes `useAtomRefProp(ref, prop)` with `useAtomRef`, so the + * property ref is memoized for the `[ref, prop]` pair and then subscribed + * through `ref.subscribe`. + * + * @see {@link useAtomRefProp} for returning the property ref directly + * @see {@link useAtomRef} for subscribing to a whole atom ref value + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRefPropValue = (ref: AtomRef.AtomRef, prop: K): A[K] => + useAtomRef(useAtomRefProp(ref, prop)) diff --git a/.repos/effect/packages/atom/react/src/ReactHydration.ts b/.repos/effect/packages/atom/react/src/ReactHydration.ts new file mode 100644 index 000000000..e160d6796 --- /dev/null +++ b/.repos/effect/packages/atom/react/src/ReactHydration.ts @@ -0,0 +1,109 @@ +/** + * React helpers for applying dehydrated Effect Atom state to a React subtree. + * The `HydrationBoundary` component reads the nearest `RegistryContext`, + * hydrates new Atom values before children render, and delays updates for + * existing Atom values until after commit so React transitions do not update + * the current UI too early. + * + * @since 4.0.0 + */ +"use client" +import * as Hydration from "effect/unstable/reactivity/Hydration" +import * as React from "react" +import { RegistryContext } from "./RegistryContext.ts" + +/** + * Props for a boundary that applies dehydrated Atom values to the nearest + * {@link RegistryContext} while rendering its children. + * + * @category components + * @since 4.0.0 + */ +export interface HydrationBoundaryProps { + state?: Iterable + children?: React.ReactNode +} + +/** + * Provides a React hydration boundary that loads dehydrated Atom values into + * the current Atom registry. + * + * **When to use** + * + * Use to apply dehydrated Atom state to a React subtree that reads from the + * nearest `RegistryContext`. + * + * **Details** + * + * New Atom values are hydrated during render so descendants can read them + * immediately, while values for existing Atoms are deferred until after commit + * so transition data does not update the current UI before React accepts it. + * + * @see {@link Hydration.dehydrate} for producing dehydrated Atom state + * @see {@link Hydration.hydrate} for lower-level non-React hydration + * + * @category components + * @since 4.0.0 + */ +export const HydrationBoundary: React.FC = ({ + children, + state +}) => { + const registry = React.useContext(RegistryContext) + + // This useMemo is for performance reasons only, everything inside it must + // be safe to run in every render and code here should be read as "in render". + // + // This code needs to happen during the render phase, because after initial + // SSR, hydration needs to happen _before_ children render. Also, if hydrating + // during a transition, we want to hydrate as much as is safe in render so + // we can prerender as much as possible. + // + // For any Atom values that already exist in the registry, we want to hold back on + // hydrating until _after_ the render phase. The reason for this is that during + // transitions, we don't want the existing Atom values and subscribers to update to + // the new data on the current page, only _after_ the transition is committed. + // If the transition is aborted, we will have hydrated any _new_ Atom values, but + // we throw away the fresh data for any existing ones to avoid unexpectedly + // updating the UI. + const hydrationQueue: Array | undefined = React.useMemo(() => { + if (state) { + const dehydratedAtoms = Array.from(state) as Array + const nodes = registry.getNodes() + + const newDehydratedAtoms: Array = [] + const existingDehydratedAtoms: Array = [] + + for (const dehydratedAtom of dehydratedAtoms) { + const existingNode = nodes.get(dehydratedAtom.key) + + if (!existingNode) { + // This is a new Atom value, safe to hydrate immediately + newDehydratedAtoms.push(dehydratedAtom) + } else { + // This Atom value already exists, queue it for later hydration + existingDehydratedAtoms.push(dehydratedAtom) + } + } + + if (newDehydratedAtoms.length > 0) { + // It's actually fine to call this with state that already exists + // in the registry, or is older. hydrate() is idempotent. + Hydration.hydrate(registry, newDehydratedAtoms) + } + + if (existingDehydratedAtoms.length > 0) { + return existingDehydratedAtoms + } + } + return undefined + }, [registry, state]) + + React.useEffect(() => { + if (hydrationQueue) { + Hydration.hydrate(registry, hydrationQueue) + } + }, [registry, hydrationQueue]) + + return React.createElement(React.Fragment, {}, children) +} diff --git a/.repos/effect/packages/atom/react/src/RegistryContext.ts b/.repos/effect/packages/atom/react/src/RegistryContext.ts new file mode 100644 index 000000000..0a179bbea --- /dev/null +++ b/.repos/effect/packages/atom/react/src/RegistryContext.ts @@ -0,0 +1,108 @@ +/** + * React context and provider for the Atom registry used by Effect Atom hooks. + * The registry stores atom values, schedules update work, and cleans up unused + * atoms. Sharing one registry through React context lets components in the same + * subtree read and write the same atom state. + * + * @since 4.0.0 + */ +"use client" + +import type * as Atom from "effect/unstable/reactivity/Atom" +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" +import * as React from "react" +import * as Scheduler from "scheduler" + +/** + * Schedules Atom registry work with React's scheduler at low priority and + * returns a cancellation function for the scheduled task. + * + * @category context + * @since 4.0.0 + */ +export function scheduleTask(f: () => void): () => void { + const node = Scheduler.unstable_scheduleCallback(Scheduler.unstable_LowPriority, f) + return () => Scheduler.unstable_cancelCallback(node) +} + +/** + * Provides a React context that supplies the `AtomRegistry` used by Atom hooks and + * hydration helpers, defaulting to a standalone registry when no provider is + * present. + * + * **When to use** + * + * Use to supply an existing `AtomRegistry` through React context when hooks or + * hydration helpers need to share registry state that is managed outside + * `RegistryProvider`. + * + * @see {@link RegistryProvider} for creating and providing a registry for a React subtree + * + * @category context + * @since 4.0.0 + */ +export const RegistryContext = React.createContext(AtomRegistry.make({ + scheduleTask, + defaultIdleTTL: 400 +})) + +/** + * Provides a stable `AtomRegistry` to a React subtree, optionally seeding + * initial atom values and overriding registry scheduling or idle settings. + * + * **When to use** + * + * Use to scope atom state, scheduling, and idle cleanup to a React subtree. + * + * **Details** + * + * The provider creates one `AtomRegistry` with `AtomRegistry.make`, passes it + * through `RegistryContext.Provider`, and forwards `initialValues`, + * `scheduleTask`, `timeoutResolution`, and `defaultIdleTTL` only when that + * registry is created. + * + * **Gotchas** + * + * Option changes after the first render do not rebuild the registry. When the + * provider unmounts, registry disposal is delayed briefly and canceled if the + * provider remounts before the timeout fires. + * + * @see {@link RegistryContext} for the React context supplied by this provider + * + * @category context + * @since 4.0.0 + */ +export const RegistryProvider = (options: { + readonly children?: React.ReactNode | undefined + readonly initialValues?: Iterable, any]> | undefined + readonly scheduleTask?: ((f: () => void) => () => void) | undefined + readonly timeoutResolution?: number | undefined + readonly defaultIdleTTL?: number | undefined +}) => { + const ref = React.useRef<{ + readonly registry: AtomRegistry.AtomRegistry + timeout?: number | undefined + }>(null) + if (ref.current === null) { + ref.current = { + registry: AtomRegistry.make({ + scheduleTask: options.scheduleTask ?? scheduleTask, + initialValues: options.initialValues, + timeoutResolution: options.timeoutResolution, + defaultIdleTTL: options.defaultIdleTTL + }) + } + } + React.useEffect(() => { + if (ref.current?.timeout !== undefined) { + clearTimeout(ref.current.timeout) + } + return () => { + ref.current!.timeout = setTimeout(() => { + ref.current?.registry.dispose() + ref.current = null + }, 500) as any + } + }, [ref]) + return React.createElement(RegistryContext.Provider, { value: ref.current.registry }, options?.children) +} diff --git a/.repos/effect/packages/atom/react/src/ScopedAtom.ts b/.repos/effect/packages/atom/react/src/ScopedAtom.ts new file mode 100644 index 000000000..9e7e3e7a0 --- /dev/null +++ b/.repos/effect/packages/atom/react/src/ScopedAtom.ts @@ -0,0 +1,157 @@ +/** + * React helpers for creating Atom instances that belong to one component + * subtree. `make` returns a scoped atom with a provider, context, and `use` + * accessor. Each provider creates its own Atom once, so different subtrees can + * use the same scoped atom definition without sharing state. + * + * @since 4.0.0 + */ +"use client" + +import type * as Atom from "effect/unstable/reactivity/Atom" +import * as React from "react" + +/** + * Literal type used as the `ScopedAtom` type identifier. + * + * **Details** + * + * Used as the computed property key and marker value stored on `ScopedAtom` + * objects. + * + * @category type IDs + * @since 4.0.0 + */ +export type TypeId = "~@effect/atom-react/ScopedAtom" + +/** + * Type identifier for ScopedAtom. + * + * **Details** + * + * Used as the computed property key and marker value stored on `ScopedAtom` + * objects. + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId: TypeId = "~@effect/atom-react/ScopedAtom" + +/** + * Scoped Atom interface with a provider-backed instance. + * + * **Example** (Providing and reading a scoped atom) + * + * ```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)) + * + * function View() { + * const atom = Counter.use() + * const value = useAtomValue(atom) + * return React.createElement("div", null, value) + * } + * + * export function App() { + * return React.createElement(Counter.Provider, null, React.createElement(View)) + * } + * + * renderToStaticMarkup(React.createElement(App)) // => "

" + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface ScopedAtom, Input = never> { + readonly [TypeId]: TypeId + use(): A + Provider: [Input] extends [never] ? React.FC<{ readonly children?: React.ReactNode | undefined }> + : React.FC<{ readonly children?: React.ReactNode | undefined; readonly value: Input }> + Context: React.Context +} + +/** + * Creates a ScopedAtom from a factory function. + * + * **When to use** + * + * Use to create an atom instance that is owned by a React provider and scoped + * to a component subtree. + * + * **Details** + * + * The returned scoped atom includes a `Provider`, `Context`, and `use` + * accessor. The provider creates the atom once for its lifetime, passing the + * `value` prop to the factory when the scoped atom expects input. + * + * **Gotchas** + * + * `use` must run under the matching provider. Changing the provider `value` + * prop after mount does not recreate the atom. + * + * **Example** (Creating a scoped atom with input) + * + * ```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)) + * + * function UserName() { + * const atom = User.use() + * const value = useAtomValue(atom) + * return React.createElement("span", null, value) + * } + * + * export function App() { + * return React.createElement( + * User.Provider, + * { value: "Ada" }, + * React.createElement(UserName) + * ) + * } + * + * renderToStaticMarkup(React.createElement(App)) // => "Ada" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const make = , Input = never>( + f: (() => A) | ((input: Input) => A) +): ScopedAtom => { + const Context = React.createContext(undefined as unknown as A) + + const use = (): A => { + const atom = React.useContext(Context) + if (atom === undefined) { + throw new Error("ScopedAtom used outside of its Provider") + } + return atom + } + + const Provider: React.FC<{ readonly children?: React.ReactNode | undefined; readonly value?: Input }> = (props) => { + const atom = React.useRef(null) + if (atom.current === null) { + if ("value" in props) { + atom.current = (f as (input: Input) => A)(props.value as Input) + } else { + atom.current = (f as () => A)() + } + } + return React.createElement(Context.Provider, { value: atom.current }, props.children) + } + + return { + [TypeId]: TypeId, + use, + Provider: Provider as any, + Context + } +} diff --git a/.repos/effect/packages/atom/react/src/index.ts b/.repos/effect/packages/atom/react/src/index.ts new file mode 100644 index 000000000..554a0df30 --- /dev/null +++ b/.repos/effect/packages/atom/react/src/index.ts @@ -0,0 +1,23 @@ +/** + * @since 4.0.0 + */ + +/** + * @since 4.0.0 + */ +export * from "./Hooks.ts" + +/** + * @since 4.0.0 + */ +export * from "./RegistryContext.ts" + +/** + * @since 4.0.0 + */ +export * from "./ReactHydration.ts" + +/** + * @since 4.0.0 + */ +export * from "./ScopedAtom.ts" diff --git a/.repos/effect/packages/atom/react/test/index.test.tsx b/.repos/effect/packages/atom/react/test/index.test.tsx new file mode 100644 index 000000000..06c2668b9 --- /dev/null +++ b/.repos/effect/packages/atom/react/test/index.test.tsx @@ -0,0 +1,693 @@ +// +import { act, render, screen, waitFor } from "@testing-library/react" +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 AtomRegistry from "effect/unstable/reactivity/AtomRegistry" +import * as Hydration from "effect/unstable/reactivity/Hydration" +import * as React from "react" +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 * as ScopedAtom from "../src/ScopedAtom.ts" + +describe("atom-react", () => { + let registry: AtomRegistry.AtomRegistry + + beforeEach(() => { + registry = AtomRegistry.make() + }) + + describe("runtime", () => { + test("can inject test layers", () => { + class TheNumber extends Context.Service()("TheNumber", { + make: Effect.succeed({ n: 42 as number }) + }) { + static readonly layer = Layer.effect(this, this.make) + } + const runtime = Atom.runtime(TheNumber.layer) + const numberAtom = runtime.atom(TheNumber.use((_) => Effect.succeed(_.n))) + + function TestComponent() { + const value = useAtomValue(numberAtom, AsyncResult.getOrThrow) + return
{value}
+ } + + render( + + + + ) + + expect(screen.getByTestId("value")).toHaveTextContent("69") + }) + }) + + describe("useAtomValue", () => { + test("should read value from simple Atom", () => { + const atom = Atom.make(42) + + function TestComponent() { + const value = useAtomValue(atom) + return
{value}
+ } + + render() + + expect(screen.getByTestId("value")).toHaveTextContent("42") + }) + + test("should read value with transform function", () => { + const atom = Atom.make(42) + + function TestComponent() { + const value = useAtomValue(atom, (x) => x * 2) + return
{value}
+ } + + render() + + expect(screen.getByTestId("value")).toHaveTextContent("84") + }) + + test("should update when Atom value changes", async () => { + const atom = Atom.make("initial") + + function TestComponent() { + const value = useAtomValue(atom) + return
{value}
+ } + + render( + + + + ) + + expect(screen.getByTestId("value")).toHaveTextContent("initial") + + act(() => { + registry.set(atom, "updated") + }) + + await waitFor(() => { + expect(screen.getByTestId("value")).toHaveTextContent("updated") + }) + }) + + test("should work with computed Atom", () => { + const baseAtom = Atom.make(10) + const computedAtom = Atom.make((get) => get(baseAtom) * 2) + + function TestComponent() { + const value = useAtomValue(computedAtom) + return
{value}
+ } + + render() + + expect(screen.getByTestId("value")).toHaveTextContent("20") + }) + + test("suspense success", () => { + const atom = Atom.make(Effect.never) + + function TestComponent() { + const value = useAtomSuspense(atom).value + return
{value}
+ } + + render( + Loading...}> + + + ) + + 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") + }) + }) + }) + + describe("ScopedAtom", () => { + test("throws when used outside Provider", () => { + const counter = ScopedAtom.make(() => Atom.make(0)) + + function TestComponent() { + counter.use() + return
ok
+ } + + expect(() => render()).toThrow("ScopedAtom used outside of its Provider") + }) + + test("scopes atom instances per Provider", async () => { + const counter = ScopedAtom.make(() => Atom.make(0)) + + function Count() { + const atom = counter.use() + const value = useAtomValue(atom) + return
{value}
+ } + + function SetTo({ value }: { readonly value: number }) { + const atom = counter.use() + const registry = React.useContext(RegistryContext) + React.useEffect(() => { + registry.set(atom, value) + }, [registry, atom, value]) + return null + } + + render( +
+ + + + + + + + +
+ ) + + await waitFor(() => { + const values = screen.getAllByTestId("value") + expect(values[0]).toHaveTextContent("1") + expect(values[1]).toHaveTextContent("2") + }) + }) + + test("input factory uses provider value once", () => { + const makeAtom = vi.fn((value: number) => Atom.make(value)) + const scoped = ScopedAtom.make(makeAtom) + + function Count() { + const atom = scoped.use() + const value = useAtomValue(atom) + return
{value}
+ } + + const { rerender } = render( + + + + ) + + expect(screen.getByTestId("value")).toHaveTextContent("10") + expect(makeAtom).toHaveBeenCalledTimes(1) + expect(makeAtom).toHaveBeenCalledWith(10) + + rerender( + + + + ) + + expect(screen.getByTestId("value")).toHaveTextContent("10") + expect(makeAtom).toHaveBeenCalledTimes(1) + }) + + test("integrates with useAtomValue", async () => { + const scoped = ScopedAtom.make(() => Atom.make(0)) + + function Counter() { + const atom = scoped.use() + const value = useAtomValue(atom) + return
{value}
+ } + + function IncrementOnce() { + const atom = scoped.use() + const registry = React.useContext(RegistryContext) + React.useEffect(() => { + registry.set(atom, 1) + }, [registry, atom]) + return null + } + + render( + + + + + ) + + await waitFor(() => { + expect(screen.getByTestId("value")).toHaveTextContent("1") + }) + }) + }) + + test("suspense error", () => { + const atom = Atom.make(Effect.fail(new Error("test"))) + function TestComponent() { + const value = useAtomSuspense(atom).value + return
{value}
+ } + + render( + Error}> + Loading...}> + + + , + { + onCaughtError: ((error: unknown) => { + if (error instanceof Error && error.message === "test") { + return + } + // eslint-disable-next-line no-console + console.error(error) + }) as unknown as undefined // todo: fix idk why the types are weird + } + ) + + expect(screen.getByTestId("error")).toBeInTheDocument() + }) + + describe("hydration", () => { + test("basic hydration with number atom and result atoms", () => { + const atomBasic = Atom.make(0).pipe( + Atom.serializable({ + key: "basic", + schema: Schema.Number + }) + ) + const e: Effect.Effect = Effect.never + const makeAtomResult = (key: string) => + Atom.make(e).pipe( + Atom.serializable({ + key, + schema: AsyncResult.Schema({ + success: Schema.Number, + error: Schema.String + }) + }) + ) + + const atomResult1 = makeAtomResult("success") + const atomResult2 = makeAtomResult("errored") + const atomResult3 = makeAtomResult("pending") + + // Use a server-side registry to generate properly encoded dehydrated state + const serverRegistry = AtomRegistry.make() + serverRegistry.mount(atomBasic) + serverRegistry.set(atomBasic, 1) + serverRegistry.mount(atomResult1) + ;(serverRegistry.getNodes().get("success") as any).setValue( + AsyncResult.success(123) + ) + serverRegistry.mount(atomResult2) + ;(serverRegistry.getNodes().get("errored") as any).setValue( + AsyncResult.failure(Cause.fail("error")) + ) + serverRegistry.mount(atomResult3) + // atomResult3 stays Initial (just mounted, effect is Effect.never) + + const dehydratedState = Hydration.dehydrate(serverRegistry, { + encodeInitialAs: "value-only" + }) + + function Basic() { + const value = useAtomValue(atomBasic) + return
{value}
+ } + + function Result1() { + const value = useAtomValue(atomResult1) + return AsyncResult.match(value, { + onSuccess: (result) =>
{result.value}
, + onFailure: () =>
Error
, + onInitial: () =>
Loading...
+ }) + } + + function Result2() { + const value = useAtomValue(atomResult2) + return AsyncResult.match(value, { + onSuccess: (result) =>
{result.value}
, + onFailure: () =>
Error
, + onInitial: () =>
Loading...
+ }) + } + + function Result3() { + const value = useAtomValue(atomResult3) + return AsyncResult.match(value, { + onSuccess: (result) =>
{result.value}
, + onFailure: () =>
Error
, + onInitial: () =>
Loading...
+ }) + } + + render( + + + + + + + + + ) + + expect(screen.getByTestId("hydration-basic-value")).toHaveTextContent("1") + expect(screen.getByTestId("value-1")).toHaveTextContent("123") + expect(screen.getByTestId("error-2")).toBeInTheDocument() + expect(screen.getByTestId("loading-3")).toBeInTheDocument() + }) + + test("hydration streaming with resultPromise", async () => { + const latch = Latch.makeUnsafe() + let start = 0 + let stop = 0 + const atom = Atom.make( + Effect.gen(function*() { + start = start + 1 + yield* latch.await + stop = stop + 1 + return 1 + }) + ).pipe( + Atom.serializable({ + key: "test", + schema: AsyncResult.Schema({ + success: Schema.Number + }) + }) + ) + + registry.mount(atom) + + expect(start).toBe(1) + expect(stop).toBe(0) + + const dehydratedState = Hydration.dehydrate(registry, { + encodeInitialAs: "promise" + }) + + function TestComponent() { + const value = useAtomValue(atom) + return
{value._tag}
+ } + + render( + // provide a fresh registry each time to simulate hydration + + + + + + ) + + expect(screen.getByTestId("value")).toHaveTextContent("Initial") + + act(() => { + Effect.runSync(latch.open) + }) + await Effect.runPromise(latch.await) + + const result = registry.get(atom) + expect(result._tag).toBe("Success") + if (result._tag === "Success") { + expect(result.value).toBe(1) + } + + expect(screen.getByTestId("value")).toHaveTextContent("Success") + expect(start).toBe(1) + expect(stop).toBe(1) + }) + + test("HydrationBoundary splits new vs existing atoms", () => { + const newAtom = Atom.make(0).pipe( + Atom.serializable({ key: "new-atom", schema: Schema.Number }) + ) + + // Use a server registry to generate dehydrated state + const serverRegistry = AtomRegistry.make() + serverRegistry.mount(newAtom) + serverRegistry.set(newAtom, 99) + const dehydratedState = Hydration.dehydrate(serverRegistry) + + function NewValue() { + const value = useAtomValue(newAtom) + return
{value}
+ } + + // Render with a fresh client registry (no pre-existing atoms) + render( + + + + + + ) + + // New atom should be hydrated immediately during render + expect(screen.getByTestId("new")).toHaveTextContent("99") + }) + + test("dehydrate with encodeInitialAs ignore (default)", () => { + const atom = Atom.make(Effect.never as Effect.Effect).pipe( + Atom.serializable({ + key: "initial-atom", + schema: AsyncResult.Schema({ success: Schema.Number }) + }) + ) + + registry.mount(atom) + + // Default behavior: Initial values should be ignored + const state = Hydration.dehydrate(registry) + const values = Hydration.toValues(state) + + expect(values.length).toBe(0) + }) + + test("dehydrate with encodeInitialAs value-only", () => { + const atom = Atom.make(Effect.never as Effect.Effect).pipe( + Atom.serializable({ + key: "initial-atom", + schema: AsyncResult.Schema({ success: Schema.Number }) + }) + ) + + registry.mount(atom) + + // value-only: should encode the Initial value without a resultPromise + const state = Hydration.dehydrate(registry, { + encodeInitialAs: "value-only" + }) + const values = Hydration.toValues(state) + + expect(values.length).toBe(1) + expect(values[0].key).toBe("initial-atom") + expect(values[0].resultPromise).toBeUndefined() + }) + + test("serializable encode/decode survives JSON roundtrip (wire transfer)", () => { + const atom = Atom.make(0 as never).pipe( + Atom.serializable({ + key: "wire-test", + schema: AsyncResult.Schema({ + success: Schema.Struct({ name: Schema.String }), + error: Schema.String + }) + }) + ) + + const original = AsyncResult.success({ name: "hello" }) + + // Encode using the atom's serializable encode + const encoded = atom[Atom.SerializableTypeId].encode(original) + + // Simulate wire transfer (seroval / JSON serialization roundtrip) + const wireTransferred = JSON.parse(JSON.stringify(encoded)) + + // Decode after wire transfer — this was the bug: decode would fail + // because the encoded value lost its AsyncResult prototype + const decoded = atom[Atom.SerializableTypeId].decode(wireTransferred) + + expect(AsyncResult.isAsyncResult(decoded)).toBe(true) + expect(decoded._tag).toBe("Success") + if (AsyncResult.isSuccess(decoded)) { + expect(decoded.value).toEqual({ name: "hello" }) + } + }) + + test("dehydrate + JSON roundtrip + hydrate works (SSR simulation)", () => { + const atom = Atom.make(Effect.never as Effect.Effect).pipe( + Atom.serializable({ + key: "ssr-wire", + schema: AsyncResult.Schema({ success: Schema.Number }) + }) + ) + + // Server: dehydrate + const serverRegistry = AtomRegistry.make() + serverRegistry.mount(atom) + ;(serverRegistry.getNodes().get("ssr-wire") as any).setValue( + AsyncResult.success(42) + ) + const dehydratedState = Hydration.dehydrate(serverRegistry) + + // Simulate wire transfer (seroval / JSON) + const wireTransferred = JSON.parse(JSON.stringify(dehydratedState)) + + // Client: hydrate from wire-transferred state + function TestComponent() { + const value = useAtomValue(atom) + return ( +
+ {AsyncResult.isSuccess(value) ? value.value : "not-success"} +
+ ) + } + + render( + + + + + + ) + + expect(screen.getByTestId("ssr-wire-value")).toHaveTextContent("42") + }) + + test("empty state is a no-op", () => { + function TestComponent() { + return
OK
+ } + + render( + + + + ) + + expect(screen.getByTestId("hydration-empty-state")).toHaveTextContent("OK") + }) + + test("hydrate with no state is a no-op", () => { + function TestComponent() { + return
OK
+ } + + render( + + + + ) + + expect(screen.getByTestId("hydration-no-state")).toHaveTextContent("OK") + }) + }) + + describe("SSR", () => { + it("should run atom's during SSR by default", () => { + const getCount = vi.fn(() => 0) + const counterAtom = Atom.make(getCount) + + function TestComponent() { + const count = useAtomValue(counterAtom) + return
{count}
+ } + + function App() { + return + } + + const ssrHtml = renderToString() + + expect(getCount).toHaveBeenCalled() + expect(ssrHtml).toContain("0") + + render() + + expect(getCount).toHaveBeenCalled() + expect(screen.getByText("0")).toBeInTheDocument() + }) + }) + + it("should not execute Atom effects during SSR when using withServerSnapshot", () => { + const mockFetchData = vi.fn(() => 0) + + const userDataAtom = Atom.make(Effect.sync(() => mockFetchData())).pipe( + Atom.withServerValueInitial + ) + + function TestComponent() { + const result = useAtomValue(userDataAtom) + + return
{result._tag}
+ } + + function App() { + return + } + + const ssrHtml = renderToString() + + expect(mockFetchData).not.toHaveBeenCalled() + expect(ssrHtml).toContain("Initial") + + render() + + expect(mockFetchData).toHaveBeenCalled() + expect(screen.getByText("Success")).toBeInTheDocument() + }) +}) diff --git a/.repos/effect/packages/atom/react/tsconfig.json b/.repos/effect/packages/atom/react/tsconfig.json new file mode 100644 index 000000000..e2a8ca19a --- /dev/null +++ b/.repos/effect/packages/atom/react/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../../effect" } + ] +} diff --git a/.repos/effect/packages/atom/react/vitest.setup.ts b/.repos/effect/packages/atom/react/vitest.setup.ts new file mode 100644 index 000000000..5f520a536 --- /dev/null +++ b/.repos/effect/packages/atom/react/vitest.setup.ts @@ -0,0 +1,8 @@ +// oxlint-disable-next-line no-unassigned-import +import "@testing-library/jest-dom/vitest" +import { cleanup } from "@testing-library/react" +import { afterEach } from "vitest" + +afterEach(() => { + cleanup() +}) diff --git a/.repos/effect/packages/atom/solid/CHANGELOG.md b/.repos/effect/packages/atom/solid/CHANGELOG.md new file mode 100644 index 000000000..849829970 --- /dev/null +++ b/.repos/effect/packages/atom/solid/CHANGELOG.md @@ -0,0 +1,801 @@ +# @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 + +- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]: + - effect@4.0.0-beta.90 + +## 4.0.0-beta.89 + +### Patch Changes + +- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]: + - effect@4.0.0-beta.89 + +## 4.0.0-beta.88 + +### Patch Changes + +- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]: + - effect@4.0.0-beta.88 + +## 4.0.0-beta.87 + +### Patch Changes + +- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]: + - effect@4.0.0-beta.87 + +## 4.0.0-beta.86 + +### Patch Changes + +- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]: + - effect@4.0.0-beta.86 + +## 4.0.0-beta.85 + +### Patch Changes + +- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]: + - effect@4.0.0-beta.85 + +## 4.0.0-beta.84 + +### Patch Changes + +- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]: + - effect@4.0.0-beta.84 + +## 4.0.0-beta.83 + +### Patch Changes + +- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]: + - effect@4.0.0-beta.83 + +## 4.0.0-beta.82 + +### Patch Changes + +- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]: + - effect@4.0.0-beta.82 + +## 4.0.0-beta.81 + +### Patch Changes + +- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]: + - effect@4.0.0-beta.81 + +## 4.0.0-beta.80 + +### Patch Changes + +- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]: + - effect@4.0.0-beta.80 + +## 4.0.0-beta.79 + +### Patch Changes + +- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]: + - effect@4.0.0-beta.79 + +## 4.0.0-beta.78 + +### Patch Changes + +- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]: + - effect@4.0.0-beta.78 + +## 4.0.0-beta.77 + +### Patch Changes + +- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]: + - effect@4.0.0-beta.77 + +## 4.0.0-beta.76 + +### Patch Changes + +- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]: + - effect@4.0.0-beta.76 + +## 4.0.0-beta.75 + +### Patch Changes + +- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]: + - effect@4.0.0-beta.75 + +## 4.0.0-beta.74 + +### Patch Changes + +- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]: + - effect@4.0.0-beta.74 + +## 4.0.0-beta.73 + +### Patch Changes + +- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]: + - effect@4.0.0-beta.73 + +## 4.0.0-beta.72 + +### Patch Changes + +- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]: + - effect@4.0.0-beta.72 + +## 4.0.0-beta.71 + +### Patch Changes + +- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]: + - effect@4.0.0-beta.71 + +## 4.0.0-beta.70 + +### Patch Changes + +- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]: + - effect@4.0.0-beta.70 + +## 4.0.0-beta.69 + +### Patch Changes + +- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]: + - effect@4.0.0-beta.69 + +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + +## 4.0.0-beta.67 + +### Patch Changes + +- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]: + - effect@4.0.0-beta.67 + +## 4.0.0-beta.66 + +### Patch Changes + +- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]: + - effect@4.0.0-beta.66 + +## 4.0.0-beta.65 + +### Patch Changes + +- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]: + - effect@4.0.0-beta.65 + +## 4.0.0-beta.64 + +### Patch Changes + +- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]: + - effect@4.0.0-beta.64 + +## 4.0.0-beta.63 + +### Patch Changes + +- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]: + - effect@4.0.0-beta.63 + +## 4.0.0-beta.62 + +### Patch Changes + +- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]: + - effect@4.0.0-beta.62 + +## 4.0.0-beta.61 + +### Patch Changes + +- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]: + - effect@4.0.0-beta.61 + +## 4.0.0-beta.60 + +### Patch Changes + +- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]: + - effect@4.0.0-beta.60 + +## 4.0.0-beta.59 + +### Patch Changes + +- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]: + - effect@4.0.0-beta.59 + +## 4.0.0-beta.58 + +### Patch Changes + +- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]: + - effect@4.0.0-beta.58 + +## 4.0.0-beta.57 + +### Patch Changes + +- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]: + - effect@4.0.0-beta.57 + +## 4.0.0-beta.56 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]: + - effect@4.0.0-beta.55 + +## 4.0.0-beta.54 + +### Patch Changes + +- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]: + - effect@4.0.0-beta.54 + +## 4.0.0-beta.53 + +### Patch Changes + +- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]: + - effect@4.0.0-beta.53 + +## 4.0.0-beta.52 + +### Patch Changes + +- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]: + - effect@4.0.0-beta.52 + +## 4.0.0-beta.51 + +### Patch Changes + +- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]: + - effect@4.0.0-beta.51 + +## 4.0.0-beta.50 + +### Patch Changes + +- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]: + - effect@4.0.0-beta.50 + +## 4.0.0-beta.49 + +### Patch Changes + +- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]: + - effect@4.0.0-beta.49 + +## 4.0.0-beta.48 + +### Patch Changes + +- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]: + - effect@4.0.0-beta.48 + +## 4.0.0-beta.47 + +### Patch Changes + +- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]: + - effect@4.0.0-beta.47 + +## 4.0.0-beta.46 + +### Patch Changes + +- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]: + - effect@4.0.0-beta.46 + +## 4.0.0-beta.45 + +### Patch Changes + +- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]: + - effect@4.0.0-beta.45 + +## 4.0.0-beta.44 + +### Patch Changes + +- [#1938](https://github.com/Effect-TS/effect-smol/pull/1938) [`23bbaac`](https://github.com/Effect-TS/effect-smol/commit/23bbaace8fd22a76e5f57aea8b4899374c40194e) Thanks @tim-smart! - allow atoms to be computed in solid bindings + +- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]: + - effect@4.0.0-beta.44 + +## 4.0.0-beta.43 + +### Patch Changes + +- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]: + - effect@4.0.0-beta.43 + +## 4.0.0-beta.42 + +### Patch Changes + +- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]: + - effect@4.0.0-beta.42 + +## 4.0.0-beta.41 + +### Patch Changes + +- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]: + - effect@4.0.0-beta.41 + +## 4.0.0-beta.40 + +### Patch Changes + +- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]: + - effect@4.0.0-beta.40 + +## 4.0.0-beta.39 + +### Patch Changes + +- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]: + - effect@4.0.0-beta.39 + +## 4.0.0-beta.38 + +### Patch Changes + +- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]: + - effect@4.0.0-beta.38 + +## 4.0.0-beta.37 + +### Patch Changes + +- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]: + - effect@4.0.0-beta.37 + +## 4.0.0-beta.36 + +### Patch Changes + +- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]: + - effect@4.0.0-beta.36 + +## 4.0.0-beta.35 + +### Patch Changes + +- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]: + - effect@4.0.0-beta.35 + +## 4.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]: + - effect@4.0.0-beta.34 + +## 4.0.0-beta.33 + +### Patch Changes + +- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]: + - effect@4.0.0-beta.33 + +## 4.0.0-beta.32 + +### Patch Changes + +- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]: + - effect@4.0.0-beta.32 + +## 4.0.0-beta.31 + +### Patch Changes + +- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]: + - effect@4.0.0-beta.31 + +## 4.0.0-beta.30 + +### Patch Changes + +- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]: + - effect@4.0.0-beta.30 + +## 4.0.0-beta.29 + +### Patch Changes + +- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]: + - effect@4.0.0-beta.29 + +## 4.0.0-beta.28 + +### Patch Changes + +- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]: + - effect@4.0.0-beta.28 + +## 4.0.0-beta.27 + +### Patch Changes + +- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]: + - effect@4.0.0-beta.27 + +## 4.0.0-beta.26 + +### Patch Changes + +- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]: + - effect@4.0.0-beta.26 + +## 4.0.0-beta.25 + +### Patch Changes + +- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]: + - effect@4.0.0-beta.25 + +## 4.0.0-beta.24 + +### Patch Changes + +- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]: + - effect@4.0.0-beta.24 + +## 4.0.0-beta.23 + +### Patch Changes + +- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]: + - effect@4.0.0-beta.23 + +## 4.0.0-beta.22 + +### Patch Changes + +- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]: + - effect@4.0.0-beta.22 + +## 4.0.0-beta.21 + +### Patch Changes + +- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]: + - effect@4.0.0-beta.21 + +## 4.0.0-beta.20 + +### Patch Changes + +- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]: + - effect@4.0.0-beta.20 + +## 4.0.0-beta.19 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Patch Changes + +- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]: + - effect@4.0.0-beta.18 + +## 4.0.0-beta.17 + +### Patch Changes + +- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]: + - effect@4.0.0-beta.17 + +## 4.0.0-beta.16 + +### Patch Changes + +- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]: + - effect@4.0.0-beta.16 + +## 4.0.0-beta.15 + +### Patch Changes + +- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]: + - effect@4.0.0-beta.15 + +## 4.0.0-beta.14 + +### Patch Changes + +- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]: + - effect@4.0.0-beta.14 + +## 4.0.0-beta.13 + +### Patch Changes + +- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]: + - effect@4.0.0-beta.13 + +## 4.0.0-beta.12 + +### Patch Changes + +- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]: + - effect@4.0.0-beta.12 + +## 4.0.0-beta.11 + +### Patch Changes + +- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]: + - effect@4.0.0-beta.11 + +## 4.0.0-beta.10 + +### Patch Changes + +- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]: + - effect@4.0.0-beta.10 + +## 4.0.0-beta.9 + +### Patch Changes + +- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]: + - effect@4.0.0-beta.9 + +## 4.0.0-beta.8 + +### Patch Changes + +- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]: + - effect@4.0.0-beta.8 + +## 4.0.0-beta.7 + +### Patch Changes + +- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]: + - effect@4.0.0-beta.7 + +## 4.0.0-beta.6 + +### Patch Changes + +- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]: + - effect@4.0.0-beta.6 + +## 4.0.0-beta.5 + +### Patch Changes + +- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]: + - effect@4.0.0-beta.5 + +## 4.0.0-beta.4 + +### Patch Changes + +- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]: + - effect@4.0.0-beta.4 + +## 4.0.0-beta.3 + +### Patch Changes + +- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]: + - effect@4.0.0-beta.3 + +## 4.0.0-beta.2 + +### Patch Changes + +- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]: + - effect@4.0.0-beta.2 + +## 4.0.0-beta.1 + +### Patch Changes + +- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]: + - effect@4.0.0-beta.1 + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta + +### Patch Changes + +- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]: + - effect@4.0.0-beta.0 diff --git a/.repos/effect/packages/atom/solid/LICENSE b/.repos/effect/packages/atom/solid/LICENSE new file mode 100644 index 000000000..7f6fe480f --- /dev/null +++ b/.repos/effect/packages/atom/solid/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-present The Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.repos/effect/packages/atom/solid/README.md b/.repos/effect/packages/atom/solid/README.md new file mode 100644 index 000000000..4d984b0de --- /dev/null +++ b/.repos/effect/packages/atom/solid/README.md @@ -0,0 +1,14 @@ +# @effect/atom-solid + +[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 + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/atom-solid) diff --git a/.repos/effect/packages/atom/solid/package.json b/.repos/effect/packages/atom/solid/package.json new file mode 100644 index 000000000..0bfe68b26 --- /dev/null +++ b/.repos/effect/packages/atom/solid/package.json @@ -0,0 +1,74 @@ +{ + "name": "@effect/atom-solid", + "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.git", + "directory": "packages/atom/solid" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "solid", + "database" + ], + "keywords": [ + "typescript", + "solid", + "database" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./internal/*": null, + "./index": null, + "./*/index": null + } + }, + "scripts": { + "build": "tsc -b tsconfig.json && pnpm babel", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "peerDependencies": { + "effect": "workspace:^", + "solid-js": ">=1.9.14 <2.0.0" + }, + "devDependencies": { + "@solidjs/testing-library": "^0.8.10", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^7.0.1", + "effect": "workspace:^", + "jsdom": "^30.0.1", + "solid-js": "^1.9.15" + } +} diff --git a/.repos/effect/packages/atom/solid/src/Hooks.ts b/.repos/effect/packages/atom/solid/src/Hooks.ts new file mode 100644 index 000000000..d4d98de17 --- /dev/null +++ b/.repos/effect/packages/atom/solid/src/Hooks.ts @@ -0,0 +1,359 @@ +/** + * Solid hooks for using Effect Atoms from components and computations. The + * hooks read and write atoms through the current `RegistryContext`, mount atoms + * for cleanup, subscribe callbacks, seed initial values, expose `AsyncResult` + * atoms as Solid resources, and read values from `AtomRef` references. + * + * @since 4.0.0 + */ +import * as Cause from "effect/Cause" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult" +import * as Atom from "effect/unstable/reactivity/Atom" +import type * as AtomRef from "effect/unstable/reactivity/AtomRef" +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" +import type { Accessor, ResourceOptions, ResourceReturn } from "solid-js" +import { createComputed, createEffect, createMemo, createResource, createSignal, onCleanup, useContext } from "solid-js" +import { RegistryContext } from "./RegistryContext.ts" + +const initialValuesSet = new WeakMap>>() + +/** + * Seeds initial atom values in the current Solid atom registry. + * + * **When to use** + * + * Use to seed atom values from a Solid component after the current registry + * already exists. + * + * **Details** + * + * For each atom in the current registry, this hook applies the first value + * supplied through the hook. Later calls for the same atom in that registry are + * ignored. + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomInitialValues = (initialValues: Iterable, any]>): void => { + const registry = useContext(RegistryContext) + let set = initialValuesSet.get(registry) + if (set === undefined) { + set = new WeakSet() + initialValuesSet.set(registry, set) + } + for (const [atom, value] of initialValues) { + if (!set.has(atom)) { + set.add(atom) + ;(registry as any).ensureNode(atom).setValue(value) + } + } +} + +/** + * Subscribes to an atom in the current Solid registry and returns its value as + * a Solid accessor. + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomValue: { +
(atom: () => Atom.Atom): Accessor + (atom: () => Atom.Atom, f: (_: A) => B): Accessor +} = (atom: () => Atom.Atom, f?: (_: A) => A): Accessor => { + const registry = useContext(RegistryContext) + return createAtomAccessor(registry, f ? () => Atom.map(atom(), f) : atom) +} + +function createAtomAccessor(registry: AtomRegistry.AtomRegistry, atom: () => Atom.Atom): Accessor { + const [value, setValue] = createSignal(null as any) + createComputed(() => { + onCleanup(registry.subscribe(atom(), setValue as any, constImmediate)) + }) + return value +} + +const constImmediate = { immediate: true } + +function mountAtom(registry: AtomRegistry.AtomRegistry, atom: () => Atom.Atom): void { + createComputed(() => { + onCleanup(registry.mount(atom())) + }) +} + +function setAtom( + registry: AtomRegistry.AtomRegistry, + atom: () => Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): "promise" extends Mode ? ( + (value: W) => Promise> + ) : + "promiseExit" extends Mode ? ( + (value: W) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) +{ + const memo = createMemo(atom) + if (options?.mode === "promise" || options?.mode === "promiseExit") { + return ((value: W) => { + registry.set(memo(), value) + const promise = Effect.runPromiseExit( + AtomRegistry.getResult(registry, memo() as Atom.Atom>, { + suspendOnWaiting: true + }) + ) + return options!.mode === "promise" ? promise.then(flattenExit) : promise + }) as any + } + return ((value: W | ((value: R) => W)) => { + registry.set(memo(), typeof value === "function" ? (value as any)(registry.get(memo())) : value) + }) as any +} + +const flattenExit = (exit: Exit.Exit): A => { + if (Exit.isSuccess(exit)) return exit.value + throw Cause.squash(exit.cause) +} + +/** + * Mounts an atom in the current Solid registry for the lifetime of the current + * Solid computation. + * + * **When to use** + * + * Use to keep an atom mounted from a Solid owner without reading, writing, or + * refreshing it. + * + * **Details** + * + * The hook uses the current `RegistryContext`, mounts inside a Solid + * computation, and releases the mount through Solid cleanup when the + * computation changes or the owner is disposed. + * + * @see {@link useAtomSet} for mounting a writable atom while returning a setter + * @see {@link useAtomRefresh} for mounting an atom while returning a refresh callback + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomMount = (atom: () => Atom.Atom): void => { + const registry = useContext(RegistryContext) + mountAtom(registry, atom) +} + +/** + * Returns a setter for a writable atom without subscribing to its value. + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomSet = < + R, + W, + Mode extends "value" | "promise" | "promiseExit" = never +>( + atom: () => Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): "promise" extends Mode ? ( + (value: W) => Promise> + ) : + "promiseExit" extends Mode ? ( + (value: W) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) => +{ + const registry = useContext(RegistryContext) + mountAtom(registry, atom) + return setAtom(registry, atom, options) +} + +/** + * Mounts an atom and returns a callback that refreshes the current atom. + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRefresh = (atom: () => Atom.Atom): () => void => { + const registry = useContext(RegistryContext) + mountAtom(registry, atom) + const memo = createMemo(atom) + return () => registry.refresh(memo()) +} + +/** + * Returns a Solid accessor for a writable atom together with a setter for + * updating it. + * + * **When to use** + * + * Use when a Solid component or computation needs both a reactive accessor for + * a writable atom and a write function for that same atom. + * + * **Details** + * + * The setter accepts either a write value or an updater function. For + * `AsyncResult` atoms, `promise` and `promiseExit` modes return promises for the + * success value or full `Exit`. + * + * @see {@link useAtomValue} for subscribing to an atom without a setter + * @see {@link useAtomSet} for updating a writable atom without subscribing to its value + * + * @category hooks + * @since 4.0.0 + */ +export const useAtom = ( + atom: () => Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): readonly [ + value: Accessor, + write: "promise" extends Mode ? ( + (value: W) => Promise> + ) : + "promiseExit" extends Mode ? ( + (value: W) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) +] => { + const registry = useContext(RegistryContext) + return [ + createAtomAccessor(registry, atom), + setAtom(registry, atom, options) + ] as const +} + +/** + * Subscribes a callback to an atom in the current Solid registry. + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomSubscribe = ( + atom: () => Atom.Atom, + f: (_: A) => void, + options?: { readonly immediate?: boolean } +): void => { + const registry = useContext(RegistryContext) + createEffect(() => { + onCleanup(registry.subscribe(atom(), f, options)) + }) +} + +/** + * Converts an `AsyncResult` atom into a Solid resource. + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomResource = ( + atom: () => Atom.Atom>, + options?: ResourceOptions & { + readonly suspendOnWaiting?: boolean | undefined + } +): ResourceReturn => { + const result = useAtomValue(atom) + return createResource(result, (result) => { + if (AsyncResult.isInitial(result) || (options?.suspendOnWaiting && result.waiting)) { + return constUnresolvedPromise + } else if (AsyncResult.isSuccess(result)) { + return Promise.resolve(result.value) + } + return Promise.reject(Cause.squash(result.cause)) + }) +} + +const constUnresolvedPromise = new Promise(() => {}) + +/** + * Subscribes to an atom ref and returns its value as a Solid accessor. + * + * **When to use** + * + * Use when a Solid component or computation should render from an + * `AtomRef.ReadonlyRef` directly instead of reading an atom through the current + * registry. + * + * **Details** + * + * The hook accepts a thunk for the ref, reads `ref().value`, subscribes with + * `ref.subscribe`, and releases the subscription through Solid cleanup when + * the selected ref changes or the owner is disposed. + * + * @see {@link useAtomValue} for reading an `Atom` from the current registry + * @see {@link useAtomRefPropValue} for reading a property ref value + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRef = (ref: () => AtomRef.ReadonlyRef): Accessor => { + const [value, setValue] = createSignal(null as A) + createComputed(() => { + const r = ref() + setValue(r.value as any) + onCleanup(r.subscribe(setValue)) + }) + return value +} + +/** + * Returns a Solid accessor for a property ref derived from an atom ref. + * + * **When to use** + * + * Use to derive an `AtomRef` for one property of an object-shaped atom ref in a + * Solid computation. + * + * **Details** + * + * The returned accessor memoizes `ref().prop(prop)`, updating when the source + * ref thunk produces a different ref. + * + * **Gotchas** + * + * The `prop` argument is captured as a plain value. Recreate the hook call when + * the property key should change. + * + * @see {@link useAtomRef} for subscribing to an atom ref value + * @see {@link useAtomRefPropValue} for subscribing directly to a property value + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRefProp = ( + ref: () => AtomRef.AtomRef, + prop: K +): Accessor> => createMemo(() => ref().prop(prop)) + +/** + * Returns a Solid accessor for the value of a property ref derived from an atom + * ref. + * + * **When to use** + * + * Use when a Solid component or computation needs the value of one property + * from an object-shaped `AtomRef` without keeping the intermediate property ref. + * + * **Details** + * + * The hook composes `useAtomRefProp(ref, prop)` with `useAtomRef`, returning a + * Solid accessor for the selected property value. + * + * **Gotchas** + * + * The `prop` argument is captured as a plain value. Recreate the hook call when + * the property key should change. + * + * @see {@link useAtomRef} for subscribing to a whole atom ref value + * @see {@link useAtomRefProp} for returning the property ref directly + * + * @category hooks + * @since 4.0.0 + */ +export const useAtomRefPropValue = (ref: () => AtomRef.AtomRef, prop: K): Accessor => + useAtomRef(useAtomRefProp(ref, prop)) diff --git a/.repos/effect/packages/atom/solid/src/RegistryContext.ts b/.repos/effect/packages/atom/solid/src/RegistryContext.ts new file mode 100644 index 000000000..7c1b6c0f2 --- /dev/null +++ b/.repos/effect/packages/atom/solid/src/RegistryContext.ts @@ -0,0 +1,80 @@ +/** + * Solid context and provider for the Atom registry used by Effect Atom hooks. + * The registry stores atom values, schedules update work, and cleans up unused + * atoms. Sharing one registry through Solid context lets components and + * computations in the same owner tree read and write the same atom state. + * + * @since 4.0.0 + */ +import type * as Atom from "effect/unstable/reactivity/Atom" +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" +import type { JSX } from "solid-js" +import { createComponent, createContext, onCleanup } from "solid-js" + +/** + * Provides a Solid context that carries the `AtomRegistry` used by atom hooks in the + * current owner tree. + * + * **When to use** + * + * Use when you need to integrate lower-level Solid context APIs, such as custom + * providers or hooks, instead of using the default atom hook setup. + * + * **Details** + * + * When no provider is present, the context uses a standalone default registry. + * + * @see {@link RegistryProvider} for creating and providing a registry for a Solid subtree + * + * @category context + * @since 4.0.0 + */ +export const RegistryContext = createContext(AtomRegistry.make()) + +/** + * Creates an `AtomRegistry` for a Solid subtree, optionally seeding initial atom + * values and scheduler settings, and disposes the registry when the owner is + * cleaned up. + * + * **When to use** + * + * Use to scope atom state, scheduling, and cleanup to a Solid subtree. + * + * **Details** + * + * The provider creates an `AtomRegistry` with `AtomRegistry.make`, forwards + * `initialValues`, `scheduleTask`, `timeoutResolution`, and + * `defaultIdleTTL`, and supplies the registry through `RegistryContext`. + * + * **Gotchas** + * + * Provider options are consumed when the registry is created; they are not + * reactive updates. A custom `scheduleTask` should return a cancellation + * function that is safe to call during Solid cleanup. + * + * @see {@link RegistryContext} for the context supplied by this provider + * + * @category context + * @since 4.0.0 + */ +export const RegistryProvider = (options: { + readonly children?: JSX.Element | undefined + readonly initialValues?: Iterable, any]> | undefined + readonly scheduleTask?: ((f: () => void) => () => void) | undefined + readonly timeoutResolution?: number | undefined + readonly defaultIdleTTL?: number | undefined +}) => { + const registry = AtomRegistry.make({ + scheduleTask: options.scheduleTask, + initialValues: options.initialValues, + timeoutResolution: options.timeoutResolution, + defaultIdleTTL: options.defaultIdleTTL ?? 400 + }) + onCleanup(() => registry.dispose()) + return createComponent(RegistryContext.Provider, { + value: registry, + get children() { + return options.children + } + }) +} diff --git a/.repos/effect/packages/atom/solid/src/index.ts b/.repos/effect/packages/atom/solid/src/index.ts new file mode 100644 index 000000000..e8c448255 --- /dev/null +++ b/.repos/effect/packages/atom/solid/src/index.ts @@ -0,0 +1,13 @@ +/** + * @since 4.0.0 + */ + +/** + * @since 4.0.0 + */ +export * from "./Hooks.ts" + +/** + * @since 4.0.0 + */ +export * from "./RegistryContext.ts" diff --git a/.repos/effect/packages/atom/solid/test/index.test.tsx b/.repos/effect/packages/atom/solid/test/index.test.tsx new file mode 100644 index 000000000..cd90407be --- /dev/null +++ b/.repos/effect/packages/atom/solid/test/index.test.tsx @@ -0,0 +1,296 @@ +import { + RegistryContext, + useAtom, + useAtomInitialValues, + useAtomRef, + useAtomRefProp, + useAtomRefPropValue, + useAtomResource, + useAtomValue +} from "@effect/atom-solid" +import { assert, describe, it } from "@effect/vitest" +import { AsyncResult, Atom, AtomRef, AtomRegistry } from "effect/unstable/reactivity" +import { type Accessor, createComponent, createEffect, createRoot, type Resource } from "solid-js" + +describe("atom-solid", () => { + describe("useAtomValue", () => { + it("reads value from simple Atom", () => { + const atom = Atom.make(42) + let observed: number | undefined + const dispose = renderAtomValue(atom, (value) => { + observed = value + }) + assert.strictEqual(observed, 42) + dispose() + }) + + it("reads value with transform function", () => { + const atom = Atom.make(42) + let observed: number | undefined + const dispose = renderAtomValue(atom, (value) => { + observed = value + }, { map: (value) => value * 2 }) + assert.strictEqual(observed, 84) + dispose() + }) + + it("updates when Atom value changes", () => { + const registry = AtomRegistry.make() + const atom = Atom.make("initial") + let observed: string | undefined + const dispose = renderAtomValue(atom, (value) => { + observed = value + }, { registry }) + assert.strictEqual(observed, "initial") + registry.set(atom, "updated") + assert.strictEqual(observed, "updated") + dispose() + }) + + it("works with computed Atom", () => { + const baseAtom = Atom.make(10) + const computedAtom = Atom.make((get) => get(baseAtom) * 2) + let observed: number | undefined + const dispose = renderAtomValue(computedAtom, (value) => { + observed = value + }) + assert.strictEqual(observed, 20) + dispose() + }) + }) + + describe("useAtom", () => { + it("updates value with setter", () => { + const atom = Atom.make(0) + let observed: number | undefined + const dispose = createRoot((dispose) => { + const [value, setValue] = useAtom(() => atom) + createEffect(() => { + observed = value() + }) + createEffect(() => { + if (value() !== 0) { + return + } + setValue(1) + setValue((current) => current + 1) + }) + return dispose + }) + assert.strictEqual(observed, 2) + dispose() + }) + }) + + describe("useAtomInitialValues", () => { + it("applies initial values once per registry", () => { + const registry = AtomRegistry.make() + const atom = Atom.make(0) + createRoot((dispose) => { + createComponent(RegistryContext.Provider, { + value: registry, + get children() { + useAtomInitialValues([[atom, 1]]) + useAtomInitialValues([[atom, 2]]) + assert.strictEqual(registry.get(atom), 1) + return null + } + }) + return dispose + }) + }) + }) + + describe("AtomRef", () => { + it("updates when AtomRef changes", () => { + const ref = AtomRef.make(0) + let observed: number | undefined + const dispose = renderAtomRef(ref, (value) => { + observed = value + }) + assert.strictEqual(observed, 0) + ref.set(1) + assert.strictEqual(observed, 1) + dispose() + }) + + it("updates when AtomRef prop changes", () => { + const ref = AtomRef.make({ count: 0, label: "a" }) + const propRef = useAtomRefProp(() => ref, "count") + let observed: number | undefined + const dispose = renderAtomRef(propRef(), (value) => { + observed = value + }) + assert.strictEqual(observed, 0) + ref.set({ count: 1, label: "a" }) + assert.strictEqual(observed, 1) + dispose() + }) + + it("updates when AtomRef prop value changes", () => { + const ref = AtomRef.make({ count: 0, label: "a" }) + let observed: number | undefined + const dispose = renderAccessor(() => useAtomRefPropValue(() => ref, "count"), (value) => { + observed = value + }) + assert.strictEqual(observed, 0) + ref.set({ count: 2, label: "a" }) + assert.strictEqual(observed, 2) + dispose() + }) + }) + + describe("useAtomResource", () => { + it("suspends on Initial result", () => { + const atom = Atom.make(AsyncResult.initial()) + const { resource, dispose } = renderAtomResource(() => atom) + assert.strictEqual(resource.loading, true) + assert.strictEqual(resource(), undefined) + dispose() + }) + }) + + // + // it("suspends on waiting when suspendOnWaiting is true", () => { + // const atom = Atom.make(AsyncResult.success(1, { waiting: true })) + // const { resource, dispose } = renderAtomResource(atom, { suspendOnWaiting: true }) + // assert.strictEqual(resource.loading, true) + // assert.strictEqual(resource(), undefined) + // dispose() + // }) + // + // it("returns success value by default", async () => { + // const atom = Atom.make(AsyncResult.success(5, { waiting: true })) + // const { resource, dispose } = renderAtomResource(atom) + // await Promise.resolve() + // await Promise.resolve() + // assert.strictEqual(resource.loading, false) + // assert.strictEqual(resource(), 5) + // dispose() + // }) + // + // it("surfaces failure via Cause.squash", async () => { + // const error = new Error("boom") + // const atom = Atom.make(AsyncResult.fail(error)) + // let resource: ReturnType[0] | undefined + // let caught: unknown + // const dispose = createRoot((dispose) => { + // catchError(() => { + // ;[resource] = useAtomResource(atom) + // createEffect(() => { + // resource?.() + // }) + // }, (err) => { + // caught = err + // }) + // return dispose + // }) + // await Promise.resolve() + // await Promise.resolve() + // assert.ok(caught instanceof Error) + // assert.strictEqual(caught.message, "boom") + // assert.strictEqual(resource?.error, caught) + // assert.strictEqual(resource?.loading, false) + // dispose() + // }) + // + // it("preserves success result when preserveResult is true", async () => { + // const atom = Atom.make(Effect.succeed(7)) + // const { resource, dispose } = renderAtomResource(atom, { preserveResult: true }) + // await Promise.resolve() + // await Promise.resolve() + // const result = resource()! + // assert.strictEqual(AsyncResult.isSuccess(result), true) + // if (AsyncResult.isSuccess(result)) { + // assert.strictEqual(result.value, 7) + // } + // assert.strictEqual(resource.error, undefined) + // dispose() + // }) + // + // it("preserves failure result when preserveResult is true", async () => { + // const error = new Error("failure") + // const atom = Atom.make(Effect.fail(error)) + // const { resource, dispose } = renderAtomResource(atom, { preserveResult: true }) + // await Promise.resolve() + // await Promise.resolve() + // const result = resource()! + // assert.strictEqual(AsyncResult.isFailure(result), true) + // if (AsyncResult.isFailure(result)) { + // const squashed = Cause.squash(result.cause) + // assert.ok(squashed instanceof Error) + // assert.strictEqual(squashed.message, "failure") + // } + // assert.strictEqual(resource.error, undefined) + // dispose() + // }) + // }) +}) + +const renderAtomRef = function(ref: AtomRef.ReadonlyRef, onValue: (_: A) => void) { + return createRoot((dispose) => { + const accessor = useAtomRef(() => ref) + createEffect(() => { + onValue(accessor()) + }) + return dispose + }) +} + +const renderAccessor = function(makeAccessor: () => Accessor, onValue: (_: A) => void) { + return createRoot((dispose) => { + const accessor = makeAccessor() + createEffect(() => { + onValue(accessor()) + }) + return dispose + }) +} + +const renderAtomValue = function( + atom: Atom.Atom, + onValue: (_: B) => void, + options?: { readonly registry?: AtomRegistry.AtomRegistry; readonly map?: (_: A) => B } +) { + return createRoot((dispose) => { + const run = () => { + const accessor = options?.map ? useAtomValue(() => atom, options.map) : useAtomValue(() => atom) + createEffect(() => { + onValue(accessor() as B) + }) + return null + } + + if (options?.registry) { + createComponent(RegistryContext.Provider, { + value: options.registry, + get children() { + return run() + } + }) + } else { + run() + } + + return dispose + }) +} + +const renderAtomResource = function( + atom: () => Atom.Atom>, + options?: { + readonly suspendOnWaiting?: boolean | undefined + } +) { + let resource: + | Resource + | undefined + const dispose = createRoot((dispose) => { + ;[resource] = useAtomResource(atom, options) + createEffect(() => { + resource?.() + }) + return dispose + }) + return { resource: resource!, dispose } +} diff --git a/.repos/effect/packages/atom/solid/tsconfig.json b/.repos/effect/packages/atom/solid/tsconfig.json new file mode 100644 index 000000000..e2a8ca19a --- /dev/null +++ b/.repos/effect/packages/atom/solid/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../../effect" } + ] +} diff --git a/.repos/effect/packages/atom/solid/vitest.setup.ts b/.repos/effect/packages/atom/solid/vitest.setup.ts new file mode 100644 index 000000000..ce48643f7 --- /dev/null +++ b/.repos/effect/packages/atom/solid/vitest.setup.ts @@ -0,0 +1,8 @@ +// oxlint-disable-next-line no-unassigned-import +import "@testing-library/jest-dom/vitest" +import { cleanup } from "@solidjs/testing-library" +import { afterEach } from "vitest" + +afterEach(() => { + cleanup() +}) diff --git a/.repos/effect/packages/atom/vue/CHANGELOG.md b/.repos/effect/packages/atom/vue/CHANGELOG.md new file mode 100644 index 000000000..0cfa84462 --- /dev/null +++ b/.repos/effect/packages/atom/vue/CHANGELOG.md @@ -0,0 +1,799 @@ +# @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 + +- Updated dependencies [[`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5)]: + - effect@4.0.0-beta.90 + +## 4.0.0-beta.89 + +### Patch Changes + +- Updated dependencies [[`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad), [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669), [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6)]: + - effect@4.0.0-beta.89 + +## 4.0.0-beta.88 + +### Patch Changes + +- Updated dependencies [[`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b), [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4), [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da)]: + - effect@4.0.0-beta.88 + +## 4.0.0-beta.87 + +### Patch Changes + +- Updated dependencies [[`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956), [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed)]: + - effect@4.0.0-beta.87 + +## 4.0.0-beta.86 + +### Patch Changes + +- Updated dependencies [[`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64), [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e), [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab), [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc), [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9), [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12), [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46), [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9), [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e)]: + - effect@4.0.0-beta.86 + +## 4.0.0-beta.85 + +### Patch Changes + +- Updated dependencies [[`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c), [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134), [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a), [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a), [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5), [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1)]: + - effect@4.0.0-beta.85 + +## 4.0.0-beta.84 + +### Patch Changes + +- Updated dependencies [[`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e), [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda), [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f), [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b), [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610), [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246), [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc), [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a), [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2), [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72)]: + - effect@4.0.0-beta.84 + +## 4.0.0-beta.83 + +### Patch Changes + +- Updated dependencies [[`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9)]: + - effect@4.0.0-beta.83 + +## 4.0.0-beta.82 + +### Patch Changes + +- Updated dependencies [[`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3)]: + - effect@4.0.0-beta.82 + +## 4.0.0-beta.81 + +### Patch Changes + +- Updated dependencies [[`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914), [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39), [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484), [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36)]: + - effect@4.0.0-beta.81 + +## 4.0.0-beta.80 + +### Patch Changes + +- Updated dependencies [[`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6), [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182), [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12), [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d), [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b)]: + - effect@4.0.0-beta.80 + +## 4.0.0-beta.79 + +### Patch Changes + +- Updated dependencies [[`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3), [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079), [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3), [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44), [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6), [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd), [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f), [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6), [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d), [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75), [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03)]: + - effect@4.0.0-beta.79 + +## 4.0.0-beta.78 + +### Patch Changes + +- Updated dependencies [[`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1), [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba), [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd)]: + - effect@4.0.0-beta.78 + +## 4.0.0-beta.77 + +### Patch Changes + +- Updated dependencies [[`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23), [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51)]: + - effect@4.0.0-beta.77 + +## 4.0.0-beta.76 + +### Patch Changes + +- Updated dependencies [[`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12), [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84), [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386)]: + - effect@4.0.0-beta.76 + +## 4.0.0-beta.75 + +### Patch Changes + +- Updated dependencies [[`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7), [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e), [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3), [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00), [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427)]: + - effect@4.0.0-beta.75 + +## 4.0.0-beta.74 + +### Patch Changes + +- Updated dependencies [[`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e)]: + - effect@4.0.0-beta.74 + +## 4.0.0-beta.73 + +### Patch Changes + +- Updated dependencies [[`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75), [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e)]: + - effect@4.0.0-beta.73 + +## 4.0.0-beta.72 + +### Patch Changes + +- Updated dependencies [[`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb), [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5), [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239)]: + - effect@4.0.0-beta.72 + +## 4.0.0-beta.71 + +### Patch Changes + +- Updated dependencies [[`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293), [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230), [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63), [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485), [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04), [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349), [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618), [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d), [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771), [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba), [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190)]: + - effect@4.0.0-beta.71 + +## 4.0.0-beta.70 + +### Patch Changes + +- Updated dependencies [[`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e), [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839)]: + - effect@4.0.0-beta.70 + +## 4.0.0-beta.69 + +### Patch Changes + +- Updated dependencies [[`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a), [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178), [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118), [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b), [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470)]: + - effect@4.0.0-beta.69 + +## 4.0.0-beta.68 + +### Patch Changes + +- Updated dependencies [[`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5), [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa), [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557), [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6)]: + - effect@4.0.0-beta.68 + +## 4.0.0-beta.67 + +### Patch Changes + +- Updated dependencies [[`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f), [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8), [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836), [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527), [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87), [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85), [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b), [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075), [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f), [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e), [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd)]: + - effect@4.0.0-beta.67 + +## 4.0.0-beta.66 + +### Patch Changes + +- Updated dependencies [[`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2), [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436), [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b), [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900), [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4), [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3)]: + - effect@4.0.0-beta.66 + +## 4.0.0-beta.65 + +### Patch Changes + +- Updated dependencies [[`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2)]: + - effect@4.0.0-beta.65 + +## 4.0.0-beta.64 + +### Patch Changes + +- Updated dependencies [[`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5)]: + - effect@4.0.0-beta.64 + +## 4.0.0-beta.63 + +### Patch Changes + +- Updated dependencies [[`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac), [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102)]: + - effect@4.0.0-beta.63 + +## 4.0.0-beta.62 + +### Patch Changes + +- Updated dependencies [[`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7)]: + - effect@4.0.0-beta.62 + +## 4.0.0-beta.61 + +### Patch Changes + +- Updated dependencies [[`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f), [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c), [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b)]: + - effect@4.0.0-beta.61 + +## 4.0.0-beta.60 + +### Patch Changes + +- Updated dependencies [[`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09), [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620), [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4), [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe), [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a)]: + - effect@4.0.0-beta.60 + +## 4.0.0-beta.59 + +### Patch Changes + +- Updated dependencies [[`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d)]: + - effect@4.0.0-beta.59 + +## 4.0.0-beta.58 + +### Patch Changes + +- Updated dependencies [[`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec), [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec)]: + - effect@4.0.0-beta.58 + +## 4.0.0-beta.57 + +### Patch Changes + +- Updated dependencies [[`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060), [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851)]: + - effect@4.0.0-beta.57 + +## 4.0.0-beta.56 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- Updated dependencies [[`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93), [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a)]: + - effect@4.0.0-beta.55 + +## 4.0.0-beta.54 + +### Patch Changes + +- Updated dependencies [[`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8), [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96)]: + - effect@4.0.0-beta.54 + +## 4.0.0-beta.53 + +### Patch Changes + +- Updated dependencies [[`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e), [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141), [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5), [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88), [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac)]: + - effect@4.0.0-beta.53 + +## 4.0.0-beta.52 + +### Patch Changes + +- Updated dependencies [[`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1), [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499), [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e)]: + - effect@4.0.0-beta.52 + +## 4.0.0-beta.51 + +### Patch Changes + +- Updated dependencies [[`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344), [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a), [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7), [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd), [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27), [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82), [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0), [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0)]: + - effect@4.0.0-beta.51 + +## 4.0.0-beta.50 + +### Patch Changes + +- Updated dependencies [[`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414), [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b)]: + - effect@4.0.0-beta.50 + +## 4.0.0-beta.49 + +### Patch Changes + +- Updated dependencies [[`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45), [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5), [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac)]: + - effect@4.0.0-beta.49 + +## 4.0.0-beta.48 + +### Patch Changes + +- Updated dependencies [[`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8), [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070), [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070)]: + - effect@4.0.0-beta.48 + +## 4.0.0-beta.47 + +### Patch Changes + +- Updated dependencies [[`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45), [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db), [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12), [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406)]: + - effect@4.0.0-beta.47 + +## 4.0.0-beta.46 + +### Patch Changes + +- Updated dependencies [[`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee)]: + - effect@4.0.0-beta.46 + +## 4.0.0-beta.45 + +### Patch Changes + +- Updated dependencies [[`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb)]: + - effect@4.0.0-beta.45 + +## 4.0.0-beta.44 + +### Patch Changes + +- Updated dependencies [[`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1), [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced), [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82), [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877), [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e), [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664), [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9), [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9), [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b), [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb), [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5), [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02), [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22), [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4), [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888), [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0), [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226), [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee), [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c), [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf), [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060), [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4), [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9), [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6), [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a), [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a), [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4), [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0), [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970), [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780), [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020)]: + - effect@4.0.0-beta.44 + +## 4.0.0-beta.43 + +### Patch Changes + +- Updated dependencies [[`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596), [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e), [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e), [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8), [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02)]: + - effect@4.0.0-beta.43 + +## 4.0.0-beta.42 + +### Patch Changes + +- Updated dependencies [[`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee), [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa), [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f), [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133), [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6), [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623)]: + - effect@4.0.0-beta.42 + +## 4.0.0-beta.41 + +### Patch Changes + +- Updated dependencies [[`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283), [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30), [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814), [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86), [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84), [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48), [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f), [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95)]: + - effect@4.0.0-beta.41 + +## 4.0.0-beta.40 + +### Patch Changes + +- Updated dependencies [[`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a), [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8)]: + - effect@4.0.0-beta.40 + +## 4.0.0-beta.39 + +### Patch Changes + +- Updated dependencies [[`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0), [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071), [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513), [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b), [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996)]: + - effect@4.0.0-beta.39 + +## 4.0.0-beta.38 + +### Patch Changes + +- Updated dependencies [[`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd), [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb), [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6), [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd), [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0)]: + - effect@4.0.0-beta.38 + +## 4.0.0-beta.37 + +### Patch Changes + +- Updated dependencies [[`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2), [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80), [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608), [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6), [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2), [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4), [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c), [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd), [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe), [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7)]: + - effect@4.0.0-beta.37 + +## 4.0.0-beta.36 + +### Patch Changes + +- Updated dependencies [[`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81), [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e), [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e), [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6), [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b), [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480), [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c), [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c), [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185), [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611), [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32), [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61), [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b), [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542)]: + - effect@4.0.0-beta.36 + +## 4.0.0-beta.35 + +### Patch Changes + +- Updated dependencies [[`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8), [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04), [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07), [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2), [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216), [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98), [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801), [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7)]: + - effect@4.0.0-beta.35 + +## 4.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [[`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1), [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f), [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685), [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e), [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28), [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2), [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9), [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0), [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba)]: + - effect@4.0.0-beta.34 + +## 4.0.0-beta.33 + +### Patch Changes + +- Updated dependencies [[`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295)]: + - effect@4.0.0-beta.33 + +## 4.0.0-beta.32 + +### Patch Changes + +- Updated dependencies [[`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a), [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74), [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba), [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e), [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732), [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7), [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c), [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f), [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3), [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f), [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245), [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0), [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d), [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb), [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7), [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45), [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4), [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e)]: + - effect@4.0.0-beta.32 + +## 4.0.0-beta.31 + +### Patch Changes + +- Updated dependencies [[`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544), [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28), [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf), [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7), [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2), [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45), [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462), [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064), [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee), [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423)]: + - effect@4.0.0-beta.31 + +## 4.0.0-beta.30 + +### Patch Changes + +- Updated dependencies [[`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32), [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f), [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4), [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5), [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103), [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748), [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b), [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268)]: + - effect@4.0.0-beta.30 + +## 4.0.0-beta.29 + +### Patch Changes + +- Updated dependencies [[`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662), [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07), [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d), [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f), [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67)]: + - effect@4.0.0-beta.29 + +## 4.0.0-beta.28 + +### Patch Changes + +- Updated dependencies [[`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6), [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556), [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa), [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270), [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834), [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57), [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc), [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8), [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf), [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc), [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2), [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e), [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc), [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429), [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70), [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a), [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341)]: + - effect@4.0.0-beta.28 + +## 4.0.0-beta.27 + +### Patch Changes + +- Updated dependencies [[`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4), [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7), [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142), [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7)]: + - effect@4.0.0-beta.27 + +## 4.0.0-beta.26 + +### Patch Changes + +- Updated dependencies [[`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42), [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86), [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07), [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f), [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc), [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99), [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f), [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541), [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1), [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446), [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c), [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926)]: + - effect@4.0.0-beta.26 + +## 4.0.0-beta.25 + +### Patch Changes + +- Updated dependencies [[`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714), [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f), [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63), [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d)]: + - effect@4.0.0-beta.25 + +## 4.0.0-beta.24 + +### Patch Changes + +- Updated dependencies [[`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9), [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399), [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0), [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2), [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385)]: + - effect@4.0.0-beta.24 + +## 4.0.0-beta.23 + +### Patch Changes + +- Updated dependencies [[`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966)]: + - effect@4.0.0-beta.23 + +## 4.0.0-beta.22 + +### Patch Changes + +- Updated dependencies [[`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479), [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183), [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294), [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4), [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b), [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e), [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95), [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939), [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b)]: + - effect@4.0.0-beta.22 + +## 4.0.0-beta.21 + +### Patch Changes + +- Updated dependencies [[`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb), [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7), [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57), [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28), [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55), [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2), [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e), [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb), [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882), [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007)]: + - effect@4.0.0-beta.21 + +## 4.0.0-beta.20 + +### Patch Changes + +- Updated dependencies [[`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74), [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732), [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8), [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c), [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34), [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827)]: + - effect@4.0.0-beta.20 + +## 4.0.0-beta.19 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Patch Changes + +- Updated dependencies [[`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8), [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5), [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d)]: + - effect@4.0.0-beta.18 + +## 4.0.0-beta.17 + +### Patch Changes + +- Updated dependencies [[`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781)]: + - effect@4.0.0-beta.17 + +## 4.0.0-beta.16 + +### Patch Changes + +- Updated dependencies [[`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a), [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b), [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb)]: + - effect@4.0.0-beta.16 + +## 4.0.0-beta.15 + +### Patch Changes + +- Updated dependencies [[`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba), [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e), [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b), [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28), [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566), [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2)]: + - effect@4.0.0-beta.15 + +## 4.0.0-beta.14 + +### Patch Changes + +- Updated dependencies [[`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350), [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9)]: + - effect@4.0.0-beta.14 + +## 4.0.0-beta.13 + +### Patch Changes + +- Updated dependencies [[`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd), [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b), [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562), [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2), [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa), [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08), [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de), [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb), [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f), [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab)]: + - effect@4.0.0-beta.13 + +## 4.0.0-beta.12 + +### Patch Changes + +- Updated dependencies [[`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07), [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857), [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb), [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5), [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca), [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc), [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2), [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb), [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160), [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882), [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a), [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e), [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd)]: + - effect@4.0.0-beta.12 + +## 4.0.0-beta.11 + +### Patch Changes + +- Updated dependencies [[`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16), [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199), [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310), [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c), [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814), [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3), [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1), [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997), [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5), [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49), [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0), [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8), [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5), [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e)]: + - effect@4.0.0-beta.11 + +## 4.0.0-beta.10 + +### Patch Changes + +- Updated dependencies [[`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74), [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4), [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a), [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36), [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2), [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2), [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab), [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8)]: + - effect@4.0.0-beta.10 + +## 4.0.0-beta.9 + +### Patch Changes + +- Updated dependencies [[`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350), [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422)]: + - effect@4.0.0-beta.9 + +## 4.0.0-beta.8 + +### Patch Changes + +- Updated dependencies [[`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752), [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df)]: + - effect@4.0.0-beta.8 + +## 4.0.0-beta.7 + +### Patch Changes + +- Updated dependencies [[`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a), [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1), [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf), [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430), [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e), [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430)]: + - effect@4.0.0-beta.7 + +## 4.0.0-beta.6 + +### Patch Changes + +- Updated dependencies [[`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1), [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b), [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85), [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5), [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253), [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b), [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8), [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d), [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4), [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c), [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906), [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f), [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a), [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4), [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11), [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d), [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005), [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe), [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894), [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0)]: + - effect@4.0.0-beta.6 + +## 4.0.0-beta.5 + +### Patch Changes + +- Updated dependencies [[`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8), [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083), [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8)]: + - effect@4.0.0-beta.5 + +## 4.0.0-beta.4 + +### Patch Changes + +- Updated dependencies [[`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32), [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c)]: + - effect@4.0.0-beta.4 + +## 4.0.0-beta.3 + +### Patch Changes + +- Updated dependencies [[`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530), [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e)]: + - effect@4.0.0-beta.3 + +## 4.0.0-beta.2 + +### Patch Changes + +- Updated dependencies [[`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0), [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78), [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae)]: + - effect@4.0.0-beta.2 + +## 4.0.0-beta.1 + +### Patch Changes + +- Updated dependencies [[`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743), [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43)]: + - effect@4.0.0-beta.1 + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta + +### Patch Changes + +- Updated dependencies [[`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66)]: + - effect@4.0.0-beta.0 diff --git a/.repos/effect/packages/atom/vue/LICENSE b/.repos/effect/packages/atom/vue/LICENSE new file mode 100644 index 000000000..7f6fe480f --- /dev/null +++ b/.repos/effect/packages/atom/vue/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-present The Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.repos/effect/packages/atom/vue/README.md b/.repos/effect/packages/atom/vue/README.md new file mode 100644 index 000000000..c6e27a485 --- /dev/null +++ b/.repos/effect/packages/atom/vue/README.md @@ -0,0 +1,14 @@ +# @effect/atom-vue + +[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 + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/atom-vue) diff --git a/.repos/effect/packages/atom/vue/package.json b/.repos/effect/packages/atom/vue/package.json new file mode 100644 index 000000000..20c039cc1 --- /dev/null +++ b/.repos/effect/packages/atom/vue/package.json @@ -0,0 +1,70 @@ +{ + "name": "@effect/atom-vue", + "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.git", + "directory": "packages/atom/vue" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "vue", + "database" + ], + "keywords": [ + "typescript", + "vue", + "database" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./internal/*": null, + "./index": null, + "./*/index": null + } + }, + "scripts": { + "build": "tsc -b tsconfig.json && pnpm babel", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "devDependencies": { + "effect": "workspace:^", + "vue": "^3.5.41" + }, + "peerDependencies": { + "effect": "workspace:^", + "vue": ">=3.5.39 <4.0.0" + } +} diff --git a/.repos/effect/packages/atom/vue/src/index.ts b/.repos/effect/packages/atom/vue/src/index.ts new file mode 100644 index 000000000..a2f434007 --- /dev/null +++ b/.repos/effect/packages/atom/vue/src/index.ts @@ -0,0 +1,211 @@ +/** + * @since 4.0.0 + */ +import * as Cause from "effect/Cause" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult" +import type * as Atom from "effect/unstable/reactivity/Atom" +import type * as AtomRef from "effect/unstable/reactivity/AtomRef" +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" +import { computed, type ComputedRef, inject, type InjectionKey, type Ref, shallowRef, watchEffect } from "vue" + +/** + * @since 4.0.0 + * @category re-exports + */ +export * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" + +/** + * @since 4.0.0 + * @category re-exports + */ +export * as AsyncResult from "effect/unstable/reactivity/AsyncResult" + +/** + * @since 4.0.0 + * @category re-exports + */ +export * as Atom from "effect/unstable/reactivity/Atom" + +/** + * @since 4.0.0 + * @category re-exports + */ +export * as AtomRef from "effect/unstable/reactivity/AtomRef" + +/** + * @since 4.0.0 + * @category re-exports + */ +export * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi" + +/** + * @since 4.0.0 + * @category re-exports + */ +export * as AtomRpc from "effect/unstable/reactivity/AtomRpc" + +/** + * @since 4.0.0 + * @category symbols + */ +export const registryKey = Symbol.for("@effect/atom-vue/registryKey") as InjectionKey + +/** + * @since 4.0.0 + * @category constants + */ +export const defaultRegistry: AtomRegistry.AtomRegistry = AtomRegistry.make() + +/** + * @since 4.0.0 + * @category accessors + */ +export const injectRegistry = (): AtomRegistry.AtomRegistry => { + return inject(registryKey, defaultRegistry) +} + +const useAtomValueRef = >(atom: () => A) => { + const registry = injectRegistry() + const atomRef = computed(atom) + const value = shallowRef(undefined as any as A) + watchEffect((onCleanup) => { + onCleanup(registry.subscribe(atomRef.value, (nextValue: Atom.Type) => { + value.value = nextValue + }, { immediate: true })) + }) + return [value as Readonly>>, atomRef, registry] as const +} + +/** + * @since 4.0.0 + * @category composables + */ +export const useAtom = ( + atom: () => Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): readonly [ + Readonly>, + write: "promise" extends Mode ? ( + (value: W) => Promise> + ) : + "promiseExit" extends Mode ? ( + (value: W) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) +] => { + const [value, atomRef, registry] = useAtomValueRef(atom) + return [value as Readonly>, setAtom(registry, atomRef, options)] +} + +/** + * @since 4.0.0 + * @category composables + */ +export const useAtomValue = (atom: () => Atom.Atom): Readonly> => useAtomValueRef(atom)[0] + +const flattenExit = (exit: Exit.Exit): A => { + if (Exit.isSuccess(exit)) return exit.value + throw Cause.squash(exit.cause) +} + +function setAtom( + registry: AtomRegistry.AtomRegistry, + atomRef: ComputedRef>, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): "promise" extends Mode ? ( + ( + value: W, + options?: { + readonly signal?: AbortSignal | undefined + } | undefined + ) => Promise> + ) : + "promiseExit" extends Mode ? ( + ( + value: W, + options?: { + readonly signal?: AbortSignal | undefined + } | undefined + ) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) +{ + if (options?.mode === "promise" || options?.mode === "promiseExit") { + return ((value: W, opts?: any) => { + registry.set(atomRef.value, value) + const promise = Effect.runPromiseExit( + AtomRegistry.getResult( + registry, + atomRef.value as Atom.Atom>, + { suspendOnWaiting: true } + ), + opts + ) + return options!.mode === "promise" ? promise.then(flattenExit) : promise + }) as any + } + return ((value: W | ((value: R) => W)) => { + registry.set(atomRef.value, typeof value === "function" ? (value as any)(registry.get(atomRef.value)) : value) + }) as any +} + +/** + * @since 4.0.0 + * @category composables + */ +export const useAtomSet = < + R, + W, + Mode extends "value" | "promise" | "promiseExit" = never +>( + atom: () => Atom.Writable, + options?: { + readonly mode?: ([R] extends [AsyncResult.AsyncResult] ? Mode : "value") | undefined + } +): "promise" extends Mode ? ( + ( + value: W, + options?: { + readonly signal?: AbortSignal | undefined + } | undefined + ) => Promise> + ) : + "promiseExit" extends Mode ? ( + ( + value: W, + options?: { + readonly signal?: AbortSignal | undefined + } | undefined + ) => Promise, AsyncResult.AsyncResult.Failure>> + ) : + ((value: W | ((value: R) => W)) => void) => +{ + const registry = injectRegistry() + const atomRef = computed(atom) + watchEffect((onCleanup) => { + onCleanup(registry.mount(atomRef.value)) + }) + return setAtom(registry, atomRef, options) +} + +/** + * @since 4.0.0 + * @category composables + */ +export const useAtomRef = (atomRef: () => AtomRef.ReadonlyRef): Readonly> => { + const atomRefRef = computed(atomRef) + const value = shallowRef(atomRefRef.value.value) + watchEffect((onCleanup) => { + const ref = atomRefRef.value + onCleanup(ref.subscribe((next: A) => { + value.value = next + })) + }) + return value as Readonly> +} diff --git a/.repos/effect/packages/atom/vue/test/index.test.ts b/.repos/effect/packages/atom/vue/test/index.test.ts new file mode 100644 index 000000000..79cf1df55 --- /dev/null +++ b/.repos/effect/packages/atom/vue/test/index.test.ts @@ -0,0 +1,5 @@ +import { describe, test } from "vitest" + +describe("atom-vue", () => { + test("", () => {}) +}) diff --git a/.repos/effect/packages/atom/vue/tsconfig.json b/.repos/effect/packages/atom/vue/tsconfig.json new file mode 100644 index 000000000..dc6af14d2 --- /dev/null +++ b/.repos/effect/packages/atom/vue/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../../effect" } + ], + "compilerOptions": { + "paths": { + "effect": ["../../effect/src/index.ts"], + "effect/*": ["../../effect/src/*.ts"] + } + } +} diff --git a/.repos/effect/packages/effect/CHANGELOG.md b/.repos/effect/packages/effect/CHANGELOG.md new file mode 100644 index 000000000..39af4f04f --- /dev/null +++ b/.repos/effect/packages/effect/CHANGELOG.md @@ -0,0 +1,3519 @@ +# 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 + +- [#2483](https://github.com/Effect-TS/effect-smol/pull/2483) [`d237fdf`](https://github.com/Effect-TS/effect-smol/commit/d237fdf726481f76eb52a6196e111b24122bc3d5) Thanks @tim-smart! - Fix `Config.schema` so missing array values are treated as missing data, allowing `Config.withDefault` to apply. + +## 4.0.0-beta.89 + +### Patch Changes + +- [#2475](https://github.com/Effect-TS/effect-smol/pull/2475) [`b7d46ab`](https://github.com/Effect-TS/effect-smol/commit/b7d46ab7e1a29d8711817bab583c9febf48a0dad) Thanks @tim-smart! - Update `Schema.Void` to model ignored `void` return values. + + Runtime parsing now accepts any present value and discards it as `undefined`. + This matches TypeScript `void` return values, where callers do not observe the + returned value. Use `Schema.Undefined` when the input must be exactly + `undefined`. + +- [#2479](https://github.com/Effect-TS/effect-smol/pull/2479) [`7777e15`](https://github.com/Effect-TS/effect-smol/commit/7777e1540fd3680dd8346723cffec812b9384669) Thanks @tim-smart! - Add custom error callbacks to Effect.fromOption. + +- [#2480](https://github.com/Effect-TS/effect-smol/pull/2480) [`5376197`](https://github.com/Effect-TS/effect-smol/commit/5376197ca8e50358a41b1fd3cec27bd1ec680ec6) Thanks @tim-smart! - render causes in OtlpTracer exception events + +## 4.0.0-beta.88 + +### Patch Changes + +- [#2472](https://github.com/Effect-TS/effect-smol/pull/2472) [`911f1b8`](https://github.com/Effect-TS/effect-smol/commit/911f1b84790ce42b3a70c95b33e6f6fd9e74de8b) Thanks @tim-smart! - Add adaptive consume and feedback operations to the unstable persistent RateLimiterStore API, including in-memory and Redis-backed bounded cooldown, learning, learned pacing, and expiry behavior for 429 Retry-After feedback. + +- [#2457](https://github.com/Effect-TS/effect-smol/pull/2457) [`8beeeea`](https://github.com/Effect-TS/effect-smol/commit/8beeeea52879d8613a39468848f01c3092bd54d4) Thanks @P0lip! - Localize missing rpc method errors to the provided request id + +- [#2428](https://github.com/Effect-TS/effect-smol/pull/2428) [`c306fcf`](https://github.com/Effect-TS/effect-smol/commit/c306fcfeb1ef38455156932a1faf49292b1318da) Thanks @MrGovindan! - Add `isOpen` to `Latch` to allow querying the latch's open state + +## 4.0.0-beta.87 + +### Patch Changes + +- [#2468](https://github.com/Effect-TS/effect-smol/pull/2468) [`5a0c1a4`](https://github.com/Effect-TS/effect-smol/commit/5a0c1a4faee5707b5cc35e646ff1ffdad70f1956) Thanks @gcanti! - Expose the original input schema on `Schema.toType`, `Schema.toEncoded`, `Schema.toCodecJson`, and `Schema.toCodecStringTree` results via the `schema` property. This aligns these schema wrappers with other wrappers that retain their source schema for type-level and runtime introspection. + +- [#2466](https://github.com/Effect-TS/effect-smol/pull/2466) [`1eea2ea`](https://github.com/Effect-TS/effect-smol/commit/1eea2ea3795ba47316b82b1ac8d4612c0ba389ed) Thanks @gcanti! - Use `URL.canParse` to validate URL string schema decoding before constructing a `URL`. This avoids relying on thrown exceptions for routine validation while preserving the same invalid URL issue and successful decode output. + +## 4.0.0-beta.86 + +### Patch Changes + +- [#2462](https://github.com/Effect-TS/effect-smol/pull/2462) [`0b5795a`](https://github.com/Effect-TS/effect-smol/commit/0b5795a0ab4395e8f15955d8d96f2303084bfc64) Thanks @tim-smart! - Add `Statement.valuesUnprepared` for returning unprepared SQL statement rows as arrays. + +- [#2455](https://github.com/Effect-TS/effect-smol/pull/2455) [`3e3a859`](https://github.com/Effect-TS/effect-smol/commit/3e3a859ec6351a9e0d31674aabbd48fcefabb12e) Thanks @fubhy! - Fix `Cron.next` skipping earlier matching days when the upcoming day-of-month does not exist in the current month. + +- [#2454](https://github.com/Effect-TS/effect-smol/pull/2454) [`7dbec24`](https://github.com/Effect-TS/effect-smol/commit/7dbec240dbf3bca599a20c486632abce694ef5ab) Thanks @StarpTech! - Exclude response metadata from HTTP server span failures after response headers have been sent. + +- [#2449](https://github.com/Effect-TS/effect-smol/pull/2449) [`d8c00a1`](https://github.com/Effect-TS/effect-smol/commit/d8c00a171ac7141e8adc08c332d1162d9a9d56fc) Thanks @gcanti! - Fix Schema handling of encoded-side checks for container ASTs. + + Checks added after `flip` are now preserved as `encodingChecks` across + `Declaration`, `Arrays`, `Objects`, and `Union`, even when rebuilding the AST + does not change child nodes. `toType` now projects those checks consistently, + and parsing applies encoded-side checks to the local encoded value when an + encoding chain is present without allowing encoded-side `parseOptions` + annotations to affect the current parser side. + +- [#2446](https://github.com/Effect-TS/effect-smol/pull/2446) [`85b6317`](https://github.com/Effect-TS/effect-smol/commit/85b631701e935866f2762bd595237aa718370cd9) Thanks @IMax153! - Allow schemas provided to CLI flags / arguments to utilize the environment required by the CLI + +- [#2452](https://github.com/Effect-TS/effect-smol/pull/2452) [`6d0fda0`](https://github.com/Effect-TS/effect-smol/commit/6d0fda0d0cbdfffc523c89c57dfdb1608f84fb12) Thanks @gcanti! - Remove the `keepDeclarations` option from `Schema.toCodecStringTree`. + +- [#2461](https://github.com/Effect-TS/effect-smol/pull/2461) [`108a933`](https://github.com/Effect-TS/effect-smol/commit/108a9335ff8571928197e5847a09c28ac83d6f46) Thanks @tim-smart! - Fail RpcClient HTTP requests with a defect when the response stream closes before the request receives a terminal response. + +- [#2442](https://github.com/Effect-TS/effect-smol/pull/2442) [`7e1f455`](https://github.com/Effect-TS/effect-smol/commit/7e1f455fab5005d769b939c91e519d450f802cf9) Thanks @gcanti! - Improve Schema type-level performance by lazily computing schema views, + specializing common struct projections, and using lighter schema constraints at + API boundaries that do not need the full schema protocol. + + This also adds the Schema type-performance benchmark suite, introduces + `Schema.toCodecArrayFromSingle`, preserves canonical StringTree array codecs, + renames the arbitrary-generation annotation constraint for clarity, and updates + affected codec, parser, channel, SQL, HTTP API, persistence, RPC, AI, OpenAPI, + and workflow typings to match the refined Schema surface. + +- [#2464](https://github.com/Effect-TS/effect-smol/pull/2464) [`46b3e79`](https://github.com/Effect-TS/effect-smol/commit/46b3e79944cfdae7901eb148135c85b7eb39834e) Thanks @tim-smart! - do not use performance.timeOrigin and calculate origins lazily + +## 4.0.0-beta.85 + +### Patch Changes + +- [#2436](https://github.com/Effect-TS/effect-smol/pull/2436) [`328d97c`](https://github.com/Effect-TS/effect-smol/commit/328d97cc53c0dcb89077a5623e35b095eaa59a8c) Thanks @MohanedMashaly! - change default operation in redis from LPUSH TO RPUSH + +- [#2431](https://github.com/Effect-TS/effect-smol/pull/2431) [`8441836`](https://github.com/Effect-TS/effect-smol/commit/8441836e6dde70e8ae2126be9cefe9b45798b134) Thanks @gcanti! - Derive template literal arbitraries from encoded parts, closes [#2414](https://github.com/Effect-TS/effect-smol/issues/2414). + +- [#2439](https://github.com/Effect-TS/effect-smol/pull/2439) [`074e436`](https://github.com/Effect-TS/effect-smol/commit/074e4361091289104cb0ab6959dc3b0ea7794a6a) Thanks @gcanti! - Allow schema class `.extend` to accept a `Struct` and preserve checks from the extension schema, closes [#2419](https://github.com/Effect-TS/effect-smol/issues/2419). + +- [#2444](https://github.com/Effect-TS/effect-smol/pull/2444) [`c1dfd60`](https://github.com/Effect-TS/effect-smol/commit/c1dfd60663eb13a58916f3712d877499943b628a) Thanks @bweis! - Avoid throwing when `Error.stackTraceLimit` is non-writable (frozen intrinsics / SES / deterministic sandboxes such as Temporal). + + Effect manipulates `Error.stackTraceLimit` in several internal spots to capture short or empty stack traces cheaply. In hardened environments where `Error` is frozen and `stackTraceLimit` is read-only, assigning to it throws, which broke Effect entirely. Stack-trace-limit manipulation is now best-effort and silently no-ops when the property cannot be modified, mirroring Node's own internal guard. Behavior in normal (writable) environments is unchanged. + +- [#2425](https://github.com/Effect-TS/effect-smol/pull/2425) [`2ba316b`](https://github.com/Effect-TS/effect-smol/commit/2ba316bd15fcbf1c50626500d44a2c9b3bec19f5) Thanks @tim-smart! - Add Random.choice for selecting a random element from an iterable. + +- [#2434](https://github.com/Effect-TS/effect-smol/pull/2434) [`7ce7344`](https://github.com/Effect-TS/effect-smol/commit/7ce7344c41056c79e2ee19ee6a9346c0f1d227c1) Thanks @gcanti! - Use semantic matching for TemplateLiteral parsing and index signature keys + + Replace regex-based TemplateLiteral parsing with backtracking segmentation over + template literal parts, applying part checks during matching. + + Use schema membership when selecting Record index signature keys, including + checked string, number, symbol, and TemplateLiteral parameters. Tighten valid + index signature parameters on both type and encoded sides, and preserve key + parameter semantics in codec transformations. + +## 4.0.0-beta.84 + +### Patch Changes + +- [#2420](https://github.com/Effect-TS/effect-smol/pull/2420) [`87f52ba`](https://github.com/Effect-TS/effect-smol/commit/87f52ba16c4370ffa3f84bf8e53038e1419c284e) Thanks @tim-smart! - Add `Effect.transposeOption` for converting an `Option>` into an `Effect, E, R>`. + +- [#2374](https://github.com/Effect-TS/effect-smol/pull/2374) [`b8ee07f`](https://github.com/Effect-TS/effect-smol/commit/b8ee07ffda8903b5ec2e45a786ddcba59f128fda) Thanks @gcanti! - Import unconstrained JSON Schema nodes as `Schema.Json` instead of `Schema.Unknown`. + +- [#2407](https://github.com/Effect-TS/effect-smol/pull/2407) [`867c0d7`](https://github.com/Effect-TS/effect-smol/commit/867c0d70a09079b040260d45a1e92ff04dbfbf2f) Thanks @gcanti! - Normalize error behavior for Schema and SchemaParser boundary APIs. + + `SchemaError` now extends `Data.TaggedError`, so it is also a native `Error`. + SchemaParser Promise APIs now reject an `Error` whose cause is the + `SchemaIssue.Issue` for schema failures. + + Schema and SchemaParser `Effect` and `Exit` adapters now preserve full causes + while mapping schema issue failures to their public error type. The `is`, + `asserts`, `Promise`, `Sync`, `Result`, `Option`, `make`, and `makeOption` + adapters now distinguish schema issues from non-schema causes. Schema-only + failures are converted to the adapter's normal representation (`false`, + rejected or thrown schema error, `Result.fail`, or `None`), while non-schema + causes throw or reject with an `Error` whose cause is the underlying `Cause`. + +- [#2409](https://github.com/Effect-TS/effect-smol/pull/2409) [`b93bc6c`](https://github.com/Effect-TS/effect-smol/commit/b93bc6c9cb27b909a41d094c97c4f9d25bbc6d6b) Thanks @tim-smart! - Fix Stream.runForEachWhile so it continues across chunk boundaries while the predicate returns true and stops when the predicate returns false. + +- [#2424](https://github.com/Effect-TS/effect-smol/pull/2424) [`57d387f`](https://github.com/Effect-TS/effect-smol/commit/57d387f92c30ab63e15e3e641f0a903b65886610) Thanks @tim-smart! - Fix cluster workflow activity defect hydration + +- [#2403](https://github.com/Effect-TS/effect-smol/pull/2403) [`bacca41`](https://github.com/Effect-TS/effect-smol/commit/bacca4141c2400effae1eabfdb36c89a459cf246) Thanks @lloydrichards! - align ProcessInput.Input runtime field name with type definition on Prompt.custom + +- [#2423](https://github.com/Effect-TS/effect-smol/pull/2423) [`0f8ac79`](https://github.com/Effect-TS/effect-smol/commit/0f8ac7959d29ed68c68ce25aabd6bf0cb7e63ecc) Thanks @tim-smart! - RpcGroup.toHandlers is definition first + +- [#2383](https://github.com/Effect-TS/effect-smol/pull/2383) [`25b4482`](https://github.com/Effect-TS/effect-smol/commit/25b448270c01317703f25107e1480d4cd0246d9a) Thanks @gcanti! - Fix config path composition and directory-backed lookup behavior. + + `ConfigProvider.orElse` now keeps each side's own `nested` and `mapInput` + behavior. Applying `nested` or `mapInput` to a combined provider now applies the + same transformation to both sides. + + `ConfigProvider` path transformations now compose as a single path function. + This makes `nested` and `mapInput` behave consistently with normal function + composition. + + `Config.nested` now tracks the logical config path in `Config` itself instead of + wrapping the provider. This keeps lookup paths and schema error paths aligned. + The low-level `Config.make` constructor is no longer exported; use config + constructors and combinators, or implement custom lookup behavior with + `ConfigProvider.make`. + + `ConfigProvider.fromDir` now returns `undefined` when neither a file nor a + directory exists at the requested path, so `orElse` can fall back instead of + failing with `SourceError`. + +- [#2415](https://github.com/Effect-TS/effect-smol/pull/2415) [`9cf3a25`](https://github.com/Effect-TS/effect-smol/commit/9cf3a25c66b0c44a52be9829870c44517ea52db2) Thanks @gcanti! - Fix `Effect.try` thunk usage and `Effect.tryPromise` mapper and signal handling defects. + + `Effect.try` now supports passing a thunk directly, matching `Effect.tryPromise`. Thrown values from direct-thunk usage are mapped to `Cause.UnknownError`. + + When a promise handled by `Effect.tryPromise` rejected and the custom `catch` mapper threw while mapping that rejection, the effect could remain pending and produce an unhandled rejection. The mapper is now guarded consistently with the synchronous throw path, so a thrown mapper error becomes an Effect defect. The JSDoc for `Effect.try` and `Effect.tryPromise` was also corrected. + + `Effect.tryPromise` now also only creates an `AbortController` when the wrapped thunk declares an `AbortSignal` parameter. + +- [#2417](https://github.com/Effect-TS/effect-smol/pull/2417) [`8def767`](https://github.com/Effect-TS/effect-smol/commit/8def7674b1787f91035298cda4d122937e87ef72) Thanks @tim-smart! - deduplicate SqlResolver.findById requests + +## 4.0.0-beta.83 + +### Patch Changes + +- [#2394](https://github.com/Effect-TS/effect-smol/pull/2394) [`1f2e8ce`](https://github.com/Effect-TS/effect-smol/commit/1f2e8ceef09e0a791c850ed2ade01f97089596f9) Thanks @IMax153! - Fix published HttpApi declaration files by exporting schema metadata types referenced by public declarations. + +## 4.0.0-beta.82 + +### Patch Changes + +- [#2391](https://github.com/Effect-TS/effect-smol/pull/2391) [`193690b`](https://github.com/Effect-TS/effect-smol/commit/193690b642ea802bbed40d663bd677251bbe9dc3) Thanks @IMax153! - Fix HttpApiEndpoint endpoint error inference when success schemas include streams. + +## 4.0.0-beta.81 + +### Patch Changes + +- [#2387](https://github.com/Effect-TS/effect-smol/pull/2387) [`93cb4f8`](https://github.com/Effect-TS/effect-smol/commit/93cb4f8fbfb9e07cb9dc86ce6b155fd1f8167914) Thanks @gcanti! - `Config.withDefault` now only recovers from missing data for literal/union + schemas. Invalid present values now propagate validation errors instead of + using the default, closes [#2384](https://github.com/Effect-TS/effect-smol/issues/2384). + +- [#2388](https://github.com/Effect-TS/effect-smol/pull/2388) [`60341d9`](https://github.com/Effect-TS/effect-smol/commit/60341d9ca744d0473ce3fab621ca9bd225af3a39) Thanks @gcanti! - `Config.withDefault` no longer recovers from schema filter failures. A filter + failure means a present value reached refinement checks, so using the default + could hide invalid configuration values. + +- [#2389](https://github.com/Effect-TS/effect-smol/pull/2389) [`1105ab5`](https://github.com/Effect-TS/effect-smol/commit/1105ab56cb724212f7ea7b431396ce82e8fd0484) Thanks @gcanti! - Fix `Schema.toTaggedUnion(...).isAnyOf` narrowing for custom discriminant keys, closes [#2386](https://github.com/Effect-TS/effect-smol/issues/2386). + + Previously, the type predicate always extracted union members by `_tag`, even + when `toTaggedUnion` was created with a different discriminant key. Runtime + behavior already used the supplied key, so this aligns the type-level narrowing + with the existing runtime behavior. + +- [#2270](https://github.com/Effect-TS/effect-smol/pull/2270) [`4500fbf`](https://github.com/Effect-TS/effect-smol/commit/4500fbfe00763d8a72af6e5d6c5988e8bd4ade36) Thanks @IMax153! - Add HTTP API streaming response support + +## 4.0.0-beta.80 + +### Patch Changes + +- [#2205](https://github.com/Effect-TS/effect-smol/pull/2205) [`d944330`](https://github.com/Effect-TS/effect-smol/commit/d94433090ee03f426d43e13b883abae4494e55e6) Thanks @lloydrichards! - add support for merging external events into `Prompt.custom` render loops via an optional `events` dequeue and `receive` handler. + + The prompt races user input against events from the dequeue, allowing background events to trigger re-renders without waiting for a keypress: + + ```ts + const eventQueue = yield * Queue.make(); + + const prompt = Prompt.custom( + { count: 0 }, + Queue.asDequeue(eventQueue), // <-- provide the event queue as a dequeue to the prompt + { + render: (state) => Effect.succeed(`Count: ${state.count}`), + process: (input, state) => + Effect.succeed( + Match.value(input).pipe( + // handle user input + Match.tag("Input", () => Action.Submit({ value: state.count })), + // handle external events from the queue + Match.tag("Event", (input) => + Action.NextFrame({ state: { count: state.count + input.value } }), + ), + Match.exhaustive, + ), + ), + clear: () => Effect.succeed(""), + }, + ); + ``` + +- [#2369](https://github.com/Effect-TS/effect-smol/pull/2369) [`f48659f`](https://github.com/Effect-TS/effect-smol/commit/f48659fdcc84930ebc1e5b45b540c0f973389182) Thanks @gcanti! - Round fractional durations symmetrically when normalizing to nanoseconds. + +- [#2373](https://github.com/Effect-TS/effect-smol/pull/2373) [`7652aaa`](https://github.com/Effect-TS/effect-smol/commit/7652aaa3bdbc39f241fe58b54b9a43b713e22e12) Thanks @StarpTech! - Stream.fromReadableStream: swallow the `reader.cancel()` rejection in the finalizer. Cancelling the reader of an already-errored ReadableStream rejects with the stored error, which turned the typed `onError` failure into a defect. + +- [#2371](https://github.com/Effect-TS/effect-smol/pull/2371) [`98630b7`](https://github.com/Effect-TS/effect-smol/commit/98630b7c8f679c352ba6796636c85688fa009d8d) Thanks @gcanti! - Emit `Schema.ObjectKeyword` as an object-or-array JSON Schema union. + +- [#2376](https://github.com/Effect-TS/effect-smol/pull/2376) [`90ae23c`](https://github.com/Effect-TS/effect-smol/commit/90ae23cf07284da5e1bcd9dffa882e85df7e617b) Thanks @fubhy! - Add `Graph.successors` and `Graph.predecessors`, deprecate `Graph.neighborsDirected`, and fix graph algorithm edge cases around reversal, undirected edge queries, shortest-path weight validation, topological sort initials, and strongly connected components. + +## 4.0.0-beta.79 + +### Patch Changes + +- [#2364](https://github.com/Effect-TS/effect-smol/pull/2364) [`b9704dc`](https://github.com/Effect-TS/effect-smol/commit/b9704dc9de9f1649ad502371014fe869b69a49a3) Thanks @mikearnaldi! - Fix module-level side effects that defeated bundler tree-shaking. + + Bare top-level statements cannot be `#__PURE__`-annotated by the build, so + bundlers must retain them and everything they reference, even in bundles that + never use the code: + - `Option`: the standalone `Object.defineProperty(SomeProto, "valueOrUndefined", ...)` + statement anchored the whole `Option` proto chain into every bundle. It is + now folded into the `SomeProto` initializer. + - `Headers`: same pattern with `Object.defineProperties(Proto, ...)`, folded + into the initializer. + - `Logger`: module-level `process.stdout.isTTY` property reads (potential + getters, never droppable) moved inside `consolePretty`. + - `Utils`: when `internalCall` was unused, its dropped binding left behind a + retained initializer tail (`standard`/`forced` probe with computed property + reads). The selection is now wrapped in a single pure-annotated call. + + A minimal `Effect.succeed(123).pipe(Effect.runFork)` bundle shrinks by ~1.3% + gzipped; bundles that don't use `Option` or `Headers` no longer pay for them. + +- [#2339](https://github.com/Effect-TS/effect-smol/pull/2339) [`a207113`](https://github.com/Effect-TS/effect-smol/commit/a207113f66837bb54416926718a9a7d66774d079) Thanks @tim-smart! - Fix EntityManager defect restarts so in-flight requests are replayed instead of being dropped when the old entity scope is interrupted. + +- [#2362](https://github.com/Effect-TS/effect-smol/pull/2362) [`5e9b9e2`](https://github.com/Effect-TS/effect-smol/commit/5e9b9e217b164ebfd4a002dd4380b3b1563200c3) Thanks @fubhy! - Fix Graph traversal and shortest-path algorithms to traverse undirected edges independently of their stored source/target orientation. + +- [#2366](https://github.com/Effect-TS/effect-smol/pull/2366) [`7c128ae`](https://github.com/Effect-TS/effect-smol/commit/7c128aef458a1e2d224712e51c483c9badad1d44) Thanks @IMax153! - Fix string seed encoding in Random.withSeed so short, trailing, and astral UTF-8 bytes affect deterministic streams. + +- [#2352](https://github.com/Effect-TS/effect-smol/pull/2352) [`0ada457`](https://github.com/Effect-TS/effect-smol/commit/0ada457c0513d8d908254ab77ebb7d29d2b523d6) Thanks @alvarosevilla95! - Fix the Redis `RateLimiterStore` token-bucket failing with opaque errors under memory pressure: it now writes its keys with a TTL and guards against a missing refill timestamp. + +- [#2359](https://github.com/Effect-TS/effect-smol/pull/2359) [`d7cc5a2`](https://github.com/Effect-TS/effect-smol/commit/d7cc5a2bede3de10943aa0c6bdb4f26836a91efd) Thanks @gcanti! - Fix `Struct` key renaming and `Schema.encodeKeys` to support symbol keys, and reject duplicate encoded keys. + +- [#2365](https://github.com/Effect-TS/effect-smol/pull/2365) [`aad63be`](https://github.com/Effect-TS/effect-smol/commit/aad63becf65e0a6b076e94f8973be7bbe7fbd46f) Thanks @gcanti! - Fix `Schema` encoding so container-level checks are validated against the decoded value instead of the encoded output. + + Disallow adding checks directly to `Schema.suspend(...)`; add the checks to the suspended schema instead. + + Fix `StructWithRest` so index signatures do not re-parse or overwrite fixed properties. + +- [#2342](https://github.com/Effect-TS/effect-smol/pull/2342) [`09809f6`](https://github.com/Effect-TS/effect-smol/commit/09809f60f19ec98232f98b33e33e02ecb7e4fbd6) Thanks @gcanti! - Use generic ordered constraints for schema arbitrary derivation. + + Range checks such as `isGreaterThan`, `isLessThan`, and `isBetween` now populate `ctx.constraints.ordered` + instead of type-specific range fields on `number`, `date`, or `bigint` constraints. Custom `toArbitrary` + annotations that read range constraints should migrate to `ctx.constraints.ordered`. + + This also fixes BigDecimal arbitrary generation by adapting decimal bounds to the generated scale, avoiding + invalid fast-check bigint ranges for narrow decimal intervals. + +- [#2368](https://github.com/Effect-TS/effect-smol/pull/2368) [`2fddda5`](https://github.com/Effect-TS/effect-smol/commit/2fddda5311929f46b61e503f0ade4fc749e8c77d) Thanks @IMax153! - Encode HTTP API client path parameters when building request URLs. + +- [#2348](https://github.com/Effect-TS/effect-smol/pull/2348) [`5f21768`](https://github.com/Effect-TS/effect-smol/commit/5f2176833399757c4500d8875b7f2fba0393de75) Thanks @gcanti! - Update Schema arbitrary derivation to use the new filter metadata, candidate generation, optional derivation reports, recursion-aware generation, and the renamed `OrderedConstraint` model. + + Migration from the previous v4 API: + - Replace filter annotations from `toArbitraryConstraint: constraint` to `arbitrary: { constraint }`. When a filter cannot be described as a constraint, use `arbitrary: { candidate }` to add a weighted source that is still checked by the filter. + - Replace bucketed constraints with the flat `Schema.Annotations.ToArbitrary.Constraint` shape: + - `string.minLength`, `array.minLength`, object property counts, collection sizes -> `minLength` + - `string.maxLength`, `array.maxLength`, object property counts, collection sizes -> `maxLength` + - `string.patterns` -> `patterns` + - `number.isInteger` -> `integer` + - `number.noNaN` -> `noNaN` + - `number.noDefaultInfinity` -> `noInfinity` + - `date.noInvalidDate` -> `valid` + - `array.comparator` for uniqueness -> `unique` using Effect equality + - `ordered.min` / `minExcluded` / `max` / `maxExcluded` -> `ordered.minimum` / `exclusiveMinimum` / `maximum` / `exclusiveMaximum` + - In arbitrary hooks, read `context.constraint` instead of `context.constraints`. Replace `context.isSuspend` with `context.recursion`; when combining finite and recursive branches, pass `context.recursion` to `fc.oneof` with the finite branch first. + - Generic declaration hooks now receive type parameters as `{ arbitrary, terminal }`. Atomic declarations may still return a bare `FastCheck.Arbitrary`, but generic declarations should return `{ arbitrary, terminal }` when they can preserve a finite terminal branch. + - `Schema.toArbitrary(schema, { report: true })` now returns `{ value, report }`; without `{ report: true }`, it keeps returning the arbitrary directly. `Schema.toArbitraryLazy` always returns a lazy arbitrary. + +- [#2343](https://github.com/Effect-TS/effect-smol/pull/2343) [`f27003e`](https://github.com/Effect-TS/effect-smol/commit/f27003e00524ff83f20dd9909f62b2f8795efe03) Thanks @MohanedMashaly! - Add meta-var that shows log level and bash options in command line. + +## 4.0.0-beta.78 + +### Patch Changes + +- [#2333](https://github.com/Effect-TS/effect-smol/pull/2333) [`7836b8e`](https://github.com/Effect-TS/effect-smol/commit/7836b8eb8bb0f3e04cdf554ee070caccf74f00c1) Thanks @tim-smart! - Fix Schema.Defect JSON encoding for Error values whose message property is not a string. + +- [#2329](https://github.com/Effect-TS/effect-smol/pull/2329) [`35d49a3`](https://github.com/Effect-TS/effect-smol/commit/35d49a3a09bdba6b513de87ddcead9e61a1042ba) Thanks @alvarosevilla95! - Retry Redis scripts after `NOSCRIPT` and declare the token bucket refill key + +- [#2334](https://github.com/Effect-TS/effect-smol/pull/2334) [`4093258`](https://github.com/Effect-TS/effect-smol/commit/40932580e65bafab5f23c5f14b520cb411d0b2cd) Thanks @tim-smart! - clean up otlp config + +## 4.0.0-beta.77 + +### Patch Changes + +- [#2326](https://github.com/Effect-TS/effect-smol/pull/2326) [`6e9a5ca`](https://github.com/Effect-TS/effect-smol/commit/6e9a5ca62a61156fd67b2518ad3ab14ac0d25f23) Thanks @fubhy! - Prefer OTEL resource environment variables over explicit `OtlpResource.fromConfig` options. + +- [#2325](https://github.com/Effect-TS/effect-smol/pull/2325) [`302f398`](https://github.com/Effect-TS/effect-smol/commit/302f3984ce206e35d86ddd99d3b72be144850a51) Thanks @fubhy! - Add OTEL environment variable configuration for unstable OTLP observability. + +## 4.0.0-beta.76 + +### Patch Changes + +- [#2320](https://github.com/Effect-TS/effect-smol/pull/2320) [`016108a`](https://github.com/Effect-TS/effect-smol/commit/016108a472af7048ddbbfd05f233e67529fafe12) Thanks @gcanti! - Add `Schema.isGUID` and update `Schema.isUUID` to accept the RFC 9562 max UUID. + +- [#2319](https://github.com/Effect-TS/effect-smol/pull/2319) [`95c03d2`](https://github.com/Effect-TS/effect-smol/commit/95c03d2c55930668c215b5a41c23cf7742fead84) Thanks @fubhy! - Add support for configuring Scalar API reference pages with a custom fetch implementation. + +- [#2318](https://github.com/Effect-TS/effect-smol/pull/2318) [`07299a3`](https://github.com/Effect-TS/effect-smol/commit/07299a33c09fd52faa9810d30835a2622c752386) Thanks @gcanti! - Replace the `Schema.Error` and `Schema.Defect` schema constants with constructor + functions, `Schema.Error()` and `Schema.Defect()`. + + Unify `Schema.ErrorWithStack` into `Schema.Error({ includeStack: true })` and + `Schema.DefectWithStack` into `Schema.Defect({ includeStack: true })`. + + Error causes are encoded by default using the same JSON defect encoding + semantics used by `Schema.Defect`; pass `{ excludeCause: true }` to omit nested + cause data. + + Equivalent `Schema.Error` and `Schema.Defect` options are canonicalized, so + repeated constructor calls with the same option values reuse the same schema. + + `Schema.Defect()` now models defects as `unknown` values with a JSON encoded + form. Error-shaped JSON objects with a string `message` decode to JavaScript + `Error` values, so non-`Error` objects such as `{ message: "boom" }` do not + round-trip unchanged. Other non-`Error` values are normalized through JSON + serialization, with non-JSON values falling back to Effect's formatted string + representation. + +## 4.0.0-beta.75 + +### Patch Changes + +- [#2294](https://github.com/Effect-TS/effect-smol/pull/2294) [`81b187c`](https://github.com/Effect-TS/effect-smol/commit/81b187c17a0d8817b58232826939154010ae49d7) Thanks @mattiamanzati! - Align workflow tags with RPCs by changing `Workflow.make` to accept the tag as its first argument, exposing workflow tags as `_tag`, and supporting `class MyWorkflow extends Workflow.make(...) {}`. + +- [#2312](https://github.com/Effect-TS/effect-smol/pull/2312) [`ad4b535`](https://github.com/Effect-TS/effect-smol/commit/ad4b535e17f94ce35261829d5a3675f0a7808b4e) Thanks @gcanti! - Validate `Schema.StructWithRest` fixed fields against rest index signatures at the type level so schemas cannot be constructed with incompatible decoded, encoded, or make shapes. This keeps `StructWithRest` types sound and updates the generated OpenAI conversation-items request schema to keep accepting arbitrary additional fields under the stricter validation. + +- [#2314](https://github.com/Effect-TS/effect-smol/pull/2314) [`a29c2e7`](https://github.com/Effect-TS/effect-smol/commit/a29c2e7e3570920156702671d6f3367cd0195f6c) Thanks @gcanti! - Preserve `Schema.Redacted` options when roundtripping through schema representations. + This keeps `label` validation and `disallowJsonEncode` behavior intact when + schemas are revived from a representation or emitted through code generation. + +- [#2298](https://github.com/Effect-TS/effect-smol/pull/2298) [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3) Thanks @gcanti! - Remove the `Types.MergeRecord` alias. Use `Types.MergeLeft` instead. + +- [#2298](https://github.com/Effect-TS/effect-smol/pull/2298) [`1fdd9ae`](https://github.com/Effect-TS/effect-smol/commit/1fdd9aeed92b6bb70987c862e7f6f66ead0339b3) Thanks @gcanti! - Align Schema adapter failures: `Schema` result, promise, and sync adapters now surface `SchemaError`, while `SchemaParser` result, promise, and sync adapters expose `SchemaIssue.Issue`. Mark `SchemaParser` option adapters as internal because their error details are discarded. + +- [#2313](https://github.com/Effect-TS/effect-smol/pull/2313) [`ffea4ec`](https://github.com/Effect-TS/effect-smol/commit/ffea4ecf2925f6a4c9fd13079d47584cbf2bed00) Thanks @MohanedMashaly! - Add -v alias for version flag + +- [#2306](https://github.com/Effect-TS/effect-smol/pull/2306) [`4255c9b`](https://github.com/Effect-TS/effect-smol/commit/4255c9ba78bb98c7838fbe9dccdd8465e9da5427) Thanks @sam-goodwin! - Fix `HttpApiSecurity` bearer/http credential decoding + +## 4.0.0-beta.74 + +### Patch Changes + +- [#2295](https://github.com/Effect-TS/effect-smol/pull/2295) [`b1fc6a4`](https://github.com/Effect-TS/effect-smol/commit/b1fc6a4b4d0ca7fa9fd162799ae17c86f2f7ee8e) Thanks @jgoux! - Fix CLI parsing so command-local flags can override globals without breaking global flags before subcommands. + +## 4.0.0-beta.73 + +### Patch Changes + +- [#2291](https://github.com/Effect-TS/effect-smol/pull/2291) [`361ca30`](https://github.com/Effect-TS/effect-smol/commit/361ca30eb6e134feece547d6e00f82be4cb23f75) Thanks @tim-smart! - Add HttpApiSecurity.http for passing custom schemes + +- [#2289](https://github.com/Effect-TS/effect-smol/pull/2289) [`b9598c6`](https://github.com/Effect-TS/effect-smol/commit/b9598c6a209e75bfdb87ee3b024ecd1e3923ff6e) Thanks @tim-smart! - make EntityResource lazy by default + +## 4.0.0-beta.72 + +### Patch Changes + +- [#2287](https://github.com/Effect-TS/effect-smol/pull/2287) [`73e67d1`](https://github.com/Effect-TS/effect-smol/commit/73e67d119a84d697773eaecb4865c6a71eb1a9cb) Thanks @tim-smart! - Ensure ClusterWorkflowEngine routes durable clock wakeups and registered workflow deferred completions through the owning workflow's shard group. + +- [#2286](https://github.com/Effect-TS/effect-smol/pull/2286) [`01d71ec`](https://github.com/Effect-TS/effect-smol/commit/01d71ec5a75f3c2747a8d3b1ad9701d1e27b7ce5) Thanks @tim-smart! - Add default value support to `Prompt.file`. + +- [#2285](https://github.com/Effect-TS/effect-smol/pull/2285) [`fcd707e`](https://github.com/Effect-TS/effect-smol/commit/fcd707e091a16e1b35343c901cc4052274e32239) Thanks @tim-smart! - Add default value support to CLI integer prompts. + +## 4.0.0-beta.71 + +### Patch Changes + +- [#2252](https://github.com/Effect-TS/effect-smol/pull/2252) [`d8ac76b`](https://github.com/Effect-TS/effect-smol/commit/d8ac76b5bad458c42cebe8a0c1b3843f955ac293) Thanks @tim-smart! - Added `Schedule.tap`, which allows observing full schedule metadata without altering schedule inputs or outputs. + +- [#2261](https://github.com/Effect-TS/effect-smol/pull/2261) [`2c3c00a`](https://github.com/Effect-TS/effect-smol/commit/2c3c00af6faba7b7d422af26a7a2bbc35636d230) Thanks @gcanti! - Add JSON Schema custom annotation passthrough option, closes [#2260](https://github.com/Effect-TS/effect-smol/issues/2260) + +- [#2269](https://github.com/Effect-TS/effect-smol/pull/2269) [`3751e7c`](https://github.com/Effect-TS/effect-smol/commit/3751e7cf353e7a54cd692c37401207d9afba1e63) Thanks @gcanti! - Schema: reintroduce `.value` on `Schema.Array` and `Schema.NonEmptyArray` for consistency with other collection wrappers (`Chunk`, `HashSet`, etc.), closes [#2268](https://github.com/Effect-TS/effect-smol/issues/2268). + +- [#2272](https://github.com/Effect-TS/effect-smol/pull/2272) [`fc5f25b`](https://github.com/Effect-TS/effect-smol/commit/fc5f25b03ada5fc2431987768a74d3d3e75ca485) Thanks @gcanti! - Clarify that `Data.$is(tag)` only checks the `_tag` field, not the full structure, closes [#2271](https://github.com/Effect-TS/effect-smol/issues/2271). + +- [#2257](https://github.com/Effect-TS/effect-smol/pull/2257) [`7ccced4`](https://github.com/Effect-TS/effect-smol/commit/7ccced42867c14c013b01160b3d292f14c05bd04) Thanks @bwbuchanan! - Fixed the `catch*` combinators silently dropping unhandled error types + +- [#2263](https://github.com/Effect-TS/effect-smol/pull/2263) [`a2e1fe5`](https://github.com/Effect-TS/effect-smol/commit/a2e1fe5835c98c8ee4393a091b1d11b75126e349) Thanks @patroza! - Use `WeakMap` for `pendingBatches` instead of `Map`, to allow GC to collect resolvers + +- [#2266](https://github.com/Effect-TS/effect-smol/pull/2266) [`4a4a36b`](https://github.com/Effect-TS/effect-smol/commit/4a4a36b10e6e616cad07584a43908f6a7e07e618) Thanks @gcanti! - Fix schema arbitrary constraints for exclusive BigInt, Date, and integer number bounds. + +- [#2249](https://github.com/Effect-TS/effect-smol/pull/2249) [`d350292`](https://github.com/Effect-TS/effect-smol/commit/d3502922b4740fa9d745797cbc3775cb67839b6d) Thanks @tim-smart! - allow encoding Redacted by default, and add option to disallow encoding + +- [#2276](https://github.com/Effect-TS/effect-smol/pull/2276) [`730afb6`](https://github.com/Effect-TS/effect-smol/commit/730afb66696adf9bd5a328cbca29df9c05968771) Thanks @tim-smart! - Fix AtomRef notifications when a listener re-subscribes itself during notification. + +- [#2250](https://github.com/Effect-TS/effect-smol/pull/2250) [`df1b008`](https://github.com/Effect-TS/effect-smol/commit/df1b008f370f414c2a67a7b8139ef747af8e5fba) Thanks @tim-smart! - Fix `Argument.variadic(argument)` so it supports direct calls without options. + +- [#2277](https://github.com/Effect-TS/effect-smol/pull/2277) [`6d469d5`](https://github.com/Effect-TS/effect-smol/commit/6d469d567a7c41d7e5343bdee21d45b07b0e8190) Thanks @tim-smart! - Fix string messages and annotations being double-quoted by simple and logfmt loggers. + +## 4.0.0-beta.70 + +### Patch Changes + +- [#2228](https://github.com/Effect-TS/effect-smol/pull/2228) [`af7782d`](https://github.com/Effect-TS/effect-smol/commit/af7782d3008d08b043f3a3f261516001514b2b4e) Thanks @avallete! - Add `Command.withHidden` to hide subcommands from `--help` output, shell completions, and "did you mean?" suggestions, while keeping them fully invocable by exact name. + + Useful for experimental or internal subcommands that should be accepted but not advertised on the public CLI surface. + + ```ts + import { Command } from "effect/unstable/cli"; + + const experimental = Command.make("experimental").pipe(Command.withHidden); + + const root = Command.make("mycli").pipe( + Command.withSubcommands([experimental]), + ); + ``` + +- [#2244](https://github.com/Effect-TS/effect-smol/pull/2244) [`7212d70`](https://github.com/Effect-TS/effect-smol/commit/7212d701a3eee7b3553ff502e2c066126e52e839) Thanks @tim-smart! - Fix TestClock adjustment when its layer is provided to programs run without an ambient Scope. + +## 4.0.0-beta.69 + +### Patch Changes + +- [#2227](https://github.com/Effect-TS/effect-smol/pull/2227) [`70ea04a`](https://github.com/Effect-TS/effect-smol/commit/70ea04aa96a2a7859d738d414e1f0e3ed081a27a) Thanks @avallete! - Add `Flag.withHidden` (and `Param.withHidden`) to hide flags from `--help` output and shell completions while keeping them fully parseable on the command line. + + Useful for experimental, internal, or deprecated flags that should be accepted but not advertised, e.g. `--experimental-foo`, debug toggles, or escape hatches that are not yet committed to the public CLI surface. + + ```ts + import { Flag } from "effect/unstable/cli"; + + const experimental = Flag.boolean("experimental-foo").pipe(Flag.withHidden); + ``` + +- [#2240](https://github.com/Effect-TS/effect-smol/pull/2240) [`d0ea8b0`](https://github.com/Effect-TS/effect-smol/commit/d0ea8b03f7d73ae076c1db12666141e480d11178) Thanks @tim-smart! - pass workflow parent on discard + +- [#2237](https://github.com/Effect-TS/effect-smol/pull/2237) [`a57674b`](https://github.com/Effect-TS/effect-smol/commit/a57674b64845e9e75a456cf907bfdcb858859118) Thanks @notkadez! - Fix `Stream.scoped` and `Channel.scoped` so pull effects run with the scoped resource scope. + +- [#2239](https://github.com/Effect-TS/effect-smol/pull/2239) [`59aa334`](https://github.com/Effect-TS/effect-smol/commit/59aa334fbd0a504dda3c36f6d2ef1be7449b4b8b) Thanks @tim-smart! - fix RpcWorker Protocol service key + +- [#2242](https://github.com/Effect-TS/effect-smol/pull/2242) [`8f4208e`](https://github.com/Effect-TS/effect-smol/commit/8f4208ee83bc7bdaa6793b5429847b45aab72470) Thanks @tim-smart! - Accept `.mjs` and `.mts` migration files in SQL migrator loaders. + +## 4.0.0-beta.68 + +### Patch Changes + +- [#2210](https://github.com/Effect-TS/effect-smol/pull/2210) [`af8267f`](https://github.com/Effect-TS/effect-smol/commit/af8267f2f3588c3fb611e9286f6f933f29ce1217) Thanks @tim-smart! - Add Stream.broadcastN for fixed-size stream broadcasts. + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - update Model uuid helpers + +- [#2180](https://github.com/Effect-TS/effect-smol/pull/2180) [`0176eaf`](https://github.com/Effect-TS/effect-smol/commit/0176eaf3ecd7c1b99a10268f2af02d7e8ce161e5) Thanks @IMax153! - Add a platform-agnostic `Crypto` service for cryptographic random bytes, secure random generators, UUIDv4 / UUIDv7 generation, and digest operations. UUID generation should now use the `Crypto` service's `randomUUIDv4` or `randomUUIDv7`, which format bytes from the platform `Crypto` service; UUIDv7 also uses the `Clock` service timestamp. `Random.nextUUIDv4` has been removed because the base `Random` service is not cryptographically secure. + +- [#2221](https://github.com/Effect-TS/effect-smol/pull/2221) [`f136bb7`](https://github.com/Effect-TS/effect-smol/commit/f136bb763048cbc6b17edd26496dba3e2415b9fa) Thanks @gcanti! - Change `Schema.asserts` and `SchemaParser.asserts` to assert a value directly with `asserts(schema, input)` and remove `Schema.Codec.ToAsserts`. + +- [#2209](https://github.com/Effect-TS/effect-smol/pull/2209) [`6f38f07`](https://github.com/Effect-TS/effect-smol/commit/6f38f07d5941a211b251383aaab0f4f55e8a6557) Thanks @tim-smart! - Fix Channel.decodeText corrupting UTF-8 characters split across chunk boundaries. + +- [#2207](https://github.com/Effect-TS/effect-smol/pull/2207) [`aec9c40`](https://github.com/Effect-TS/effect-smol/commit/aec9c401a53db227f18bf5e0c84db7130ad862d6) Thanks @tim-smart! - rename Model.Generated to Model.GeneratedByDb + +## 4.0.0-beta.67 + +### Patch Changes + +- [#2185](https://github.com/Effect-TS/effect-smol/pull/2185) [`a42ef66`](https://github.com/Effect-TS/effect-smol/commit/a42ef6632abbddfa820995ae310ccc84ae8d9b6f) Thanks @lloydrichards! - add rows to Terminal + +- [#2111](https://github.com/Effect-TS/effect-smol/pull/2111) [`35594f8`](https://github.com/Effect-TS/effect-smol/commit/35594f811cafe471acd490114b103a1f8392c8d8) Thanks @thiagofelix! - Fix `EntityProxyServer.layerHttpApi` using `path.entityId` instead of `params.entityId` + +- [#2201](https://github.com/Effect-TS/effect-smol/pull/2201) [`8bddd62`](https://github.com/Effect-TS/effect-smol/commit/8bddd628cb623f9533d345082583ff51cead6836) Thanks @sjh9714! - Fix `MutableList.filter` and `MutableList.remove` length updates. + +- [#2181](https://github.com/Effect-TS/effect-smol/pull/2181) [`4be4c8d`](https://github.com/Effect-TS/effect-smol/commit/4be4c8d60862aa963869ee2ed9ffa048ffac0527) Thanks @zeyuri! - Fix workflow proxy RPC handlers to provide the context expected by RpcServer. + +- [#2177](https://github.com/Effect-TS/effect-smol/pull/2177) [`0c9d3ab`](https://github.com/Effect-TS/effect-smol/commit/0c9d3ab43eb721a370ed8306260cbac218c27e87) Thanks @mikearnaldi! - Add forked memo maps so nested layer scopes can reuse parent allocations without leaking sibling-local layers. Update `@effect/vitest` to fork memo maps for nested `it.layer` suites, isolating sibling setup while preserving parent sharing. + +- [#2206](https://github.com/Effect-TS/effect-smol/pull/2206) [`b156acc`](https://github.com/Effect-TS/effect-smol/commit/b156accd2691b4a051f823affdece7c39923ce85) Thanks @tim-smart! - add `availableShardGroups` to ShardingConfig, to ensure advisory locks do not conflict + +- [#2184](https://github.com/Effect-TS/effect-smol/pull/2184) [`d16c034`](https://github.com/Effect-TS/effect-smol/commit/d16c03434ee3e6dcd3bfc82b65d99e881d89025b) Thanks @gcanti! - Restore support for passing schema parse options when creating decode and encode helpers, closes [#2174](https://github.com/Effect-TS/effect-smol/issues/2174). + +- [#2176](https://github.com/Effect-TS/effect-smol/pull/2176) [`b559d68`](https://github.com/Effect-TS/effect-smol/commit/b559d68845f848a10153395778f035682d399075) Thanks @patroza! - Allow Schema decoding defaults to require Effect services. + + The `Effect` passed to `Schema.withDecodingDefault`, `Schema.withDecodingDefaultKey`, `Schema.withDecodingDefaultType`, and `Schema.withDecodingDefaultTypeKey` now accepts a context `R` in its third type parameter. The required services are propagated into the resulting schema's `DecodingServices`. `SchemaGetter.withDefault` is widened in the same way. + +- [#2113](https://github.com/Effect-TS/effect-smol/pull/2113) [`a3de5d9`](https://github.com/Effect-TS/effect-smol/commit/a3de5d9215e5cc4a62e2666efbd7c1bf595eb84f) Thanks @patroza! - Allow Schema constructor and decoding defaults to fail with `SchemaError`. + + The `Effect` passed to `Schema.withConstructorDefault`, `Schema.withDecodingDefault`, `Schema.withDecodingDefaultKey`, `Schema.withDecodingDefaultType`, and `Schema.withDecodingDefaultTypeKey` now accepts `SchemaError` in its error channel. When a default fails, the parser unwraps the underlying `SchemaIssue.Issue` and propagates it as a parse failure with the surrounding path attached. This makes it easy to use another schema's `makeEffect` / `decode*` as the default value. + +- [#2172](https://github.com/Effect-TS/effect-smol/pull/2172) [`7e6c12e`](https://github.com/Effect-TS/effect-smol/commit/7e6c12ec9b3a5945f6c26e272cc8f6390541ad3e) Thanks @gcanti! - Rename `SchemaParser.makeUnsafe` to `SchemaParser.make`. + +- [#2167](https://github.com/Effect-TS/effect-smol/pull/2167) [`098167a`](https://github.com/Effect-TS/effect-smol/commit/098167a220fe07da6f14455818733ab1b269c9dd) Thanks @tim-smart! - update dependencies + +## 4.0.0-beta.66 + +### Patch Changes + +- [#2163](https://github.com/Effect-TS/effect-smol/pull/2163) [`ca2498e`](https://github.com/Effect-TS/effect-smol/commit/ca2498e702ac2d83fb7187707b7eb069bdb261a2) Thanks @tim-smart! - remove Effect.Yieldable + +- [#2161](https://github.com/Effect-TS/effect-smol/pull/2161) [`cd7d1fb`](https://github.com/Effect-TS/effect-smol/commit/cd7d1fba7e2e2c5ac3ad64e1be433440a5bda436) Thanks @wking-io! - Fix request ID tracking in the RPC server HTTP protocol finalizer. + +- [#2158](https://github.com/Effect-TS/effect-smol/pull/2158) [`19a7033`](https://github.com/Effect-TS/effect-smol/commit/19a703367ec817cffc41d152da9b594827408e2b) Thanks @ColaFanta! - Change `Type_<>` implementation, from using `Exclude` type util to `keyof F as xx`, this implementation keeps IDE provenance link. This enables clicking "Go to definition (F12)" in VSCode on an object made from Schema Struct jumps to the correct Struct field definition. + +- [#2153](https://github.com/Effect-TS/effect-smol/pull/2153) [`33d26b4`](https://github.com/Effect-TS/effect-smol/commit/33d26b4210b2e974f146a71e7eed962f8ce00900) Thanks @Gabrola! - Allow `HttpApiTest.groups` to accept an optional `baseUrl` override while preserving the existing default of `"http://localhost:3000"`. + +- [#2160](https://github.com/Effect-TS/effect-smol/pull/2160) [`856766b`](https://github.com/Effect-TS/effect-smol/commit/856766b2c506aaed6d2df1d63bf3a5b1b062e1d4) Thanks @tim-smart! - Remove the auto-incrementing suffix from HTTP server logger log span names. + +- [#2164](https://github.com/Effect-TS/effect-smol/pull/2164) [`079c7df`](https://github.com/Effect-TS/effect-smol/commit/079c7df82559bb9ce10a86dffb85d25e6ce07dc3) Thanks @tim-smart! - Add the unstable workflow DurableQueue module. + +## 4.0.0-beta.65 + +### Patch Changes + +- [#2148](https://github.com/Effect-TS/effect-smol/pull/2148) [`6f11454`](https://github.com/Effect-TS/effect-smol/commit/6f11454a9b6c3bd00f6b35fd7af14a2f2d63a0a2) Thanks @tim-smart! - Add `UniqueViolation` as a new SQL error reason. Supported unique constraint violations now classify as `UniqueViolation` instead of the broader `ConstraintError` reason. + + This covers PostgreSQL, PGlite, MySQL, MSSQL, and the shared SQLite classification used by the SQLite-family clients. `UniqueViolation.constraint` contains the best available constraint, index, or key identifier and falls back to exactly `"unknown"` when no reliable identifier is available. + +## 4.0.0-beta.64 + +### Patch Changes + +- [#2137](https://github.com/Effect-TS/effect-smol/pull/2137) [`7d4877a`](https://github.com/Effect-TS/effect-smol/commit/7d4877a1929cdb690280ea254326c04f2ec97ea5) Thanks @tim-smart! - Add optional soft delete column support to SqlModel repositories and resolvers. + +## 4.0.0-beta.63 + +### Patch Changes + +- [#2136](https://github.com/Effect-TS/effect-smol/pull/2136) [`7f927ff`](https://github.com/Effect-TS/effect-smol/commit/7f927ffb7a9801dcfc4096c29e369d13d65cd0ac) Thanks @tim-smart! - add HttpApiTest module + +- [#2123](https://github.com/Effect-TS/effect-smol/pull/2123) [`a696b3e`](https://github.com/Effect-TS/effect-smol/commit/a696b3e83a8504cdbe261a18c10a1cc0619ae102) Thanks @lewxdev! - add `Effect.acquireDisposable` + +## 4.0.0-beta.62 + +### Patch Changes + +- [#2131](https://github.com/Effect-TS/effect-smol/pull/2131) [`4ab4b90`](https://github.com/Effect-TS/effect-smol/commit/4ab4b9007dc27a52ffabc6fcb37c96eeec795bf7) Thanks @tim-smart! - Allow Kubernetes pod condition `lastTransitionTime` values to be null in K8sHttpClient schemas. + +## 4.0.0-beta.61 + +### Patch Changes + +- [#2130](https://github.com/Effect-TS/effect-smol/pull/2130) [`50790af`](https://github.com/Effect-TS/effect-smol/commit/50790af9b190c38d10fb0723837d49b66432638f) Thanks @tim-smart! - Record fiber runtime start metrics when fibers are constructed so yielded fibers are only counted once. + +- [#2120](https://github.com/Effect-TS/effect-smol/pull/2120) [`71f7c3d`](https://github.com/Effect-TS/effect-smol/commit/71f7c3df997deda92c84146d569696dab3bd645c) Thanks @tim-smart! - Port `Effect.firstSuccessOf` from Effect v3. + +- [#2122](https://github.com/Effect-TS/effect-smol/pull/2122) [`aae8797`](https://github.com/Effect-TS/effect-smol/commit/aae8797b9cb383be0c182dd58d03d787c354238b) Thanks @tim-smart! - fix empty body decoding in HttpApiBuilder + +## 4.0.0-beta.60 + +### Patch Changes + +- [#2115](https://github.com/Effect-TS/effect-smol/pull/2115) [`f69d567`](https://github.com/Effect-TS/effect-smol/commit/f69d5675dcff9f4137295752baf066b7153fdc09) Thanks @tim-smart! - add Rpc.custom + +- [#2119](https://github.com/Effect-TS/effect-smol/pull/2119) [`7909c95`](https://github.com/Effect-TS/effect-smol/commit/7909c954b8f6244a35a4b429f8dd0dff45dad620) Thanks @gcanti! - Remove `Inspectable.stringifyCircular` and fix `Formatter.formatJson` so shared object references are preserved while only circular references are omitted. + +- [`bbb4dcc`](https://github.com/Effect-TS/effect-smol/commit/bbb4dcc6c406b83a416b4ad3541cc02037c420e4) Thanks @tim-smart! - allow using Duration.Input with accessors + +- [#2117](https://github.com/Effect-TS/effect-smol/pull/2117) [`7af2207`](https://github.com/Effect-TS/effect-smol/commit/7af2207901eabf3132c1b7010a69b3899c06fbbe) Thanks @gcanti! - Add `Schema.DurationFromString` and `SchemaTransformation.durationFromString`, support `"Infinity"` and `"-Infinity"` in `Duration.fromInput`, and simplify config duration parsing around the shared schema codec, closes [#2092](https://github.com/Effect-TS/effect-smol/issues/2092). + +- [#2116](https://github.com/Effect-TS/effect-smol/pull/2116) [`848b40a`](https://github.com/Effect-TS/effect-smol/commit/848b40a4bd4bf54a5098617d50c33c88eee8270a) Thanks @gcanti! - Add a `Config.literals` convenience constructor for `Schema.Literals`, closes [#2091](https://github.com/Effect-TS/effect-smol/issues/2091). + +## 4.0.0-beta.59 + +### Patch Changes + +- [#2106](https://github.com/Effect-TS/effect-smol/pull/2106) [`56837ea`](https://github.com/Effect-TS/effect-smol/commit/56837ea2a338395b35550641374e9e589bd8b71d) Thanks @IMax153! - Fix entity proxy RPC handlers to provide the context expected by RpcServer. + +## 4.0.0-beta.58 + +### Patch Changes + +- [#2097](https://github.com/Effect-TS/effect-smol/pull/2097) [`11993d4`](https://github.com/Effect-TS/effect-smol/commit/11993d4934c66f5dc611b8bbf553f01d501ef8f7) Thanks @Leka74! - Add an exhaustive finalizer to the AsyncResult builder. + +- [#2098](https://github.com/Effect-TS/effect-smol/pull/2098) [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec) Thanks @tim-smart! - generate binary arrays from streams with less copying + +- [#2098](https://github.com/Effect-TS/effect-smol/pull/2098) [`96c8b22`](https://github.com/Effect-TS/effect-smol/commit/96c8b22c2057ccddbf10ed269d7697f22119b3ec) Thanks @tim-smart! - improve http body consumption + +## 4.0.0-beta.57 + +### Patch Changes + +- [#2085](https://github.com/Effect-TS/effect-smol/pull/2085) [`a971f5c`](https://github.com/Effect-TS/effect-smol/commit/a971f5cbd92dfe4274420bf0966595eb35531060) Thanks @tim-smart! - add Effect.abortSignal + +- [#2088](https://github.com/Effect-TS/effect-smol/pull/2088) [`8e110c5`](https://github.com/Effect-TS/effect-smol/commit/8e110c5f02a429ccc43a91df8678e402138c0851) Thanks @tim-smart! - ensure each sql client gets a unique transaction service + +## 4.0.0-beta.56 + +## 4.0.0-beta.55 + +### Patch Changes + +- [#2081](https://github.com/Effect-TS/effect-smol/pull/2081) [`42cc744`](https://github.com/Effect-TS/effect-smol/commit/42cc744570968deb365fb46d47b53d3277050c93) Thanks @gcanti! - Export the `Schema.encodeKeys` interface, closes [#2070](https://github.com/Effect-TS/effect-smol/issues/2070). + + Previously the interface was internal, so exporting a value whose inferred type referenced it triggered TypeScript error `TS4023: Exported variable has or is using name 'encodeKeys' from external module ... but cannot be named`, e.g.: + +- [#2067](https://github.com/Effect-TS/effect-smol/pull/2067) [`04855ce`](https://github.com/Effect-TS/effect-smol/commit/04855ceeca4d40c55a5750dd9893b691f8ea741a) Thanks @mrazauskas! - fix `isNullish()` type predicate + +## 4.0.0-beta.54 + +### Patch Changes + +- [#2078](https://github.com/Effect-TS/effect-smol/pull/2078) [`e4b74f9`](https://github.com/Effect-TS/effect-smol/commit/e4b74f9c01a0e9b6cd58416de4af3a26d51da7c8) Thanks @tim-smart! - add Socket.make + +- [#2075](https://github.com/Effect-TS/effect-smol/pull/2075) [`4c72808`](https://github.com/Effect-TS/effect-smol/commit/4c728081851c66dacf889a816535671bc841ae96) Thanks @tim-smart! - ensure workflow failures are not squashed by suspension interrupts + +## 4.0.0-beta.53 + +### Patch Changes + +- [#2068](https://github.com/Effect-TS/effect-smol/pull/2068) [`0768509`](https://github.com/Effect-TS/effect-smol/commit/07685094e931af07d104165195826a535b55fa7e) Thanks @tim-smart! - Fix `AtomHttpApi` query and mutation error inference to include endpoint middleware and client middleware errors, matching `HttpApiClient` behavior (including response-only mutation mode). + +- [#2062](https://github.com/Effect-TS/effect-smol/pull/2062) [`476aede`](https://github.com/Effect-TS/effect-smol/commit/476aede69c6efa06b5781ca5eb3e3b128ca29141) Thanks @aldotestino! - Fix `HttpIncomingMessage.schemaBodyJson` to forward parse options via the `parseOptions` annotation key. + +- [#2074](https://github.com/Effect-TS/effect-smol/pull/2074) [`4f79c54`](https://github.com/Effect-TS/effect-smol/commit/4f79c542e7b508c235ff485d862cc8b29a8260c5) Thanks @tim-smart! - fix Latch.release + +- [#2069](https://github.com/Effect-TS/effect-smol/pull/2069) [`4be6a7c`](https://github.com/Effect-TS/effect-smol/commit/4be6a7cf35dab2a01d652f56dd35f0358c5a7e88) Thanks @mikearnaldi! - Fix `TestClock.currentTimeNanosUnsafe()` to floor fractional millisecond instants before converting them to `BigInt`. + +- [#2065](https://github.com/Effect-TS/effect-smol/pull/2065) [`88927eb`](https://github.com/Effect-TS/effect-smol/commit/88927ebb896162cdba103b36553280b58e0facac) Thanks @tim-smart! - add Effectable module + +## 4.0.0-beta.52 + +### Patch Changes + +- [#2057](https://github.com/Effect-TS/effect-smol/pull/2057) [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499) Thanks @tim-smart! - add HttpApiSchemaError for determining where a schema error originates from + +- [#2055](https://github.com/Effect-TS/effect-smol/pull/2055) [`cf3a311`](https://github.com/Effect-TS/effect-smol/commit/cf3a311d863a8abb818840c3b80f847e621c43c1) Thanks @tim-smart! - ensure tagged enum \_tag is correctly set + +- [#2057](https://github.com/Effect-TS/effect-smol/pull/2057) [`8e04bfc`](https://github.com/Effect-TS/effect-smol/commit/8e04bfc95554b74eac205d67a20388e056b21499) Thanks @tim-smart! - make HttpApi schema errors defects unless transformed + +- [#2058](https://github.com/Effect-TS/effect-smol/pull/2058) [`131fdd5`](https://github.com/Effect-TS/effect-smol/commit/131fdd5b1f26531e265fe1a08f002002f47c276e) Thanks @tim-smart! - mcp http request with no session header is 404 response + +## 4.0.0-beta.51 + +### Patch Changes + +- [#2049](https://github.com/Effect-TS/effect-smol/pull/2049) [`778d2af`](https://github.com/Effect-TS/effect-smol/commit/778d2afe9b5154bc1f9abae46d93ea7e54c87344) Thanks @bohdanbirdie! - Add `RpcSerialization.makeMsgPack` for creating MessagePack serialization with custom msgpackr options. On Cloudflare Workers with `allow_eval_during_startup` (default for `compatibility_date >= 2025-06-01`), pass `{ useRecords: false }` to prevent msgpackr's JIT code generation via `new Function()`, which is blocked during request handling. Also fixes silent error swallowing in the `msgPack` decode path — non-incomplete errors are now rethrown instead of returning `[]`. + +- [#2010](https://github.com/Effect-TS/effect-smol/pull/2010) [`4e24dcf`](https://github.com/Effect-TS/effect-smol/commit/4e24dcf75037f65eebc1eb68623bc7cbf9d5512a) Thanks @tim-smart! - process schema properties / elements concurrently + +- [#2052](https://github.com/Effect-TS/effect-smol/pull/2052) [`4b1c015`](https://github.com/Effect-TS/effect-smol/commit/4b1c0150e9bdb5559ed32d250deb66e17b4240c7) Thanks @gcanti! - Schema: expand `FilterOutput` and add `FilterIssue` for richer filter failures. + + The return type of a `Schema.makeFilter` predicate now supports two additional shapes: + - `{ path, issue }` where `issue` is `string | SchemaIssue.Issue` (previously only `{ path, message: string }` was accepted). The `issue` arm lets you attach a fully-formed `Issue` at a nested path without manually constructing a `Pointer`. + - `ReadonlyArray` to report several failures at once. An empty array is success, a single-element array is equivalent to returning that element, and multi-entry arrays are grouped into an `Issue.Composite`. This removes the need to import `SchemaIssue` and hand-build a `Composite` for multi-field validators. + + The single-failure shapes (`undefined`, `true`, `false`, `string`, `SchemaIssue.Issue`) are unchanged. + + **Breaking**: the object shape renamed from `{ path, message }` to `{ path, issue }`. Call sites that used the old shape must rename the field; the migration is mechanical. + + ```ts + // before + Schema.makeFilter((o) => ({ path: ["a"], message: "bad" })); + + // after + Schema.makeFilter((o) => ({ path: ["a"], issue: "bad" })); + ``` + + Also renamed `{ path, message }` to `{ path, issue }` in the accepted return type of `SchemaGetter.checkEffect`. + +- [#2047](https://github.com/Effect-TS/effect-smol/pull/2047) [`454f8ad`](https://github.com/Effect-TS/effect-smol/commit/454f8adad822929c3ef60f8280d0987226b049fd) Thanks @gcanti! - Fix `SchemaAST.isJson` rejecting DAGs as cycles, closes [#2021](https://github.com/Effect-TS/effect-smol/issues/2021). + + The previous implementation marked every visited object in a single `seen` set and never removed it, so any value that referenced the same object through two different paths (a DAG, e.g. `{ x: shared, y: shared }`) was treated as a cycle and returned `false`. Cycle detection now tracks only the current recursion path (popping on exit) and memoizes fully validated subtrees, so DAGs are accepted while true cycles are still rejected. + +- [#2051](https://github.com/Effect-TS/effect-smol/pull/2051) [`6754a0c`](https://github.com/Effect-TS/effect-smol/commit/6754a0cd18626b06805a079cc5265525a5eb7d27) Thanks @tim-smart! - disable sql traces for EventLog, RunnerStorage + +- [#2053](https://github.com/Effect-TS/effect-smol/pull/2053) [`90f7fd5`](https://github.com/Effect-TS/effect-smol/commit/90f7fd5243871b30980964135db4512b8119fa82) Thanks @tim-smart! - remove use of bigint literals + +- [#2046](https://github.com/Effect-TS/effect-smol/pull/2046) [`d7e1519`](https://github.com/Effect-TS/effect-smol/commit/d7e151974934201fd93fa4c8a1192ee9a5d965a0) Thanks @gcanti! - Remove the `options` parameter from `OpenApi.fromApi`. + + The parameter only carried `additionalProperties`, but the function caches results in a `WeakMap` keyed solely on the `api` instance. Passing different options across calls for the same api was silently ignored, making the parameter order-dependent and effectively single-shot. No call sites were using it, so the signature is now simply `fromApi(api)`. + +- [#2044](https://github.com/Effect-TS/effect-smol/pull/2044) [`72a8122`](https://github.com/Effect-TS/effect-smol/commit/72a81228e09782bae512f7d041bbfbc78bc668d0) Thanks @tim-smart! - ensure envelope payloads are correctly encoded for notify path + +## 4.0.0-beta.50 + +### Patch Changes + +- [#2038](https://github.com/Effect-TS/effect-smol/pull/2038) [`07be594`](https://github.com/Effect-TS/effect-smol/commit/07be594825de60f8e1b2102d21dbb9b8fc63b414) Thanks @tim-smart! - add support for deferred responses in rpc + +- [#2040](https://github.com/Effect-TS/effect-smol/pull/2040) [`ae02433`](https://github.com/Effect-TS/effect-smol/commit/ae02433103ce28f53a0c9bfb4a44e75773289b7b) Thanks @tim-smart! - require a option to make AtomRpc.query atoms serializatable + +## 4.0.0-beta.49 + +### Patch Changes + +- [#2035](https://github.com/Effect-TS/effect-smol/pull/2035) [`7d87873`](https://github.com/Effect-TS/effect-smol/commit/7d8787340ff549370f6f2a88b612e9ebbfd6ba45) Thanks @tim-smart! - Add support for common HTTP status string literals in `HttpApiSchema.status` (for example, `HttpApiSchema.status("Created")` resolves to status code `201`). + +- [#2036](https://github.com/Effect-TS/effect-smol/pull/2036) [`c2f6f90`](https://github.com/Effect-TS/effect-smol/commit/c2f6f901b200a6e515b4f02c93ce8005b7bbf1c5) Thanks @tim-smart! - add RpcGroup.omit + +- [#2034](https://github.com/Effect-TS/effect-smol/pull/2034) [`216f13c`](https://github.com/Effect-TS/effect-smol/commit/216f13c1fce454a21b489bb915714a17e791a1ac) Thanks @IMax153! - Fix issue with exported CLI `Completions` types + +## 4.0.0-beta.48 + +### Patch Changes + +- [#2025](https://github.com/Effect-TS/effect-smol/pull/2025) [`4da56ec`](https://github.com/Effect-TS/effect-smol/commit/4da56ecff129b2da40137ffede23a73cc4e532d8) Thanks @tim-smart! - update dependencies + +- [#2029](https://github.com/Effect-TS/effect-smol/pull/2029) [`a5e6f77`](https://github.com/Effect-TS/effect-smol/commit/a5e6f774bab195cf50ecdc818240765f69a3bf4a) Thanks @tim-smart! - omit scope from HttpApi handlers + +- [#2023](https://github.com/Effect-TS/effect-smol/pull/2023) [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070) Thanks @tim-smart! - EventLog Identity string encodes to base 64 + +- [#2023](https://github.com/Effect-TS/effect-smol/pull/2023) [`f1ba5b8`](https://github.com/Effect-TS/effect-smol/commit/f1ba5b8584d325a541156928cecf041b37fd5070) Thanks @tim-smart! - disable tracer propagation for otlp exporter + +## 4.0.0-beta.47 + +### Patch Changes + +- [#2017](https://github.com/Effect-TS/effect-smol/pull/2017) [`c584726`](https://github.com/Effect-TS/effect-smol/commit/c58472674e750e6938df955044eab88feda95e45) Thanks @gcanti! - Schema: add `annotateEncoded` function for annotating the encoded side of a schema. + +- [#2013](https://github.com/Effect-TS/effect-smol/pull/2013) [`86a91a4`](https://github.com/Effect-TS/effect-smol/commit/86a91a4f0c59286dfa9393232d8020dea70ed4db) Thanks @gcanti! - Schema: add withDecodingDefaultTypeKey / withDecodingDefaultType, closes #2012 + +- [#2018](https://github.com/Effect-TS/effect-smol/pull/2018) [`131caf9`](https://github.com/Effect-TS/effect-smol/commit/131caf9525151a0cb29803a8f1dffa0f4f479d12) Thanks @gcanti! - Schema: allow `Class` constructors to accept `void` when all fields are optional, closes #2015. + +- [#2016](https://github.com/Effect-TS/effect-smol/pull/2016) [`c3615c8`](https://github.com/Effect-TS/effect-smol/commit/c3615c88379b9daf252df0db72c6ac5a20326406) Thanks @gcanti! - Schema: rename `"~rebuild.out"` to `"Rebuild"` + +## 4.0.0-beta.46 + +### Patch Changes + +- [#2008](https://github.com/Effect-TS/effect-smol/pull/2008) [`3a30b9e`](https://github.com/Effect-TS/effect-smol/commit/3a30b9e2ec2bd8b8193e1aa139f6878a07e3f5ee) Thanks @tim-smart! - fix eventlog skipping entries + +## 4.0.0-beta.45 + +### Patch Changes + +- [#1883](https://github.com/Effect-TS/effect-smol/pull/1883) [`5c3af6d`](https://github.com/Effect-TS/effect-smol/commit/5c3af6d554f60be34f8fc21d598d9a298ae11beb) Thanks @tim-smart! - Add EventLogServerUnencrypted module + +## 4.0.0-beta.44 + +### Patch Changes + +- [#1943](https://github.com/Effect-TS/effect-smol/pull/1943) [`e3f0621`](https://github.com/Effect-TS/effect-smol/commit/e3f0621454c3f5d11070d30619da27c9232cadc1) Thanks @gcanti! - Add `DateFromString`, `BigIntFromString`, `BigDecimalFromString`, `TimeZoneNamedFromString`, `TimeZoneFromString`, and `DateTimeZonedFromString` schemas, closes #1941. + +- [#1996](https://github.com/Effect-TS/effect-smol/pull/1996) [`5b476ab`](https://github.com/Effect-TS/effect-smol/commit/5b476abc0bd7e9bb59135ea1bcad2e4936227ced) Thanks @gcanti! - Schema: add `StringFromBase64`, `StringFromBase64Url`, `StringFromHex`, and `StringFromUriComponent` schemas for decoding encoded strings into UTF-8 strings, closes #1995. + +- [#1952](https://github.com/Effect-TS/effect-smol/pull/1952) [`6b40e5a`](https://github.com/Effect-TS/effect-smol/commit/6b40e5a4a6bd2087c15a3d7374d25057fdedfa16) Thanks @tim-smart! - Effect.repeat now uses effect return value when using options + +- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename Atom's `Context` type to `AtomContext` + +- [#1975](https://github.com/Effect-TS/effect-smol/pull/1975) [`3b09fb3`](https://github.com/Effect-TS/effect-smol/commit/3b09fb31c40c2802b01f21c23bcdd1fe7fb0aa82) Thanks @tim-smart! - catch defects when building Entity handlers + +- [#2000](https://github.com/Effect-TS/effect-smol/pull/2000) [`2370410`](https://github.com/Effect-TS/effect-smol/commit/237041062e5af4594d32db91597e34e70a632877) Thanks @tim-smart! - fix cache constructor inference by moving the lookup option + +- [#1928](https://github.com/Effect-TS/effect-smol/pull/1928) [`dabc272`](https://github.com/Effect-TS/effect-smol/commit/dabc272444a700eb629c07ba3e77671a841ca86e) Thanks @tim-smart! - Add `schema.makeEffect(input, options?)` to `Schema.Bottom` and schema-backed classes, matching the existing constructor behavior exposed by `makeUnsafe` / `makeOption` while returning an `Effect` failure with `Schema.SchemaError`. + +- [#1949](https://github.com/Effect-TS/effect-smol/pull/1949) [`08b63c3`](https://github.com/Effect-TS/effect-smol/commit/08b63c3df11bd35c9fd6090dbd166287fdc40664) Thanks @tim-smart! - Update the unstable HTTP middleware logger to annotate only the request path in `http.url` instead of including the full URL (query / fragment), and add a regression test. + +- [#1962](https://github.com/Effect-TS/effect-smol/pull/1962) [`dfff04c`](https://github.com/Effect-TS/effect-smol/commit/dfff04c4c2b1d352dfad83992a6dce1280c85cf9) Thanks @tim-smart! - Add `KeyValueStore.layerSql` to back key-value storage with a SQL database via `SqlClient`. + +- [#1963](https://github.com/Effect-TS/effect-smol/pull/1963) [`9baed9e`](https://github.com/Effect-TS/effect-smol/commit/9baed9e17e84702e6e480fcef6f86404f9e24be9) Thanks @tim-smart! - Fix `Unify.unify` so Layer unions merge correctly, and add type tests covering Layer unification. + +- [#2004](https://github.com/Effect-TS/effect-smol/pull/2004) [`7846792`](https://github.com/Effect-TS/effect-smol/commit/7846792adc7e1631d62d26d657bd7ba6139f369b) Thanks @tim-smart! - Fix `Stream.toQueue` types and implementation to return a `Queue.Dequeue` in both overloads and delegate to `Channel.toQueueArray`. + +- [#1974](https://github.com/Effect-TS/effect-smol/pull/1974) [`1556a24`](https://github.com/Effect-TS/effect-smol/commit/1556a247623636b7ebe438fb56d77f1a7bf957bb) Thanks @juliusmarminge! - Fix unstable CLI boolean flags so `Flag.optional(Flag.boolean(...))` returns `Option.none()` when omitted, and support canonical `--no-` negation for boolean flags. + +- [#1980](https://github.com/Effect-TS/effect-smol/pull/1980) [`7c11bc2`](https://github.com/Effect-TS/effect-smol/commit/7c11bc292ab8e46252fe8f7576fb685917bfb8b5) Thanks @tim-smart! - fix Entity.keepAlive + +- [#1929](https://github.com/Effect-TS/effect-smol/pull/1929) [`b5ea591`](https://github.com/Effect-TS/effect-smol/commit/b5ea5913ec1d45d0dd12a327b9dd966bda2f6d02) Thanks @gcanti! - Simplify and align the default-value APIs. + + `Schema.withConstructorDefault` now accepts an `Effect` instead of `(o: Option) => Option | Effect>`. + + `Schema.withDecodingDefault` / `Schema.withDecodingDefaultKey` now accept an `Effect` instead of `() => T`, enabling effectful defaults. + + `SchemaGetter.withDefault` follows the same change, accepting `Effect` instead of `() => T`. + +- [#1966](https://github.com/Effect-TS/effect-smol/pull/1966) [`0853afa`](https://github.com/Effect-TS/effect-smol/commit/0853afaeb1633b2d7f8b66893bd01c3aa1ef2c22) Thanks @gcanti! - Reuse existing references when duplicate identifiers have the same representation, closes #1927. + +- [#1942](https://github.com/Effect-TS/effect-smol/pull/1942) [`ac845f3`](https://github.com/Effect-TS/effect-smol/commit/ac845f3ab40e0b8719576e7f9bc16ea2e0e02cd4) Thanks @gcanti! - Fix `ErrorClass` and `TaggedErrorClass` `toString` to match native `Error` output format (e.g. `E: my message` instead of `E({"message":"my message"})`), closes #1940. + + Also fix prototype properties (e.g. `name`) being lost after `.extend()`. + +- [#1956](https://github.com/Effect-TS/effect-smol/pull/1956) [`b80c462`](https://github.com/Effect-TS/effect-smol/commit/b80c46247480f47bb64fc480fab48a3f37bc8888) Thanks @gcanti! - Add `Schema.resolveAnnotationsKey` API to retrieve the context (key-level) annotations from a schema, closes #1947. + + Also rename `Schema.resolveInto` to `Schema.resolveAnnotations`. + +- [#2005](https://github.com/Effect-TS/effect-smol/pull/2005) [`b3f535d`](https://github.com/Effect-TS/effect-smol/commit/b3f535d9a7ac13b5fb984c29f93561c57a081ff0) Thanks @gcanti! - Fix `Stream.splitLines` to correctly handle standalone `\r` as a line terminator and flush the final unterminated line when the stream ends, closes #2002. + +- [#1936](https://github.com/Effect-TS/effect-smol/pull/1936) [`6fe2e93`](https://github.com/Effect-TS/effect-smol/commit/6fe2e93cc2f1b173ef89651d74b6a5d2626b3226) Thanks @IMax153! - Fix `Stream.groupedWithin` dropping partial batches when the upstream ends or goes idle. + +- [#1981](https://github.com/Effect-TS/effect-smol/pull/1981) [`cda8004`](https://github.com/Effect-TS/effect-smol/commit/cda800451c1ffbdddfc08415aed7b2d91e0412ee) Thanks @tim-smart! - add rpc ConnectionHooks + +- [#1965](https://github.com/Effect-TS/effect-smol/pull/1965) [`8335477`](https://github.com/Effect-TS/effect-smol/commit/8335477a8a936a24b5f3ee6203c1b268bd1bfc3c) Thanks @tim-smart! - return resolvers directly from SqlModel.makeResolvers + +- [#1960](https://github.com/Effect-TS/effect-smol/pull/1960) [`8c836f9`](https://github.com/Effect-TS/effect-smol/commit/8c836f99ab1e896b9580a71d67773625baff2eaf) Thanks @IMax153! - Add `ChildProcessHandle.unref`, returning an `Effect` that restores the child process reference when run. + +- [#1984](https://github.com/Effect-TS/effect-smol/pull/1984) [`718ff6f`](https://github.com/Effect-TS/effect-smol/commit/718ff6fe3e3d3820cefd67d2bff1b2224fe08060) Thanks @jannabiforever! - Make `Effect.retry` with `times` argument to propagate the original error. + +- [#1930](https://github.com/Effect-TS/effect-smol/pull/1930) [`7eed84f`](https://github.com/Effect-TS/effect-smol/commit/7eed84fc33c5781a6fb11bf4fd189d424902ebd4) Thanks @mikearnaldi! - Add `Stream.service` and `Stream.serviceOption` for accessing services as single-element streams. + +- [#1935](https://github.com/Effect-TS/effect-smol/pull/1935) [`5df46fe`](https://github.com/Effect-TS/effect-smol/commit/5df46fe2f654d59ab5fc1578f4fc27fa40368ef9) Thanks @gcanti! - Schema: add `asClass` API to turn any schema into a class with static method support. + + **Example** + + ```ts + import { Schema } from "effect"; + + class MyString extends Schema.asClass(Schema.String) { + static readonly decodeUnknownSync = Schema.decodeUnknownSync(this); + } + + MyString.decodeUnknownSync("a"); // "a" + ``` + +- [#1958](https://github.com/Effect-TS/effect-smol/pull/1958) [`82dd0f2`](https://github.com/Effect-TS/effect-smol/commit/82dd0f26c6442b07143762ef7bc33742d3978dd6) Thanks @gcanti! - Schema: add `MissingSelfGeneric` compile-time error for `Class`, `TaggedClass`, `ErrorClass`, and `TaggedErrorClass` when the `Self` type parameter is omitted. + +- [#1957](https://github.com/Effect-TS/effect-smol/pull/1957) [`03ae41e`](https://github.com/Effect-TS/effect-smol/commit/03ae41e7304cffac9f18feea22b73468feafc43a) Thanks @gcanti! - Schema: remove `"~annotate.in"` type from `Bottom` interface, inlining it where needed + +- [#1951](https://github.com/Effect-TS/effect-smol/pull/1951) [`4677a0a`](https://github.com/Effect-TS/effect-smol/commit/4677a0a58f95eea38a211efcd3f345f237a9e44a) Thanks @gcanti! - Rename `Schema.makeUnsafe` instance method back to `Schema.make` on all schemas and schema-backed classes. + + Also remove the `static readonly make` override from `ShardId` to avoid conflicting with the inherited schema `make` method. The module-level `ShardId.make(group, id)` function is still available. + +- [#1999](https://github.com/Effect-TS/effect-smol/pull/1999) [`87e1fc8`](https://github.com/Effect-TS/effect-smol/commit/87e1fc8b67e4901d75f567b2fecc3841ab762cc4) Thanks @tim-smart! - use NoInfer in Layer constructors to prevent type erasure + +- [#1971](https://github.com/Effect-TS/effect-smol/pull/1971) [`c1af1b7`](https://github.com/Effect-TS/effect-smol/commit/c1af1b756f63291e9c0298cf95c98a6920a0c2a0) Thanks @joepjoosten! - Allow unstable CLI fallback prompts to be created dynamically from an `Effect`. + +- [#1961](https://github.com/Effect-TS/effect-smol/pull/1961) [`7bb5dce`](https://github.com/Effect-TS/effect-smol/commit/7bb5dce60e1d904ef049a0287dec2b2e6113c970) Thanks @IMax153! - Rename the `ServiceMap` module to `Context` across exports, docs, and tests. + +- [#1973](https://github.com/Effect-TS/effect-smol/pull/1973) [`c8a877b`](https://github.com/Effect-TS/effect-smol/commit/c8a877b53e8f29616335719e5dd1c3992dddf780) Thanks @joepjoosten! - Underline the active label in CLI multi-select prompts and add a scratchpad example for manual verification. + +- [#1967](https://github.com/Effect-TS/effect-smol/pull/1967) [`7da961a`](https://github.com/Effect-TS/effect-smol/commit/7da961ae4916229d2246699a5d3b20e5b2dd2020) Thanks @tim-smart! - clean up ShardId + +## 4.0.0-beta.43 + +### Patch Changes + +- [#1904](https://github.com/Effect-TS/effect-smol/pull/1904) [`2ae33d0`](https://github.com/Effect-TS/effect-smol/commit/2ae33d050914915f7cb9c25ab0a020901e08d596) Thanks @juliusmarminge! - Fix JSON-RPC serialization for `id` values that are falsey but valid, including `0` and `""`, while still mapping `null` to Effect's internal notification sentinel. + +- [#1900](https://github.com/Effect-TS/effect-smol/pull/1900) [`979811a`](https://github.com/Effect-TS/effect-smol/commit/979811a4c3f7ed21ed18ef560c49fb7f5569e80e) Thanks @tim-smart! - Fix AI structured output schema generation for `Schema.Class` and `Schema.ErrorClass` by resolving top-level `$ref` entries before passing JSON Schema to providers and default codec transformers. + +- [#1908](https://github.com/Effect-TS/effect-smol/pull/1908) [`eb7dbef`](https://github.com/Effect-TS/effect-smol/commit/eb7dbeffa883386ad912815e62c0820cac1fdf8e) Thanks @tim-smart! - Fix stream requests in Entity.toLayerQueue + +- [#1907](https://github.com/Effect-TS/effect-smol/pull/1907) [`cf50eb4`](https://github.com/Effect-TS/effect-smol/commit/cf50eb49cb04706dae5185f624708117c413dee8) Thanks @tim-smart! - add WorkflowEngine interruptUnsafe + +- [#1903](https://github.com/Effect-TS/effect-smol/pull/1903) [`1d046fe`](https://github.com/Effect-TS/effect-smol/commit/1d046fe484560e23f3e22cb23eec6433f8f1fa02) Thanks @kitlangton! - Add `Layer.suspend` as a lazy constructor for dynamically choosing a layer while preserving normal layer sharing. + +## 4.0.0-beta.42 + +### Patch Changes + +- [#1897](https://github.com/Effect-TS/effect-smol/pull/1897) [`924e216`](https://github.com/Effect-TS/effect-smol/commit/924e216caa7e0bbf22e994a0cd2ce8b1f0f0b3ee) Thanks @IMax153! - Append concrete choice values to CLI flag help descriptions so generated help shows valid command-line inputs. + +- [#1894](https://github.com/Effect-TS/effect-smol/pull/1894) [`80e7f0c`](https://github.com/Effect-TS/effect-smol/commit/80e7f0cd9116e811e97b0ce30a77a8d1ecd072aa) Thanks @tim-smart! - Fix `MutableList.appendAll` / `appendAllUnsafe` so empty arrays are treated as a no-op instead of leaving behind an empty internal bucket. + +- [#1895](https://github.com/Effect-TS/effect-smol/pull/1895) [`f8328bf`](https://github.com/Effect-TS/effect-smol/commit/f8328bf0314da3dc7f31d314f94a5840e8d5217f) Thanks @tim-smart! - Changed socket close handling so all close codes are treated as errors by default unless `closeCodeIsError` is overridden. + +- [#1899](https://github.com/Effect-TS/effect-smol/pull/1899) [`66d1c06`](https://github.com/Effect-TS/effect-smol/commit/66d1c06039079129707a230f7ad8c676439d7133) Thanks @gcanti! - SchemaRepresentation: support `anyOf`/`oneOf` with sibling keywords in `fromJsonSchemaMultiDocument` + +- [#1893](https://github.com/Effect-TS/effect-smol/pull/1893) [`bee800b`](https://github.com/Effect-TS/effect-smol/commit/bee800bf285192a01bec72a7b7b51bc1159434e6) Thanks @gcanti! - `Number.remainder`: fix incorrect results for small floats in scientific notation (e.g. `1e-7`). + +- [#1898](https://github.com/Effect-TS/effect-smol/pull/1898) [`8930441`](https://github.com/Effect-TS/effect-smol/commit/8930441dee6f94c59c583d18d3ebd677cf1f2623) Thanks @mikearnaldi! - Rename `Effect.transaction` to `Effect.tx` and `Effect.retryTransaction` to `Effect.txRetry`, remove `Effect.transactionWith` / `Effect.withTxState`, make nested `Effect.tx` calls compose into the active transaction, and make the public `Tx*` APIs establish atomic transactions without requiring `Transaction` in common usage. + +## 4.0.0-beta.41 + +### Patch Changes + +- [#1881](https://github.com/Effect-TS/effect-smol/pull/1881) [`36f5c21`](https://github.com/Effect-TS/effect-smol/commit/36f5c2174d31ab42c4598bf81f178f40d0802283) Thanks @gcanti! - Added `BigDecimal.sumAll` and `BigDecimal.multiplyAll` for feature parity with `Number` and `BigInt`, closes #1880. + +- [#1869](https://github.com/Effect-TS/effect-smol/pull/1869) [`d8ce758`](https://github.com/Effect-TS/effect-smol/commit/d8ce758669d6297ae932ac3251d83e7b49b22f30) Thanks @gcanti! - Schema: collapse same-type literal branches in JSON Schema output into a single `enum` array, closes #1868. + + Before: + + ```json + { + "anyOf": [ + { "type": "string", "enum": ["A"] }, + { "type": "string", "enum": ["B"] } + ] + } + ``` + + After: + + ```json + { + "type": "string", + "enum": ["A", "B"] + } + ``` + +- [#1879](https://github.com/Effect-TS/effect-smol/pull/1879) [`11aab4c`](https://github.com/Effect-TS/effect-smol/commit/11aab4c6d37d5691adafc2d33da1a631b28ce814) Thanks @tim-smart! - Highlight active option labels in `Prompt.select` and `Prompt.multiSelect` using cyan text so selection state is visible beyond the pointer / checkbox icon. + +- [#1884](https://github.com/Effect-TS/effect-smol/pull/1884) [`3bc1efb`](https://github.com/Effect-TS/effect-smol/commit/3bc1efb53dd75b4a40de46f1f80c7f8a7d50af86) Thanks @tim-smart! - Fail RpcClient HTTP requests when the server response contains no RPC messages instead of leaving requests pending. + +- [#1875](https://github.com/Effect-TS/effect-smol/pull/1875) [`70e724e`](https://github.com/Effect-TS/effect-smol/commit/70e724e604604d4be1061cd8da0d360494998c84) Thanks @IMax153! - Fix AI text method toolkit typing to support generic handler toolkits, preserve toolkit union inference, and keep response part narrowing by tool name. + +- [#1876](https://github.com/Effect-TS/effect-smol/pull/1876) [`738dee7`](https://github.com/Effect-TS/effect-smol/commit/738dee7edfd70af82dc4d2376db3a8ebe603eb48) Thanks @tim-smart! - Track ManagedRuntime fibers in a scope + +- [#1886](https://github.com/Effect-TS/effect-smol/pull/1886) [`2111963`](https://github.com/Effect-TS/effect-smol/commit/2111963f19b4c28c800664a8fac9590c1321885f) Thanks @tim-smart! - add ClusterSchema.WithTransaction annotation + +- [#1877](https://github.com/Effect-TS/effect-smol/pull/1877) [`198a553`](https://github.com/Effect-TS/effect-smol/commit/198a553d9ce45f6a00bfc4d65ed0640669602d95) Thanks @tim-smart! - allow Context.Key to be covariant + +## 4.0.0-beta.40 + +### Patch Changes + +- [#1863](https://github.com/Effect-TS/effect-smol/pull/1863) [`f62860f`](https://github.com/Effect-TS/effect-smol/commit/f62860f0e5e45978fabf7256ae620a13152a772a) Thanks @tim-smart! - fix issues with metro bundler + +- [#1866](https://github.com/Effect-TS/effect-smol/pull/1866) [`973f281`](https://github.com/Effect-TS/effect-smol/commit/973f2812529aadc1cc54598b2039799fa72b80f8) Thanks @tim-smart! - add Stream.timeoutOrElse + +## 4.0.0-beta.39 + +### Patch Changes + +- [#1844](https://github.com/Effect-TS/effect-smol/pull/1844) [`f91fd3d`](https://github.com/Effect-TS/effect-smol/commit/f91fd3db39fe5628439fd175fba201a65a1aa9d0) Thanks @tim-smart! - Relax `HttpApiClient.urlBuilder` to accept `HttpApi.Any` instead of requiring `HttpApi.AnyWithProps`. + This allows use in helpers generic over `HttpApi.Any` while preserving inferred URL builder types. + +- [#1851](https://github.com/Effect-TS/effect-smol/pull/1851) [`edaae9d`](https://github.com/Effect-TS/effect-smol/commit/edaae9d65f464f941d7eddd723cd33d324f4b071) Thanks @tim-smart! - Re-export additional core runtime references from `effect/References`, including logger and error reporter references. + +- [#1856](https://github.com/Effect-TS/effect-smol/pull/1856) [`b47db0b`](https://github.com/Effect-TS/effect-smol/commit/b47db0bd5802064b6a24b3ea27c6ff2e0520d513) Thanks @gcanti! - Fix `Struct` utility return types (for example `pick`) to preserve the previous simplified shape instead of exposing raw utility types like `Pick`, closes #1855. + +- [#1849](https://github.com/Effect-TS/effect-smol/pull/1849) [`82d3c8e`](https://github.com/Effect-TS/effect-smol/commit/82d3c8e4f3f49b00df611b25aa6f8f74ec21b59b) Thanks @tim-smart! - Fix the `Queue.takeN` documentation example to end the queue before showing a partial batch. + +- [#1848](https://github.com/Effect-TS/effect-smol/pull/1848) [`7c22b31`](https://github.com/Effect-TS/effect-smol/commit/7c22b315d198dcbf44ae8cdb8b37879e1c9e3996) Thanks @tim-smart! - Remove `Schedule.compose` in favor of `Schedule.both`, and update schedule examples to use `Schedule.both`. + +## 4.0.0-beta.38 + +### Patch Changes + +- [#1842](https://github.com/Effect-TS/effect-smol/pull/1842) [`f4dbe5b`](https://github.com/Effect-TS/effect-smol/commit/f4dbe5b26b9c2d33fae024bf44afbdf8541792cd) Thanks @gcanti! - Schema: rename `MakeOptions.disableValidation` to `disableChecks`. Apply constructor defaults when `disableChecks` is true, closes #1841. + +- [#1837](https://github.com/Effect-TS/effect-smol/pull/1837) [`a71a607`](https://github.com/Effect-TS/effect-smol/commit/a71a607c89fb6669a12a562c2c23be81dfbe1adb) Thanks @kitlangton! - Fix `HttpApiBuilder` security middleware caching so separate handler builds do not reuse the first provided middleware implementation. + +- [#1840](https://github.com/Effect-TS/effect-smol/pull/1840) [`66a0494`](https://github.com/Effect-TS/effect-smol/commit/66a0494ed75cd12f2721dcbb1d8a072e3d9e14b6) Thanks @tim-smart! - Rename HttpApiClient request option `withResponse` to `responseMode` and add support for `responseMode: "response-only"` to return the raw `HttpClientResponse` without decoding. + +- [#1838](https://github.com/Effect-TS/effect-smol/pull/1838) [`5ef7218`](https://github.com/Effect-TS/effect-smol/commit/5ef7218fc559d57301fe929b8a0cab4033f4f1fd) Thanks @tim-smart! - Update `HttpApiClient.urlBuilder` to mirror client shape, and encode params/query via endpoint schemas before building URLs. + +- [#1700](https://github.com/Effect-TS/effect-smol/pull/1700) [`472d260`](https://github.com/Effect-TS/effect-smol/commit/472d260655bc311fba5c2c6e23bb77d8f7e36ba0) Thanks @tim-smart! - add `useCodecs` option to HttpClientEndpoint constructors + +## 4.0.0-beta.37 + +### Patch Changes + +- [#1812](https://github.com/Effect-TS/effect-smol/pull/1812) [`f7a0b71`](https://github.com/Effect-TS/effect-smol/commit/f7a0b711da8fdd645597dee29cacc5619c6afcf2) Thanks @tim-smart! - Consolidate the SqlError changes to the new reason-based shape across effect and the SQL drivers, classifying native failures into structured reasons with Unknown fallback where native codes are unavailable. + +- [#1816](https://github.com/Effect-TS/effect-smol/pull/1816) [`1e223c3`](https://github.com/Effect-TS/effect-smol/commit/1e223c30ccf835dfbb21284535d78549efaeca80) Thanks @tim-smart! - unstable/http HttpClientRequest: add toWeb and fromWeb conversions for web Request objects + +- [#1829](https://github.com/Effect-TS/effect-smol/pull/1829) [`53740f4`](https://github.com/Effect-TS/effect-smol/commit/53740f47aa76d114b7d535649fb50efc54a09608) Thanks @tim-smart! - Fix sql migrator lock handling to only treat duplicate migration-row inserts as a concurrent migration lock. + +- [#1831](https://github.com/Effect-TS/effect-smol/pull/1831) [`8c7cf89`](https://github.com/Effect-TS/effect-smol/commit/8c7cf89f719e580cbce1bf6c24e6996f1992a0a6) Thanks @tim-smart! - Fix `Schedule.fixed` to run the next iteration immediately when the previous action takes longer than the configured interval. + +- [#1833](https://github.com/Effect-TS/effect-smol/pull/1833) [`b6b81a9`](https://github.com/Effect-TS/effect-smol/commit/b6b81a940eaafcbc792d25413d6c02c707de31b2) Thanks @tim-smart! - Fix `Unify.unify` so unions of `Effect` values collapse to a single unified `Effect` type again. + +- [#1825](https://github.com/Effect-TS/effect-smol/pull/1825) [`8f4c1f9`](https://github.com/Effect-TS/effect-smol/commit/8f4c1f97ed60f8810b0b327b50117ffb2d8260d4) Thanks @skoshx! - Fix DevToolsClient not flushing final span events on teardown. + + The stream consumer was `forkScoped`, causing it to be interrupted before + it could drain remaining queue items. Replaced with `forkChild` and + `Fiber.await` in the finalizer so the stream drains naturally after the + queue is failed. + +- [#1824](https://github.com/Effect-TS/effect-smol/pull/1824) [`f2479f9`](https://github.com/Effect-TS/effect-smol/commit/f2479f9d3113b1f012db17a3852b4e28f478cf9c) Thanks @tim-smart! - Ignore unsupported Ctrl key combinations in interactive CLI prompts to avoid rendering control characters such as Ctrl+L form feed into prompt input. + +- [#1819](https://github.com/Effect-TS/effect-smol/pull/1819) [`c919921`](https://github.com/Effect-TS/effect-smol/commit/c9199217fad65529421d2cf95ecfff41257090fd) Thanks @j! - HttpServerResponse: fix `fromWeb` to preserve Content-Type header when response has a body + + Previously, when converting a web `Response` to an `HttpServerResponse` via `fromWeb`, the `Content-Type` header was not passed to `Body.stream()`, causing it to default to `application/octet-stream`. This affected any code using `HttpApp.fromWebHandler` to wrap web handlers, as JSON responses would incorrectly have their Content-Type set to `application/octet-stream` instead of `application/json`. + +- [#1821](https://github.com/Effect-TS/effect-smol/pull/1821) [`7af90c2`](https://github.com/Effect-TS/effect-smol/commit/7af90c2e3c99038eafa39650433839523790e2fe) Thanks @gcanti! - Schema: relax `asserts` and `is` constraints. + +- [#1822](https://github.com/Effect-TS/effect-smol/pull/1822) [`f3be185`](https://github.com/Effect-TS/effect-smol/commit/f3be18569e5ca57c25eabf00df3ca601ebab43c7) Thanks @tim-smart! - improve runSync error when executing async effects + +## 4.0.0-beta.36 + +### Patch Changes + +- [#1793](https://github.com/Effect-TS/effect-smol/pull/1793) [`60fcbcc`](https://github.com/Effect-TS/effect-smol/commit/60fcbcc43d09471e8f7e0969955d99dcefc5be81) Thanks @tim-smart! - Ensure streamed tool results are emitted before the finish part so chat history includes tool outputs before stream termination. + +- [#1762](https://github.com/Effect-TS/effect-smol/pull/1762) [`0a60837`](https://github.com/Effect-TS/effect-smol/commit/0a6083713124440e630030375bab367e8d7df24e) Thanks @kitlangton! - Allow unstable HttpApi middleware to declare multiple error schemas with arrays. + + Middleware errors now follow endpoint error behavior for response status resolution, client decoding, and generated API schemas. + +- [#1805](https://github.com/Effect-TS/effect-smol/pull/1805) [`49164d2`](https://github.com/Effect-TS/effect-smol/commit/49164d2c20a8d21b66514992c4a15d8521f6b36e) Thanks @tim-smart! - Fix `Effect.cachedWithTTL` and `Effect.cachedInvalidateWithTTL` to start TTL expiration when the cached value is produced instead of when computation starts. + +- [#1808](https://github.com/Effect-TS/effect-smol/pull/1808) [`334b6e4`](https://github.com/Effect-TS/effect-smol/commit/334b6e4f76fe11941b516d61f57e268bc31f0ca6) Thanks @tim-smart! - Backport `Cron.prev` with reverse lookup tables and cron stepping logic, including DST-aware reverse traversal. + +- [#1789](https://github.com/Effect-TS/effect-smol/pull/1789) [`5700695`](https://github.com/Effect-TS/effect-smol/commit/5700695f76ae6da6b94c9c87d4dd2b8054fb829b) Thanks @mikearnaldi! - Fix `Stream.scanEffect` hanging and repeatedly emitting the initial state. + +- [#1810](https://github.com/Effect-TS/effect-smol/pull/1810) [`f8f4456`](https://github.com/Effect-TS/effect-smol/commit/f8f445644f3aa7ec093cab7445198a62ba18a480) Thanks @tim-smart! - Support key-derived `idleTimeToLive` in `LayerMap` options (`make`, `fromRecord`, and `LayerMap.Service`) and add `LayerMap` tests for dynamic TTL behavior. + +- [#1802](https://github.com/Effect-TS/effect-smol/pull/1802) [`969d24f`](https://github.com/Effect-TS/effect-smol/commit/969d24fdfa48c4838e811983848d9cb4e9b3b12c) Thanks @kitlangton! - PubSub.publish and PubSub.publishAll now return false on shutdown instead of interrupting, matching Queue.offer semantics. + +- [#1796](https://github.com/Effect-TS/effect-smol/pull/1796) [`851eda0`](https://github.com/Effect-TS/effect-smol/commit/851eda0533946e39bacaaf581896320d7a4f3e8c) Thanks @tim-smart! - Improve `Prompt.file` to support incremental filtering while typing, including backspace and ctrl-u handling. + +- [#1806](https://github.com/Effect-TS/effect-smol/pull/1806) [`8059c1c`](https://github.com/Effect-TS/effect-smol/commit/8059c1c3eba9a90af7cd889ea261bcb8fff0c185) Thanks @tim-smart! - Fix a regression in `PubSub.shutdown` so shutting down a pubsub interrupts suspended subscribers (including `takeAll`) by ensuring subscriptions are scoped under the pubsub shutdown scope. + +- [#1797](https://github.com/Effect-TS/effect-smol/pull/1797) [`6f83295`](https://github.com/Effect-TS/effect-smol/commit/6f8329546a73eaddc7cb5e85ea8e37e73fbfb611) Thanks @tim-smart! - Add \`Ctrl-A\` and \`Ctrl-E\` key handling for editable CLI text prompts to move the cursor to the beginning or end of the current input line. + +- [#1633](https://github.com/Effect-TS/effect-smol/pull/1633) [`65f7f57`](https://github.com/Effect-TS/effect-smol/commit/65f7f5737575fed668987462c96d29a446707c32) Thanks @kitlangton! - Schema: add `decodeUnknownResult` / `decodeResult` and `encodeUnknownResult` / `encodeResult` helpers for synchronous `Result`-based parsing. + +- [#1798](https://github.com/Effect-TS/effect-smol/pull/1798) [`e7fabd2`](https://github.com/Effect-TS/effect-smol/commit/e7fabd2265db690eae5cfc9b83730c84699aef61) Thanks @gcanti! - Schema: allow using `Struct` type helpers directly, e.g. `Schema.Struct.Type` instead of `Schema.Schema.Type>`. + +- [#1794](https://github.com/Effect-TS/effect-smol/pull/1794) [`89c3e98`](https://github.com/Effect-TS/effect-smol/commit/89c3e985401eb38f33a3ae21a94ad27de3c1d28b) Thanks @tim-smart! - Fix ai LanguageModel streaming finish parts so finish events are always emitted when a toolkit is provided. + +- [#1785](https://github.com/Effect-TS/effect-smol/pull/1785) [`53794ab`](https://github.com/Effect-TS/effect-smol/commit/53794ab7af30aa5c5004ecf53659fafbe4b10542) Thanks @KhraksMamtsov! - add missing Equivalence.Date + +## 4.0.0-beta.35 + +### Patch Changes + +- [#1782](https://github.com/Effect-TS/effect-smol/pull/1782) [`9252b43`](https://github.com/Effect-TS/effect-smol/commit/9252b43560f507709c2985abcf52a7837b23ddf8) Thanks @gcanti! - Add `Schema.ArrayEnsure`. + +- [#1784](https://github.com/Effect-TS/effect-smol/pull/1784) [`7daf387`](https://github.com/Effect-TS/effect-smol/commit/7daf3870a656882a488a60f67881e6808c8f4d04) Thanks @gcanti! - Add `Config.Success` type utility, closes #1783. + +- [#1778](https://github.com/Effect-TS/effect-smol/pull/1778) [`e1664a3`](https://github.com/Effect-TS/effect-smol/commit/e1664a38bc31ef4ceb4e9324c7226e1e99bf9c07) Thanks @tim-smart! - Allow `Effect.acquireRelease` release finalizers to depend on the surrounding environment. + +- [#1777](https://github.com/Effect-TS/effect-smol/pull/1777) [`fdaa6e0`](https://github.com/Effect-TS/effect-smol/commit/fdaa6e0a41b6b6605438fa8557441792135380a2) Thanks @tim-smart! - Remove an unreachable array branch in `decodeJsonRpcRaw` to simplify JSON-RPC decode logic without changing behavior. + +- [#1774](https://github.com/Effect-TS/effect-smol/pull/1774) [`19aa47e`](https://github.com/Effect-TS/effect-smol/commit/19aa47ef7b470e427620edca8970dd9cdd551216) Thanks @tim-smart! - Align CLI help flag and global flag descriptions to a single column even when some flag names are very long. + +- [#1780](https://github.com/Effect-TS/effect-smol/pull/1780) [`c667dad`](https://github.com/Effect-TS/effect-smol/commit/c667dad07777b860e4764a3ba9a6cc41c236cd98) Thanks @tim-smart! - Fix `LanguageModel` incremental prompt fallback to reliably retry with the full prompt when an incremental request fails with `InvalidRequestError`. + +- [#1781](https://github.com/Effect-TS/effect-smol/pull/1781) [`764d150`](https://github.com/Effect-TS/effect-smol/commit/764d1501bc5026b60fc8aef6cb02a5a87c762801) Thanks @gcanti! - Fix `DateTime.makeUnsafe` incorrectly appending "Z" to date strings containing "GMT" + +- [#1772](https://github.com/Effect-TS/effect-smol/pull/1772) [`3c27098`](https://github.com/Effect-TS/effect-smol/commit/3c27098b5685a63db2c2eff654a250c94d3fcfa7) Thanks @tim-smart! - make Layer.mock work with Stream and Channel + +## 4.0.0-beta.34 + +### Patch Changes + +- [#1758](https://github.com/Effect-TS/effect-smol/pull/1758) [`f2f75ee`](https://github.com/Effect-TS/effect-smol/commit/f2f75ee564bce1cd95f5189c7bdeeed4f92dacb1) Thanks @tim-smart! - Use a normal Map in ResponseIdTracker and clear it on divergence / reset instead of reallocating a WeakMap. + +- [#1764](https://github.com/Effect-TS/effect-smol/pull/1764) [`342fc4b`](https://github.com/Effect-TS/effect-smol/commit/342fc4b051739e32e7977159f26ff9541eda664f) Thanks @tim-smart! - Add unstable EmbeddingModel support across core and OpenAI providers. + - Add the unstable EmbeddingModel module API surface in `effect`, including service, request, response, and provider types. + - Implement the unstable EmbeddingModel runtime constructor in `effect`, with `RequestResolver` batching, `embed` / `embedMany` spans, provider error propagation, deterministic ordering, and empty-input `embedMany` fast-path behavior. + - Add and align EmbeddingModel behavior tests in `effect` for embedding usage, batching, ordering, and error handling. + - Add `OpenAiEmbeddingModel` in `@effect/ai-openai`, including model / make / layer constructors, config overrides, and provider output index validation with deterministic reordering. + - Add OpenAI-compatible EmbeddingModel provider support in `@effect/ai-openai-compat`, including config overrides, layer constructors, and output index validation. + +- [#1766](https://github.com/Effect-TS/effect-smol/pull/1766) [`5d704ee`](https://github.com/Effect-TS/effect-smol/commit/5d704ee10d20e8eb107e34bb8a21feb5aa4a7685) Thanks @tim-smart! - Fix JSDoc wording for `Effect.catch` to consistently reference the current API name. + +- [#1771](https://github.com/Effect-TS/effect-smol/pull/1771) [`00add69`](https://github.com/Effect-TS/effect-smol/commit/00add69b59551e9df34772eb927638b093f6d71e) Thanks @tim-smart! - Add `EmbeddingModel.ModelDimensions` and require dimensions in embedding provider `model` constructors. + +- [#1767](https://github.com/Effect-TS/effect-smol/pull/1767) [`58217d3`](https://github.com/Effect-TS/effect-smol/commit/58217d318a7d716ccd707cce0f41573946939c28) Thanks @gcanti! - Add `isMutableHashMap` and `isMutableHashSet`, and align nominal guard implementations and tests across collections and transactional data types. + +- [#1765](https://github.com/Effect-TS/effect-smol/pull/1765) [`f4e2aba`](https://github.com/Effect-TS/effect-smol/commit/f4e2aba01b76d1e3059b297e3cc942284dfeafb2) Thanks @tim-smart! - retry incremental prompt on invalid request + +- [#1756](https://github.com/Effect-TS/effect-smol/pull/1756) [`e3b44b6`](https://github.com/Effect-TS/effect-smol/commit/e3b44b6a2af9ee21dc5c1e928f0c20af857fa7a9) Thanks @tim-smart! - add HttpApiMiddleware.layerSchemaErrorTransform + +- [#1732](https://github.com/Effect-TS/effect-smol/pull/1732) [`e1472b7`](https://github.com/Effect-TS/effect-smol/commit/e1472b7525c5d57a48bdec2353c3b742f7f916c0) Thanks @KhraksMamtsov! - port Url module from v3 + +- [#1761](https://github.com/Effect-TS/effect-smol/pull/1761) [`7686320`](https://github.com/Effect-TS/effect-smol/commit/7686320cd123fa352b5c3d076fb18a3cac0a9bba) Thanks @gcanti! - Fix `Tool.make` type and runtime behavior when `parameters` is not provided. + +## 4.0.0-beta.33 + +### Patch Changes + +- [#1754](https://github.com/Effect-TS/effect-smol/pull/1754) [`571447d`](https://github.com/Effect-TS/effect-smol/commit/571447da67334449f8ae3d6ecb3d77ea4e0c4295) Thanks @tim-smart! - narrow types for Effect.retry/repeat while option + +## 4.0.0-beta.32 + +### Patch Changes + +- [#1717](https://github.com/Effect-TS/effect-smol/pull/1717) [`bf8fff8`](https://github.com/Effect-TS/effect-smol/commit/bf8fff8a5f54b6df74cb7bbb42346fe9ba52435a) Thanks @gcanti! - Schema: add `OptionFromOptionalNullOr` schema, closes #1707. + +- [#1722](https://github.com/Effect-TS/effect-smol/pull/1722) [`1af3ef3`](https://github.com/Effect-TS/effect-smol/commit/1af3ef3e3ca7fd417d0fc15f8ca8fe207eba4f74) Thanks @tim-smart! - Fix `RpcSerialization.json` decode so JSON array payloads are not wrapped in an extra outer array. + +- [#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: + - addHttpApi + API-level middleware applied across merged groups + - missing middleware service diagnostics + - missing addHttpApi group layer diagnostics + +- [#1727](https://github.com/Effect-TS/effect-smol/pull/1727) [`2ad6c1b`](https://github.com/Effect-TS/effect-smol/commit/2ad6c1b2c85a3a0fe351e3d56636a75eb76b4b4e) Thanks @tim-smart! - Make all built-in `HttpApiError` classes implement `HttpServerRespondable`, so they can be returned directly from plain HTTP server handlers outside of `HttpApi`. + +- [#1739](https://github.com/Effect-TS/effect-smol/pull/1739) [`398ac3e`](https://github.com/Effect-TS/effect-smol/commit/398ac3e01cb75efce0e4e2913d1450cf65866732) Thanks @tim-smart! - Use predicate-based `dual` dispatch for `Stream.merge` so data-last calls with optional `options` are handled correctly. + +- [#1741](https://github.com/Effect-TS/effect-smol/pull/1741) [`51fe22f`](https://github.com/Effect-TS/effect-smol/commit/51fe22f3266e417b6c541aaed4b75d246fac91e7) Thanks @tim-smart! - Add `Layer.tap`, `Layer.tapError`, and `Layer.tapCause` APIs for effectful observation of layer success and failure without changing layer outputs. + +- [#1740](https://github.com/Effect-TS/effect-smol/pull/1740) [`4605db6`](https://github.com/Effect-TS/effect-smol/commit/4605db69cfacddbdbf1525865ddfde135158090c) Thanks @tim-smart! - Refactor call sites with multiple `Context` mutations to use `Context.mutate` for batched updates. + +- [#1750](https://github.com/Effect-TS/effect-smol/pull/1750) [`f4de1b0`](https://github.com/Effect-TS/effect-smol/commit/f4de1b087c998d0bad1d9468f70b7d16c13b9f6f) Thanks @gcanti! - Improve unstable AI structured output handling for empty tool params and add `Tool.EmptyParams`, closes #1749. + +- [#1525](https://github.com/Effect-TS/effect-smol/pull/1525) [`60214f2`](https://github.com/Effect-TS/effect-smol/commit/60214f2080b2aeb091f691140eb20acb741691c3) Thanks @tim-smart! - use Option instead of undefined | A + +- [#1747](https://github.com/Effect-TS/effect-smol/pull/1747) [`c4b8b0f`](https://github.com/Effect-TS/effect-smol/commit/c4b8b0ffa8efb47c4cd7578a8943d6868509373f) Thanks @tim-smart! - seperate scheduler dispatch from yield decisions + +- [#1729](https://github.com/Effect-TS/effect-smol/pull/1729) [`6d9393a`](https://github.com/Effect-TS/effect-smol/commit/6d9393a0770a18722d23340e77f15455de341245) Thanks @tim-smart! - add Context.mutate + +- [#1753](https://github.com/Effect-TS/effect-smol/pull/1753) [`6de4efe`](https://github.com/Effect-TS/effect-smol/commit/6de4efe463c783614ceb0c094d77a336a899cbe0) Thanks @tim-smart! - Add dtslint coverage for `Stream.catchIf` to lock in predicate and refinement inference behavior in both data-first and data-last forms. + +- [#1716](https://github.com/Effect-TS/effect-smol/pull/1716) [`4f969d1`](https://github.com/Effect-TS/effect-smol/commit/4f969d1563ba755ffa116c8ae409bb3436bd881d) Thanks @gcanti! - Remove unused `effect/NullOr` module. + +- [#1721](https://github.com/Effect-TS/effect-smol/pull/1721) [`6cc67c8`](https://github.com/Effect-TS/effect-smol/commit/6cc67c855e054ee3f3ac3485dca5f7805e79e8fb) Thanks @IMax153! - Correct the type of the schema parameter accepted by the `fileSchema` methods in the CLI to be `Schema.Decoder` + +- [#1709](https://github.com/Effect-TS/effect-smol/pull/1709) [`8531a22`](https://github.com/Effect-TS/effect-smol/commit/8531a22ffbb52e11a030b09f358cafbfdf5edff7) Thanks @mikearnaldi! - Add module-level helpers for `Semaphore`, `Latch`, and extracted `PartitionedSemaphore` operations. + +- [#1752](https://github.com/Effect-TS/effect-smol/pull/1752) [`b226760`](https://github.com/Effect-TS/effect-smol/commit/b22676067617f15c00722a3a63fd7c2c172c3d45) Thanks @tim-smart! - simplify SubscriptionRef + +- [#1743](https://github.com/Effect-TS/effect-smol/pull/1743) [`47a51ab`](https://github.com/Effect-TS/effect-smol/commit/47a51aba0ecdf3ef478bfa28a498bca188399bd4) Thanks @tim-smart! - default ws close codes to 1001 in case they are undefined + +- [#1728](https://github.com/Effect-TS/effect-smol/pull/1728) [`1521d02`](https://github.com/Effect-TS/effect-smol/commit/1521d02e1f19f1d795edaaf862c1a1031d9c755e) Thanks @tim-smart! - add graceful shutdown to http servers + +## 4.0.0-beta.31 + +### Patch Changes + +- [#1696](https://github.com/Effect-TS/effect-smol/pull/1696) [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462) Thanks @krzkaczor! - Add `DurationObject` to `Duration.Input` to support Temporal-style object input. + + Durations can now be created from objects with named unit properties like `{ hours: 1, minutes: 30 }`, similar to `Temporal.Duration.from()`. Supported fields: `weeks`, `days`, `hours`, `minutes`, `seconds`, `millis`, `micros`, `nanos`. + +- [#1705](https://github.com/Effect-TS/effect-smol/pull/1705) [`6f23f0e`](https://github.com/Effect-TS/effect-smol/commit/6f23f0ed4cba573cd9395c2e582f582fe7271544) Thanks @tim-smart! - Preserve message item ordering in the default logger when logging a `Cause` with message values. + +- [#1711](https://github.com/Effect-TS/effect-smol/pull/1711) [`654aaec`](https://github.com/Effect-TS/effect-smol/commit/654aaec593305521b65dd042c204d761cc6e8c28) Thanks @tim-smart! - Fix `RpcGroup.toLayer` and `RpcGroup.toLayerHandler` service requirement inference so handler dependencies are preserved for non-stream RPC handlers. + +- [#1712](https://github.com/Effect-TS/effect-smol/pull/1712) [`2958a42`](https://github.com/Effect-TS/effect-smol/commit/2958a42078966a8713a98f00485ab36484d5eccf) Thanks @tim-smart! - Expose CLI completions as a public unstable module at `effect/unstable/cli/Completions`. + +- [#1713](https://github.com/Effect-TS/effect-smol/pull/1713) [`95d27a2`](https://github.com/Effect-TS/effect-smol/commit/95d27a239ed5147302605ab0b3147a056541b0c7) Thanks @tim-smart! - Make `Layer.mock` a dual API so it supports both `Layer.mock(Service)(impl)` and `Layer.mock(Service, impl)`. + +- [#1704](https://github.com/Effect-TS/effect-smol/pull/1704) [`0fbaea8`](https://github.com/Effect-TS/effect-smol/commit/0fbaea8f9555a8044cec31a770394db613fc78e2) Thanks @tim-smart! - Support toolkit unions in `LanguageModel` options. + +- [#1701](https://github.com/Effect-TS/effect-smol/pull/1701) [`21d5d5e`](https://github.com/Effect-TS/effect-smol/commit/21d5d5e0439fd4d9bb6e508377215b1087555d45) Thanks @tim-smart! - wrap httpapi request context with HttpRouter.Request + +- [#1696](https://github.com/Effect-TS/effect-smol/pull/1696) [`5a84853`](https://github.com/Effect-TS/effect-smol/commit/5a8485397b7f321ae021640c1999821143659462) Thanks @krzkaczor! - allow assigning Temporal types to DateTime & Duration input + +- [#1698](https://github.com/Effect-TS/effect-smol/pull/1698) [`6e49959`](https://github.com/Effect-TS/effect-smol/commit/6e499590357a104c81779b3176cd3f84e4f91064) Thanks @tim-smart! - Include toolkit tool handler requirements in AI generation API environment inference. + +- [#1703](https://github.com/Effect-TS/effect-smol/pull/1703) [`8f5805d`](https://github.com/Effect-TS/effect-smol/commit/8f5805dbdd0d1bc0ff0727cc398c8d80e544edee) Thanks @tim-smart! - Relax `Ndjson` byte-stream channel signatures to accept plain `Uint8Array`. + +- [#1710](https://github.com/Effect-TS/effect-smol/pull/1710) [`990df2c`](https://github.com/Effect-TS/effect-smol/commit/990df2c3ceeb32e659acc10cc9485617f7b3c423) Thanks @gcanti! - Schema: `toCodecJson` now returns `Codec` instead of `Codec`. + + Http: the `json` property on `HttpIncomingMessage`, `HttpClientResponse`, `HttpServerRequest`, and `HttpServerResponse` now returns `Effect` instead of `Effect`. + +## 4.0.0-beta.30 + +### Patch Changes + +- [#1675](https://github.com/Effect-TS/effect-smol/pull/1675) [`c88e5b7`](https://github.com/Effect-TS/effect-smol/commit/c88e5b723ff09da4edaef6ce14d927ca01104a32) Thanks @gijsbartman! - Fix consolePretty ignoring explicit colors option in non-TTY environments. + + When colors is explicitly set to true, prettyLoggerTty was still gating it with processStdoutIsTTY check, making it impossible to enable colors in non-TTY environments like Vite dev server. + +- [#1690](https://github.com/Effect-TS/effect-smol/pull/1690) [`947d0e4`](https://github.com/Effect-TS/effect-smol/commit/947d0e4268ba5c4020ead380aa80812c7342408f) Thanks @gcanti! - Fix `Cause.hasInterruptsOnly` to return `false` for empty causes. + +- [#1620](https://github.com/Effect-TS/effect-smol/pull/1620) [`7517908`](https://github.com/Effect-TS/effect-smol/commit/75179085d159b88a1ab0bce70669d76dcf0d79a4) Thanks @kitlangton! - Fix `TaggedUnion.match` to use `Unify` for return types, allowing + branches to return distinct Effect types that are properly merged. + +- [#1680](https://github.com/Effect-TS/effect-smol/pull/1680) [`a49ecd5`](https://github.com/Effect-TS/effect-smol/commit/a49ecd5a183d7e7d33f47ff95e9d2dea5a12ead5) Thanks @KhraksMamtsov! - make HttpClientResponse pipeable + +- [#1681](https://github.com/Effect-TS/effect-smol/pull/1681) [`6993e33`](https://github.com/Effect-TS/effect-smol/commit/6993e3329122c834c20bacea72d8678232f4f103) Thanks @mikearnaldi! - Add an optional `message` field to `Effect.ignore` and `Effect.ignoreCause` for custom log output. + +- [#1695](https://github.com/Effect-TS/effect-smol/pull/1695) [`514f2a2`](https://github.com/Effect-TS/effect-smol/commit/514f2a2ae54580fcacdbe2ea2196a83a852d0748) Thanks @gcanti! - Remove unused APIs from the `Utils` module. + +- [#1644](https://github.com/Effect-TS/effect-smol/pull/1644) [`3214b47`](https://github.com/Effect-TS/effect-smol/commit/3214b47676de2d33fddc5fecfc2d226e6e83cc7b) Thanks @patroza! - fix: update Service interface to use 'this: void' in 'of' method signatures + +- [#1693](https://github.com/Effect-TS/effect-smol/pull/1693) [`95ec5ed`](https://github.com/Effect-TS/effect-smol/commit/95ec5ed345de77c893049e182d37a37cf164a268) Thanks @tim-smart! - fix cli subcommand context + +## 4.0.0-beta.29 + +### Patch Changes + +- [#1672](https://github.com/Effect-TS/effect-smol/pull/1672) [`9d93adb`](https://github.com/Effect-TS/effect-smol/commit/9d93adb1c1795d1978391b30d7d2972c88052662) Thanks @gcanti! - Add `Newtype` module. + +- [#1677](https://github.com/Effect-TS/effect-smol/pull/1677) [`b52721c`](https://github.com/Effect-TS/effect-smol/commit/b52721cf0d11a567722b060c8536e3bdd4161f07) Thanks @gcanti! - Fix `Schema.isUUID` so the `version` parameter is optional in its public signature. + +- [#1667](https://github.com/Effect-TS/effect-smol/pull/1667) [`a891c7b`](https://github.com/Effect-TS/effect-smol/commit/a891c7b12f415b2287613dd4b91a09dfd38ef30d) Thanks @tim-smart! - Preserve `Atom.withReactivity(...)` refresh behavior when registry initial values seed the wrapped atom. + +- [#1678](https://github.com/Effect-TS/effect-smol/pull/1678) [`ef26cdf`](https://github.com/Effect-TS/effect-smol/commit/ef26cdfb65d9955fc7e161629191930c2cc2c63f) Thanks @tim-smart! - Abort HTTP client requests when response streams are consumed only partially. + +- [#1665](https://github.com/Effect-TS/effect-smol/pull/1665) [`82fd3ed`](https://github.com/Effect-TS/effect-smol/commit/82fd3ed922063ee5a34f96f3993c15c7515e4f67) Thanks @tim-smart! - Remove placeholder fallback behavior from CLI prompt inputs now that default values are prefilled. + +## 4.0.0-beta.28 + +### Minor Changes + +- [#1637](https://github.com/Effect-TS/effect-smol/pull/1637) [`42bc7ce`](https://github.com/Effect-TS/effect-smol/commit/42bc7ce5480f6f2953c39f8cb5c850d61df6f5a2) Thanks @tim-smart! - Add a new `effect/unstable/http/HttpStaticServer` module for static file serving with MIME resolution, directory index fallback, SPA fallback, and safe path resolution. + +### Patch Changes + +- [#1659](https://github.com/Effect-TS/effect-smol/pull/1659) [`ff533f2`](https://github.com/Effect-TS/effect-smol/commit/ff533f203cd06302ad08032a27e01269b4a2d4c6) Thanks @tim-smart! - Persist MCP HTTP session and protocol headers after initialize so follow-up JSON-RPC requests include `MCP-Protocol-Version`. + +- [#1663](https://github.com/Effect-TS/effect-smol/pull/1663) [`dc803ee`](https://github.com/Effect-TS/effect-smol/commit/dc803ee52ebd3e9f931118f0dfcb804542847556) Thanks @tim-smart! - Add `HttpServerResponse.fromClientResponse` for directly converting client responses into server responses. + +- [#1657](https://github.com/Effect-TS/effect-smol/pull/1657) [`d660b1c`](https://github.com/Effect-TS/effect-smol/commit/d660b1c99cb93d4f79715e91c7a4486801c0eefa) Thanks @tim-smart! - Add `Ctrl-U` line clearing support to editable CLI prompts. + +- [#1645](https://github.com/Effect-TS/effect-smol/pull/1645) [`93a05e3`](https://github.com/Effect-TS/effect-smol/commit/93a05e3eaa624058b162aedd66aad70102837270) Thanks @gijsbartman! - ensure transformed Atom's don't extend idle ttl + +- [#1655](https://github.com/Effect-TS/effect-smol/pull/1655) [`2a65cf6`](https://github.com/Effect-TS/effect-smol/commit/2a65cf6fd81ef63d944e6fb51f058d439bf4a834) Thanks @tim-smart! - Make `AtomRpc.query` and `AtomHttpApi.query` return serializable atoms by default when query results are schema-backed. + + The atom serialization key now uses each API's built-in request schemas so dehydrated state can be keyed consistently across server and client. + +- [#1662](https://github.com/Effect-TS/effect-smol/pull/1662) [`a561a40`](https://github.com/Effect-TS/effect-smol/commit/a561a40cc41c548c2cf3153aca065ee92ee8aa57) Thanks @tim-smart! - Add `HttpServerRequest.toClientRequest` for direct server-to-client request conversion. + +- [#1648](https://github.com/Effect-TS/effect-smol/pull/1648) [`29cd24d`](https://github.com/Effect-TS/effect-smol/commit/29cd24d1fe78480a72eeb38a90281ffddc0530bc) Thanks @gcanti! - Fix `Types.VoidIfEmpty` to correctly detect empty object types. Remove deprecated `Types.MatchRecord` in favor of the simplified implementation, closes #1647. + +- [#1664](https://github.com/Effect-TS/effect-smol/pull/1664) [`662a8e6`](https://github.com/Effect-TS/effect-smol/commit/662a8e6857dac64a7cd13bd8df4b0674654622f8) Thanks @tim-smart! - Add `HttpServerRequest.fromClientRequest` for direct client-request-backed server request conversion. + +- [#1656](https://github.com/Effect-TS/effect-smol/pull/1656) [`d2b52ba`](https://github.com/Effect-TS/effect-smol/commit/d2b52bae5b9336cf59729fbdcc4d7f09512b0cbf) Thanks @tim-smart! - Persist MCP client capability context across HTTP requests by resolving initialized payloads through the standard `Mcp-Session-Id` HTTP header in `McpServer`. + + Adds a regression test that initializes an MCP HTTP client, verifies the MCP server echoes `Mcp-Session-Id`, and then checks a later tool call can still read `McpServer.clientCapabilities`. + +- [#1639](https://github.com/Effect-TS/effect-smol/pull/1639) [`407c3b4`](https://github.com/Effect-TS/effect-smol/commit/407c3b43a5d1414558e0e33b6f1fc0e6a6d489cc) Thanks @tim-smart! - Add `Scheduler.PreventSchedulerYield` and expose it via `References` so fibers can skip scheduler `shouldYield` checks when needed. + +- [#1649](https://github.com/Effect-TS/effect-smol/pull/1649) [`e741322`](https://github.com/Effect-TS/effect-smol/commit/e74132226cbfee24234311c7c1c13e6b7391384e) Thanks @tim-smart! - Set `Schema.TaggedErrorClass` instance `name` to the tag value, matching `Data.TaggedError` behavior. + +- [#1646](https://github.com/Effect-TS/effect-smol/pull/1646) [`5c75fa8`](https://github.com/Effect-TS/effect-smol/commit/5c75fa8fb71163bc4c035ba1a215574dfd4badfc) Thanks @tim-smart! - Simplify internal and documented request usage by passing request resolvers directly to `Effect.request` instead of wrapping them with `Effect.succeed`. + +- [#1641](https://github.com/Effect-TS/effect-smol/pull/1641) [`747177b`](https://github.com/Effect-TS/effect-smol/commit/747177b0602f12d4461a843e953dfdffbeb0a429) Thanks @tim-smart! - Don't transform Tool result schemas, as they aren't sent to the providers as + json schemas + +- [#1636](https://github.com/Effect-TS/effect-smol/pull/1636) [`326cd48`](https://github.com/Effect-TS/effect-smol/commit/326cd4828bce573fe985f35152155464bf4c5a70) Thanks @tim-smart! - Add `Cookies.expireCookie` / `expireCookieUnsafe` and `HttpServerResponse.expireCookie` / `expireCookieUnsafe` for emitting expired cookies. + +- [#1653](https://github.com/Effect-TS/effect-smol/pull/1653) [`627e922`](https://github.com/Effect-TS/effect-smol/commit/627e922b8d1e9521eae5e1caa5d667ad00b1619a) Thanks @tim-smart! - expose mcp client capabilities + +- [#1660](https://github.com/Effect-TS/effect-smol/pull/1660) [`662287e`](https://github.com/Effect-TS/effect-smol/commit/662287e9abc76c941ccc2ee330aa07904d571341) Thanks @tim-smart! - Add `HttpServerResponse.toClientResponse` for converting server responses into `HttpClientResponse` values. + +## 4.0.0-beta.27 + +### Patch Changes + +- [#1621](https://github.com/Effect-TS/effect-smol/pull/1621) [`903a839`](https://github.com/Effect-TS/effect-smol/commit/903a839e94239e6ec4568315af28e405bcad95f4) Thanks @kitlangton! - unstable/http Headers: add `removeMany` combinator for removing multiple headers at once + +- [#1622](https://github.com/Effect-TS/effect-smol/pull/1622) [`91a0168`](https://github.com/Effect-TS/effect-smol/commit/91a016836680a6669308ecf464d3584bcc4ae1b7) Thanks @tim-smart! - Add `Model.BooleanSqlite`, a model field schema that uses `0 | 1` encoding for database variants and plain `boolean` encoding for JSON variants. + +- [#1631](https://github.com/Effect-TS/effect-smol/pull/1631) [`c890f9a`](https://github.com/Effect-TS/effect-smol/commit/c890f9a1b3a989ed22528bd5a43326342e05b142) Thanks @gcanti! - unstable/httpapi HttpApiBuilder: fix void responses producing a non-empty body instead of `Response.empty`, closes #1628. + +- [#1618](https://github.com/Effect-TS/effect-smol/pull/1618) [`1e985f2`](https://github.com/Effect-TS/effect-smol/commit/1e985f237d250b51b91de22dde77160c1e778ce7) Thanks @tim-smart! - Default `Effect.context()` to `Effect.context()` when no type parameter is provided. + +## 4.0.0-beta.26 + +### Patch Changes + +- [#1603](https://github.com/Effect-TS/effect-smol/pull/1603) [`fb21462`](https://github.com/Effect-TS/effect-smol/commit/fb21462642cdd5b1bada92f3eba18ae20445be42) Thanks @tim-smart! - Add `responseText` to `AiError.StructuredOutputError` and populate it from `LanguageModel.generateObject` so failed structured output decodes include the full LLM text. + +- [#1613](https://github.com/Effect-TS/effect-smol/pull/1613) [`2ed26b1`](https://github.com/Effect-TS/effect-smol/commit/2ed26b139805700e3df39efaa768ff01565e5c86) Thanks @lucas-barake! - Add `disableFatalDefects` to `RpcServer.layerHttp`, `RpcServer.toHttpEffect`, and `RpcServer.toHttpEffectWebsocket` option types to match existing runtime support. + +- [#1599](https://github.com/Effect-TS/effect-smol/pull/1599) [`e832a57`](https://github.com/Effect-TS/effect-smol/commit/e832a57b570fe38f010c1fd99bceac5a325a9e07) Thanks @tim-smart! - add trait for customizing exit codes + +- [#1611](https://github.com/Effect-TS/effect-smol/pull/1611) [`7f01be7`](https://github.com/Effect-TS/effect-smol/commit/7f01be7f8db363d4b2e88e6b5571e96bb815786f) Thanks @WebWalks! - Fixed the Error Type on AtomHttpApiClient (Server errors were being incorrectly reported, and we could not determine \_tag to handle) + +- [#1612](https://github.com/Effect-TS/effect-smol/pull/1612) [`e965143`](https://github.com/Effect-TS/effect-smol/commit/e9651431e114479e6becf8ca7b1ed99ac7e91ccc) Thanks @tim-smart! - Expose the optional `orElse` fallback parameter in `Effect.catchTags`. + +- [#1606](https://github.com/Effect-TS/effect-smol/pull/1606) [`b9b80f1`](https://github.com/Effect-TS/effect-smol/commit/b9b80f1f15e152ceef0a727d150b7dc230abae99) Thanks @gcanti! - Schema: `toJsonSchemaDocument` now emits JSON Schema `false` for unannotated + `Never` index signatures (including `additionalProperties`) instead of `{ not: {} }`. + Annotated `Never` still emits a schema object so metadata like `description` is preserved. + +- [#1607](https://github.com/Effect-TS/effect-smol/pull/1607) [`98252aa`](https://github.com/Effect-TS/effect-smol/commit/98252aa0c0b17fc73fbdad65d0a1104965f9fc0f) Thanks @gcanti! - Schema: improve `Schema.Unknown` / `Schema.ObjectKeyword` handling in `toCodecJson` and `toCodecStringTree` + +- [#1616](https://github.com/Effect-TS/effect-smol/pull/1616) [`56fbd94`](https://github.com/Effect-TS/effect-smol/commit/56fbd94311ad19a05001ad649d9e34ab00c74541) Thanks @lucas-barake! - Add `Atom.swr` to `effect/unstable/reactivity` for staleTime-gated stale-while-revalidate reads, optional mount and window-focus revalidation, and forceful manual refresh. + +- [#1600](https://github.com/Effect-TS/effect-smol/pull/1600) [`3faa109`](https://github.com/Effect-TS/effect-smol/commit/3faa109b7d093fbf14ad410d3e11d663f16e28f1) Thanks @tim-smart! - add args to Stdio service + +- [#1610](https://github.com/Effect-TS/effect-smol/pull/1610) [`692ecfe`](https://github.com/Effect-TS/effect-smol/commit/692ecfed99fe58056b7a5afe001f4fcd1a61c446) Thanks @kitlangton! - Refine unstable CLI parent/subcommand flag composition. + - Add `Command.withSharedFlags` conflict validation against existing subcommands, including the `withSubcommands(...).withSharedFlags(...)` composition order. + - Reorder `Command` type parameters to `Command` for clearer parent-context modeling. + - Make `Command.withSubcommands` input typing sound for downstream input-based combinators by reflecting that subcommand paths only carry parent context input. + +- [#1604](https://github.com/Effect-TS/effect-smol/pull/1604) [`1e70b72`](https://github.com/Effect-TS/effect-smol/commit/1e70b72d0b210474d0e96a15a5cfc279eae37e0c) Thanks @lucas-barake! - Fix `unstable/sql/SqlSchema` request input typing so `findAll` and `findNonEmpty` accept `Request["Type"]` instead of `Request["Encoded"]`. + +- [#1602](https://github.com/Effect-TS/effect-smol/pull/1602) [`ecf0782`](https://github.com/Effect-TS/effect-smol/commit/ecf07829ef2dfc01d8943c96c4fe9c1b44b97926) Thanks @tim-smart! - Replace the default HttpApi schema-validation error with `HttpApiError.BadRequestNoContent`. + +## 4.0.0-beta.25 + +### Patch Changes + +- [#1597](https://github.com/Effect-TS/effect-smol/pull/1597) [`fa17bb5`](https://github.com/Effect-TS/effect-smol/commit/fa17bb5be9f2533d01e11322b14804c7dec43714) Thanks @tim-smart! - Fix `Effect.forkScoped` data-first typings to include `Scope` in requirements. + +- [#1598](https://github.com/Effect-TS/effect-smol/pull/1598) [`f46e5b5`](https://github.com/Effect-TS/effect-smol/commit/f46e5b5ca2a918ee4d9270167e79db223077c96f) Thanks @tim-smart! - compare transaction connections by reference + +- [#1596](https://github.com/Effect-TS/effect-smol/pull/1596) [`ce4767c`](https://github.com/Effect-TS/effect-smol/commit/ce4767cadcacc6ce8ff4c3a0d0fbc82ede655f63) Thanks @tim-smart! - improve HttpClient.withRateLimiter initial state tracking + +- [#1594](https://github.com/Effect-TS/effect-smol/pull/1594) [`c830a8b`](https://github.com/Effect-TS/effect-smol/commit/c830a8b6c292a6528d7f9318759d34800b00372d) Thanks @tim-smart! - HttpClient.withRateLimiter adds delay from retry-after headers + +## 4.0.0-beta.24 + +### Patch Changes + +- [#1586](https://github.com/Effect-TS/effect-smol/pull/1586) [`a909e1c`](https://github.com/Effect-TS/effect-smol/commit/a909e1c1ac2bc707527f5073776e3e7d239688d9) Thanks @gcanti! - Schema: add `Chunk` schema, closes #1585. + +- [#1588](https://github.com/Effect-TS/effect-smol/pull/1588) [`8814a4e`](https://github.com/Effect-TS/effect-smol/commit/8814a4ef78d67144d27689370af10099ea210399) Thanks @gcanti! - Fix `Schema.toTaggedUnion` discriminant detection for class-based schemas, including unique symbol tags, closes #1584. + +- [#1591](https://github.com/Effect-TS/effect-smol/pull/1591) [`3f942c5`](https://github.com/Effect-TS/effect-smol/commit/3f942c51cefa7b2ffa7c49e8c8a2c887570ba4c0) Thanks @tim-smart! - Add `HttpClient.withRateLimiter` for integrating the `RateLimiter` service with HTTP clients, including optional response-header driven limit updates and automatic 429 retry behavior. + +- [#1583](https://github.com/Effect-TS/effect-smol/pull/1583) [`774ed59`](https://github.com/Effect-TS/effect-smol/commit/774ed59c52b2ab578bbb897c4f551f812231e1d2) Thanks @patroza! - feat: Support Reference classes + +- [#1592](https://github.com/Effect-TS/effect-smol/pull/1592) [`f54b8d3`](https://github.com/Effect-TS/effect-smol/commit/f54b8d398fedad1815fd1f4c49814ab938cfc385) Thanks @tim-smart! - Fix `HttpApi.prefix` so it updates endpoint path types the same way `HttpApiGroup.prefix` does. + +## 4.0.0-beta.23 + +### Patch Changes + +- [#1561](https://github.com/Effect-TS/effect-smol/pull/1561) [`5c73c41`](https://github.com/Effect-TS/effect-smol/commit/5c73c41b69eaeab80fcd62c9bfda490b446d1966) Thanks @gcanti! - SchemaRepresentation: only create references for recursive/mutually recursive schemas and schemas with an `identifier` annotation, closes #1560. + +## 4.0.0-beta.22 + +### Patch Changes + +- [#1578](https://github.com/Effect-TS/effect-smol/pull/1578) [`0874332`](https://github.com/Effect-TS/effect-smol/commit/0874332f7c81118b06ac2eb105e0710211631479) Thanks @tim-smart! - Proxy function arity from `Effect.fn` APIs so wrapped functions preserve the original `length` value. + +- [#1580](https://github.com/Effect-TS/effect-smol/pull/1580) [`c592dcd`](https://github.com/Effect-TS/effect-smol/commit/c592dcde0697e322065c8f418c0480ef910cb183) Thanks @tim-smart! - simplify Filter by removing Args type parameter + +- [#1575](https://github.com/Effect-TS/effect-smol/pull/1575) [`1dbe28d`](https://github.com/Effect-TS/effect-smol/commit/1dbe28dac8299cd3e218c9768450cfd173b5e294) Thanks @tim-smart! - fix Chat constructor types + +- [#1581](https://github.com/Effect-TS/effect-smol/pull/1581) [`564d730`](https://github.com/Effect-TS/effect-smol/commit/564d730b6bbf38dd8548a3b046e7a693b28699a4) Thanks @tim-smart! - fix Duration.toMillis regression + +- [#1579](https://github.com/Effect-TS/effect-smol/pull/1579) [`3cfadc4`](https://github.com/Effect-TS/effect-smol/commit/3cfadc458b070c6cba6c5674b72a059f1e49118b) Thanks @tim-smart! - Remove fiber-level keep-alive intervals and keep the process alive from `Runtime.makeRunMain` instead. + +- [#1571](https://github.com/Effect-TS/effect-smol/pull/1571) [`6634fd0`](https://github.com/Effect-TS/effect-smol/commit/6634fd07da067d80b8261fb2959d1a952b9e412e) Thanks @tim-smart! - Add `HttpApiClient.urlBuilder` for type-safe endpoint URL construction from group + method/path keys. + +- [#1573](https://github.com/Effect-TS/effect-smol/pull/1573) [`d10dabe`](https://github.com/Effect-TS/effect-smol/commit/d10dabeb7af9a368f995829cd36ad08167cd8f95) Thanks @tim-smart! - Expose a `chunkSize` option on `Stream.fromIterable` to control emitted chunk boundaries when constructing streams from iterables. + +- [#1574](https://github.com/Effect-TS/effect-smol/pull/1574) [`f82f549`](https://github.com/Effect-TS/effect-smol/commit/f82f549a09e950e9d4987f279a800f4d953f0939) Thanks @tim-smart! - Fix AI tool handler error typing so `LanguageModel.generateText` with a toolkit exposes wrapped `AiError` values rather than leaking raw `AiErrorReason` in the error channel. + +- [#1577](https://github.com/Effect-TS/effect-smol/pull/1577) [`78a3382`](https://github.com/Effect-TS/effect-smol/commit/78a3382ddfbe034408f7480fa794733d9e82147b) Thanks @tim-smart! - fix VariantSchema.Union + +## 4.0.0-beta.21 + +### Patch Changes + +- [#1555](https://github.com/Effect-TS/effect-smol/pull/1555) [`e691909`](https://github.com/Effect-TS/effect-smol/commit/e691909495ccb162ea7bfa351dd74632b99997cb) Thanks @tim-smart! - fix Stream.withSpan options + +- [#1548](https://github.com/Effect-TS/effect-smol/pull/1548) [`d5f413f`](https://github.com/Effect-TS/effect-smol/commit/d5f413f3c8fc57f2413cc5649c2003d6d4e5a6d7) Thanks @effect-bot! - Fix `TxPubSub.publish` and `TxPubSub.publishAll` overloads to require `Effect.Transaction` in their return environment. + +- [#1557](https://github.com/Effect-TS/effect-smol/pull/1557) [`139d152`](https://github.com/Effect-TS/effect-smol/commit/139d152941e562a073b5be12e8d66c8a4d4a8a57) Thanks @A386official! - Fix MCP resource template parameter names resolving as `param0`, `param1` instead of actual names by checking `isParam` on the original schema before `toCodecStringTree` transformation. + +- [#1547](https://github.com/Effect-TS/effect-smol/pull/1547) [`947e3d4`](https://github.com/Effect-TS/effect-smol/commit/947e3d436ab8a017efda9b29be523efd1ca8df28) Thanks @effect-bot! - Fix `Schedule.reduce` to persist state updates when the combine function returns a synchronous value. + +- [#1545](https://github.com/Effect-TS/effect-smol/pull/1545) [`84b2cce`](https://github.com/Effect-TS/effect-smol/commit/84b2ccefe2aa3a7413b86738a4dc33cdb311ca55) Thanks @effect-bot! - Fix TupleWithRest post-rest validation to check each tail index sequentially. + +- [#1552](https://github.com/Effect-TS/effect-smol/pull/1552) [`7f5305e`](https://github.com/Effect-TS/effect-smol/commit/7f5305e69f5a33309e77b08a576edb25d7daaee2) Thanks @tim-smart! - Constrain `HttpServerRequest.source` to `object` and key server-side request weak caches by `request.source` so middleware request wrappers share the same cache entries. + +- [#1556](https://github.com/Effect-TS/effect-smol/pull/1556) [`9e6fd84`](https://github.com/Effect-TS/effect-smol/commit/9e6fd8471c93a3c643929151a3bdb62cb9c0ca0e) Thanks @tim-smart! - rename WorkflowEngine.layer + +- [#1558](https://github.com/Effect-TS/effect-smol/pull/1558) [`fdb8a4b`](https://github.com/Effect-TS/effect-smol/commit/fdb8a4b172721fbefe98bd5aa6fe4f0efd1da3eb) Thanks @tim-smart! - Fix `Workflow.executionId` to use schema `makeUnsafe` instead of the removed `.make` API. + +- [#1553](https://github.com/Effect-TS/effect-smol/pull/1553) [`0f986ef`](https://github.com/Effect-TS/effect-smol/commit/0f986ef22f196fe091a7afdbd179485a7d888882) Thanks @kaylynb! - Fix spans never having parent span + +- [#1541](https://github.com/Effect-TS/effect-smol/pull/1541) [`9355fc0`](https://github.com/Effect-TS/effect-smol/commit/9355fc0ffb5b7382146a5aed9eea83974b10d007) Thanks @tim-smart! - Add `Effect.findFirst` and `Effect.findFirstFilter` for short-circuiting effectful searches over iterables. + +## 4.0.0-beta.20 + +### Patch Changes + +- [#1533](https://github.com/Effect-TS/effect-smol/pull/1533) [`842a624`](https://github.com/Effect-TS/effect-smol/commit/842a624f79d5e1407460b0ef3ab27d14d48ccf74) Thanks @tim-smart! - move ChildProcess apis into spawner service + +- [#1536](https://github.com/Effect-TS/effect-smol/pull/1536) [`4785eef`](https://github.com/Effect-TS/effect-smol/commit/4785eef5d7cf1edb96ef2509aed2ba4d1edf3862) Thanks @tim-smart! - add Context.Key type, used a base for Context.Service and Context.Reference + +- [#1531](https://github.com/Effect-TS/effect-smol/pull/1531) [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c) Thanks @gcanti! - Revert `Config.withDefault` to v3 behavior, closes #1530. + + Make `Config.withDefault` accept an eager value instead of `LazyArg`, aligning with CLI module conventions. + +- [#1535](https://github.com/Effect-TS/effect-smol/pull/1535) [`12ee8e2`](https://github.com/Effect-TS/effect-smol/commit/12ee8e27df7eb393d83a5e403390d0cfc82ca732) Thanks @tim-smart! - change default ErrorReporter severity to Info + +- [#1529](https://github.com/Effect-TS/effect-smol/pull/1529) [`e542c94`](https://github.com/Effect-TS/effect-smol/commit/e542c942bee4729138b02222f4421220a90a57d8) Thanks @tim-smart! - Add dedicated AiError metadata interfaces per reason so provider packages can safely augment metadata without conflicting module declarations. + +- [#1531](https://github.com/Effect-TS/effect-smol/pull/1531) [`8fac95b`](https://github.com/Effect-TS/effect-smol/commit/8fac95bd9e0338b7a82da8da579c1ac22afa045c) Thanks @gcanti! - Fix `Config.withDefault` type inference, closes #1530. + +- [#1528](https://github.com/Effect-TS/effect-smol/pull/1528) [`6f4ebd1`](https://github.com/Effect-TS/effect-smol/commit/6f4ebd193c2595983394127dd808601b75430d34) Thanks @tim-smart! - Add `Model.ModelName` and provide it from AI model constructors. + +- [#1537](https://github.com/Effect-TS/effect-smol/pull/1537) [`989d1cc`](https://github.com/Effect-TS/effect-smol/commit/989d1cca936fce0cc459057825ba40e3f5ef3827) Thanks @tim-smart! - Revert `Effect.partition` to Effect v3 behavior by accumulating failures from the effect error channel and never failing. + +## 4.0.0-beta.19 + +## 4.0.0-beta.18 + +### Minor Changes + +- [#1515](https://github.com/Effect-TS/effect-smol/pull/1515) [`01e31fd`](https://github.com/Effect-TS/effect-smol/commit/01e31fdf8e5206849d23cbafd23a346f2f177ab8) Thanks @mikearnaldi! - Add transactional STM modules: TxDeferred, TxPriorityQueue, TxPubSub, TxReentrantLock, TxSubscriptionRef. + + Refactor transaction model: remove `Effect.atomic`/`Effect.atomicWith`, add `Effect.withTxState`. All Tx operations now return `Effect` requiring explicit `Effect.transaction(...)` at boundaries. + + Expose `TxPubSub.acquireSubscriber`/`releaseSubscriber` for composable transaction boundaries. Fix `TxSubscriptionRef.changes` race condition ensuring current value is delivered first. + + Remove `TxRandom` module. + +### Patch Changes + +- [#1518](https://github.com/Effect-TS/effect-smol/pull/1518) [`0890aab`](https://github.com/Effect-TS/effect-smol/commit/0890aab15ed9c5ba52c383a72fdc6a444d7504d5) Thanks @IMax153! - Fix `Command.withGlobalFlags` type inference when mixing `GlobalFlag.action` and `GlobalFlag.setting`. + + `Setting` service identifiers are now correctly removed from command requirements in mixed global flag arrays. + +- [#1520](https://github.com/Effect-TS/effect-smol/pull/1520) [`725260b`](https://github.com/Effect-TS/effect-smol/commit/725260b53f5142d6af7a93a2f9f464f974eda92d) Thanks @IMax153! - Ensure that OpenAI JSON schemas for tool calls and structured outputs are properly transformed + +## 4.0.0-beta.17 + +### Patch Changes + +- [#1516](https://github.com/Effect-TS/effect-smol/pull/1516) [`8f59c32`](https://github.com/Effect-TS/effect-smol/commit/8f59c32922597a48392744f7203e284866747781) Thanks @gcanti! - Fix `Schema.encodeKeys` to encode non-remapped struct fields during encoding. + +## 4.0.0-beta.16 + +### Patch Changes + +- [#1513](https://github.com/Effect-TS/effect-smol/pull/1513) [`bf9096c`](https://github.com/Effect-TS/effect-smol/commit/bf9096c52a7d8791d93d232739e523eb84f6625a) Thanks @gcanti! - Add `SchemaParser.makeOption` and `Schema.makeOption` for constructing schema values as `Option`. + +- [#1508](https://github.com/Effect-TS/effect-smol/pull/1508) [`29f81ca`](https://github.com/Effect-TS/effect-smol/commit/29f81ca07c67dba265804b140a7487fb15a5fc6b) Thanks @gcanti! - Schema: add `OptionFromUndefinedOr` and `OptionFromNullishOr` schemas. + +- [#1498](https://github.com/Effect-TS/effect-smol/pull/1498) [`68eb28c`](https://github.com/Effect-TS/effect-smol/commit/68eb28c2b0fc67a9f6204ade9bd16c5b37803bfb) Thanks @kaylynb! - Fix OpenApi Multipart file upload schema generation + +## 4.0.0-beta.15 + +### Patch Changes + +- [#1500](https://github.com/Effect-TS/effect-smol/pull/1500) [`24ae609`](https://github.com/Effect-TS/effect-smol/commit/24ae60995d2fd7d621be356cdfdfd328c79639ba) Thanks @qadama831! - Unwrap `_Success` schema to enable field access. + +- [#1486](https://github.com/Effect-TS/effect-smol/pull/1486) [`0e3c059`](https://github.com/Effect-TS/effect-smol/commit/0e3c059987caa55ebd0c134f7c7b147c639c328e) Thanks @tim-smart! - Fix `Stream.groupedWithin` to stop emitting empty arrays when schedule ticks fire while upstream is idle. + +- [#1503](https://github.com/Effect-TS/effect-smol/pull/1503) [`e843b0a`](https://github.com/Effect-TS/effect-smol/commit/e843b0a7d7e7b600a0b3bd477f24e2e4cd26bc8b) Thanks @tim-smart! - allow creating standalone http handlers from HttpApiEndpoints + +- [#1499](https://github.com/Effect-TS/effect-smol/pull/1499) [`f4389a2`](https://github.com/Effect-TS/effect-smol/commit/f4389a2cca3c5bbf00d69779f52ce41255f15a28) Thanks @tim-smart! - fix atom node timeout cleanup + +- [#1494](https://github.com/Effect-TS/effect-smol/pull/1494) [`5b73de0`](https://github.com/Effect-TS/effect-smol/commit/5b73de095b3402d0c5c74092ace6ce18ebfad566) - Refine `ExtractServices` to omit tool handler requirements when automatic tool resolution is explicitly disabled through the `disableToolCallResolution` option. + +- [#1496](https://github.com/Effect-TS/effect-smol/pull/1496) [`595d2d6`](https://github.com/Effect-TS/effect-smol/commit/595d2d6e7d50419f3532bd39266191532ace38f2) Thanks @IMax153! - Refactor unstable CLI global flags to command-scoped declarations. + + ### Breaking changes + - Remove `GlobalFlag.add`, `GlobalFlag.remove`, and `GlobalFlag.clear` + - Add `Command.withGlobalFlags(...)` as the declaration API for command/subcommand scope + - Change `GlobalFlag.setting` constructor to curried form which carries type-level identifier: + - before: `GlobalFlag.setting({ flag, ... })` + - after: `GlobalFlag.setting("id")({ flag })` + - Change setting context identity to a stable type-level string: + - `effect/unstable/cli/GlobalFlag/${id}` + + ### Behavior changes + - Global flags are now scoped by command path (root-to-leaf declarations) + - Out-of-scope global flags are rejected for the selected subcommand path + - Help now renders only global flags active for the requested command path + - Setting defaults are sourced from `Flag` combinators (`optional`, `withDefault`) rather than setting constructor defaults + +## 4.0.0-beta.14 + +### Patch Changes + +- [#1471](https://github.com/Effect-TS/effect-smol/pull/1471) [`c414700`](https://github.com/Effect-TS/effect-smol/commit/c414700ef1932e4b67d0102856de417336912350) Thanks @IMax153! - Make CLI global settings directly yieldable and simplify built-in names. + + `GlobalFlag.setting` now takes `{ flag, defaultValue }` and returns a setting that is a `Context.Reference`, so handlers and `Command.provide*` effects can `yield*` global setting values directly. + + Built-in settings keep internal behavior in `runWith` (for example, `--log-level` still configures `References.MinimumLogLevel`) while also being readable as values. + + Also renamed built-in globals: + - `GlobalFlag.CompletionsFlag` -> `GlobalFlag.Completions` + - `GlobalFlag.LogLevelFlag` -> `GlobalFlag.LogLevel` + +- [#1490](https://github.com/Effect-TS/effect-smol/pull/1490) [`a30c969`](https://github.com/Effect-TS/effect-smol/commit/a30c9699c0d736cf3952041e45d508b7d58907a9) Thanks @gcanti! - Fix `OpenApi.fromApi` preserving multiple response content types for one status code, closes #1485. + +## 4.0.0-beta.13 + +### Patch Changes + +- [#1454](https://github.com/Effect-TS/effect-smol/pull/1454) [`368f4c3`](https://github.com/Effect-TS/effect-smol/commit/368f4c363dd117e6f5a19ad77b161176cfd29fdd) Thanks @lucas-barake! - Expose `NoSuchElementError` in the error type of stream-based `Atom.make` overloads. + +- [#1469](https://github.com/Effect-TS/effect-smol/pull/1469) [`db8a579`](https://github.com/Effect-TS/effect-smol/commit/db8a579e93e93ff73b1e60712732e03b597b916b) Thanks @tim-smart! - Update unstable schema variant helpers to use array-based arguments for `FieldOnly`, `FieldExcept`, and `Union`, aligning `VariantSchema` and `Model` with other v4 API shapes. + +- [#1457](https://github.com/Effect-TS/effect-smol/pull/1457) [`668b703`](https://github.com/Effect-TS/effect-smol/commit/668b70337e9ddbb0d1ae2282a95c282ce404e562) Thanks @tim-smart! - Run request resolver batch fibers with request services by using `Effect.runForkWith`, so resolver delay effects and `runAll` execution see the request service map. + +- [#1461](https://github.com/Effect-TS/effect-smol/pull/1461) [`d40e76b`](https://github.com/Effect-TS/effect-smol/commit/d40e76b973543979e60e04a6baca04a8c65bdfc2) Thanks @mikearnaldi! - Fix `Schedule.fixed` double-executing the effect due to clock jitter. + + The `elapsedSincePrevious > window` check included sleep time from the + previous step, so any timer imprecision (e.g. 1001ms for a 1000ms sleep) + triggered an immediate zero-delay re-execution. + +- [#1464](https://github.com/Effect-TS/effect-smol/pull/1464) [`6e18cf8`](https://github.com/Effect-TS/effect-smol/commit/6e18cf883e9905ca718a6697b6a2a4bbd42739aa) Thanks @gcanti! - Use the `identifier` annotation as the expected message when available, closes #1458. + +- [#1475](https://github.com/Effect-TS/effect-smol/pull/1475) [`86062e8`](https://github.com/Effect-TS/effect-smol/commit/86062e8a0c61bca5412fc40d2cf151d676901f08) Thanks @tim-smart! - Add a CI check job that runs `pnpm ai-docgen` and fails if it produces uncommitted changes. + +- [#1448](https://github.com/Effect-TS/effect-smol/pull/1448) [`c27ce75`](https://github.com/Effect-TS/effect-smol/commit/c27ce75d34c74dcfc6dba1bf77f1ce88f410a0de) Thanks @IMax153! - Refactor CLI built-in options to use Effect services with `GlobalFlag` + + Built-in CLI flags (`--help`, `--version`, `--completions`, `--log-level`) are now implemented as Effect services using `Context.Reference`. This provides: + - **Visibility**: Built-in flags now appear in help output's "GLOBAL FLAGS" section + - **Extensibility**: Users can register custom global flags via `GlobalFlag.add` + - **Override capability**: Built-in flag behavior can be replaced or disabled + - **Composability**: Flags compose via Effect's service system + + New `GlobalFlag` module exports: + - `Action` and `Setting` types for different flag behaviors + - `Help`, `Version`, `Completions`, `LogLevel` references for built-in flags + - `add`, `remove`, `clear` functions for managing global flags + + Example: + + ```typescript + const app = Command.make("myapp"); + Command.run(app, { version: "1.0.0" }).pipe( + GlobalFlag.add(CustomFlag, customFlagValue), + ); + ``` + +- [#1468](https://github.com/Effect-TS/effect-smol/pull/1468) [`e2d4fbf`](https://github.com/Effect-TS/effect-smol/commit/e2d4fbfeeda6a5d2a4c5aeb0501d8240c248b9eb) Thanks @lucas-barake! - Fix `Rpc.ExtractProvides` to use middleware service ID instead of constructor type. + +- [#1465](https://github.com/Effect-TS/effect-smol/pull/1465) [`114ab42`](https://github.com/Effect-TS/effect-smol/commit/114ab42ad0edc590d29169675a493e0e915aa58f) Thanks @lloydrichards! - tighten Schema on \_meta fields in McpSchema; closes #1463 + +- [#1470](https://github.com/Effect-TS/effect-smol/pull/1470) [`484caec`](https://github.com/Effect-TS/effect-smol/commit/484caec47cccac8b86db2910742e406dfc7173ab) Thanks @tim-smart! - Add `Command.withAlias` for unstable CLI commands, including subcommand parsing by alias and help output that renders aliases as `name, alias` in subcommand listings. + +## 4.0.0-beta.12 + +### Patch Changes + +- [#1439](https://github.com/Effect-TS/effect-smol/pull/1439) [`70a74e8`](https://github.com/Effect-TS/effect-smol/commit/70a74e88a8767c9d4acdb9e5f25aec9a33588d07) Thanks @gcanti! - Add `Config.nested` combinator to scope a config under a named prefix, closes #1437. + +- [#1452](https://github.com/Effect-TS/effect-smol/pull/1452) [`b5b6e10`](https://github.com/Effect-TS/effect-smol/commit/b5b6e10621d54bf8c9857fec0d647ced78ecd857) Thanks @tim-smart! - make fiber keepAlive setInterval evaluation lazy + +- [#1431](https://github.com/Effect-TS/effect-smol/pull/1431) [`f5ce5a9`](https://github.com/Effect-TS/effect-smol/commit/f5ce5a915359c6ebf254079e1da23cab6cde34fb) Thanks @tim-smart! - Add `Random.nextBoolean` for generating random boolean values. + +- [#1450](https://github.com/Effect-TS/effect-smol/pull/1450) [`a29eb70`](https://github.com/Effect-TS/effect-smol/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668) Thanks @tim-smart! - use cause annotations for detecting client aborts + +- [#1445](https://github.com/Effect-TS/effect-smol/pull/1445) [`c7b36e5`](https://github.com/Effect-TS/effect-smol/commit/c7b36e541a23e9a00f64e25b23851e51a37dfce5) Thanks @mattiamanzati! - Fix `Graph.toMermaid` to escape special characters using HTML entity codes per the Mermaid specification. + +- [#1443](https://github.com/Effect-TS/effect-smol/pull/1443) [`9381d6d`](https://github.com/Effect-TS/effect-smol/commit/9381d6d4d9d819a81a46e56d0364c76e92a4fbca) Thanks @mikearnaldi! - Fix `HttpClient.retryTransient` autocomplete leaking `Schedule` internals by splitting the `{...} | Schedule` union into separate overloads. + +- [#1444](https://github.com/Effect-TS/effect-smol/pull/1444) [`88439f1`](https://github.com/Effect-TS/effect-smol/commit/88439f13ca13549f3e4822c48c4f019c14fc2bcc) Thanks @gcanti! - Schema.encodeKeys: relax input constraint from Struct to schemas with fields so Schema.Class works, closes #1412. + +- [#1438](https://github.com/Effect-TS/effect-smol/pull/1438) [`e35307d`](https://github.com/Effect-TS/effect-smol/commit/e35307dbeb8eb26a9923f958b894a8eaaf259bf2) Thanks @mikearnaldi! - Atom.searchParam: decode initial URL values correctly when a schema is provided + +- [#1425](https://github.com/Effect-TS/effect-smol/pull/1425) [`c7df4bc`](https://github.com/Effect-TS/effect-smol/commit/c7df4bce34009474c63d62a807abfdafb76971eb) Thanks @candrewlee14! - Fix LanguageModel stripping of resolved approval artifacts across multi-round conversations. + + Previously, `stripResolvedApprovals` only ran when there were pending approvals + in the current round. Stale artifacts from earlier rounds would leak to the + provider, causing errors. The stripping now runs unconditionally. + + In streaming mode, pre-resolved tool results are also emitted as stream parts + so `Chat.streamText` persists them to history, preventing re-resolution on + subsequent rounds. + +- [#1453](https://github.com/Effect-TS/effect-smol/pull/1453) [`accaf3b`](https://github.com/Effect-TS/effect-smol/commit/accaf3be7ac8da36e2334c509c23b8c9e88ea160) Thanks @tim-smart! - allow mcp errors to be encoded correctly + +- [#1440](https://github.com/Effect-TS/effect-smol/pull/1440) [`3e1c270`](https://github.com/Effect-TS/effect-smol/commit/3e1c2707bbdf67720af1509642b8ced195790882) Thanks @lloydrichards! - extend McpSchema to work with extensions + +- [#1447](https://github.com/Effect-TS/effect-smol/pull/1447) [`6cd81f7`](https://github.com/Effect-TS/effect-smol/commit/6cd81f73baad86f5bbfa455a55d75cde71e9611a) Thanks @tim-smart! - remove all non-regional service usage + +- [#1451](https://github.com/Effect-TS/effect-smol/pull/1451) [`f222da3`](https://github.com/Effect-TS/effect-smol/commit/f222da3cdb44554f3324c2c52d0d005ee575053e) Thanks @tim-smart! - Add `Effect.annotateLogsScoped` to apply log annotations for the current scope and automatically restore previous annotations when the scope closes. + +- [#1434](https://github.com/Effect-TS/effect-smol/pull/1434) [`61f901d`](https://github.com/Effect-TS/effect-smol/commit/61f901d830005b66e22d1de889fda132aeea97cd) Thanks @tim-smart! - Fix JSON-RPC serialization to return an object for non-batched requests while preserving array responses for true batch requests. + +## 4.0.0-beta.11 + +### Patch Changes + +- [#1429](https://github.com/Effect-TS/effect-smol/pull/1429) [`88659ed`](https://github.com/Effect-TS/effect-smol/commit/88659edb26e3623d557dccfe914c2c949672da16) Thanks @tim-smart! - Add grouped subcommand support to `Command.withSubcommands`, including help output sections for named groups while keeping ungrouped commands under `SUBCOMMANDS`. + +- [#1426](https://github.com/Effect-TS/effect-smol/pull/1426) [`f2915e8`](https://github.com/Effect-TS/effect-smol/commit/f2915e8e2efe80d50c281e53f297b9701d6dc199) Thanks @tim-smart! - Add `Effect.validate` for validating collections while accumulating all failures, equivalent to the v3 `Effect.validateAll` behavior. + +- [#1430](https://github.com/Effect-TS/effect-smol/pull/1430) [`eb71ace`](https://github.com/Effect-TS/effect-smol/commit/eb71acebbe0f228e4920278013beee3b67d62310) Thanks @tim-smart! - Add `Command.withExamples` to attach concrete usage examples to CLI commands, expose them through `HelpDoc.examples`, and render them in the default help formatter. + +- [#1415](https://github.com/Effect-TS/effect-smol/pull/1415) [`2a16999`](https://github.com/Effect-TS/effect-smol/commit/2a169996c7513d377ac47adbfd68e1490457135c) Thanks @mikearnaldi! - HashMap: compare HAMT bit positions as unsigned to preserve entry lookup when bit 31 is set + +- [#1417](https://github.com/Effect-TS/effect-smol/pull/1417) [`d42dd52`](https://github.com/Effect-TS/effect-smol/commit/d42dd52f11203f8e749fb5d3ecf7153e4a5a6814) Thanks @mikearnaldi! - unstable/http Headers: hide inspectable prototype methods from for..in iteration to avoid invalid header names in runtime fetch polyfills + +- [#1418](https://github.com/Effect-TS/effect-smol/pull/1418) [`339adaf`](https://github.com/Effect-TS/effect-smol/commit/339adaf850a62a892adebcb208c2d9dddf3b97b3) Thanks @mikearnaldi! - runtime: guard keepAlive setInterval / clearInterval so Effect.runPromise works in runtimes that block timer APIs + +- [#1416](https://github.com/Effect-TS/effect-smol/pull/1416) [`de19645`](https://github.com/Effect-TS/effect-smol/commit/de1964526d01102dd1cb99c8cfdd3e8df1f49ef1) Thanks @mikearnaldi! - Queue.collect: stop duplicating drained messages by appending each batch once + +- [#1413](https://github.com/Effect-TS/effect-smol/pull/1413) [`9b1dc3b`](https://github.com/Effect-TS/effect-smol/commit/9b1dc3bcf2a1b68d0a67e3465db5ad01a1a56997) Thanks @gcanti! - Fix `Schema.TupleWithRest` incorrectly accepting inputs with missing post-rest elements, closes #1410. + +- [#1409](https://github.com/Effect-TS/effect-smol/pull/1409) [`e4cb2f5`](https://github.com/Effect-TS/effect-smol/commit/e4cb2f55b30f4771ec1bf613ced36d6d96464dd5) Thanks @tim-smart! - add ErrorReporter module + +- [#1427](https://github.com/Effect-TS/effect-smol/pull/1427) [`8bced95`](https://github.com/Effect-TS/effect-smol/commit/8bced954ecb35d4489197a57b0efe927e7d75f49) Thanks @tim-smart! - Add `Command.annotate` and `Command.annotateMerge` to unstable CLI commands, and include command annotations in `HelpDoc` so custom help formatters can access command metadata. + +- [#1401](https://github.com/Effect-TS/effect-smol/pull/1401) [`9431420`](https://github.com/Effect-TS/effect-smol/commit/94314207c8019918200fbcb97aec992219f801f0) Thanks @tim-smart! - Add `WorkflowEngine.layer`, an in-memory layer for the unstable workflow engine. + +- [#1428](https://github.com/Effect-TS/effect-smol/pull/1428) [`948dca2`](https://github.com/Effect-TS/effect-smol/commit/948dca22e4f672ba7a6db57f9899272bec7c08b8) Thanks @tim-smart! - Add `Command.withShortDescription` and use short descriptions for CLI subcommand listings, with fallback to the full command description. + +- [#1405](https://github.com/Effect-TS/effect-smol/pull/1405) [`d18e327`](https://github.com/Effect-TS/effect-smol/commit/d18e32765a2665e31ffb31e746bf983fcfac34c5) Thanks @candrewlee14! - Strip resolved tool approval artifacts from prompt before sending to provider, preventing errors when providers reject pre-resolved approval requests. + +- [#1424](https://github.com/Effect-TS/effect-smol/pull/1424) [`ab512f7`](https://github.com/Effect-TS/effect-smol/commit/ab512f7be1c0e6b359da921e22cd4944e4c57d3e) Thanks @tim-smart! - expose more atom Node properties + +## 4.0.0-beta.10 + +### Patch Changes + +- [#1396](https://github.com/Effect-TS/effect-smol/pull/1396) [`371acab`](https://github.com/Effect-TS/effect-smol/commit/371acabb58d56f3a7a5e3e33d3d5fdc9f5573c74) Thanks @gcanti! - Add `unstable/encoding` subpath export. + +- [#1392](https://github.com/Effect-TS/effect-smol/pull/1392) [`856d774`](https://github.com/Effect-TS/effect-smol/commit/856d7741f1e296dd5048c6ff2b44b95d023e6ae4) Thanks @tim-smart! - Fix a race in `Semaphore.take` where interruption could leak permits after a waiter was resumed. + +- [#1388](https://github.com/Effect-TS/effect-smol/pull/1388) [`b9e9202`](https://github.com/Effect-TS/effect-smol/commit/b9e92023c38caa322975d77cfe83e2d34ac9305a) Thanks @tim-smart! - Export `Effect` do notation APIs (`Do`, `bindTo`, `bind`, and `let`) from `effect/Effect` and add runtime and type-level coverage. + +- [#1387](https://github.com/Effect-TS/effect-smol/pull/1387) [`1d1a974`](https://github.com/Effect-TS/effect-smol/commit/1d1a974bd280c81bff5d4505491cda03ba7a3f36) Thanks @tim-smart! - short circuit when Fiber.joinAll is called with an empty iterable + +- [#1386](https://github.com/Effect-TS/effect-smol/pull/1386) [`6bfe2a6`](https://github.com/Effect-TS/effect-smol/commit/6bfe2a659bc6335db75709931f405da45301cba2) Thanks @tim-smart! - simplify http logger disabling + +- [#1381](https://github.com/Effect-TS/effect-smol/pull/1381) [`b12c811`](https://github.com/Effect-TS/effect-smol/commit/b12c81157be287b1649c210616a244b50ec094d2) Thanks @tim-smart! - Fix `UrlParams.Input` usage to accept interface-typed records in HTTP client and server helpers while keeping coercion constraints for url parameter values. + +- [#1383](https://github.com/Effect-TS/effect-smol/pull/1383) [`d17d98a`](https://github.com/Effect-TS/effect-smol/commit/d17d98ad78e2b44d95ef434adab79ac3c35e75ab) Thanks @tim-smart! - Rename `HttpClient.retryTransient` option `mode` to `retryOn` and rename `"both"` to `"errors-and-responses"`. + +- [#1399](https://github.com/Effect-TS/effect-smol/pull/1399) [`68c3c7c`](https://github.com/Effect-TS/effect-smol/commit/68c3c7cb1e06ed94fa5c4c123a234b4ccbfdecd8) Thanks @tim-smart! - Add `Random.shuffle` to shuffle iterables with seeded randomness support. + +## 4.0.0-beta.9 + +### Patch Changes + +- [#1376](https://github.com/Effect-TS/effect-smol/pull/1376) [`3386557`](https://github.com/Effect-TS/effect-smol/commit/338655731564a7be9f8859dedbf4d5bcac6eb350) Thanks @gcanti! - HttpApiEndpoint: relax `params`, `query`, and `headers` constraints to accept a full schema in addition to a record of fields. + +- [#1379](https://github.com/Effect-TS/effect-smol/pull/1379) [`b6666e3`](https://github.com/Effect-TS/effect-smol/commit/b6666e3cf6bd44ba1a8704e65c256c30359cb422) Thanks @tim-smart! - Fix `AtomHttpApi.query` to forward v4 `params` / `query` request fields to `HttpApiClient` at runtime. + Also align `AtomHttpApi` endpoint type inference with v4 `HttpApiEndpoint` params/query naming and add a regression test. + +## 4.0.0-beta.8 + +### Patch Changes + +- [#1371](https://github.com/Effect-TS/effect-smol/pull/1371) [`246e672`](https://github.com/Effect-TS/effect-smol/commit/246e672dbbd7848d60e0c78fd66671b2f10b3752) Thanks @IMax153! - Fix `ChildProcess` options type and implement `PgMigrator` + +- [#1372](https://github.com/Effect-TS/effect-smol/pull/1372) [`807dec0`](https://github.com/Effect-TS/effect-smol/commit/807dec03801b4c58a6d00c237b6d98d6386911df) Thanks @pawelblaszczyk5! - Remove superfluous error from SqlSchema.findAll signature + +## 4.0.0-beta.7 + +### Patch Changes + +- [#1366](https://github.com/Effect-TS/effect-smol/pull/1366) [`a2bda6d`](https://github.com/Effect-TS/effect-smol/commit/a2bda6d4ef6de9d9b0c53ae2df5434f778d6161a) Thanks @tim-smart! - rename SqlSchema.findOne\* apis + +- [#1360](https://github.com/Effect-TS/effect-smol/pull/1360) [`1f95a2b`](https://github.com/Effect-TS/effect-smol/commit/1f95a2b5aa9524bb38f4437f4691a664bf463ca1) Thanks @tim-smart! - Add `Schedule.jittered` to randomize schedule delays between 80% and 120% of the original delay. + +- [#1364](https://github.com/Effect-TS/effect-smol/pull/1364) [`a8d5e79`](https://github.com/Effect-TS/effect-smol/commit/a8d5e792fec201a83af0eb92fc79928d055125fd) Thanks @gcanti! - Schema: avoid eager resolution for type-level helpers, closes #1332 + +- [#1369](https://github.com/Effect-TS/effect-smol/pull/1369) [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf) Thanks @tim-smart! - align HttpClientRequest constructors with http method names + +- [#1369](https://github.com/Effect-TS/effect-smol/pull/1369) [`a5386ba`](https://github.com/Effect-TS/effect-smol/commit/a5386ba67005dff697d45a45398f398773f58dcf) Thanks @tim-smart! - remove body restriction for HttpClientRequest's + +- [#1358](https://github.com/Effect-TS/effect-smol/pull/1358) [`06d8a03`](https://github.com/Effect-TS/effect-smol/commit/06d8a0391631e6130e3ab25227e59817852e227f) Thanks @tim-smart! - Add `LogLevel.isEnabled` for checking a log level against `References.MinimumLogLevel`. + +- [#1363](https://github.com/Effect-TS/effect-smol/pull/1363) [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430) Thanks @tim-smart! - rename DurationInput to Duration.Input + +- [#1363](https://github.com/Effect-TS/effect-smol/pull/1363) [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430) Thanks @tim-smart! - DateTime.distance now returns a Duration + +- [#1367](https://github.com/Effect-TS/effect-smol/pull/1367) [`f9e883e`](https://github.com/Effect-TS/effect-smol/commit/f9e883e266fbda870336ee62f46b7ac85ba3de6e) Thanks @tim-smart! - refactor SqlSchema apis + +- [#1363](https://github.com/Effect-TS/effect-smol/pull/1363) [`8caac76`](https://github.com/Effect-TS/effect-smol/commit/8caac76a35821edfe03c75dab5eb056e8fc05430) Thanks @tim-smart! - remove rpc client nesting to improve type performance + +## 4.0.0-beta.6 + +### Patch Changes + +- [#1338](https://github.com/Effect-TS/effect-smol/pull/1338) [`3247da2`](https://github.com/Effect-TS/effect-smol/commit/3247da28331f345f68be5dbd2974a7e03d300fe1) Thanks @Leka74! - Add `showOperationId` to `HttpApiScalar.ScalarConfig`. + +- [#1326](https://github.com/Effect-TS/effect-smol/pull/1326) [`f205705`](https://github.com/Effect-TS/effect-smol/commit/f2057050dbd034b8c186be2d40c3d03ee63a5a3b) Thanks @gcanti! - Schema: add `BigDecimal` schema with comparison checks (`isGreaterThanBigDecimal`, `isGreaterThanOrEqualToBigDecimal`, `isLessThanBigDecimal`, `isLessThanOrEqualToBigDecimal`, `isBetweenBigDecimal`). + +- [#1328](https://github.com/Effect-TS/effect-smol/pull/1328) [`f35022c`](https://github.com/Effect-TS/effect-smol/commit/f35022c212e4111527e1bb43f360a67b2b49fa85) Thanks @gcanti! - Schema: add `DateTimeZoned`, `TimeZoneOffset`, `TimeZoneNamed`, and `TimeZone` schemas. + +- [#1325](https://github.com/Effect-TS/effect-smol/pull/1325) [`8622721`](https://github.com/Effect-TS/effect-smol/commit/86227217b02d43680a3c6f3c21731b1d852c91f5) Thanks @KhraksMamtsov! - Make `Data.Class`, `Data.TaggedClass`, and `Cause.YieldableError` pipeable. + +- [#1323](https://github.com/Effect-TS/effect-smol/pull/1323) [`fc660ab`](https://github.com/Effect-TS/effect-smol/commit/fc660ab8b5ebae38b8d6b96cbf2f9b880cc09253) Thanks @KhraksMamtsov! - Port `Pipeable.Class` from v3. + + ```ts + class MyClass extends Pipeable.Class() { + constructor(public a: number) { + super(); + } + methodA() { + return this.a; + } + } + console.log(new MyClass(2).pipe((x) => x.methodA())); // 2 + ``` + + ```ts + class A { + constructor(public a: number) {} + methodA() { + return this.a; + } + } + class B extends Pipeable.Class(A) { + constructor(private b: string) { + super(b.length); + } + methodB() { + return [this.b, this.methodA()]; + } + } + console.log(new B("pipe").pipe((x) => x.methodB())); // ['pipe', 4] + ``` + +- [#1337](https://github.com/Effect-TS/effect-smol/pull/1337) [`f37dc33`](https://github.com/Effect-TS/effect-smol/commit/f37dc335f64622fa9ce8d6d1d5dd8fc3f260257b) Thanks @IMax153! - Encoding: consolidate `effect/encoding` sub-modules (Base64, Base64Url, Hex, EncodingError) into a top-level `Encoding` module. Functions are now prefixed: `encodeBase64`, `decodeBase64`, `encodeHex`, `decodeHex`, etc. The `effect/encoding` sub-path export is removed. + +- [#1351](https://github.com/Effect-TS/effect-smol/pull/1351) [`3662f32`](https://github.com/Effect-TS/effect-smol/commit/3662f328fcfa3b2fa01ffa79da40e12e93fcede8) Thanks @tim-smart! - add `Schema.HashSet` for decoding and encoding `HashSet` values. + +- [#1336](https://github.com/Effect-TS/effect-smol/pull/1336) [`a7d436f`](https://github.com/Effect-TS/effect-smol/commit/a7d436f438dcd7f49b9485e4e95a4511f31fad7d) Thanks @mikearnaldi! - Extract `Semaphore` and `Latch` into their own modules. + + `Semaphore.make` / `Semaphore.makeUnsafe` replace `Effect.makeSemaphore` / `Effect.makeSemaphoreUnsafe`. + `Latch.make` / `Latch.makeUnsafe` replace `Effect.makeLatch` / `Effect.makeLatchUnsafe`. + + Merge `PartitionedSemaphore` into `Semaphore` as `Semaphore.Partitioned`, `Semaphore.makePartitioned`, `Semaphore.makePartitionedUnsafe`. + +- [#1345](https://github.com/Effect-TS/effect-smol/pull/1345) [`6856a41`](https://github.com/Effect-TS/effect-smol/commit/6856a415d7eddd9d73d60919e976f1d071421be4) Thanks @tim-smart! - allocate less effects when reading a file + +- [#1350](https://github.com/Effect-TS/effect-smol/pull/1350) [`8c417d0`](https://github.com/Effect-TS/effect-smol/commit/8c417d03475e5e12d00dca0c4781d0af7e66b86c) Thanks @tim-smart! - Add "Previously Known As" JSDoc migration notes for the `Semaphore` and `Latch` APIs extracted from `Effect`. + +- [#1355](https://github.com/Effect-TS/effect-smol/pull/1355) [`5419570`](https://github.com/Effect-TS/effect-smol/commit/5419570ba47ce882a3a10882707b46f66e464906) Thanks @tim-smart! - ensure non-middleware http errors are correctly handled + +- [#1352](https://github.com/Effect-TS/effect-smol/pull/1352) [`449c5ed`](https://github.com/Effect-TS/effect-smol/commit/449c5ed5318e8a874e730420bcf52918fa2ec80f) Thanks @tim-smart! - Add `Schema.HashMap` for decoding and encoding `HashMap` values. + +- [#1347](https://github.com/Effect-TS/effect-smol/pull/1347) [`4b5ec12`](https://github.com/Effect-TS/effect-smol/commit/4b5ec12f87f95f2a3cd8fe4d5b26c6eb0529381a) Thanks @tim-smart! - use .toJSON for default .toString implementations + +- [#1329](https://github.com/Effect-TS/effect-smol/pull/1329) [`df87937`](https://github.com/Effect-TS/effect-smol/commit/df879375fc3b169c43f9c434b3775e12b80dffe4) Thanks @gcanti! - Schema: extract shared `dateTimeUtcFromString` transformation for `DateTimeUtc` and `DateTimeUtcFromString`. + +- [#1318](https://github.com/Effect-TS/effect-smol/pull/1318) [`5dbfca8`](https://github.com/Effect-TS/effect-smol/commit/5dbfca8d1dbb6d18d1605d4f8562e99c86e2ff11) Thanks @gcanti! - Schema: rename `$` suffix to `$` prefix for type-level identifiers that conflict with built-in names (`Array$` → `$Array`, `Record$` → `$Record`, `ReadonlyMap$` → `$ReadonlyMap`, `ReadonlySet$` → `$ReadonlySet`). + +- [#1356](https://github.com/Effect-TS/effect-smol/pull/1356) [`e629497`](https://github.com/Effect-TS/effect-smol/commit/e6294973d55597ab6b6deca6babbe1e946b2c91d) Thanks @tim-smart! - allow passing void for request constructors + +- [#1348](https://github.com/Effect-TS/effect-smol/pull/1348) [`981c991`](https://github.com/Effect-TS/effect-smol/commit/981c991cd78db34def815d5754379d737157f005) Thanks @tim-smart! - Fix `Schedule.andThenResult` to initialize the right schedule only after the left schedule completes. + This removes the extra immediate transition tick and correctly completes when the right schedule is finite. + +- [#1320](https://github.com/Effect-TS/effect-smol/pull/1320) [`1ca2ed6`](https://github.com/Effect-TS/effect-smol/commit/1ca2ed67301a5dc40ae0ed94346b99f26fd22bbe) Thanks @gcanti! - Struct: add `Struct.Record` constructor for creating records with the given keys and value. + +- [#1342](https://github.com/Effect-TS/effect-smol/pull/1342) [`45722bd`](https://github.com/Effect-TS/effect-smol/commit/45722bde974458311f11ad237711363a10ec6894) Thanks @cevr! - `Schema.TaggedErrorClass`, `Schema.Class`, and `Schema.ErrorClass` constructors now allow omitting the props argument when all fields have constructor defaults (e.g. `new MyError()` instead of `new MyError({})`). + +- [#1322](https://github.com/Effect-TS/effect-smol/pull/1322) [`eb2a85e`](https://github.com/Effect-TS/effect-smol/commit/eb2a85ed4dc162b2535d304799333a5a20477fd0) Thanks @tim-smart! - Add a `requireServicesAt` option to `PersistedCache.make` so lookup-service requirements can be configured like `Cache`. + +## 4.0.0-beta.5 + +### Patch Changes + +- [#1317](https://github.com/Effect-TS/effect-smol/pull/1317) [`f6e133e`](https://github.com/Effect-TS/effect-smol/commit/f6e133e9a16b32317bd09ff08c12b97a0ae44600) Thanks @tim-smart! - support tag unions in Effect.catchTag/Reason + +- [#1314](https://github.com/Effect-TS/effect-smol/pull/1314) [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8) Thanks @zeyuri! - Fix `Atom.serializable` encode/decode for wire transfer. + + Use `Schema.toCodecJson` instead of `Schema.encodeSync`/`Schema.decodeSync` directly, so that encoded values are plain JSON objects that survive serialization roundtrips (JSON, seroval, etc.). Previously, `AsyncResult.Schema` encode produced instances with custom prototypes that were lost after wire transfer, causing decode to fail with "Expected AsyncResult" errors during SSR hydration. + +- [#1315](https://github.com/Effect-TS/effect-smol/pull/1315) [`a88e206`](https://github.com/Effect-TS/effect-smol/commit/a88e206e44dc66ca5a2b45bedc797877c5dbb083) Thanks @tim-smart! - add Filter.reason api + +- [#1314](https://github.com/Effect-TS/effect-smol/pull/1314) [`e3893cc`](https://github.com/Effect-TS/effect-smol/commit/e3893ccf2632338c7d8e745f639dcd825a9d42f8) Thanks @zeyuri! - Port ReactHydration to effect-smol. + + Add `Hydration` module to `effect/unstable/reactivity` with `dehydrate`, `hydrate`, and `toValues` for SSR state serialization. Add `HydrationBoundary` React component to `@effect/atom-react` with two-phase hydration (new atoms in render, existing atoms after commit). + +## 4.0.0-beta.4 + +### Patch Changes + +- [#1308](https://github.com/Effect-TS/effect-smol/pull/1308) [`c5a18ef`](https://github.com/Effect-TS/effect-smol/commit/c5a18ef44171e3880bf983faee74529908974b32) Thanks @tim-smart! - improve Schema.TaggedUnion .match auto completion + +- [#1310](https://github.com/Effect-TS/effect-smol/pull/1310) [`bc6b885`](https://github.com/Effect-TS/effect-smol/commit/bc6b885b94d887a200657c0775dfa874dc15bc0c) Thanks @tim-smart! - Add `Schedule.duration`, a one-shot schedule that waits for the provided duration and then completes. + +## 4.0.0-beta.3 + +### Patch Changes + +- [#1303](https://github.com/Effect-TS/effect-smol/pull/1303) [`3a0cf36`](https://github.com/Effect-TS/effect-smol/commit/3a0cf36eff106ba48d74e133c1598cd40613e530) Thanks @tim-smart! - add Result.failVoid + +- [#1307](https://github.com/Effect-TS/effect-smol/pull/1307) [`c4da328`](https://github.com/Effect-TS/effect-smol/commit/c4da328d32fad1d61e0e538f5d371edf61521d7e) Thanks @tim-smart! - Add `HttpClientRequest.bodyFormDataRecord` and `HttpBody.makeFormDataRecord` helpers for creating multipart form bodies from plain records. + +## 4.0.0-beta.2 + +### Patch Changes + +- [#1302](https://github.com/Effect-TS/effect-smol/pull/1302) [`a22ce73`](https://github.com/Effect-TS/effect-smol/commit/a22ce73b2bd9305b7ba665694d2255c0e6d5a8d0) Thanks @tim-smart! - allow undefined for VariantSchema.Overridable input + +- [#1299](https://github.com/Effect-TS/effect-smol/pull/1299) [`ebdabf7`](https://github.com/Effect-TS/effect-smol/commit/ebdabf79ff4e62c8384aa8cf9a8d2787d536ee78) Thanks @tim-smart! - Port `SqlSchema.findOne` from effect v3 to return `Option` on empty results and add `SqlSchema.single` for the fail-on-empty behavior. + +- [#1298](https://github.com/Effect-TS/effect-smol/pull/1298) [`8f663bb`](https://github.com/Effect-TS/effect-smol/commit/8f663bb121021bf12bd264e8ae385187cb7a5dae) Thanks @tim-smart! - Add `Effect.catchNoSuchElement`, a renamed port of v3 `Effect.optionFromOptional` that converts `NoSuchElementError` failures into `Option.none`. + +## 4.0.0-beta.1 + +### Patch Changes + +- [#1293](https://github.com/Effect-TS/effect-smol/pull/1293) [`0fecf70`](https://github.com/Effect-TS/effect-smol/commit/0fecf70048057623eed7c584a06671773a2b1743) Thanks @mikearnaldi! - Add `Effect.filter` support for synchronous `Filter.Filter` overloads and correctly handle non-effect `Result` return values at runtime. + +- [#1294](https://github.com/Effect-TS/effect-smol/pull/1294) [`709569e`](https://github.com/Effect-TS/effect-smol/commit/709569ed76bead9ebb0670599e4d890a07ca5a43) Thanks @tim-smart! - Fix `Prompt.text` and related text prompts to initialize from `default` values so users can edit the default input directly. + +## 4.0.0-beta.0 + +### Major Changes + +- [#1183](https://github.com/Effect-TS/effect-smol/pull/1183) [`be642ab`](https://github.com/Effect-TS/effect-smol/commit/be642ab1b3b4cd49e53c9732d7aba1b367fddd66) Thanks @tim-smart! - v4 beta diff --git a/.repos/effect/packages/effect/CONFIG.md b/.repos/effect/packages/effect/CONFIG.md new file mode 100644 index 000000000..8b932b790 --- /dev/null +++ b/.repos/effect/packages/effect/CONFIG.md @@ -0,0 +1,787 @@ +# Configuration in Effect + +This guide shows you how to load and validate configuration in an Effect application. Two modules work together: + +- **`ConfigProvider`** — reads raw data from a source (environment variables, JSON objects, `.env` files, directory trees). +- **`Config`** — describes what shape and types you expect, then decodes the raw data into typed values. + +You describe _what_ you need with `Config`, and the library figures out _how_ to read and validate it using a `ConfigProvider`. + +## Getting Started + +### Reading a Single Value + +The simplest case: read one value from an environment variable. + +```ts +import { Config, Effect } from "effect" + +const program = Effect.gen(function*() { + const host = yield* Config.string("HOST") + console.log(host) +}) + +Effect.runSync(program) +// reads HOST from process.env +``` + +When you yield a `Config` inside `Effect.gen`, it automatically uses the default `ConfigProvider` (which reads from `process.env`). + +### Reading Multiple Values + +Use `Config.all` to group related keys: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const dbConfig = Config.all({ + host: Config.string("host"), + port: Config.int("port") +}) + +const provider = ConfigProvider.fromUnknown({ + host: "localhost", + port: 5432 +}) + +const result = Effect.runSync(dbConfig.parse(provider)) +// { host: "localhost", port: 5432 } +``` + +### Reading Structured Config with a Schema + +For larger configs, use `Config.schema` with a `Schema.Struct`: + +```ts +import { Config, ConfigProvider, Effect, Schema } from "effect" + +const AppConfig = Config.schema( + Schema.Struct({ + host: Schema.String, + port: Schema.Int, + debug: Schema.Boolean + }) +) + +const provider = ConfigProvider.fromUnknown({ + host: "localhost", + port: 8080, + debug: true +}) + +const result = Effect.runSync(AppConfig.parse(provider)) +// { host: "localhost", port: 8080, debug: true } +``` + +The schema automatically decodes raw string values into their target types. For example, when reading from environment variables, `"8080"` becomes the number `8080` and `"true"` becomes the boolean `true`. + +## 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. + +### 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 Absent Input + +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 provider = ConfigProvider.fromUnknown({}) +Effect.runSync(port.parse(provider)) // 3000 +``` + +### `Config.option` — Optional Values + +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 provider = ConfigProvider.fromUnknown({}) +Effect.runSync(maybePort.parse(provider)) // { _tag: "None" } +``` + +### `Config.map` — Transform a Value + +```ts +import { Config } from "effect" + +const upperHost = Config.string("HOST").pipe( + Config.map((s) => s.toUpperCase()) +) +``` + +### `Config.orElse` — Fallback on Any Error + +Unlike `withDefault`, this catches **all** `ConfigError`s: + +```ts +import { Config } from "effect" + +const host = Config.string("HOST").pipe( + Config.orElse(() => Config.succeed("localhost")) +) +``` + +### `Config.nested` — Scope a Config Under a Prefix + +Prepends a logical path segment to every key the inner config reads. The prefix is used for both provider lookups and schema error paths: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const dbConfig = Config.all({ + host: Config.string("host"), + port: Config.int("port") +}).pipe(Config.nested("database")) + +const provider = ConfigProvider.fromUnknown({ + database: { host: "localhost", port: 5432 } +}) + +Effect.runSync(dbConfig.parse(provider)) +// { host: "localhost", port: 5432 } +``` + +With environment variables, nesting uses `_` as separator: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const host = Config.string("host").pipe(Config.nested("database")) + +const provider = ConfigProvider.fromEnv({ + env: { database_host: "localhost" } +}) + +Effect.runSync(host.parse(provider)) // "localhost" +``` + +Multiple `Config.nested` calls compose with the outermost prefix first: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const config = Config.string("host").pipe( + Config.nested("database"), + Config.nested("production") +) + +const provider = ConfigProvider.fromUnknown({ + production: { + database: { + host: "localhost" + } + } +}) + +Effect.runSync(config.parse(provider)) // "localhost" +``` + +### `Config.all` — Combine Multiple Configs + +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") +}) + +// As a tuple +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` for separated scalar input such as `"a,b,c"`, and `Config.Record` for input such as `"a=1,b=2"`. + +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`. + +If you need custom lookup behavior for a new backing source, implement a `ConfigProvider` with `ConfigProvider.make` instead. + +## Config Schemas + +For reusable codecs you can pass directly to `Config.schema`: + +| 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.Array(value)` | `Array` | Also parses flat `"v1,v2"` strings | +| `Config.Record(key, value)` | `Record` | Also parses flat `"k1=v1,k2=v2"` strings | + +## 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. + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const provider = ConfigProvider.fromEnv({ + env: { + DATABASE_HOST: "localhost", + DATABASE_PORT: "5432" + } +}) + +const host = Config.string("HOST").parse( + provider.pipe(ConfigProvider.nested("DATABASE")) +) + +Effect.runSync(host) // "localhost" +``` + +**How `_` splitting works**: env var names are split on `_` to build a tree. This means `DATABASE_HOST=localhost` is accessible at both `["DATABASE_HOST"]` (flat) and `["DATABASE", "HOST"]` (nested). Querying `["DATABASE"]` returns a Record node with child key `"HOST"`. + +Pass `{ env: { ... } }` for testing. Omit to use `process.env` (merged with `import.meta.env` when available). + +### `ConfigProvider.fromUnknown` — Plain JS Objects + +Ideal for testing or embedding config in code: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const provider = ConfigProvider.fromUnknown({ + database: { + host: "localhost", + port: 5432, + credentials: { + username: "admin", + password: "secret" + } + }, + servers: ["server1", "server2", "server3"] +}) +``` + +Path traversal follows standard JS rules: string segments index into object keys, numeric segments index into arrays. Primitive values are automatically stringified. + +### `ConfigProvider.fromDotEnvContents` — Parse `.env` Strings + +When you already have the `.env` content as a string: + +```ts +import { ConfigProvider } from "effect" + +const contents = ` +# Database settings +HOST=localhost +PORT=3000 +SECRET="my-secret-value" +` + +const provider = ConfigProvider.fromDotEnvContents(contents) +``` + +Supports `export` prefixes, single/double/backtick quoting, inline comments, and escaped newlines. Enable variable expansion with `{ expandVariables: true }`: + +```ts +import { ConfigProvider } from "effect" + +const contents = ` +PASSWORD=secret +DB_PASS=$PASSWORD +` + +const provider = ConfigProvider.fromDotEnvContents(contents, { + expandVariables: true +}) +``` + +### `ConfigProvider.fromDotEnv` — Load `.env` Files + +Reads a `.env` file from disk. Returns an `Effect` (requires `FileSystem` in context): + +```ts +import { ConfigProvider, Effect } from "effect" + +const program = Effect.gen(function*() { + const provider = yield* ConfigProvider.fromDotEnv() + // or: yield* ConfigProvider.fromDotEnv({ path: "/custom/.env" }) + return provider +}) +``` + +### `ConfigProvider.fromDir` — Directory Trees + +Reads config from a file-system tree where each file is a leaf and each directory is a container. Useful for Kubernetes ConfigMap/Secret volume mounts. + +``` +/etc/myapp/ + database/ + host # contains "localhost" + port # contains "5432" + api_key # contains "sk-abc123" +``` + +```ts +import { ConfigProvider, Effect } from "effect" + +const program = Effect.gen(function*() { + const provider = yield* ConfigProvider.fromDir({ + rootPath: "/etc/myapp" + }) + return provider +}) +``` + +Requires `Path` and `FileSystem` in the Effect context. + +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 + +Build a provider from any backing store: + +```ts +import { ConfigProvider, Effect } from "effect" + +const data: Record = { + host: "localhost", + port: "5432" +} + +const provider = ConfigProvider.make((path) => { + const key = path.join(".") + const value = data[key] + return Effect.succeed( + value !== undefined ? ConfigProvider.makeValue(value) : undefined + ) +}) +``` + +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 + +### `ConfigProvider.orElse` — Fallback Sources + +Falls back to a second provider when the first returns `undefined` (path not found). Does **not** catch `SourceError`. + +```ts +import { ConfigProvider } from "effect" + +const envProvider = ConfigProvider.fromEnv({ + env: { HOST: "prod.example.com" } +}) +const defaults = ConfigProvider.fromUnknown({ + HOST: "localhost", + PORT: "3000" +}) + +const combined = ConfigProvider.orElse(envProvider, defaults) +``` + +Each side keeps its own path transformations. If you combine providers that were already scoped or mapped, those transformations remain local to that side: + +```ts +import { ConfigProvider } from "effect" + +const envProvider = ConfigProvider.fromEnv({ + env: { DATABASE_HOST: "localhost" } +}).pipe(ConfigProvider.constantCase) + +const defaults = ConfigProvider.fromEnv({ + env: { APP_PORT: "3000" } +}).pipe(ConfigProvider.nested("APP")) + +const combined = envProvider.pipe(ConfigProvider.orElse(defaults)) +``` + +### `ConfigProvider.nested` — Prefix All Lookups + +Prepends path segments so that all lookups are scoped: + +```ts +import { ConfigProvider } from "effect" + +const provider = ConfigProvider.fromEnv({ + env: { APP_HOST: "localhost", APP_PORT: "3000" } +}) + +// Lookups for ["HOST"] now resolve to ["APP", "HOST"] +const scoped = ConfigProvider.nested(provider, "APP") +``` + +Accepts a single string or a full `Path` array. + +Provider transformations compose in application order. A later `nested` becomes the outer prefix: + +```ts +import { ConfigProvider } from "effect" + +const provider = ConfigProvider.fromEnv({ + env: { B_A_KEY: "value" } +}).pipe( + ConfigProvider.nested("A"), + ConfigProvider.nested("B") +) + +// path ["KEY"] resolves to ["B", "A", "KEY"] +``` + +When `nested` is applied to a provider built with `ConfigProvider.orElse`, the prefix is applied to both operands. + +### `ConfigProvider.constantCase` — CamelCase to SCREAMING_SNAKE_CASE + +Bridges camelCase schema keys to environment variable naming: + +```ts +import { ConfigProvider } from "effect" + +const provider = ConfigProvider.fromEnv({ + env: { DATABASE_HOST: "localhost" } +}).pipe(ConfigProvider.constantCase) + +// path ["databaseHost"] now resolves to ["DATABASE_HOST"] +``` + +Ordering matters with `nested`. `constantCase` is a path transform, so it only converts the path it receives at that point in the pipeline: + +```ts +import { ConfigProvider } from "effect" + +const convertedPrefix = ConfigProvider.fromEnv({ + env: { APP_HOST: "localhost" } +}).pipe( + ConfigProvider.nested("app"), + ConfigProvider.constantCase +) + +// path ["host"] resolves to ["APP", "HOST"] + +const literalPrefix = ConfigProvider.fromEnv({ + env: { app_HOST: "localhost" } +}).pipe( + ConfigProvider.constantCase, + ConfigProvider.nested("app") +) + +// path ["host"] resolves to ["app", "HOST"] +``` + +Put `constantCase` after `nested` when the prefix should be converted too. + +### `ConfigProvider.mapInput` — Arbitrary Path Transforms + +Transform the whole path before lookup: + +```ts +import { ConfigProvider } from "effect" + +const provider = ConfigProvider.fromEnv({ + env: { APP_HOST: "localhost" } +}) + +const upper = ConfigProvider.mapInput( + provider, + (path) => path.map((seg) => typeof seg === "string" ? seg.toUpperCase() : seg) +) +``` + +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 +import { ConfigProvider } from "effect" + +const appendLeaf = ConfigProvider.mapInput((path) => [...path, "leaf"]) + +const provider = ConfigProvider.fromEnv({ + env: { APP_KEY_leaf: "value" } +}).pipe( + ConfigProvider.nested("APP"), + appendLeaf +) + +// path ["KEY"] resolves to ["APP", "KEY", "leaf"] +``` + +When `mapInput` is applied to a provider built with `ConfigProvider.orElse`, the mapping is applied to both operands. + +## Installing a Provider + +### Using `ConfigProvider.layer` + +Replaces the active provider for all downstream effects: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const TestLayer = ConfigProvider.layer( + ConfigProvider.fromUnknown({ port: 8080 }) +) + +const program = Effect.gen(function*() { + const port = yield* Config.int("port") + return port +}) + +Effect.runSync(Effect.provide(program, TestLayer)) // 8080 +``` + +### Using `ConfigProvider.layerAdd` + +Adds a provider without replacing the existing one. By default, the new provider is a **fallback**: + +```ts +import { ConfigProvider } from "effect" + +const defaults = ConfigProvider.fromUnknown({ + HOST: "localhost", + PORT: "3000" +}) + +// process.env is tried first; `defaults` is the fallback +const DefaultsLayer = ConfigProvider.layerAdd(defaults) +``` + +Set `{ asPrimary: true }` to make the new provider the primary source instead. + +### Using `Effect.provideService` + +For one-off overrides without layers: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const provider = ConfigProvider.fromUnknown({ HOST: "localhost" }) + +const program = Effect.gen(function*() { + const host = yield* Config.string("HOST") + return host +}).pipe( + Effect.provideService(ConfigProvider.ConfigProvider, provider) +) +``` + +## Two Ways to Run a Config + +1. **Yield in `Effect.gen`** — automatically uses the current `ConfigProvider` from the service map: + + ```ts + const program = Effect.gen(function*() { + 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 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: + +- **`SourceError`** — the provider could not read data (I/O failure, permission error). Has `message` and optional `cause` properties. +- **`SchemaError`** — data was found but didn't match the schema (wrong type, out of range, missing key). + +Check `error.cause._tag` to distinguish: + +```ts +import { Config, ConfigProvider, Effect } from "effect" + +const program = Config.int("PORT").parse( + ConfigProvider.fromUnknown({ PORT: "not-a-number" }) +).pipe( + Effect.tapError((error) => + Effect.sync(() => { + if (error.cause._tag === "SchemaError") { + console.log("Validation failed:", error.message) + } else { + console.log("Source error:", error.message) + } + }) + ) +) +``` + +**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 + +```ts +import { Config, ConfigProvider, Effect, Schema } from "effect" + +// Define your config shape +const ServerConfig = Config.schema( + Schema.Struct({ + host: Schema.String, + port: Schema.Int, + logLevel: Schema.Literals(["debug", "info", "warn", "error"]) + }), + "server" +) + +const DbConfig = Config.schema( + Schema.Struct({ + url: Schema.String, + poolSize: Schema.Int + }), + "db" +) + +const AppConfig = Config.all({ + server: ServerConfig, + db: DbConfig, + debug: Config.boolean("debug").pipe(Config.withDefault(false)) +}) + +// In production, just yield it — reads from process.env +const program = Effect.gen(function*() { + const config = yield* AppConfig + console.log(config) +}) + +// For testing, provide a specific provider +const testProvider = ConfigProvider.fromUnknown({ + server: { host: "localhost", port: 3000, logLevel: "debug" }, + db: { url: "postgres://localhost/testdb", poolSize: 5 }, + debug: true +}) + +Effect.runSync( + program.pipe(Effect.provide(ConfigProvider.layer(testProvider))) +) +``` + +With environment variables, the same config reads: + +``` +server_host=localhost +server_port=3000 +server_logLevel=debug +db_url=postgres://localhost/mydb +db_poolSize=10 +debug=true +``` diff --git a/.repos/effect/packages/effect/HTTPAPI.md b/.repos/effect/packages/effect/HTTPAPI.md new file mode 100644 index 000000000..6567439ec --- /dev/null +++ b/.repos/effect/packages/effect/HTTPAPI.md @@ -0,0 +1,3555 @@ +# Overview + +The `HttpApi` modules let you describe your HTTP API once and use that description to run a server, generate documentation, and create a type-safe client. + +An API is built from three building blocks: + +- **HttpEndpoint** — a single route (path + HTTP method) with schemas for its request and response. +- **HttpApiGroup** — a collection of related endpoints (e.g., all user-related routes). +- **HttpApi** — the top-level object that combines groups into a complete API. + +``` +HttpApi +├── HttpGroup +│ ├── HttpEndpoint +│ └── HttpEndpoint +└── HttpGroup + ├── HttpEndpoint + ├── HttpEndpoint + └── HttpEndpoint +``` + +From one API definition you can: + +- **Start a server** that implements and serves every endpoint. +- **Generate documentation** (Scalar or Swagger) automatically. +- **Derive a client** with a typed method for each endpoint. + +One definition powers the server, docs, and client — change it once and everything stays in sync. + +# Getting Started + +## Defining and Implementing an API + +Let's build a minimal API with one endpoint that returns `"Hello, World!"`. You'll define what the endpoint looks like, implement it, and start a server. + +``` +HttpApi ("MyApi") +└── HttpGroup ("Greetings") + └── HttpEndpoint ("hello-world") +``` + +**Example** (Hello World) + +```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 } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +// Definition +const Api = HttpApi.make("MyApi").add( + // Define the API group + HttpApiGroup.make("Greetings").add( + // Define the endpoint + HttpApiEndpoint.get("hello", "/", { + // Define the success schema + success: Schema.String + }) + ) +) + +// Implementation +const GroupLayer = HttpApiBuilder.group( + Api, + "Greetings", // The name of the group to handle + (handlers) => + handlers.handle( + "hello", // The name of the endpoint to handle + () => Effect.succeed("Hello, World!") // The handler function + ) +) + +// Server +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +// Launch +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) +``` + +After running the code, open a browser and navigate to http://localhost:3000. The server will respond with: + +``` +Hello, World! +``` + +## Serving The Auto Generated OpenAPI Documentation + +Adding a documentation layer gives you an interactive page where you (and your API consumers) can explore endpoints, try requests, and see response shapes — all generated automatically from your API definition. You can choose between the `HttpApiScalar` module (Scalar UI) or the `HttpApiSwagger` module (Swagger UI); both do the same job. + +**Example** (Serving Scalar Documentation) + +To include Scalar in your server setup, provide the `HttpApiScalar.layer` when configuring the server. + +```ts +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + // Provide the Scalar layer so clients can access auto-generated docs + Layer.provide(GroupLayer), + Layer.provide(HttpApiScalar.layer(Api)), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) +``` + +After running the server, open your browser and navigate to http://localhost:3000/docs. + +This URL will display the Scalar documentation, allowing you to explore the API's endpoints, request parameters, and response structures interactively. + +**Example** (Serving Swagger Documentation) + +To include Swagger in your server setup, provide the `HttpApiSwagger.layer` when configuring the server. + +```ts +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)), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) +``` + +After running the server, open your browser and navigate to http://localhost:3000/docs. + +This URL will display the Swagger documentation, allowing you to explore the API's endpoints, request parameters, and response structures interactively. + +## Adding Annotations to Schemas + +Annotations attach extra information to your schemas — like a human-readable description or an identifier shown in the docs UI. They don't change runtime behavior; they enrich the generated documentation. + +```ts +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}).annotate({ + description: "A user", // The description of the user + identifier: "User" // Used in the Scalar UI under the Model section +}) +``` + +## Deriving a Client + +Once you've defined an API, you can generate a fully typed client from it using the `HttpApiClient` module. The client gives you a method for every endpoint, so calling your API feels like calling a local function — with full type safety and no manual HTTP handling. + +**Example** (Deriving and Using a Client) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { FetchHttpClient } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Greetings") + .add( + HttpApiEndpoint.get("hello", "/", { + success: Schema.String + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Greetings", + (handlers) => handlers.handle("hello", () => Effect.succeed("Hello, World!")) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) + +// Create a program that derives and uses the client +const program = Effect.gen(function*() { + // Derive the client + const client = yield* HttpApiClient.make(Api, { + baseUrl: "http://localhost:3000" + }) + // Call the "hello-world" endpoint + const hello = yield* client.Greetings.hello() + console.log(hello) +}) + +// Provide a Fetch-based HTTP client and run the program +Effect.runFork(program.pipe(Effect.provide(FetchHttpClient.layer))) +/* +Output: +[18:55:26.051] INFO (#2): Listening on http://0.0.0.0:3000 +[18:55:26.057] INFO (#12) http.span=2ms: Sent HTTP response { 'http.method': 'GET', 'http.url': '/', 'http.status': 200 } +Hello, World! +*/ +``` + +# Design Principles + +- **Schemas first**: Every piece of data flowing in or out of an endpoint — path params, query strings, headers, payloads, responses, and errors — is described by a schema. The framework uses these schemas to validate requests, serialize responses, generate docs, and type the client. +- **Metadata lives on schemas**: Configuration like HTTP status codes and content types is attached directly to the schema via annotations, not to the endpoint. This keeps all the information about a data shape in one place. + +In particular: + +- **Request** + - **Payload encoding / content type** is controlled with `HttpApiSchema.as*` helpers: + - `asJson` — parse the body as JSON (default) + - `asFormUrlEncoded` — parse the body as URL-encoded form data + - `asText` — parse the body as plain text + - `asUint8Array` — parse the body as raw binary data + - `asMultipart` — parse the body as a multipart form (for file uploads) + - `asMultipartStream` — parse the body as a streaming multipart form +- **Response** + - **Status code** is set via the `HttpApiSchema.status` API (or `httpApiStatus` annotation) + - **Encoding / content type** is controlled with `HttpApiSchema.as*` helpers: + - `asJson` — send the body as JSON (default) + - `asFormUrlEncoded` — send the body as URL-encoded form data + - `asText` — send the body as plain text + - `asUint8Array` — send the body as raw binary data + +## Anatomy of an Endpoint + +An endpoint definition describes everything the framework needs to know about a single HTTP route: which URL parameters it expects, what query strings and headers it reads, what the request body looks like, and what it can respond with (both successes and errors). All of these are optional. + +`HttpApiEndpoint` automatically coerces request and response schemas by default. Path / query / header schemas use `Schema.toCodecStringTree`, while JSON payload / success / error schemas use `Schema.toCodecJson`. This means you can define schemas in their natural domain types (for example `Schema.Int`), without manually adding string / JSON transformations. + +```ts +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +// ┌─── Endpoint name (used in the client as the method name) +// │ ┌─── Endpoint path +// ▼ ▼ +HttpApiEndpoint.patch("updateUser", "/user/:id", { + // Parameters from the route pattern (e.g. /user/:id). + // Can be a record of fields or a full schema. + params: { + // ┌─── Schema for the "id" parameter. + // ▼ + id: Schema.String + }, + + // (optional) Query string parameters (e.g. ?mode=merge). + // Can be a record of fields or a full schema. + query: { + // ┌─── Schema for the "mode" query parameter + // ▼ + mode: Schema.Literals(["merge", "replace"]) + }, + + // (optional) Request headers. + // Can be a record of fields or a full schema. + headers: { + "x-api-key": Schema.String, + "x-request-id": Schema.String + }, + + // The request payload can be a single schema or an array of schemas. + // - Default encoding is JSON. + // - Default status for success is 200. + // For GET requests, the payload must be a record of schemas. + payload: [ + // JSON payload (default encoding). + Schema.Struct({ + name: Schema.String + }), + // text/plain payload. + Schema.String.pipe(HttpApiSchema.asText()) + ], + + // Possible success responses. + // Default is 200 OK with no content if omitted. + success: [ + // JSON response (default encoding). + User, + // text/plain response with a custom status code. + Schema.String + .pipe( + HttpApiSchema.status(206), + HttpApiSchema.asText() + ) + ], + + // Possible error responses. + error: [ + // Default is 500 Internal Server Error with JSON encoding. + Schema.Finite, + + // text/plain error with a custom status code. + Schema.String + .pipe( + HttpApiSchema.status(404), + HttpApiSchema.asText() + ), + + // Any schema that encodes to `Schema.Void` is treated as "no content". + // Here it uses a custom status code. + Schema.Void + .pipe(HttpApiSchema.status(401)) + ] +}) +``` + +# Routing + +This section walks through defining endpoints for common HTTP methods — GET, POST, DELETE, and PATCH — using a user-management API as a running example: + +- `GET /users` — retrieve all users. +- `GET /users/:userId` — retrieve a specific user by ID. +- `POST /users` — create a new user. +- `DELETE /users/:userId` — delete a user by ID. +- `PATCH /users/:userId` — update a user by ID. + +## GET + +Use `HttpApiEndpoint.get` to create a GET endpoint. Provide a name (used as the method name in generated clients), a path, and optionally a `success` schema describing what the endpoint returns. Without a success schema the default response is `204 No Content`. + +**Example** (Defining a GET Endpoint to Retrieve All Users) + +```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, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +// Define a schema representing a User entity +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + // Define the "getUsers" endpoint, returning a list of users + // ┌─── Endpoint name (used in the client as the method name) + // │ ┌─── Endpoint path + // ▼ ▼ + HttpApiEndpoint.get("getUsers", "/users", { + // ┌─── success schema + // │ + // ▼ + success: Schema.Array(User) + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handle("getUsers", () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + )) +) + +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(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 +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiScalar } 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", { + success: Schema.Array(User) + }), + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: Schema.Int + }, + success: User + }), + // Define a POST endpoint for creating a new user + HttpApiEndpoint.post("createUser", "/user", { + // Define the request body schema (payload) + payload: User, + // Define the schema for a successful response + success: User + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handleAll({ + getUsers: () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ), + getUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + }, + createUser: (ctx) => { + // ┌─── User + // ▼ + const user = ctx.payload + return Effect.succeed(user) + } + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +## DELETE + +Use `HttpApiEndpoint.delete` to create an endpoint that removes a resource. + +**Example** (Defining a DELETE Endpoint with Parameters) + +```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, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const IdParam = Schema.Int + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("getUsers", "/users", { + success: Schema.Array(User) + }), + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: IdParam + }, + success: User + }), + HttpApiEndpoint.post("createUser", "/user", { + payload: User, + success: User + }), + HttpApiEndpoint.delete("deleteUser", "/user/:id", { + params: { + id: IdParam + } + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handleAll({ + getUsers: () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ), + getUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + }, + createUser: (ctx) => { + const user = ctx.payload + return Effect.succeed(user) + }, + deleteUser: (ctx) => { + const id = ctx.params.id + return Effect.log(`Deleting user ${id}`) + } + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +## PATCH + +Use `HttpApiEndpoint.patch` to create an endpoint that partially updates a resource. Like POST, you can define `payload` (the fields to update) and `success` (the response after the update). + +**Example** (Defining a PATCH Endpoint for Updating a User) + +```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, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const IdParam = Schema.Int + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("getUsers", "/users", { + success: Schema.Array(User) + }), + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: IdParam + }, + success: User + }), + HttpApiEndpoint.post("createUser", "/user", { + payload: User, + success: User + }), + HttpApiEndpoint.delete("deleteUser", "/user/:id", { + params: { + id: IdParam + } + }), + HttpApiEndpoint.patch("updateUser", "/user/:id", { + params: { + id: IdParam + }, + // Specify the schema for the request payload + payload: Schema.Struct({ + name: Schema.String // Only the name can be updated + }), + // Specify the schema for a successful response + success: User + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handleAll({ + getUsers: () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ), + getUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + }, + createUser: (ctx) => { + const user = ctx.payload + return Effect.succeed(user) + }, + deleteUser: (ctx) => { + const id = ctx.params.id + return Effect.log(`Deleting user ${id}`) + }, + updateUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + } + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +## Parameters + +Path parameters let you capture dynamic values from the URL. For example, `/user/:id` extracts the `id` segment. Use the `params` option to declare a record of fields or a full schema — the framework will parse and validate the value before your handler runs. + +**Example** (Defining a GET Endpoint to Retrieve a User by ID) + +```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, HttpApiScalar } 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", { + success: Schema.Array(User) + }), + // a GET endpoint with a parameter ":id" + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + // ┌─── schema for the "id" parameter + // ▼ + id: Schema.Int + }, + success: User + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handleAll({ + getUsers: () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ), + getUser: (ctx) => { + // ┌─── number + // ▼ + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + } + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +## Catch-All Endpoints + +Set the path to `"*"` to match any URL that no other endpoint handles. This is useful for custom "not found" pages or fallback responses. + +**Example** (Defining a Catch-All Endpoint) + +```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, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const IdParam = Schema.Int + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("getUsers", "/users", { + success: Schema.Array(User) + }), + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: IdParam + }, + success: User + }), + HttpApiEndpoint.post("createUser", "/user", { + payload: User, + success: User + }), + HttpApiEndpoint.delete("deleteUser", "/user/:id", { + params: { + id: IdParam + } + }), + HttpApiEndpoint.patch("updateUser", "/user/:id", { + params: { + id: IdParam + }, + payload: Schema.Struct({ + name: Schema.String + }), + success: User + }), + // catch-all endpoint + HttpApiEndpoint.get("catchAll", "*", { + success: Schema.String + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handleAll({ + getUsers: () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ), + getUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + }, + createUser: (ctx) => { + const user = ctx.payload + return Effect.succeed(user) + }, + deleteUser: (ctx) => { + const id = ctx.params.id + return Effect.log(`Deleting user ${id}`) + }, + updateUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + }, + catchAll: () => { + return Effect.succeed("Not found") + } + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +> [!IMPORTANT] +> The catch-all endpoint must be the last endpoint in the group. + +> [!IMPORTANT] +> (OpenAPI). A catch-all endpoint is not included in the OpenAPI specification because can't be represented as a path. + +## Prefixing + +Prefixes let you prepend a common path segment to endpoints, groups, or an entire API. This avoids repeating the same base path on every endpoint. + +**Example** (Using Prefixes for Common Path Management) + +```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, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("endpointA", "/a", { + success: Schema.String + }) + // Prefix for this endpoint + .prefix("/endpointPrefix"), + HttpApiEndpoint.get("endpointB", "/b", { + success: Schema.String + }) + ) + // Prefix for all endpoints in the group + .prefix("/groupPrefix") + ) + // Prefix for the entire API + .prefix("/apiPrefix") + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handleAll({ + endpointA: () => Effect.succeed("Endpoint A"), + endpointB: () => Effect.succeed("Endpoint B") + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test this endpoint using a GET request. For example: + +```sh +curl http://localhost:3000/apiPrefix/groupPrefix/endpointPrefix/a # Returns 200 OK +curl http://localhost:3000/apiPrefix/groupPrefix/b # Returns 200 OK +``` + +# Request + +## Query Parameters + +Query parameters are the `?key=value` pairs appended to a URL. Use the `query` option to declare a record of fields or a full schema — the framework will parse, validate, and type them for you. + +**Example** (Defining Query Parameters with Metadata) + +```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, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const Page = Schema.Int.check(Schema.isGreaterThan(0)) + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("getUsers", "/users", { + success: Schema.Array(User), + // Specify a schema for each query parameter + query: { + // Parameter "page" for pagination + page: Schema.optionalKey(Page), + // Parameter "sort" for sorting options + sort: Schema.optionalKey(Schema.Literals(["id", "name"])) + } + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("getUsers", (ctx) => { + const { page, sort } = ctx.query + console.log(`Getting users with page ${page} and sort ${sort}`) + return Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ) + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +#### Defining an Array of Values for a Query Parameter + +A single query parameter can carry multiple values (e.g., `?a=1&a=2`). Wrap the parameter's schema in `Schema.Array` to accept an array of values. + +**Example** (Defining an Array of String Values for a Query Parameter) + +```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, HttpApiScalar } 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", { + success: Schema.Array(User), + query: { + a: Schema.optionalKey(Schema.Array(Schema.String)) + } + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("getUsers", (ctx) => { + console.log(ctx.query) + return Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ) + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test this endpoint by passing an array of values in the query string. For example: + +```sh +curl "http://localhost:3000/users?a=1&a=2" # Two values for the `a` parameter +``` + +The query string sends two values (`1` and `2`) for the `a` parameter. The server will process and validate these values according to the schema. + +Both the following requests will be valid: + +```sh +curl "http://localhost:3000/users" # No values for the `a` parameter +curl "http://localhost:3000/users?a=1" # One value for the `a` parameter +``` + +## Request Headers + +Use the `headers` option to declare a record of fields or a full schema for the request headers the endpoint expects. + +> [!IMPORTANT] +> All headers are normalized to lowercase. Always use lowercase keys for the headers. + +**Example** (Describe and validate custom headers) + +```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, HttpApiScalar } 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", { + // Always use lowercase keys for the headers + headers: { + "x-api-key": Schema.String, + "x-request-id": Schema.String + }, + success: Schema.Array(User) + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handle("getUsers", () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + )) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test the endpoint by sending the headers: + +```sh +curl -H "X-API-Key: 1234567890" -H "X-Request-ID: 1234567890" http://localhost:3000/users +``` + +The server validates these headers against the declared schema before handling the request. + +## Handling Multipart Requests + +To accept file uploads, mark the payload as multipart with `HttpApiSchema.asMultipart`. Use `Multipart.FilesSchema` for the file fields — uploaded files will be persisted to disk automatically. + +**Example** (Defining an Endpoint for File Uploads) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter, Multipart } from "effect/unstable/http" +import { + HttpApi, + HttpApiBuilder, + HttpApiEndpoint, + HttpApiGroup, + HttpApiScalar, + HttpApiSchema +} from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.post("upload", "/users/upload", { + // Specify that the payload is a multipart request + payload: HttpApiSchema.asMultipart( + Schema.Struct({ + // Define a "files" field to handle file uploads + files: Multipart.FilesSchema + }) + ), + success: Schema.String + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("upload", (ctx) => { + // ┌─── readonly Multipart.PersistedFile[] + // ▼ + const { files } = ctx.payload + console.log(files) + return Effect.succeed("Uploaded") + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test this endpoint by sending a multipart request with a file upload. For example: + +```sh +echo "Sample file content" | curl -X POST -F "files=@-" http://localhost:3000/users/upload +``` + +## Changing the Request Encoding + +By default, request bodies are JSON. To accept a different format — like form-urlencoded data — pipe the payload schema through the appropriate `HttpApiSchema.as*` helper. + +**Example** (Customizing Request Encoding) + +```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, + HttpApiScalar, + 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.post("createUser", "/user", { + // Set the request payload as a string encoded with query parameters + payload: Schema.Struct({ + id: Schema.Int, + name: Schema.String + }) + // Specify the encoding as form url encoded + .pipe(HttpApiSchema.asFormUrlEncoded()), + success: User + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("createUser", (ctx) => { + const user = ctx.payload + return Effect.succeed(user) + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test this endpoint using a URL-encoded request body. For example: + +```sh +curl http://localhost:3000/user \ + --request POST \ + --header 'Accept: */*' \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data-urlencode 'id=1' \ + --data-urlencode 'name=John' +``` + +## Accessing the HttpServerRequest + +Inside a handler, `ctx.request` gives you access to the raw incoming HTTP request. Use this when you need low-level details not covered by the endpoint schema (e.g., the HTTP method or raw URL). + +**Example** (Accessing the Request Object in a GET Endpoint) + +```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 } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi").add( + HttpApiGroup.make("Greetings").add( + HttpApiEndpoint.get("hello", "/", { + success: Schema.String + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Greetings", + (handlers) => + handlers.handle( + "hello", + (ctx) => { + // ┌─── HttpServerRequest + // ▼ + const req = ctx.request + // Access the request method + console.log(req.method) + return Effect.succeed("Hello, World!") + } + ) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) +``` + +## Validating Request Cookies + +There is no `cookies` option on endpoints. Instead, validated cookie access goes through the security middleware system: define an `HttpApiSecurity.apiKey` with `in: "cookie"` and attach it to a middleware. The cookie value is decoded and handed to your security handler as a `Redacted` credential. + +**Example** (Validating a Session Cookie) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Context, Effect, Layer, Redacted, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { + HttpApi, + HttpApiBuilder, + HttpApiEndpoint, + HttpApiGroup, + HttpApiMiddleware, + HttpApiScalar, + HttpApiSecurity +} from "effect/unstable/httpapi" +import { createServer } from "node:http" + +// Define the service providing the current user +class CurrentUser + extends Context.Service()("CurrentUser") +{} + +// Define the security scheme: read the "session" cookie +const sessionCookie = HttpApiSecurity.apiKey({ in: "cookie", key: "session" }) + +class Auth extends HttpApiMiddleware.Service()("Auth", { + error: Schema.String.annotate({ + httpApiStatus: 401, + description: "Auth error" + }), + security: { session: sessionCookie } +}) {} + +const Api = HttpApi.make("api").add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("me", "/me", { + success: Schema.Struct({ id: Schema.Finite }) + }) + ) + .middleware(Auth) +) + +const AuthLayer = Layer.succeed( + Auth, + { + session: (effect, opts) => + Effect.provideServiceEffect( + effect, + CurrentUser, + Effect.gen(function*() { + const value = Redacted.value(opts.credential) + if (value !== "valid-session") { + return yield* Effect.fail("Invalid session") + } + return { id: 1, name: "John Doe" } + }) + ) + } +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handle("me", () => + Effect.gen(function*() { + const user = yield* CurrentUser + return { id: user.id } + })) +).pipe(Layer.provide(AuthLayer)) + +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(ApiLayer).pipe(NodeRuntime.runMain) + +// Valid session: +// curl "http://localhost:3000/me" --cookie "session=valid-session" +// {"id":1} +// +// Invalid session: +// curl "http://localhost:3000/me" --cookie "session=wrong" +// "Invalid session" +``` + +For quick, unvalidated access you can read cookies directly from `ctx.request.cookies` inside any handler. These cookies won't appear in the OpenAPI spec. + +**Example** (Reading Cookies Directly in a Handler) + +```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 } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("api").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("me", "/me", { + success: Schema.String + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handle("me", (ctx) => { + const lang = ctx.request.cookies.lang ?? "en" + return Effect.succeed(`Language: ${lang}`) + }) +) + +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/me" --cookie "lang=it" +// "Language: it" +``` + +## Streaming Requests + +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, 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.post("acceptStream", "/stream", { + // Define the payload as a Uint8Array with a specific encoding + payload: Schema.Uint8Array.pipe( + HttpApiSchema.asUint8Array() // default content type: application/octet-stream + ), + success: Schema.String + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + // 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 ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +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 +printf 'abc' | curl -X POST 'http://localhost:3000/stream' --data-binary @- -H "Content-Type: application/octet-stream" +# Output: "received 3 bytes" +``` + +# Response + +## Status Codes + +Success responses default to `200 OK`. To use a different status code, annotate the success schema with `HttpApiSchema.status(code)` or set the `httpApiStatus` annotation. + +**Example** (Defining a GET Endpoint with a custom status code) + +```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, + HttpApiScalar, + 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", { + success: Schema.Array(User) + .pipe(HttpApiSchema.status(206)) + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("getUsers", () => { + return Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ) + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +## Changing the Response Encoding + +Responses default to JSON. To return a different format — like CSV or plain text — pipe the success schema through the matching `HttpApiSchema.as*` helper and, optionally, set a custom `contentType`. + +**Example** (Returning Data as `text/csv`) + +```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, + HttpApiScalar, + HttpApiSchema +} from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("csv", "/users/csv", { + success: Schema.String.pipe( + // Set the success response as a string with CSV encoding + HttpApiSchema.asText({ + // Define the content type as text/csv + contentType: "text/csv" + }) + ) + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("csv", (ctx) => { + return Effect.succeed("id,name\n1,John\n2,Jane") + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test this endpoint using a GET request. For example: + +```sh +curl http://localhost:3000/users/csv +``` + +The following encodings are supported: + +- `Json` the default encoding (default content type: `application/json`) +- `Uint8Array` the encoding for binary data (default content type: `application/octet-stream`) +- `Text` the encoding for text data (default content type: `text/plain`) + +## Setting Response Headers + +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) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpEffect, HttpRouter, HttpServerResponse } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("api").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("hello", "/hello", { + success: Schema.String + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handle("hello", () => + Effect.gen(function*() { + yield* HttpEffect.appendPreResponseHandler((_req, response) => + Effect.succeed(HttpServerResponse.setHeader(response, "x-custom", "hello")) + ) + return "Hello, World!" + })) +) + +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/hello" 2>&1 | grep -i "x-custom" +// < x-custom: hello +``` + +## Setting Response Cookies + +Set cookies on the response using `HttpEffect.appendPreResponseHandler` together with `HttpServerResponse.setCookie`. For cookies tied to an `HttpApiSecurity.apiKey`, use the shortcut `HttpApiBuilder.securitySetCookie` instead. + +**Example** (Setting a Response Cookie) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpEffect, HttpRouter, HttpServerResponse } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("api").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("hello", "/hello", { + success: Schema.String + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handle("hello", () => + Effect.gen(function*() { + yield* HttpEffect.appendPreResponseHandler((_req, response) => + Effect.succeed(HttpServerResponse.setCookieUnsafe(response, "my-cookie", "my-value", { + httpOnly: true, + secure: true, + path: "/" + })) + ) + return "Hello, World!" + })) +) + +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/hello" 2>&1 | grep -i "set-cookie" +// < set-cookie: my-cookie=my-value; Path=/; HttpOnly; Secure +``` + +## Redirects + +To redirect the client to a different URL, return an `HttpServerResponse.redirect` from the handler. The redirect is not modeled in the schema — the endpoint definition stays as "no content". + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { HttpServerResponse } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("newPage", "/new", { + success: Schema.String + }), + // Schema-wise this is just "no content" (redirect headers aren't modeled here) + HttpApiEndpoint.get("oldPage", "/old") + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handleAll({ + newPage: () => Effect.succeed("You are on /new"), + oldPage: () => + Effect.succeed( + HttpServerResponse.redirect("/new", { status: 302 }) + ) + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) + +// curl "http://localhost:3000/old" -L +``` + +## Streaming Responses + +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`) + +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, 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", { + // Declare a streaming success response + // (default content type: application/octet-stream) + success: HttpApiSchema.StreamUint8Array() + }) + ) +) + +// Simulate a stream of data +const stream = Stream.make("a", "b", "c").pipe( + Stream.schedule(Schedule.spaced("500 millis")), + Stream.map((s) => new TextEncoder().encode(s)) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handle( + "getStream", + // Return the stream directly from the handler + () => Effect.succeed(stream) + ) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test the streaming response using `curl` or any similar HTTP client that supports streaming: + +```sh +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 + +Endpoints can declare the errors they may return. Each error is a schema annotated with an HTTP status code via `HttpApiSchema.status(code)`. The status is set once on the schema and reused wherever that schema appears. When your handler fails with a matching error, the framework serializes it and responds with the declared status. + +**Example** (Defining Error Responses for an Endpoint) + +```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, + HttpApiScalar, + HttpApiSchema +} from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const UserNotFound = Schema.Struct({ + _tag: Schema.tag("UserNotFound"), + message: Schema.String +}).pipe(HttpApiSchema.status(404)) + +const Unauthorized = Schema.Struct({ + _tag: Schema.tag("Unauthorized") +}).pipe(HttpApiSchema.status(401)) + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: Schema.Int + }, + success: User, + error: [UserNotFound, Unauthorized /** etc. */] + }) + ) + .add( + HttpApiEndpoint.delete("deleteUser", "/user/:id", { + params: { + id: Schema.Int + }, + error: [UserNotFound, Unauthorized /** etc. */] + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + 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}` }) + }, + 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 ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + Layer.provide(HttpApiScalar.layer(Api)), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) +``` + +You can test these endpoints. For example: + +```sh +curl http://localhost:3000/user/1 # Returns 404 Not Found +curl http://localhost:3000/user/2 # Returns 200 OK +curl -X DELETE http://localhost:3000/user/1 # Returns 404 Not Found +curl -X DELETE http://localhost:3000/user/2 # Returns 200 OK +``` + +## Predefined Error Types + +The `HttpApiError` module provides ready-made error schemas for common HTTP status codes (404, 401, etc.). Using these saves you from defining boilerplate error types and keeps error handling consistent across your API. + +**Example** (Adding a Predefined Error to an Endpoint) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { + HttpApi, + HttpApiBuilder, + HttpApiEndpoint, + HttpApiError, + HttpApiGroup, + HttpApiScalar +} 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("getUser", "/user/:id", { + params: { + id: Schema.Int + }, + success: User, + error: [ + // Add a 404 error JSON response for this endpoint + HttpApiError.NotFound, + // Add a 401 error JSON response for unauthorized access + HttpApiError.Unauthorized + ] + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("getUser", (ctx) => { + const id = ctx.params.id + if (id === 1) { + return Effect.fail(new HttpApiError.NotFound({})) + } + return Effect.succeed({ id, name: `User ${id}` }) + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +| Name | Status | Description | +| --------------------- | ------ | -------------------------------------------------------------------------------------------------- | +| `HttpApiDecodeError` | 400 | Represents an error where the request did not match the expected schema. Includes detailed issues. | +| `BadRequest` | 400 | Indicates that the request was malformed or invalid. | +| `Unauthorized` | 401 | Indicates that authentication is required but missing or invalid. | +| `Forbidden` | 403 | Indicates that the client does not have permission to access the requested resource. | +| `NotFound` | 404 | Indicates that the requested resource could not be found. | +| `MethodNotAllowed` | 405 | Indicates that the HTTP method used is not allowed for the requested resource. | +| `NotAcceptable` | 406 | Indicates that the requested resource cannot be delivered in a format acceptable to the client. | +| `RequestTimeout` | 408 | Indicates that the server timed out waiting for the client request. | +| `Conflict` | 409 | Indicates a conflict in the request, such as conflicting data. | +| `Gone` | 410 | Indicates that the requested resource is no longer available and will not return. | +| `InternalServerError` | 500 | Indicates an unexpected server error occurred. | +| `NotImplemented` | 501 | Indicates that the requested functionality is not implemented on the server. | +| `ServiceUnavailable` | 503 | Indicates that the server is temporarily unavailable, often due to maintenance or overload. | + +#### Predefined NoContent Error Types + +Each predefined error also has a `NoContent` variant that responds with the status code but no body. + +**Example** (Using a Predefined NoContent Error Type) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { + HttpApi, + HttpApiBuilder, + HttpApiEndpoint, + HttpApiError, + HttpApiGroup, + HttpApiScalar +} 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("getUser", "/user/:id", { + params: { + id: Schema.Int + }, + success: User, + error: [ + // Add a 404 error no-content response for this endpoint + HttpApiError.NotFoundNoContent, + // Add a 401 error no-content response for unauthorized access + HttpApiError.UnauthorizedNoContent + ] + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("getUser", (ctx) => { + const id = ctx.params.id + if (id === 1) { + return Effect.fail(new HttpApiError.NotFound({})) + } + return Effect.succeed({ id, name: `User ${id}` }) + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +## Customizing Schema Error Responses + +By default, when a request fails schema validation (e.g., an invalid query parameter or a malformed path parameter), the framework responds with an empty `400 Bad Request`. If you want to replace that response with a custom error, use `HttpApiMiddleware.layerSchemaErrorTransform`. + +This function creates a [middleware](#middlewares) layer that intercepts any `SchemaError` thrown during request decoding and lets you return your own error instead. + +**Example** (Returning a Custom Error on Validation Failure) + +In this example, if a client sends a non-integer `id` query parameter, the API responds with a `422` status and a JSON body describing the problem, instead of the default empty `400`. + +```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, + HttpApiMiddleware, + HttpApiScalar, + HttpApiSchema +} from "effect/unstable/httpapi" +import { createServer } from "node:http" + +// Define a custom error for validation failures +class ValidationError extends Schema.TaggedError()( + "ValidationError", + { + message: Schema.String + } +) {} + +// Define the middleware service, declaring the error it can produce +class SchemaErrorHandler extends HttpApiMiddleware.Service()( + "api/SchemaErrorHandler", + { + error: ValidationError.pipe(HttpApiSchema.status(422)) + } +) {} + +// Implement the middleware layer +const SchemaErrorHandlerLayer = HttpApiMiddleware.layerSchemaErrorTransform( + SchemaErrorHandler, + (schemaError) => + Effect.fail( + new ValidationError({ + message: `Invalid request: ${schemaError.message}` + }) + ) +) + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const Api = HttpApi.make("MyApi").add( + HttpApiGroup.make("Users").add( + HttpApiEndpoint.get("getUser", "/user", { + query: { + id: Schema.Int + }, + success: User + }) + // Attach the middleware to this endpoint only + .middleware(SchemaErrorHandler) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => handlers.handle("getUser", (ctx) => Effect.succeed({ id: ctx.query.id, name: `User ${ctx.query.id}` })) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) + +// Test: +// curl "http://localhost:3000/user?id=1" # 200 OK +// curl "http://localhost:3000/user?id=abc" # 422 with ValidationError JSON +``` + +The middleware can be attached at different scopes: + +- **Endpoint**: `.middleware(SchemaErrorHandler)` on a single endpoint (as shown above). +- **Group**: `.middleware(SchemaErrorHandler)` on a group to cover all its endpoints. +- **API**: `.middleware(SchemaErrorHandler)` on the API to cover every endpoint. + +# Middlewares + +Middleware lets you run shared logic — like logging or authentication — before (or around) your handlers. Define a middleware as a class extending `HttpApiMiddleware.Service`, implement it as a `Layer`, and attach it to an endpoint, a group, or the entire API. + +**Example** (Defining a Logger Middleware) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter, HttpServerRequest } from "effect/unstable/http" +import { + HttpApi, + HttpApiBuilder, + HttpApiEndpoint, + HttpApiGroup, + HttpApiMiddleware, + HttpApiScalar, + HttpApiSchema +} from "effect/unstable/httpapi" +import { createServer } from "node:http" + +class Logger extends HttpApiMiddleware.Service()("Http/Logger", { + // default is 500 Internal Server Error with JSON encoding + error: Schema.String + .pipe( + HttpApiSchema.status(405), // override default status code + HttpApiSchema.asText() // override default encoding + ) +}) {} + +const User = Schema.Struct({ + id: Schema.Finite, + name: Schema.String +}) + +const Api = HttpApi.make("api").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: Schema.Int + }, + success: User + }) + // Apply the middleware to a single endpoint + .middleware(Logger) + ) + // Or apply the middleware to the entire group + .middleware(Logger) +) +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handle("getUser", (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + }) +) + +const LoggerLayer = Layer.effect( + Logger, + Effect.gen(function*() { + yield* Effect.log("creating Logger middleware") + + return (res) => + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + yield* Effect.log(`Request: ${request.method} ${request.url}`) + return yield* res + }) + }) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + Layer.provide(HttpApiScalar.layer(Api)), + Layer.provide(LoggerLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +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. + +Supported authorization types: + +| Authorization Type | Description | +| ------------------------ | ---------------------------------------------------------------- | +| `HttpApiSecurity.apiKey` | API key authorization via headers, query parameters, or cookies. | +| `HttpApiSecurity.basic` | HTTP Basic authentication. | +| `HttpApiSecurity.bearer` | Bearer token authentication. | + +Attach a security scheme to an endpoint, group, or the entire API via `HttpApiMiddleware`. + +**Example** (Defining Security Middleware) + +```ts +import { Context, Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" + +// Define a schema for the "User" +class User extends Schema.Class("User")({ id: Schema.Finite }) {} + +// Define a schema for the "Unauthorized" error +class Unauthorized extends Schema.TaggedError()( + "Unauthorized", + {}, + // Specify the HTTP status code for unauthorized errors + { httpApiStatus: 401 } +) {} + +// Define a Context.Tag for the authenticated user +class CurrentUser extends Context.Service()("CurrentUser") {} + +// Create the Authorization middleware +class Authorization extends HttpApiMiddleware.Service()( + "Authorization", + { + // Define the error schema for unauthorized access + error: Unauthorized, + // Add security definitions + security: { + // ┌─── Custom name for the security definition + // ▼ + myBearer: HttpApiSecurity.bearer + // Additional security definitions can be added here. + // They will attempt to be resolved in the order they are defined. + } + } +) {} + +const api = HttpApi.make("api") + .add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("get", "/", { + success: Schema.String + }) + // Apply the middleware to a single endpoint + .middleware(Authorization) + ) + // Or apply the middleware to the entire group + .middleware(Authorization) + ) + // Or apply the middleware to the entire API + .middleware(Authorization) +``` + +## Implementing HttpApiSecurity middleware + +To enforce a security scheme, implement its middleware as a `Layer`. The layer returns an object with a handler for each security definition. Each handler receives the credential (e.g., a Bearer token as a `Redacted` value) and must return the resource the middleware provides (e.g., the current user). + +**Example** (Implementing Bearer Token Authentication Middleware) + +```ts +import { Context, Effect, Layer, Redacted, Schema } from "effect" +import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" + +class User extends Schema.Class("User")({ id: Schema.Finite }) {} + +class Unauthorized extends Schema.TaggedError()( + "Unauthorized", + {}, + // Specify the HTTP status code for unauthorized errors + { httpApiStatus: 401 } +) {} + +class CurrentUser extends Context.Service()("CurrentUser") {} + +class Authorization extends HttpApiMiddleware.Service()( + "Authorization", + { + error: Unauthorized, + security: { + myBearer: HttpApiSecurity.bearer + } + } +) {} + +const AuthorizationLayer = Layer.succeed( + Authorization, + // Return the security handlers for the middleware + { + // Define the handler for the Bearer token + // The Bearer token is redacted for security + myBearer: (effect, opts) => + Effect.provideServiceEffect( + effect, + CurrentUser, + Effect.gen(function*() { + yield* Effect.log( + "checking bearer token", + Redacted.value(opts.credential) + ) + // Return a mock User object as the CurrentUser + return new User({ id: 1 }) + }) + ) + } +) +``` + +## Adding Descriptions to Security Definitions + +Use `HttpApiSecurity.annotate` to attach metadata — like a description — to a security definition. This metadata appears in the generated docs. + +**Example** (Adding a Description to a Bearer Token Security Definition) + +```ts +import { Context, Schema } from "effect" +import { HttpApiMiddleware, HttpApiSecurity, OpenApi } from "effect/unstable/httpapi" + +class User extends Schema.Class("User")({ id: Schema.Finite }) {} + +class Unauthorized extends Schema.TaggedError()( + "Unauthorized", + {}, + // Specify the HTTP status code for unauthorized errors + { httpApiStatus: 401 } +) {} + +class CurrentUser extends Context.Service()("CurrentUser") {} + +class Authorization extends HttpApiMiddleware.Service()( + "Authorization", + { + error: Unauthorized, + security: { + myBearer: HttpApiSecurity.bearer.pipe( + // Add a description to the security definition + HttpApiSecurity.annotate(OpenApi.Description, "my description") + ) + } + } +) {} +``` + +## Setting HttpApiSecurity cookies + +Use `HttpApiBuilder.securitySetCookie` to set a security cookie from a handler. The cookie is created with `HttpOnly` and `Secure` flags by default. + +**Example** (Setting a Security Cookie in a Login Handler) + +```ts +import { Redacted, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSecurity } from "effect/unstable/httpapi" + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("login", "/login", { + params: { + success: Schema.String + } + }) + ) + ) + +// Define the security configuration for an API key stored in a cookie +const security = HttpApiSecurity.apiKey({ + // Specify that the API key is stored in a cookie + in: "cookie", + // Define the cookie name, + key: "token" +}) + +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")))) +``` + +# Using Services Inside a HttpApiEndpoint + +Handlers can access any Effect service. Because `HttpApiBuilder.group` returns an `Effect`, you can `yield*` services directly inside your handler logic. + +**Example** (Using Services in a Endpoint Implementation) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Context, Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +// Define the UsersRepository service +class UsersRepository extends Context.Service Effect.Effect +}>()("UsersRepository") {} + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: Schema.Int + }, + success: User + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers + .handle("getUser", (ctx) => { + const id = ctx.params.id + return Effect.gen(function*() { + // Access the UsersRepository service + const repository = yield* UsersRepository + return yield* repository.findById(id) + }) + }) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + Layer.provide(HttpApiScalar.layer(Api)), + Layer.provide( + Layer.succeed(UsersRepository, { + findById: (id) => Effect.succeed({ id, name: `User ${id}` }) + }) + ), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) +``` + +# OpenAPI Documentation + +Add interactive API documentation with `HttpApiScalar` (Scalar UI) or `HttpApiSwagger` (Swagger UI). Both read your API definition and generate a browsable docs page at `/docs`. + +**Example** (Adding Scalar Documentation to an API) + +```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, HttpApiScalar } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const IdParam = Schema.Int + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Users") + .add( + HttpApiEndpoint.get("getUsers", "/users", { + success: Schema.Array(User) + }), + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: IdParam + }, + success: User + }), + HttpApiEndpoint.post("createUser", "/user", { + payload: User, + success: User + }), + HttpApiEndpoint.delete("deleteUser", "/user/:id", { + params: { + id: IdParam + } + }), + HttpApiEndpoint.patch("updateUser", "/user/:id", { + params: { + id: IdParam + }, + // Specify the schema for the request payload + payload: Schema.Struct({ + name: Schema.String // Only the name can be updated + }), + // Specify the schema for a successful response + success: User + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handleAll({ + getUsers: () => + Effect.succeed( + [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] + ), + getUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + }, + createUser: (ctx) => { + const user = ctx.payload + return Effect.succeed(user) + }, + deleteUser: (ctx) => { + const id = ctx.params.id + return Effect.log(`Deleting user ${id}`) + }, + updateUser: (ctx) => { + const id = ctx.params.id + return Effect.succeed({ id, name: `User ${id}` }) + } + }) +) + +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(ApiLayer).pipe(NodeRuntime.runMain) +``` + +After running the server, open your browser and navigate to http://localhost:3000/docs. + +This URL will display the Scalar documentation, allowing you to explore the API's endpoints, request parameters, and response structures interactively. + +## Adding OpenAPI Annotations + +Annotations let you enrich the generated OpenAPI spec with titles, descriptions, server URLs, and more. They are added via the `.annotate` method on `HttpApi`, `HttpApiGroup`, or `HttpApiEndpoint`. + +#### HttpApi + +Below is a list of available annotations for a top-level `HttpApi`. They can be added using the `.annotate` method: + +| Annotation | Description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `HttpApi.AdditionalSchemas` | Adds custom schemas to the final OpenAPI specification. Only schemas with an `identifier` annotation are included. | +| `OpenApi.Description` | Sets a general description for the API. | +| `OpenApi.Title` | Sets the title of the API. | +| `OpenApi.Version` | Sets the version of the API. | +| `OpenApi.License` | Defines the license used by the API. | +| `OpenApi.Summary` | Provides a brief summary of the API. | +| `OpenApi.Servers` | Lists server URLs and optional metadata such as variables. | +| `OpenApi.Override` | Merges the supplied fields into the resulting specification. | +| `OpenApi.Transform` | Allows you to modify the final specification with a custom function. | + +**Example** (Annotating the Top-Level API) + +```ts +import { Schema } from "effect" +import { HttpApi, OpenApi } from "effect/unstable/httpapi" + +const api = HttpApi.make("api") + // Provide additional schemas + .annotate(HttpApi.AdditionalSchemas, [ + Schema.String.annotate({ identifier: "MyString" }) + ]) + // Add a description + .annotate(OpenApi.Description, "my description") + // Set license information + .annotate(OpenApi.License, { name: "MIT", url: "http://example.com" }) + // Provide a summary + .annotate(OpenApi.Summary, "my summary") + // Define servers + .annotate(OpenApi.Servers, [ + { + url: "http://example.com", + description: "example", + variables: { a: { default: "b", enum: ["c"], description: "d" } } + } + ]) + // Override parts of the generated specification + .annotate(OpenApi.Override, { + tags: [{ name: "a", description: "a-description" }] + }) + // Apply a transform function to the final specification + .annotate(OpenApi.Transform, (spec) => ({ + ...spec, + tags: [...spec.tags, { name: "b", description: "b-description" }] + })) + +// Generate the OpenAPI specification from the annotated API +const spec = OpenApi.fromApi(api) + +console.log(JSON.stringify(spec, null, 2)) +/* +Output: +{ + "openapi": "3.1.0", + "info": { + "title": "Api", + "version": "0.0.1", + "description": "my description", + "license": { + "name": "MIT", + "url": "http://example.com" + }, + "summary": "my summary" + }, + "paths": {}, + "components": { + "schemas": { + "MyString": { + "type": "string" + } + }, + "securitySchemes": {} + }, + "security": [], + "tags": [ + { + "name": "a", + "description": "a-description" + }, + { + "name": "b", + "description": "b-description" + } + ], + "servers": [ + { + "url": "http://example.com", + "description": "example", + "variables": { + "a": { + "default": "b", + "enum": [ + "c" + ], + "description": "d" + } + } + } + ] +} +*/ +``` + +#### HttpApiGroup + +The following annotations can be added to an `HttpApiGroup`: + +| Annotation | Description | +| ---------------------- | --------------------------------------------------------------------- | +| `OpenApi.Description` | Sets a description for this group. | +| `OpenApi.ExternalDocs` | Provides external documentation links for the group. | +| `OpenApi.Override` | Merges specified fields into the resulting specification. | +| `OpenApi.Transform` | Lets you modify the final group specification with a custom function. | +| `OpenApi.Exclude` | Excludes the group from the final OpenAPI specification. | + +**Example** (Annotating a Group) + +```ts +import { HttpApi, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +const api = HttpApi.make("api") + .add( + HttpApiGroup.make("group") + // Add a description for the group + .annotate(OpenApi.Description, "my description") + // Provide external documentation links + .annotate(OpenApi.ExternalDocs, { + url: "http://example.com", + description: "example" + }) + // Override parts of the final output + .annotate(OpenApi.Override, { name: "my name" }) + // Transform the final specification for this group + .annotate(OpenApi.Transform, (spec) => ({ + ...spec, + name: spec.name + "-transformed" + })) + ) + .add( + HttpApiGroup.make("excluded") + // Exclude the group from the final specification + .annotate(OpenApi.Exclude, true) + ) + +// Generate the OpenAPI spec +const spec = OpenApi.fromApi(api) + +console.log(JSON.stringify(spec, null, 2)) +/* +Output: +{ + "openapi": "3.1.0", + "info": { + "title": "Api", + "version": "0.0.1" + }, + "paths": {}, + "components": { + "schemas": {}, + "securitySchemes": {} + }, + "security": [], + "tags": [ + { + "name": "my name-transformed", + "description": "my description", + "externalDocs": { + "url": "http://example.com", + "description": "example" + } + } + ] +} +*/ +``` + +#### HttpApiEndpoint + +For an `HttpApiEndpoint`, you can use the following annotations: + +| Annotation | Description | +| ---------------------- | --------------------------------------------------------------------------- | +| `OpenApi.Description` | Adds a description for this endpoint. | +| `OpenApi.Summary` | Provides a short summary of the endpoint's purpose. | +| `OpenApi.Deprecated` | Marks the endpoint as deprecated. | +| `OpenApi.ExternalDocs` | Supplies external documentation links for the endpoint. | +| `OpenApi.Override` | Merges specified fields into the resulting specification for this endpoint. | +| `OpenApi.Transform` | Lets you modify the final endpoint specification with a custom function. | +| `OpenApi.Exclude` | Excludes the endpoint from the final OpenAPI specification. | + +**Example** (Annotating an Endpoint) + +```ts +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +const api = HttpApi.make("api").add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("get", "/", { + success: Schema.String + }) + // Add a description + .annotate(OpenApi.Description, "my description") + // Provide a summary + .annotate(OpenApi.Summary, "my summary") + // Mark the endpoint as deprecated + .annotate(OpenApi.Deprecated, true) + // Provide external documentation + .annotate(OpenApi.ExternalDocs, { + url: "http://example.com", + description: "example" + }) + ) + .add( + HttpApiEndpoint.get("excluded", "/excluded", { + success: Schema.String + }) + // Exclude this endpoint from the final specification + .annotate(OpenApi.Exclude, true) + ) +) + +// Generate the OpenAPI spec +const spec = OpenApi.fromApi(api) + +console.log(JSON.stringify(spec, null, 2)) +/* +Output: +{ + "openapi": "3.1.0", + "info": { + "title": "Api", + "version": "0.0.1" + }, + "paths": { + "/": { + "get": { + "tags": [ + "group" + ], + "operationId": "group.get", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/String_" + } + } + } + }, + "400": { + "description": "The request or response did not match the expected schema", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": [ + "HttpApiSchemaError" + ] + }, + "message": { + "$ref": "#/components/schemas/String_" + } + }, + "required": [ + "_tag", + "message" + ], + "additionalProperties": false + } + } + } + } + }, + "description": "my description", + "summary": "my summary", + "deprecated": true, + "externalDocs": { + "url": "http://example.com", + "description": "example" + } + } + } + }, + "components": { + "schemas": { + "String_": { + "type": "string" + } + }, + "securitySchemes": {} + }, + "security": [], + "tags": [ + { + "name": "group" + } + ] +} +*/ +``` + +The default response description is "Success". You can override this by annotating the schema. + +**Example** (Defining a custom response description) + +```ts +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +const User = Schema.Struct({ + id: Schema.Finite, + name: Schema.String +}).annotate({ identifier: "User" }) + +const api = HttpApi.make("api").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("getUsers", "/users", { + success: Schema.Array(User).annotate({ + description: "Returns an array of users" + }) + }) + ) +) + +const spec = OpenApi.fromApi(api) + +console.log(JSON.stringify(spec.paths, null, 2)) +/* +Output: +{ + "/users": { + "get": { + "tags": [ + "group" + ], + "operationId": "group.getUsers", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Returns an array of users", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "$ref": "#/components/schemas/String_" + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false + }, + "description": "Returns an array of users" + } + } + } + }, + "400": { + "description": "The request or response did not match the expected schema", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": [ + "HttpApiSchemaError" + ] + }, + "message": { + "$ref": "#/components/schemas/String_" + } + }, + "required": [ + "_tag", + "message" + ], + "additionalProperties": false + } + } + } + } + } + } + } +} +*/ +``` + +## Top Level Groups + +When a group is `topLevel`, its name is not prepended to operation IDs in the OpenAPI spec. Use this when the group is just for tagging and you want shorter, cleaner operation IDs. + +**Example** (Using a Top-Level Group) + +```ts +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +const api = HttpApi.make("api").add( + // Mark the group as top-level + HttpApiGroup.make("group", { topLevel: true }).add( + HttpApiEndpoint.get("get", "/", { + success: Schema.String + }) + ) +) + +// Generate the OpenAPI spec +const spec = OpenApi.fromApi(api) + +console.log(JSON.stringify(spec.paths, null, 2)) +/* +Output: +{ + "/": { + "get": { // The operation ID is not prefixed with "group" + "tags": [ + "group" + ], + "operationId": "get", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/String_" + } + } + } + }, + "400": { + "description": "The request or response did not match the expected schema", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": [ + "HttpApiSchemaError" + ] + }, + "message": { + "$ref": "#/components/schemas/String_" + } + }, + "required": [ + "_tag", + "message" + ], + "additionalProperties": false + } + } + } + } + } + } + } +} +*/ +``` + +# Deriving a Client + +The `HttpApiClient` module generates a fully typed client from your API definition. Each endpoint becomes a method — grouped by `HttpApiGroup` name — so calling your API is as simple as calling a function. + +**Example** (Deriving and Using a Client) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { FetchHttpClient } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Greetings") + .add( + HttpApiEndpoint.get("hello", "/", { + success: Schema.String + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Greetings", + (handlers) => handlers.handle("hello", () => Effect.succeed("Hello, World!")) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) + +// Create a program that derives and uses the client +const program = Effect.gen(function*() { + // Derive the client + const client = yield* HttpApiClient.make(Api, { + baseUrl: "http://localhost:3000" + }) + // Call the "hello-world" endpoint + const hello = yield* client.Greetings.hello() + console.log(hello) +}) + +// Provide a Fetch-based HTTP client and run the program +Effect.runFork(program.pipe(Effect.provide(FetchHttpClient.layer))) +/* +Output: +[18:55:26.051] INFO (#2): Listening on http://0.0.0.0:3000 +[18:55:26.057] INFO (#12) http.span=2ms: Sent HTTP response { 'http.method': 'GET', 'http.url': '/', 'http.status': 200 } +Hello, World! +*/ +``` + +## Top Level Groups + +When a group is `topLevel`, its endpoints are exposed as top-level methods on the client instead of being nested under the group name. + +**Example** (Using a Top-Level Group in the Client) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { FetchHttpClient } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Api = HttpApi.make("MyApi") + .add( + HttpApiGroup.make("Greetings", { topLevel: true }) + .add( + HttpApiEndpoint.get("hello", "/", { + success: Schema.String + }) + ) + ) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Greetings", + (handlers) => handlers.handle("hello", () => Effect.succeed("Hello, World!")) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) + +const program = Effect.gen(function*() { + const client = yield* HttpApiClient.make(Api, { + baseUrl: "http://localhost:3000" + }) + // The `hello` method is not nested under the "group" name + const hello = yield* client.hello() + console.log(hello) +}) + +Effect.runFork(program.pipe(Effect.provide(FetchHttpClient.layer))) +``` + +# Converting to a Web Handler + +If you need to plug your API into an existing HTTP server (instead of using `NodeHttpServer`), convert it to a standard web handler with `HttpApiBuilder.toWebHandler`. The returned `handler` function takes a `Request` and returns a `Response`. + +**Example** (Creating and Serving a Web Handler) + +```ts +import { Effect, Layer, Schema } from "effect" +import { HttpRouter, HttpServer } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiScalar } from "effect/unstable/httpapi" +import * as http from "node:http" + +const Api = HttpApi.make("myApi").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("get", "/", { + success: Schema.String + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => handlers.handle("get", () => Effect.succeed("Hello, world!")) +) + +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(ApiLayer) +) + +// Serving the handler using a custom HTTP server +http + .createServer(async (req, res) => { + const url = `http://${req.headers.host}${req.url}` + const init: RequestInit = { + method: req.method! + } + + const response = await handler(new Request(url, init)) + + res.writeHead( + response.status, + response.statusText, + Object.fromEntries(response.headers.entries()) + ) + const responseBody = await response.arrayBuffer() + res.end(Buffer.from(responseBody)) + }) + .listen(3000, () => { + console.log("Server running at http://localhost:3000/") + }) + .on("close", () => { + dispose() + }) +``` diff --git a/.repos/effect/packages/effect/LICENSE b/.repos/effect/packages/effect/LICENSE new file mode 100644 index 000000000..be1f5c14c --- /dev/null +++ b/.repos/effect/packages/effect/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Effectful Technologies Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.repos/effect/packages/effect/MCP.md b/.repos/effect/packages/effect/MCP.md new file mode 100644 index 000000000..66258490c --- /dev/null +++ b/.repos/effect/packages/effect/MCP.md @@ -0,0 +1,377 @@ +## Introduction + +The `McpServer.ts` module provides an implementation of an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/docs/getting-started/intro) server using the +[Effect](https://effect.website) eco system. + +## Getting Started + +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 { 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: { + message: Schema.String + }, + success: Schema.String +}) + +const MyToolkit = Toolkit.make(DemoTool) + +const DemoResource = McpServer.resource({ + uri: "file:///demo.txt", + name: "Demo Resource", + content: Effect.succeed("# Demo Content\nThis is a demo resource.") +}) + +const DemoPrompt = McpServer.prompt({ + name: "Demo Prompt", + description: "A demo prompt", + parameters: { + topic: Schema.String + }, + completion: { + topic: () => Effect.succeed(["AI", "programming", "Effect"]) + }, + content: ({ topic }) => Effect.succeed(`Tell me about ${topic}`) +}) + +const ServerLayer = Layer.mergeAll( + DemoResource, + DemoPrompt, + McpServer.toolkit(MyToolkit).pipe( + Layer.provideMerge( + MyToolkit.toLayer({ + DemoTool: ({ message }) => Effect.succeed(`Echo: ${message}`) + }) + ) + ) +).pipe( + Layer.provide( + McpServer.layerStdio({ + name: "Demo MCP Server", + version: "1.0.0", + protocols: [McpProtocol.v2025_06_18], + stdin: NodeStream.stdin, + stdout: NodeSink.stdout + }) + ), + Layer.provide(Logger.layer([Logger.consolePretty({ stderr: true })])) +) + +Layer.launch(ServerLayer).pipe(NodeRuntime.runMain) +``` + +The server exposes three main parts: + +- **`Resource`**, which represents a readable MCP resource such as a file accessible to the client +- **`Prompt`**, which defines a prompt template that can be used by the client and should not be + confused with `Prompt.ts` +- **`ToolkitLayer`**, which contains the definitions of all tools the server exposes, provided with + their implementations through `ToolImplLayer`. + +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. 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. + +## Resources + +Resources in the MCP server represent files or data that can be accessed by an MCP client. Each +resource is defined as a template that specifies its location, behavior, and metadata. The +`McpServer.resource` helper allows you to declaratively define such resources with dynamic +parameters, completions, and content generation. + +```typescript +import { Effect } from "effect" +import { Schema } from "effect/schema" +import { McpSchema, McpServer } from "effect/unstable/ai" + +const SimpleResource = McpServer.resource({ + uri: "file:///demo.txt", + name: "Demo Resource", + description: "A simple demo resource", + mimeType: "text/plain", + content: Effect.succeed("This is demo content") +}) + +const idParam = McpSchema.param("id", Schema.NumberFromString) + +const TemplateResource = McpServer.resource`file://path/to/file/${idParam}`({ + name: "Demo Resource Template", + description: "A parameterized resource template", + completion: { + id: (_: string) => Effect.succeed([1, 2, 3, 4, 5]) + }, + content: Effect.fn(function*(_uri, id) { + return `# MCP Server Demo - ID: ${id}` + }), + mimeType: "text/x-markdown", + audience: ["assistant", "user"] +}) +``` + +In this example, the resource is parameterized by an `id` that forms part of the URI. The +`completion` function enables clients to request valid parameter values dynamically. The `content` +function defines how the resource's data is generated at runtime—in this case, returning a Markdown +string containing the provided `id`. The `mimeType` specifies the format of the resource, while the +`audience` property determines who can access it (either `"assistant"` and/or `"user"`). + +## Prompts + +Prompts define reusable templates that an MCP client can invoke with parameters. They serve as +structured, parameterized instructions or messages that the client can send to the server. Using +`McpServer.prompt`, you can describe the prompt's schema, auto-completion behavior, and content +generation logic in a declarative way. + +```typescript +import { Effect } from "effect" +import { Schema } from "effect/schema" +import { McpServer } from "effect/unstable/ai" + +const DemoPrompt = McpServer.prompt({ + name: "Demo Prompt", + description: "A demo prompt to demonstrate MCP server capabilities", + parameters: { + name: Schema.String + }, + completion: { + name: () => Effect.succeed(["Tom", "Tim", "Jerry"]) + }, + content: ({ name }) => Effect.succeed(`Use the greetings tool to write a greeting for ${name}.`) +}) +``` + +In this example, the prompt defines a single parameter, `name`. The `completion` property provides +an auto-completion mechanism, allowing the client to suggest or autofill common names. The `content` +function then generates the actual prompt text dynamically based on the provided parameter. + +## Tools and Toolkit + +Tools define executable capabilities that the MCP server exposes to clients. Each tool describes a +contract while the actual logic is provided separately through an implementation layer. Tools are +grouped into toolkits, which can be combined and converted into layers. + +```typescript +import { Effect, Layer } from "effect" +import { Schema } from "effect/schema" +import { McpServer, Tool, Toolkit } from "effect/unstable/ai" + +const DemoTool = Tool.make("DemoTool", { + description: "This is a demo tool for the documentation", + parameters: { + demoId: Schema.Number, + demoName: Schema.String + }, + success: Schema.String +}) + +const OtherDemoTool = Tool.make("OtherDemoTool", { + description: "Another demo tool", + parameters: { + value: Schema.Number + }, + success: Schema.String +}) + +const MyToolkit = Toolkit.make(DemoTool, OtherDemoTool) + +const ToolkitLayer = McpServer.toolkit(MyToolkit).pipe( + Layer.provideMerge( + MyToolkit.toLayer({ + DemoTool: ({ demoId, demoName }) => Effect.succeed(`Processed ${demoName} with ID ${demoId}`), + OtherDemoTool: ({ value }) => Effect.succeed(`Other tool result: ${value * 2}`) + }) + ) +) +``` + +In this example, `Tool.make` defines new tools with typed parameters and result schemas for success +outcomes. Multiple tools can be grouped into a single `Toolkit` using `Toolkit.make`. + +The toolkit is then transformed into a layer defining the interface of the tools using +`McpServer.toolkit()`. The corresponding implementations are attached using `.toLayer`, which binds +each tool definition to its concrete logic. Finally, the completed toolkit layer can be merged with +other layers to create the MCP server. + +## Elicitation requests + +Elicitation requests are used to request additional input directly from the user. An elicitation +defines both the message shown to the user and the expected response schema, ensuring structured and +validated user input. + +```typescript +import { Effect } from "effect" +import { Schema } from "effect/schema" +import { McpServer } from "effect/unstable/ai" + +const DemoElicitation = McpServer.elicit({ + message: `Please answer the question ("yes" | "no") (default "no"):`, + schema: Schema.Struct({ + answer: Schema.Union([Schema.Literal("yes"), Schema.Literal("no")]) + }) +}).pipe( + Effect.catchTag("ElicitationDeclined", (_error) => { + return Effect.succeed({ answer: "no" }) + }) +) +``` + +In this example, the server poses a simple yes/no question to the user. The input is validated +against the defined schema, ensuring that only `"yes"` or `"no"` responses are accepted. If the user +declines to answer or the elicitation fails, a fallback value is provided—here, the default answer +is `"no"`. + +## Complete Working Example + +Here's a complete, copy/pastable MCP server example that combines all the concepts: + +```typescript +import { NodeRuntime, NodeStdio } from "@effect/platform-node" +import { Effect, Layer, Logger, Schema } from "effect" +import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from "effect/unstable/ai" + +// Define tools +const GreetTool = Tool.make("GreetTool", { + description: "Generate a greeting message", + parameters: Schema.Struct({ + name: Schema.String, + style: Schema.Union([Schema.Literal("formal"), Schema.Literal("casual")]) + }), + success: Schema.String +}) + +const CalculatorTool = Tool.make("CalculatorTool", { + description: "Perform basic arithmetic operations", + parameters: Schema.Struct({ + operation: Schema.Union([ + Schema.Literal("add"), + Schema.Literal("subtract"), + Schema.Literal("multiply"), + Schema.Literal("divide") + ]), + a: Schema.Number, + b: Schema.Number + }), + success: Schema.Number +}) + +// Create toolkit +const MyToolkit = Toolkit.make(GreetTool, CalculatorTool) + +// Define a resource +const ReadmeResource = McpServer.resource({ + uri: "file:///README.md", + name: "README", + description: "Project README file", + mimeType: "text/markdown", + content: Effect.succeed("# MCP Server Demo\n\nThis is a demo MCP server built with Effect.") +}) + +// Define a parameterized resource +const idParam = McpSchema.param("id", Schema.NumberFromString) + +const UserResource = McpServer.resource`file://users/${idParam}.json`({ + name: "User Data", + description: "User information by ID", + completion: { + id: (_: string) => Effect.succeed([1, 2, 3, 4, 5]) + }, + content: Effect.fn(function*(_uri, id) { + return JSON.stringify( + { + id, + name: `User ${id}`, + email: `user${id}@example.com` + }, + null, + 2 + ) + }), + mimeType: "application/json" +}) + +// Define a prompt +const AnalysisPrompt = McpServer.prompt({ + name: "Analyze Data", + description: "Analyze data and provide insights", + parameters: { + dataType: Schema.String, + focus: Schema.Union([Schema.Literal("summary"), Schema.Literal("details")]) + }, + completion: { + dataType: () => Effect.succeed(["sales", "users", "metrics"]), + focus: () => Effect.succeed(["summary" as const, "details" as const]) + }, + content: ({ dataType, focus }) => + Effect.succeed( + `Please analyze the ${dataType} data and provide a ${focus} analysis. Use available tools to gather information.` + ) +}) + +// Create the server layer +const ServerLayer = Layer.mergeAll( + ReadmeResource, + UserResource, + AnalysisPrompt, + McpServer.toolkit(MyToolkit).pipe( + Layer.provideMerge( + MyToolkit.toLayer({ + GreetTool: ({ name, style }) => { + const greeting = style === "formal" + ? `Good day, ${name}. It is a pleasure to meet you.` + : `Hey ${name}! What's up?` + return Effect.succeed(greeting) + }, + CalculatorTool: ({ operation, a, b }) => { + let result: number + switch (operation) { + case "add": + result = a + b + break + case "subtract": + result = a - b + break + case "multiply": + result = a * b + break + case "divide": + result = a / b + break + } + return Effect.succeed(result) + } + }) + ) + ) +).pipe( + Layer.provide( + McpServer.layerStdio({ + name: "Demo MCP Server", + version: "1.0.0", + protocols: [McpProtocol.v2025_06_18] + }) + ), + Layer.provide(NodeStdio.layer), + Layer.provide(Layer.succeed(Logger.LogToStderr)(true)) +) + +// Run the server +Layer.launch(ServerLayer).pipe(NodeRuntime.runMain) +``` diff --git a/.repos/effect/packages/effect/OPTIC.md b/.repos/effect/packages/effect/OPTIC.md new file mode 100644 index 000000000..c424778d0 --- /dev/null +++ b/.repos/effect/packages/effect/OPTIC.md @@ -0,0 +1,620 @@ +## Introduction + +`effect/Optic` provides tools for building and composing functional optics. + +Functional optics let you focus on parts of immutable data structures to read or update them in a safe, composable way. + +Immutability keeps previous references valid after an update. This is useful in many domains, not only in concurrent programs. + +## Mental model + +Think of an optic as a reusable focus into a nested structure. It behaves like a pure, composable "getter + setter": + +- **get** a focused value (or no value if the focus does not exist) +- **replace** the focused value +- **modify** the focused value with a function + +You build small optics and compose them to reach deeper fields, optional data, or union variants. + +## Glossary + +- **Iso**: reversible focus between two types, like a lossless conversion. +- **Lens**: focus on a field that is always present. +- **Prism**: focus on one case of a union. +- **Optional**: focus that might or might not exist. +- **Traversal**: focus on zero or more items inside a collection. + +## Features + +- **Unified representation of optics.** All optics compose the same way because they share a single data type: `Optional`. +- **Integration.** Generate `Iso` values from schemas with `Schema.toIso`. + +## Known Limitations + +The `Optic` module only works with **plain JavaScript objects** and collections (structs, records, tuples, and arrays). + +## Getting started + +These are the three operations you will use most: + +```ts +import { Optic } from "effect" + +type S = { readonly a: number } +const _a = Optic.id().key("a") + +/** + * Get the value of the focused field + */ +const value = _a.get({ a: 1 }) +console.log(value) // 1 + +/** + * Replace the value of the focused field + */ +const replaced = _a.replace(2, { a: 1 }) +console.log(replaced) // { a: 2 } + +/** + * Modify the value of the focused field + */ +const modified = _a.modify((n) => n + 1)({ a: 1 }) +console.log(modified) // { a: 2 } +``` + +### Nested data structures + +Suppose we have an employee object, and we want to capitalize the first character of the street name of the company address. + +**Example** (Uppercasing the first character of a street name) + +```ts +import { Optic, String } from "effect" + +// Define some nested data structures +interface Street { + readonly num: number + readonly name: string +} +interface Address { + readonly city: string + readonly street: Street +} +interface Company { + readonly name: string + readonly address: Address +} +interface Employee { + readonly name: string + readonly company: Company +} + +// A sample employee object +const from: Employee = { + name: "john", + company: { + name: "awesome inc", + address: { + city: "london", + street: { + num: 23, + name: "high street" + } + } + } +} + +// Build an optic that drills down to the street name +const _streetName = Optic.id() + .key("company") // access "company" + .key("address") // access "address" + .key("street") // access "street" + .key("name") // access "name" + +// Modify the targeted value +const capitalizeStreetName = _streetName.modify(String.capitalize) + +console.dir(capitalizeStreetName(from), { depth: null }) +/* +{ + name: 'john', + company: { + name: 'awesome inc', + address: { + city: 'london', + street: { num: 23, name: 'High street' } + } + } +} +*/ +``` + +## Basic Usage + +### Accessing a key in a struct or a tuple + +**Example** (Reading and updating a single struct field) + +```ts +import { Optic } from "effect" + +type S = { + readonly a: string +} + +// Build an optic to access the "a" field +const _a = Optic.id().key("a") + +console.log(_a.replace("b", { a: "a" })) +// { a: 'b' } +``` + +**Example** (Reading and updating the first element of a tuple) + +```ts +import { Optic } from "effect" + +type S = readonly [string] + +// Build an optic to access the first element +const _0 = Optic.id().key(0) + +console.log(_0.replace("b", ["a"])) +// ["b"] +``` + +### Choosing an optic quickly + +| Data shape | Use | +| --------------------------------- | ------------------------------------ | +| Always-present field | `key` | +| Optional field (keep `undefined`) | `key` | +| Optional field (drop `undefined`) | `optionalKey` | +| Union case | `tag` | +| Record or array index | `at` | +| Filter and update items | `forEach` + `check` / `notUndefined` | + +### Accessing a group of keys in a struct + +#### pick + +**Example** (Updating multiple fields with `pick`) + +```ts +import { Optic } from "effect" + +type S = { + readonly a: number + readonly b: number + readonly c: number +} + +// Build an optic to access the "a" and "c" fields +const _a = Optic.id().pick(["a", "c"]) + +console.log(_a.replace({ a: 4, c: 5 }, { a: 1, b: 2, c: 3 })) +// { a: 4, b: 2, c: 5 } +``` + +#### omit + +**Example** (Updating all fields except a set with `omit`) + +```ts +import { Optic } from "effect" + +type S = { + readonly a: number + readonly b: number + readonly c: number +} + +// Build an optic to access the "a" and "c" fields +const _a = Optic.id().omit(["b"]) + +console.log(_a.replace({ a: 4, c: 5 }, { a: 1, b: 2, c: 3 })) +// { a: 4, b: 2, c: 5 } +``` + +### Accessing an optional key in a struct or a tuple + +There are two ways to handle an optional key in a struct or a tuple, depending on how you want to treat the `undefined` value: + +1. when setting `undefined`, the key is preserved +2. when setting `undefined`, the key is removed + +**Example** (Preserving the key when setting `undefined`) + +```ts +import { Optic } from "effect" + +type S = { + readonly a?: number | undefined +} + +// Lens +const _a = Optic.id().key("a") + +console.log(String(_a.getResult({ a: 1 }))) +// success(1) + +console.log(String(_a.getResult({}))) +// success(undefined) + +console.log(String(_a.getResult({ a: undefined }))) +// success(undefined) + +console.log(_a.replace(2, { a: 1 })) +// { a: 2 } + +console.log(_a.replace(2, {})) +// { a: 2 } + +console.log(_a.replace(undefined, { a: 1 })) +// { a: undefined } + +console.log(_a.replace(undefined, {})) +// { a: undefined } + +console.log(_a.replace(2, { a: undefined })) +// { a: 2 } +``` + +**Example** (Removing the key when setting `undefined`) + +```ts +import { Optic } from "effect" + +type S = { + readonly a?: number +} + +// Lens +const _a = Optic.id().optionalKey("a") + +console.log(String(_a.getResult({ a: 1 }))) +// success(1) + +console.log(String(_a.getResult({}))) +// success(undefined) + +console.log(_a.replace(2, { a: 1 })) +// { a: 2 } + +console.log(_a.replace(2, {})) +// { a: 2 } + +console.log(_a.replace(undefined, { a: 1 })) +// {} + +console.log(_a.replace(undefined, {})) +// {} +``` + +**Example** (Dropping a tuple element when setting `undefined`) + +```ts +import { Optic } from "effect" + +type S = readonly [number, number?] + +// Build an optic to access the optional second element +const _1 = Optic.id().optionalKey(1) + +console.log(_1.get([1, 2])) +// 2 + +console.log(_1.get([1])) +// undefined + +console.log(_1.replace(3, [1, 2])) +// [1, 3] + +console.log(_1.replace(undefined, [1, 2])) +// [1] +``` + +### Accessing a key in a record or an array + +**Example** (Reading and updating a record entry) + +```ts +import { Optic } from "effect" + +type S = { [key: string]: number } + +// Build an optic to access the value at key "a" +const _a = Optic.id().at("a") + +console.log(_a.replace(2, { a: 1 })) +// { a: 2 } +``` + +**Example** (Reading and updating an array element) + +```ts +import { Optic } from "effect" + +type S = ReadonlyArray + +// Build an optic to access the first element +const _0 = Optic.id().at(0) + +console.log(_0.replace(3, [1, 2])) +// [3, 2] +``` + +### Accessing a member in a tagged union + +**Aside** (Convention for tagged unions) +The convention is to use `"_tag"` as the field that identifies the variant. + +**Example** (Focusing a field inside one variant) + +```ts +import { Optic } from "effect" + +// A union of two tagged types +type S = + | { + readonly _tag: "A" + readonly a: number + } + | { + readonly _tag: "B" + readonly b: number + } + +// Build an optic that focuses on the "a" field of the "A" variant +const _a = Optic.id().tag("A").key("a") + +console.log(_a.replace(2, { _tag: "A", a: 1 })) +// { _tag: 'A', a: 2 } + +console.log(_a.replace(2, { _tag: "B", b: 1 })) // no match, so no change +// { _tag: 'B', b: 1 } +``` + +### Traversing a collection + +**Example** (Incrementing only positive numbers in an array field) + +```ts +import { Optic, Schema } from "effect" + +type S = { + readonly a: ReadonlyArray +} + +// Build an optic that focuses the field "a" and then +// narrows the focus to elements that pass the positivity check +const _positive = Optic.id() + .key("a") // focus the "a" array + .forEach((item) => item.check(Schema.isGreaterThan(0))) // keep only positive elements + +// Create a function that increments only the focused elements +const addOne = _positive.modifyAll((n) => n + 1) + +console.log(addOne({ a: [1, -2, 3] })) +// { a: [ 2, -2, 4 ] } +``` + +**Technical detail** + +Unlike many optic libraries, `Traversal` is not an optic on its own. It is modeled as an `Optional` whose focus is a `ReadonlyArray`: + +```ts +export interface Traversal extends Optional> {} +``` + +To operate on each `A` inside a `Traversal`, use `forEach`. +`forEach` takes a function whose argument is an `Iso`, so you can keep drilling down by composing that `Iso` with other optics. + +### Debugging focus failures + +If a focus does not exist, `getResult` lets you see success vs failure explicitly: + +```ts +import { Optic, Result } from "effect" + +type S = { readonly a?: number } +const _a = Optic.id().at("a") + +const result = _a.getResult({}) +const message = Result.match(result, { + onSuccess: (value) => `value: ${value}`, + onFailure: () => "no focus" +}) + +console.log(message) // no focus +``` + +## Generating an Optic from a Schema + +**Example** (Generating an Optic from a Struct) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}) + +/* +const _b: Lens<{ + readonly a: string; + readonly b: number; +}, number> +*/ +const _b = Schema.toIso(schema).key("b") + +console.log(_b.replace(2, { a: "a", b: 1 })) +// { a: 'a', b: 2 } +``` + +You can also call `Schema.toIso` on custom types when their schema supplies `toCodecIso` or `toCodec` annotations. `Schema.Class` provides these, so class-based schemas work out of the box: + +**Example** (Generating an Optic from a Class schema) + +```ts +import { Schema } from "effect" + +// Define a class schema +class Person extends Schema.Class("Person")({ + name: Schema.String, + age: Schema.Number +}) {} + +const _name = Schema.toIso(Person).key("name") + +console.log(_name.replace("b", new Person({ name: "a", age: 1 }))) +// Person { name: 'b', age: 1 } +``` + +## Why use functional optics when we already have Immer? + +Immer is great: it lets you write "mutating" code that produces new immutable objects under the hood. For many teams that is enough. If you work with nested data, union types, and reusable update logic, **functional optics** (Iso, Lens, Prism, Optional, Traversal) cover use cases that Immer does not aim to address. + +Below are the main differences, with small examples. + +### Reusable focus instead of ad-hoc navigation + +**Immer:** you repeat the path to the field each time you update it. + +```ts +import { produce } from "immer" + +type S = { + readonly user: { + readonly profile: { + readonly name: string + } + } +} + +declare const state: S + +const upperName = produce(state, (draft) => { + // Navigate to the field inline + draft.user.profile.name = draft.user.profile.name.toUpperCase() +}) + +const lowerName = produce(state, (draft) => { + // Repeat the same navigation again + draft.user.profile.name = draft.user.profile.name.toLowerCase() +}) +``` + +**Optics:** define a **Lens** once, then reuse it. + +```ts +import { Optic } from "effect" + +type S = { + readonly user: { + readonly profile: { + readonly name: string + } + } +} + +// Define a reusable Lens focusing the "name" field +// Lens +const _name = Optic.id().key("user").key("profile").key("name") + +declare const state: S + +// Apply different transformations without repeating the path +const upperName = _name.modify((name) => name.toUpperCase())(state) +const lowerName = _name.modify((name) => name.toLowerCase())(state) +``` + +Why this matters: if the path changes, you update it in one place. You also get small, testable building blocks that can be shared across modules instead of repeating object navigation. + +### Declarative vs manual handling of optional data + +**Immer:** manual checks for each optional field. + +**Example** (Uppercasing titles with optional fields) + +```ts +import { produce } from "immer" + +type S = { + readonly todos?: ReadonlyArray<{ + readonly title?: string + readonly description: string + }> +} + +const state: S = { + todos: [{ title: "milk", description: "buy milk" }, { description: "buy bread" }] +} + +const next = produce(state, (draft) => { + // Guard the optional array + if (!draft.todos) return + + for (const item of draft.todos) { + // Guard the optional field + if (item.title !== undefined) { + item.title = item.title.toUpperCase() + } + } +}) + +console.log(next) +/* +{ + todos: [ + { title: 'MILK', description: 'buy milk' }, + { description: 'buy bread' } + ] +} +*/ +``` + +**Optics:** declare the focus; types carry the safety. + +**Example** (Uppercasing titles with declarative focus) + +```ts +import { Optic } from "effect" + +type S = { + readonly todos?: ReadonlyArray<{ + readonly title?: string + readonly description: string + }> +} + +const _title = Optic.id() + .key("todos") + .notUndefined() // proceed only if 'todos' exists + .forEach((item) => item.key("title").notUndefined()) // proceed only if 'title' exists + +const state: S = { + todos: [{ title: "milk", description: "buy milk" }, { description: "buy bread" }] +} + +// Modify only the focused values (titles) +console.log(_title.modifyAll((title) => title.toUpperCase())(state)) +/* +{ + todos: [ + { title: 'MILK', description: 'buy milk' }, + { description: 'buy bread' } + ] +} +*/ +``` + +### Composition over nesting + +**Immer:** you often nest update blocks or repeat the same property paths. + +**Optics:** compose small optics into larger ones. Composition keeps code flat and readable. + +**Aside** (Reusing optics across modules) +Define an optic once (for example, a `User.address` lens) and import it wherever you need it. This avoids duplicating paths and centralizes changes when the data shape evolves. diff --git a/.repos/effect/packages/effect/README.md b/.repos/effect/packages/effect/README.md new file mode 100644 index 000000000..d702d36b6 --- /dev/null +++ b/.repos/effect/packages/effect/README.md @@ -0,0 +1,47 @@ +# effect + +Effect is a library for building robust, maintainable, type-safe, and production grade applications in TypeScript. + +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 + +```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** when running Effect on Node.js. +- **Strict type-checking:** the `strict` flag must be enabled in your `tsconfig.json`: + + ```json + { + "compilerOptions": { + "strict": true + } + } + ``` + +## Documentation + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/effect) + +## Overview + +The `effect` package is a collection of modules. Some of the core ones: + +| Module | Description | +| -------- | -------------------------------------------------------------------------------------------------------------------------- | +| Effect | The core abstraction for managing side effects, concurrency, and error handling in a structured way. | +| Context | A lightweight dependency injection mechanism that enables passing services through computations without direct references. | +| Layer | A system for managing dependencies, allowing for modular and composable resource allocation. | +| Fiber | Lightweight virtual threads with resource-safe cancellation capabilities, enabling many features in Effect. | +| Stream | A powerful abstraction for handling asynchronous, event-driven data processing. | +| Schedule | A module for defining retry and repeat policies with composable schedules. | +| 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. | + +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/.repos/effect/packages/effect/SCHEMA.md b/.repos/effect/packages/effect/SCHEMA.md new file mode 100644 index 000000000..f52dbed39 --- /dev/null +++ b/.repos/effect/packages/effect/SCHEMA.md @@ -0,0 +1,7472 @@ +# Schema + +`Schema` is a TypeScript-first library for defining data shapes, validating unknown input, and transforming values between formats. + +Two key concepts appear throughout this guide: + +- **Decoding** — turning unknown external data (API responses, form submissions, config files) into typed, validated values. +- **Encoding** — turning typed values back into a serializable format (JSON, FormData, etc.). + +Use Schema to: + +- **Define types** — declare the shape of your data once and get both the TypeScript type and a runtime validator. +- **Validate input** — decode unknown data into type-safe values, with clear error messages when it doesn't match. +- **Transform values** — convert between your domain types and serialization formats like JSON, FormData, and URLSearchParams. +- **Generate tooling** — derive JSON Schemas, test data generators, equivalence checks, and more from a single schema definition. + +## Design Philosophy + +- **Lightweight by default** — only import the features you need, keeping your bundle small. +- **Familiar API** — naming conventions and patterns are consistent with popular validation libraries, so getting started is easy. +- **Explicit** — you choose which features to use. Nothing is included implicitly. + +### What's in This Guide + +1. **Elementary schemas** — built-in schemas for primitives, literals, strings, numbers, dates, and template literals. +2. **Composite schemas** — combine elementary schemas into structs (objects), tuples, arrays, records, and unions. +3. **Validation** — add runtime checks (filters) to constrain values, report multiple errors, and define custom rules. +4. **Constructors** — create validated values at runtime, with support for defaults, brands, and refinements. +5. **Transformations** — convert values between types during decoding and encoding. Transformations are reusable objects you compose with schemas. +6. **Flipping** — swap a schema's decoding and encoding directions. +7. **Classes and opaque types** — create distinct TypeScript types backed by structs, with optional methods and equality. +8. **Serialization** — convert values to and from JSON, FormData, URLSearchParams, and XML using canonical codecs. +9. **Tooling** — generate JSON Schemas, test data generators (Arbitraries), equivalence checks, optics, and JSON Patch differs from a single schema. +10. **Error handling** — format validation errors for display, with hooks for internationalization. +11. **Middlewares** — intercept decoding/encoding to provide fallbacks or inject services. +12. **Advanced topics** — internal type model and type hierarchy (for library authors). +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. + +## Primitives + +Use these schemas when a value should be exactly one of the basic JavaScript types. + +```ts +import { Schema } from "effect" + +// primitive types +Schema.String +Schema.Number +Schema.BigInt +Schema.Boolean +Schema.Symbol +Schema.Undefined +Schema.Null +``` + +Sometimes you receive data that is not the right type yet — for example, a number that should become a string. You can build a schema that converts (coerces) values to the target type during decoding: + +```ts +import { Getter, Parser, Schema } from "effect/schema" + +// ┌─── Codec +// ▼ +const schema = Schema.Unknown.pipe( + Schema.decodeTo(Schema.String, { + decode: Getter.String(), + encode: Getter.passthrough() + }) +) + +const parser = Parser.decodeUnknownSync(schema) + +console.log(parser("tuna")) // => "tuna" +console.log(parser(42)) // => "42" +console.log(parser(true)) // => "true" +console.log(parser(null)) // => "null" +``` + +## Literals + +A literal schema matches one exact value. Use it when a field must be a specific string, number, or other constant. + +```ts +import { Schema } from "effect" + +const tuna = Schema.Literal("tuna") +const twelve = Schema.Literal(12) +const twobig = Schema.Literal(2n) +const tru = Schema.Literal(true) +``` + +Symbol literals: + +```ts +import { Schema } from "effect" + +const terrific = Schema.UniqueSymbol(Symbol("terrific")) +``` + +`null`, `undefined`, and `void`: + +```ts +import { Schema } from "effect" + +Schema.Null +Schema.Undefined +Schema.Void +``` + +To allow multiple literal values: + +```ts +import { Schema } from "effect" + +const schema = Schema.Literals(["red", "green", "blue"]) +``` + +To extract the set of allowed values from a literal schema: + +```ts +import { Schema } from "effect" + +const schema = Schema.Literals(["red", "green", "blue"]) + +// readonly ["red", "green", "blue"] +schema.literals + +// readonly [Schema.Literal<"red">, Schema.Literal<"green">, Schema.Literal<"blue">] +schema.members +``` + +## Strings + +You can add validation rules to a string schema. Each rule is applied with `.check(...)` and returns a new schema that enforces that constraint. + +```ts +import { Schema } from "effect" + +Schema.String.check(Schema.isMaxLength(5)) +Schema.String.check(Schema.isMinLength(5)) +Schema.String.check(Schema.isLengthBetween(5, 5)) +Schema.String.check(Schema.isPattern(/^[a-z]+$/)) +Schema.String.check(Schema.isStartsWith("aaa")) +Schema.String.check(Schema.isEndsWith("zzz")) +Schema.String.check(Schema.isIncludes("---")) +Schema.String.check(Schema.isUppercased()) +Schema.String.check(Schema.isLowercased()) +``` + +To perform some simple string transforms: + +```ts +import { Schema, SchemaTransformation } from "effect" + +Schema.String.decode(SchemaTransformation.trim()) +Schema.String.decode(SchemaTransformation.toLowerCase()) +Schema.String.decode(SchemaTransformation.toUpperCase()) +``` + +## String formats + +Schema includes built-in checks for common string formats. + +```ts +import { Schema } from "effect" + +Schema.String.check(Schema.isUUID()) +Schema.String.check(Schema.isBase64()) +Schema.String.check(Schema.isBase64Url()) +``` + +## Numbers + +```ts +import { Schema } from "effect" + +Schema.Number // all numbers +Schema.Finite // finite numbers (i.e. not +/-Infinity or NaN) +``` + +You can add validation rules to a number schema. Each rule constrains the allowed range or value. + +```ts +import { Schema } from "effect" + +Schema.Number.check(Schema.isBetween({ minimum: 5, maximum: 10 })) +Schema.Number.check(Schema.isGreaterThan(5)) +Schema.Number.check(Schema.isGreaterThanOrEqualTo(5)) +Schema.Number.check(Schema.isLessThan(5)) +Schema.Number.check(Schema.isLessThanOrEqualTo(5)) +Schema.Number.check(Schema.isMultipleOf(5)) +``` + +## Integers + +To require that a number has no decimal part, use `isInt()`. For 32-bit integers specifically, use `isInt32()`. + +```ts +import { Schema } from "effect" + +Schema.Number.check(Schema.isInt()) +Schema.Number.check(Schema.isInt32()) +``` + +## BigInts + +Schema does not ship pre-built BigInt validation factories (unlike numbers). Instead, you create your own using helper functions and a BigInt-compatible ordering. The example below shows how. + +```ts +import { BigInt, Order, Schema } from "effect" + +const options = { order: Order.BigInt } + +const isBetween = Schema.makeIsBetween(options) +const isGreaterThan = Schema.makeIsGreaterThan(options) +const isGreaterThanOrEqualTo = Schema.makeIsGreaterThanOrEqualTo(options) +const isLessThan = Schema.makeIsLessThan(options) +const isLessThanOrEqualTo = Schema.makeIsLessThanOrEqualTo(options) +const isMultipleOf = Schema.makeIsMultipleOf({ + remainder: BigInt.remainder, + zero: 0n +}) + +const isPositive = isGreaterThan(0n) +const isNonNegative = isGreaterThanOrEqualTo(0n) +const isNegative = isLessThan(0n) +const isNonPositive = isLessThanOrEqualTo(0n) + +Schema.BigInt.check(isBetween({ minimum: 5n, maximum: 10n })) +Schema.BigInt.check(isGreaterThan(5n)) +Schema.BigInt.check(isGreaterThanOrEqualTo(5n)) +Schema.BigInt.check(isLessThan(5n)) +Schema.BigInt.check(isLessThanOrEqualTo(5n)) +Schema.BigInt.check(isMultipleOf(5n)) +Schema.BigInt.check(isPositive) +Schema.BigInt.check(isNonNegative) +Schema.BigInt.check(isNegative) +Schema.BigInt.check(isNonPositive) +``` + +## Dates + +The `Schema.Date` schema matches valid `Date` objects and rejects invalid dates +such as `new Date(NaN)`. + +## Template literals + +You can use `Schema.TemplateLiteral` to define structured string patterns made of multiple parts. Each part can be a literal or a schema, and **additional constraints** (such as `isMinLength` or `isMaxLength`) can be applied to individual parts. + +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. + +**Example** (Constraining parts of an email-like string) + +```ts +import { Schema } from "effect" + +// Construct a template literal schema for values like `${string}@${string}` +// Apply constraints to both sides of the "@" symbol +const email = Schema.TemplateLiteral([ + // Left part: must be a non-empty string + Schema.String.check(Schema.isMinLength(1)), + + // Separator + "@", + + // Right part: must be a string with a maximum length of 64 + Schema.String.check(Schema.isMaxLength(64)) +]) + +// The inferred type is `${string}@${string}` +export type Type = typeof email.Type + +console.log(String(Schema.decodeUnknownExit(email)("a@b.com"))) +/* +Success("a@b.com") +*/ + +console.log(String(Schema.decodeUnknownExit(email)("@b.com"))) +/* +Failure(Cause([Fail(SchemaError(Expected a string matching template literal parts))])) +*/ +``` + +### Template literal parser + +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. + +**Example** (Parsing a template literal into components) + +```ts +import { Schema } from "effect" + +const schema = Schema.TemplateLiteralParser([ + Schema.String.check(Schema.isMinLength(2)), + ":", + Schema.Int +]) + +// The inferred type is `readonly [string, ":", number]` +export type Type = typeof schema.Type + +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 +// at [0]))])) + +console.log(String(Schema.decodeUnknownExit(schema)("aa:1.2"))) +// Failure(Cause([Fail(SchemaError(Expected an integer +// at [2]))])) +``` + +# Defining Composite Schemas + +Once you have elementary schemas, you can combine them into composite schemas that describe objects, arrays, tuples, key-value maps, and unions. + +## Structs + +A struct schema describes a JavaScript object with a known set of keys. Each key maps to a schema that validates and types its value. + +### Optional and Mutable Keys + +By default, every key in a struct is required and readonly. Use `Schema.optionalKey` to make a key optional (the key can be absent from the object), and `Schema.mutableKey` to make it writable. + +You can mark struct properties as optional or mutable using `Schema.optionalKey` and `Schema.mutableKey`. + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.optionalKey(Schema.String), + c: Schema.mutableKey(Schema.String), + d: Schema.optionalKey(Schema.mutableKey(Schema.String)) +}) + +/* +with "exactOptionalPropertyTypes": true + +type Type = { + readonly a: string; + readonly b?: string; + c: string; + d?: string; +} +*/ +type Type = (typeof schema)["Type"] +``` + +### Optional Fields + +There are several ways to represent optional properties, depending on whether you want `undefined` in the type, `null` in the type, or just a missing key. By combining `Schema.optionalKey`, `Schema.optional`, and `Schema.NullOr`, you can represent any variant. + +```ts +import { Schema } from "effect" + +export const schema = Schema.Struct({ + // Exact Optional Property + a: Schema.optionalKey(Schema.FiniteFromString), + // Optional Property + b: Schema.optional(Schema.FiniteFromString), + // Exact Optional Property with Nullability + c: Schema.optionalKey(Schema.NullOr(Schema.FiniteFromString)), + // Optional Property with Nullability + d: Schema.optional(Schema.NullOr(Schema.FiniteFromString)) +}) + +/* +type Encoded = { + readonly a?: string; + readonly b?: string | undefined; + readonly c?: string | null; + readonly d?: string | null | undefined; +} +*/ +type Encoded = typeof schema.Encoded + +/* +type Type = { + readonly a?: number; + readonly b?: number | undefined; + readonly c?: number | null; + readonly d?: number | null | undefined; +} +*/ +type Type = typeof schema.Type +``` + +#### Omitting Values When Transforming Optional Fields + +If an optional field arrives as `undefined`, you may want to omit it from the output entirely rather than keeping it. + +```ts +import { Option, Predicate, Schema, SchemaGetter } from "effect" + +export const schema = Schema.Struct({ + a: Schema.optional(Schema.FiniteFromString).pipe( + Schema.decodeTo(Schema.optionalKey(Schema.Number), { + decode: SchemaGetter.transformOptional( + Option.filter(Predicate.isNotUndefined) // omit undefined + ), + encode: SchemaGetter.passthrough() + }) + ) +}) + +/* +type Encoded = { + readonly a?: string | undefined; +} +*/ +type Encoded = typeof schema.Encoded + +/* +type Type = { + readonly a?: number; +} +*/ +type Type = typeof schema.Type +``` + +#### Representing Optional Fields with never Type + +You can use `Schema.Never` inside an optional key to represent a field that should never have a value but may still appear as a key in the type. + +```ts +import { Schema } from "effect" + +export const schema = Schema.Struct({ + a: Schema.optionalKey(Schema.Never) +}) + +/* +type Encoded = { + readonly a?: never; +} +*/ +type Encoded = typeof schema.Encoded + +/* +type Type = { + readonly a?: never; +} +*/ +type Type = typeof schema.Type +``` + +### Decoding Defaults + +You can assign default values to fields during decoding using: + +| API | Encoded side | Default value type | +| ----------------------------------- | ------------------------- | ------------------ | +| `Schema.withDecodingDefaultKey` | key absent | `Encoded` | +| `Schema.withDecodingDefault` | key absent or `undefined` | `Encoded` | +| `Schema.withDecodingDefaultTypeKey` | key absent | `Type` | +| `Schema.withDecodingDefaultType` | key absent or `undefined` | `Type` | + +The "Key" variants use `optionalKey` (the key may be absent but not `undefined`), while the non-"Key" variants use `optional` (the key may be absent **or** `undefined`). + +The "Type" variants accept a default specified as a `Type` (decoded) value, which is useful when the schema has a transformation and you want to provide the default in the decoded representation. + +#### Encoded-Side Defaults + +`withDecodingDefaultKey` and `withDecodingDefault` accept a default specified as an +**`Encoded` value** (before any decoding transformation). This is the most common +case and works well when the Encoded and Type representations are the same, or +when you already have the value in encoded form. + +**Example** (Default as an Encoded value) + +In `FiniteFromString`, the `Encoded` type is `string` and the `Type` is `number`. +The default `"1"` is a **string** (the Encoded type), which is then decoded to `1`. + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.Struct({ + // ┌─── "1" is a string (Encoded type) + // ▼ + a: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(Effect.succeed("1"))) +}) + +// ┌─── { readonly a?: string | undefined; } +// ▼ +type Encoded = typeof schema.Encoded + +// ┌─── { readonly a: number; } +// ▼ +type Type = typeof schema.Type + +console.log(Schema.decodeUnknownSync(schema)({})) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: undefined })) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: "2" })) +// Output: { a: 2 } +``` + +#### Type-Side Defaults + +`withDecodingDefaultTypeKey` and `withDecodingDefaultType` accept a default +specified as a **`Type` value** (the decoded representation). This is useful when +the schema has a transformation and you want to provide the default directly as a +decoded value, bypassing the decoding step. + +**Example** (Default as a Type value) + +Here the default `1` is a **number** (the Type), not a string. It does not go +through the `FiniteFromString` decoding transformation. + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.Struct({ + // ┌─── 1 is a number (Type) + // ▼ + a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultType(Effect.succeed(1))) +}) + +// ┌─── { readonly a?: string | undefined; } +// ▼ +type Encoded = typeof schema.Encoded + +// ┌─── { readonly a: number; } +// ▼ +type Type = typeof schema.Type + +console.log(Schema.decodeUnknownSync(schema)({})) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: undefined })) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: "2" })) +// Output: { a: 2 } +``` + +#### Nested Decoding Defaults + +You can also apply decoding defaults within nested structures. + +**Example** (Nested struct with defaults for missing or undefined fields) + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.Struct({ + b: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(Effect.succeed("1"))) + }).pipe(Schema.withDecodingDefault(Effect.succeed({}))) +}) + +/* +type Encoded = { + readonly a?: { + readonly b?: string | undefined; + } | undefined; +} +*/ +type Encoded = typeof schema.Encoded + +/* +type Type = { + readonly a: { + readonly b: number; + }; +} +*/ +type Type = typeof schema.Type + +console.log(Schema.decodeUnknownSync(schema)({})) +// Output: { a: { b: 1 } } + +console.log(Schema.decodeUnknownSync(schema)({ a: undefined })) +// Output: { a: { b: 1 } } + +console.log(Schema.decodeUnknownSync(schema)({ a: {} })) +// Output: { a: { b: 1 } } + +console.log(Schema.decodeUnknownSync(schema)({ a: { b: undefined } })) +// Output: { a: { b: 1 } } + +console.log(Schema.decodeUnknownSync(schema)({ a: { b: "2" } })) +// Output: { a: { b: 2 } } +``` + +### Manual Decoding Defaults + +If the defaulting logic is more specific than just handling `undefined` or missing values, you can use `Schema.decodeTo` to apply custom fallback rules. + +This is useful when you need to account for values like `null` or other invalid states. + +**Example** (Providing a fallback when value is `null` or missing) + +```ts +import { Option, Predicate, Schema, SchemaGetter } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalKey(Schema.NullOr(Schema.String)).pipe( + Schema.decodeTo(Schema.FiniteFromString, { + decode: SchemaGetter.transformOptional((oe) => + oe.pipe( + // remove null values + Option.filter(Predicate.isNotNull), + // default to "1" if none + Option.orElseSome(() => "1") + ) + ), + encode: SchemaGetter.passthrough() + }) + ) +}) + +// ┌─── { readonly a?: string | null; } +// ▼ +type Encoded = typeof schema.Encoded + +// ┌─── { readonly a: number; } +// ▼ +type Type = typeof schema.Type + +console.log(Schema.decodeUnknownSync(schema)({})) +// Output: { a: 1 } + +// console.log(Schema.decodeUnknownSync(Product)({ quantity: undefined })) +// throws + +console.log(Schema.decodeUnknownSync(schema)({ a: null })) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: "2" })) +// Output: { a: 2 } +``` + +**Example** (Providing a fallback when value is `null`, `undefined`, or missing) + +```ts +import { Option, Predicate, Schema, SchemaGetter } from "effect" + +const schema = Schema.Struct({ + a: Schema.optional(Schema.NullOr(Schema.String)).pipe( + Schema.decodeTo(Schema.FiniteFromString, { + decode: SchemaGetter.transformOptional((oe) => + oe.pipe( + // remove null and undefined + Option.filter(Predicate.isNotNullish), + // default to "1" if none + Option.orElseSome(() => "1") + ) + ), + encode: SchemaGetter.passthrough() + }) + ) +}) + +// ┌─── { readonly a?: string | null | undefined; } +// ▼ +type Encoded = typeof schema.Encoded + +// ┌─── { readonly a: number; } +// ▼ +type Type = typeof schema.Type + +console.log(Schema.decodeUnknownSync(schema)({})) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: undefined })) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: null })) +// Output: { a: 1 } + +console.log(Schema.decodeUnknownSync(schema)({ a: "2" })) +// Output: { a: 2 } +``` + +### Optional Fields as Options + +Effect's `Option` type is a safer alternative to `undefined` for representing the presence or absence of a value. These helpers convert between optional struct fields and `Option` values. + +#### Exact Optional Property + +```ts +import { Option, Schema } from "effect" + +const Product = Schema.Struct({ + quantity: Schema.OptionFromOptionalKey(Schema.FiniteFromString) +}) + +// ┌─── { readonly quantity?: string; } +// ▼ +type Encoded = typeof Product.Encoded + +// ┌─── { readonly quantity: Option; } +// ▼ +type Type = typeof Product.Type + +console.log(Schema.decodeUnknownSync(Product)({})) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: "2" })) +// Output: { quantity: { _id: 'Option', _tag: 'Some', value: 2 } } + +// console.log(Schema.decodeUnknownSync(Product)({ quantity: undefined })) +// throws + +console.log(Schema.encodeSync(Product)({ quantity: Option.some(2) })) +// Output: { quantity: "2" } + +console.log(Schema.encodeSync(Product)({ quantity: Option.none() })) +// Output: {} +``` + +#### Optional Property + +```ts +import { Option, Schema } from "effect" + +const Product = Schema.Struct({ + quantity: Schema.OptionFromOptional(Schema.FiniteFromString) +}) + +// ┌─── { readonly quantity?: string | undefined; } +// ▼ +type Encoded = typeof Product.Encoded + +// ┌─── { readonly quantity: Option; } +// ▼ +type Type = typeof Product.Type + +console.log(Schema.decodeUnknownSync(Product)({})) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: "2" })) +// Output: { quantity: { _id: 'Option', _tag: 'Some', value: 2 } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: undefined })) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.encodeSync(Product)({ quantity: Option.some(2) })) +// Output: { quantity: "2" } + +console.log(Schema.encodeSync(Product)({ quantity: Option.none() })) +// Output: {} +``` + +#### Exact Optional Property with Nullability + +```ts +import { Option, Predicate, Schema, SchemaTransformation } from "effect" + +const Product = Schema.Struct({ + quantity: Schema.optionalKey(Schema.NullOr(Schema.FiniteFromString)).pipe( + Schema.decodeTo( + Schema.Option(Schema.Number), + SchemaTransformation.transformOptional({ + decode: (oe) => oe.pipe(Option.filter(Predicate.isNotNull), Option.some), + encode: Option.flatten + }) + ) + ) +}) + +// ┌─── { readonly quantity?: string | null; } +// ▼ +type Encoded = typeof Product.Encoded + +// ┌─── { readonly quantity: Option; } +// ▼ +type Type = typeof Product.Type + +console.log(Schema.decodeUnknownSync(Product)({})) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: null })) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: "2" })) +// Output: { quantity: { _id: 'Option', _tag: 'Some', value: 2 } } + +// console.log(Schema.decodeUnknownSync(Product)({ quantity: undefined })) +// throws +``` + +#### Optional Property with Nullability + +```ts +import { Schema } from "effect" + +const Product = Schema.Struct({ + quantity: Schema.OptionFromOptionalNullOr(Schema.FiniteFromString) +}) + +// ┌─── { readonly quantity?: string | null | undefined; } +// ▼ +type Encoded = typeof Product.Encoded + +// ┌─── { readonly quantity: Option; } +// ▼ +type Type = typeof Product.Type + +console.log(Schema.decodeUnknownSync(Product)({})) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: undefined })) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: null })) +// Output: { quantity: { _id: 'Option', _tag: 'None' } } + +console.log(Schema.decodeUnknownSync(Product)({ quantity: "2" })) +// Output: { quantity: { _id: 'Option', _tag: 'Some', value: 2 } +``` + +### Key Annotations + +You can annotate individual keys using the `annotateKey` method. This is useful for adding a description or customizing the error message shown when the key is missing. + +**Example** (Annotating a required `username` field) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + username: Schema.String.annotateKey({ + description: "The username used to log in", + // Custom message shown if the key is missing + messageMissingKey: "Username is required" + }) +}) + +console.log(String(Schema.decodeUnknownExit(schema)({}))) +/* +Failure(Cause([Fail(SchemaError: Username is required + at ["username"] +)])) +*/ +``` + +### Unexpected Key Message + +You can annotate a struct with a custom message to use when a key is unexpected (when `onExcessProperty` is `error`). + +**Example** (Annotating a struct with a custom message) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String +}).annotate({ messageUnexpectedKey: "Custom message" }) + +console.log(String(Schema.decodeUnknownExit(schema)({ a: "a", b: "b" }, { onExcessProperty: "error" }))) +/* +Failure(Cause([Fail(SchemaError: Custom message + at ["b"] +)])) +*/ +``` + +### Preserve unexpected keys + +You can preserve unexpected keys by setting `onExcessProperty` to `preserve`. + +**Example** (Preserving unexpected keys) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String +}) + +console.log(String(Schema.decodeUnknownExit(schema)({ a: "a", b: "b" }, { onExcessProperty: "preserve" }))) +/* +Output: +Success({"b":"b","a":"a"}) +*/ +``` + +### Index Signatures + +An index signature lets a struct accept any string key in addition to its fixed keys. Use `Schema.StructWithRest` to combine a struct with one or more record schemas. + +Filters applied to either the struct or the record are preserved when combined. + +**Example** (Combining fixed properties with an index signature) + +```ts +import { Schema } from "effect" + +// Define a schema with one fixed key "a" and any number of string keys mapping to numbers +export const schema = Schema.StructWithRest(Schema.Struct({ a: Schema.Number }), [ + Schema.Record(Schema.String, Schema.Number) +]) + +/* +type Type = { + readonly [x: string]: number; + readonly a: number; +} +*/ +type Type = typeof schema.Type + +/* +type Encoded = { + readonly [x: string]: number; + readonly a: number; +} +*/ +type Encoded = typeof schema.Encoded +``` + +If you want the record part to be mutable, you can wrap it in `Schema.mutable`. + +**Example** (Allowing dynamic keys to be mutable) + +```ts +import { Schema } from "effect" + +// Define a schema with one fixed key "a" and any number of string keys mapping to numbers +export const schema = Schema.StructWithRest(Schema.Struct({ a: Schema.Number }), [ + Schema.Record(Schema.String, Schema.mutableKey(Schema.Number)) +]) + +/* +type Type = { + [x: string]: number; + readonly a: number; +} +*/ +type Type = typeof schema.Type + +/* +type Encoded = { + [x: string]: number; + readonly a: number; +} +*/ +type Encoded = typeof schema.Encoded +``` + +### Renaming Encoded Keys + +Use `Schema.encodeKeys` to rename one or more keys only in the encoded representation of a struct. + +Pass a mapping of `{ decodedKey: encodedKey }`. During decoding, the schema expects the mapped encoded keys. During encoding, it produces those keys. Keys not in the mapping are left unchanged. + +Unlike `Struct.renameKeys`, this does not rename the struct's own field names. It only remaps keys at the encoding / decoding boundary. + +**Example** (Using snake_case keys in the encoded form) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + userId: Schema.FiniteFromString, + accountName: Schema.String +}).pipe( + Schema.encodeKeys({ + userId: "user_id", + accountName: "account_name" + }) +) + +console.log(Schema.decodeUnknownSync(schema)({ user_id: "1", account_name: "alice" })) +// { userId: 1, accountName: "alice" } + +console.log(Schema.encodeUnknownSync(schema)({ userId: 1, accountName: "alice" })) +// { user_id: "1", account_name: "alice" } +``` + +If you are building a struct from reused fields or `Schema.fieldsAssign`, apply `Schema.encodeKeys` after defining the full struct. + +### Reusing Fields + +Every `Schema.Struct` exposes a `.fields` property containing its field definitions. You can spread these fields into a new struct to reuse them, similar to how TypeScript interfaces use `extends`. + +**Example** (Single inheritance) + +```ts +import { Schema } from "effect" + +const Timestamped = Schema.Struct({ + createdAt: Schema.Date, + updatedAt: Schema.Date +}) + +const User = Schema.Struct({ + ...Timestamped.fields, + name: Schema.String, + email: Schema.String +}) + +const Post = Schema.Struct({ + ...Timestamped.fields, + title: Schema.String, + body: Schema.String +}) +``` + +**Example** (Multiple inheritance) + +```ts +import { Schema } from "effect" + +const Timestamped = Schema.Struct({ + createdAt: Schema.Date, + updatedAt: Schema.Date +}) + +const SoftDeletable = Schema.Struct({ + deletedAt: Schema.optionalKey(Schema.Date) +}) + +const User = Schema.Struct({ + ...Timestamped.fields, + ...SoftDeletable.fields, + name: Schema.String, + email: Schema.String +}) +``` + +### Deriving Structs + +You can derive new struct schemas from existing ones — picking, omitting, renaming, or transforming individual fields — without rewriting the schema from scratch. The `mapFields` method on `Schema.Struct` accepts a function that transforms the struct's fields and returns a new `Schema.Struct` based on the result. + +#### Pick + +Use `Struct.pick` to keep only a selected set of fields. + +**Example** (Picking specific fields from a struct) + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly a: Schema.String; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields(Struct.pick(["a"])) +``` + +#### Omit + +Use `Struct.omit` to remove specified fields from a struct. + +**Example** (Omitting fields from a struct) + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly a: Schema.String; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields(Struct.omit(["b"])) +``` + +#### Merge + +Use `Struct.assign` to add new fields to an existing struct. + +**Example** (Adding fields to a struct) + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly a: Schema.String; + readonly b: Schema.Number; + readonly c: Schema.Boolean; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields( + Struct.assign({ + c: Schema.Boolean + }) +) + +// or more succinctly +const schema2 = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).pipe(Schema.fieldsAssign({ c: Schema.Boolean })) +``` + +If you want to preserve the checks of the original struct, you can pass `{ unsafePreserveChecks: true }` to the `map` method. + +**Warning**: This is an unsafe operation. Since `mapFields` transformations change the schema type, the original refinement functions may no longer be valid or safe to apply to the transformed schema. Only use this option if you have verified that your refinements remain correct after the transformation. + +**Example** (Preserving checks when merging fields) + +```ts +import { Schema, Struct } from "effect" + +const original = Schema.Struct({ + a: Schema.String, + b: Schema.String +}).check(Schema.makeFilter(({ a, b }) => a === b, { title: "a === b" })) + +const schema = original.mapFields(Struct.assign({ c: Schema.String }), { + unsafePreserveChecks: true +}) + +console.log( + String( + Schema.decodeUnknownExit(schema)({ + a: "a", + b: "b", + c: "c" + }) + ) +) +// Failure(Cause([Fail(SchemaError: Expected a === b)])) +``` + +#### Mapping individual fields + +Use `Struct.evolve` to transform the value schema of individual fields. + +**Example** (Modifying the type of a single field) + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly a: Schema.optionalKey; + readonly b: Schema.Number; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields( + Struct.evolve({ + a: (field) => Schema.optionalKey(field) + }) +) +``` + +#### Mapping all fields at once + +If you want to transform the value schema of multiple fields at once, you can use `Struct.map`. + +**Example** (Making all fields optional) + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly a: Schema.optionalKey; + readonly b: Schema.optionalKey; + readonly c: Schema.optionalKey; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number, + c: Schema.Boolean +}).mapFields(Struct.map(Schema.optionalKey)) +``` + +#### Mapping a subset of fields at once + +If you want to map a subset of elements, you can use `Struct.mapPick` or `Struct.mapOmit`. + +**Example** (Making a subset of fields optional) + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly a: Schema.optionalKey; + readonly b: Schema.Number; + readonly c: Schema.optionalKey; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number, + c: Schema.Boolean +}).mapFields(Struct.mapPick(["a", "c"], Schema.optionalKey)) +``` + +Or if it's more convenient, you can use `Struct.mapOmit`. + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly a: Schema.optionalKey; + readonly b: Schema.Number; + readonly c: Schema.optionalKey; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number, + c: Schema.Boolean +}).mapFields(Struct.mapOmit(["b"], Schema.optionalKey)) +``` + +#### Mapping individual keys + +Use `Struct.evolveKeys` to rename field keys while keeping the corresponding value schemas. + +**Example** (Uppercasing keys in a struct) + +```ts +import { String } from "effect" +import { Schema } from "effect" +import { Struct } from "effect/data" + +/* +const schema: Schema.Struct<{ + readonly A: Schema.String; + readonly b: Schema.Number; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields( + Struct.evolveKeys({ + a: (key) => String.toUpperCase(key) + }) +) +``` + +If you simply want to rename keys with static keys, you can use `Struct.renameKeys`. + +**Example** (Renaming keys in a struct) + +```ts +import { Schema, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly A: Schema.String; + readonly b: Schema.Number; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields( + Struct.renameKeys({ + a: "A" + }) +) +``` + +#### Mapping individual entries + +Use `Struct.evolveEntries` when you want to transform both the key and the value of specific fields. + +**Example** (Transforming keys and value schemas) + +```ts +import { Schema, String, Struct } from "effect" + +/* +const schema: Schema.Struct<{ + readonly b: Schema.Number; + readonly A: Schema.optionalKey; +}> +*/ +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).mapFields( + Struct.evolveEntries({ + a: (key, value) => [String.toUpperCase(key), Schema.optionalKey(value)] + }) +) +``` + +#### Opaque Structs + +The previous examples can be applied to opaque structs as well. + +```ts +import { Schema, Struct } from "effect" + +class A extends Schema.Opaque()( + Schema.Struct({ + a: Schema.String, + b: Schema.Number + }) +) {} + +/* +const schema: Schema.Struct<{ + readonly a: Schema.optionalKey; + readonly b: Schema.Number; +}> +*/ +const schema = A.mapFields( + Struct.evolve({ + a: (field) => Schema.optionalKey(field) + }) +) +``` + +### Tagged Structs + +A tagged struct is a struct that includes a `_tag` field. This field is used to identify the specific variant of the object, which is especially useful when working with union types. + +When using the `make` method, the `_tag` field is optional and will be added automatically. However, when decoding or encoding, the `_tag` field must be present in the input. + +**Example** (Tagged struct as a shorthand for a struct with a `_tag` field) + +```ts +import { Schema } from "effect" + +// Defines a struct with a fixed `_tag` field +const tagged = Schema.TaggedStruct("A", { + a: Schema.String +}) + +// This is the same as writing: +const equivalent = Schema.Struct({ + _tag: Schema.tag("A"), + a: Schema.String +}) +``` + +**Example** (Accessing the literal value of the tag) + +```ts +// The `_tag` field is a schema with a known literal value +const literal = tagged.fields._tag.schema.literal +// literal: "A" +``` + +## Tuples + +A tuple schema describes a fixed-length array where each position has its own type. Use tuples when the order and count of elements matters — for example, a `[string, number]` pair. + +### Rest Elements + +You can add rest elements to a tuple using `Schema.TupleWithRest`. + +**Example** (Adding rest elements to a tuple) + +```ts +import { Schema } from "effect" + +export const schema = Schema.TupleWithRest(Schema.Tuple([Schema.FiniteFromString, Schema.String]), [ + Schema.Boolean, + Schema.String +]) + +/* +type Type = readonly [number, string, ...boolean[], string] +*/ +type Type = typeof schema.Type + +/* +type Encoded = readonly [string, string, ...boolean[], string] +*/ +type Encoded = typeof schema.Encoded +``` + +### Element Annotations + +You can annotate elements using the `annotateKey` method. + +**Example** (Annotating an element) + +```ts +import { Schema } from "effect" + +const schema = Schema.Tuple([ + Schema.String.annotateKey({ + description: "my element description", + // a message to display when the element is missing + messageMissingKey: "this element is required" + }) +]) + +console.log(String(Schema.decodeUnknownExit(schema)([]))) +/* +Failure(Cause([Fail(SchemaError: this element is required + at [0] +)])) +*/ +``` + +### Deriving Tuples + +You can map the elements of a tuple schema using the `mapElements` static method on `Schema.Tuple`. The `mapElements` static method accepts a function from `Tuple.elements` to new elements, and returns a new `Schema.Tuple` based on the result. + +#### Pick + +Use `Tuple.pick` to keep only a selected set of elements. + +**Example** (Picking specific elements from a tuple) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements(Tuple.pick([0, 2])) +``` + +#### Omit + +Use `Tuple.omit` to remove specified elements from a tuple. + +**Example** (Omitting elements from a tuple) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements(Tuple.omit([1])) +``` + +#### Adding Elements + +You can add elements to a tuple schema using the `appendElement` and `appendElements` APIs of the `Tuple` module. + +**Example** (Adding elements to a tuple) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number]) + .mapElements(Tuple.appendElement(Schema.Boolean)) // adds a single element + .mapElements(Tuple.appendElements([Schema.String, Schema.Number])) // adds multiple elements +``` + +#### Mapping individual elements + +You can evolve the elements of a tuple schema using the `evolve` API of the `Tuple` module + +**Example** + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple, + Schema.Number, + Schema.NullOr +]> +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements( + Tuple.evolve([ + (v) => Schema.NullOr(v), + undefined, // no change + (v) => Schema.NullOr(v) + ]) +) +``` + +#### Mapping all elements at once + +You can map all elements of a tuple schema using the `map` API of the `Tuple` module. + +**Example** (Making all elements nullable) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple, + Schema.NullOr, + Schema.NullOr +]> +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements(Tuple.map(Schema.NullOr)) +``` + +#### Mapping a subset of elements at once + +If you want to map a subset of elements, you can use `Tuple.mapPick` or `Tuple.mapOmit`. + +**Example** (Making a subset of elements nullable) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple, + Schema.Number, + Schema.NullOr +]> +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements( + Tuple.mapPick([0, 2], Schema.NullOr) +) +``` + +Or if it's more convenient, you can use `Tuple.mapOmit`. + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple, + Schema.Number, + Schema.NullOr +]> +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements( + Tuple.mapOmit([1], Schema.NullOr) +) +``` + +#### Renaming Indices + +You can rename the indices of a tuple schema using the `renameIndices` API of the `Tuple` module. + +**Example** (Partial index mapping) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements( + Tuple.renameIndices(["1", "0"]) // flip the first and second elements +) +``` + +**Example** (Full index mapping) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Tuple +*/ +const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements( + Tuple.renameIndices([ + "2", // last element becomes first + "1", // second element keeps its index + "0" // first element becomes third + ]) +) +``` + +## Arrays + +An array schema describes a variable-length list where every element shares the same type. + +### Unique Arrays + +You can deduplicate arrays using `Schema.UniqueArray`. + +Internally, `Schema.UniqueArray` uses `Schema.Array` and adds a check based on `Schema.isUnique` using `ToEquivalence.make(item)` for the equivalence. + +```ts +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)])) +``` + +## Records + +A record schema describes an object whose keys are dynamic (not known ahead of time). The key schema selects which own properties belong to the record, and the value schema validates the selected property values. + +Properties that are not selected by the key schema are ignored by that record. For example, `Schema.Record(Schema.String.check(Schema.isPattern(/^a/)), Schema.Number)` decodes only string keys that start with `"a"`. + +### Key Transformations + +`Schema.Record` supports transforming keys during decoding and encoding. This can be useful when working with different naming conventions. + +When a key schema has a transformation, dynamic property selection is based on the encoded property names. The selected keys are then decoded using the key schema. + +**Example** (Transforming snake_case keys to camelCase) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const SnakeToCamel = Schema.String.pipe(Schema.decode(SchemaTransformation.snakeToCamel())) + +const schema = Schema.Record(SnakeToCamel, Schema.Number) + +console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, c_d: 2 })) +// { aB: 1, cD: 2 } +``` + +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** (Keeping the later selected value when parsing sequentially) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const SnakeToCamel = Schema.String.pipe(Schema.decode(SchemaTransformation.snakeToCamel())) + +const schema = Schema.Record(SnakeToCamel, Schema.Number) + +console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, aB: 2 })) +// { aB: 2 } +``` + +### Number Keys + +Records with number keys are supported. + +**Example** (Record with number keys) + +```ts +import { Schema } from "effect" + +const schema = Schema.Record(Schema.Int, Schema.String) + +console.log(String(Schema.decodeUnknownExit(schema)({ 1: "a", 2: "b" }))) +// Success({"1":"a","2":"b"}) + +console.log(String(Schema.decodeUnknownExit(schema)({ 1.1: "ignored" }))) +// Success({}) + +console.log(String(Schema.decodeUnknownExit(schema)({ 1: null }))) +// Failure(Cause([Fail(SchemaError(Expected string +// at ["1"]))])) +``` + +### Mutability + +By default, records are tagged as `readonly`. You can mark a record as mutable using `Schema.mutableKey` as you do with structs. + +**Example** (Defining a mutable record) + +```ts +import { Schema } from "effect" + +export const schema = Schema.Record(Schema.String, Schema.mutableKey(Schema.Number)) + +/* +type Type = { + [x: string]: number; +} +*/ +type Type = typeof schema.Type + +/* +type Encoded = { + [x: string]: number; +} +*/ +type Encoded = typeof schema.Encoded +``` + +### Literal Structs + +When you pass a union of string literals as the key schema to `Schema.Record`, you get a struct-like schema where each literal becomes a required key. This mirrors how TypeScript's built-in `Record` type behaves. + +**Example** (Creating a literal struct with fixed string keys) + +```ts +import { Schema } from "effect" + +const schema = Schema.Record(Schema.Literals(["a", "b"]), Schema.Number) + +/* +type Type = { + readonly a: number; + readonly b: number; +} +*/ +type Type = typeof schema.Type +``` + +#### Mutable Keys + +By default, keys are readonly. To make them mutable, use `Schema.mutableKey` just as you would with a standard struct. + +**Example** (Literal struct with mutable keys) + +```ts +import { Schema } from "effect" + +const schema = Schema.Record(Schema.Literals(["a", "b"]), Schema.mutableKey(Schema.Number)) + +/* +type Type = { + a: number; + b: number; +} +*/ +type Type = typeof schema.Type +``` + +#### Optional Keys + +You can make the keys optional by wrapping the value schema with `Schema.optional`. + +**Example** (Literal struct with optional keys) + +```ts +import { Schema } from "effect" + +const schema = Schema.Record(Schema.Literals(["a", "b"]), Schema.optional(Schema.Number)) + +/* +type Type = { + readonly a?: number; + readonly b?: number; +} +*/ +type Type = typeof schema.Type +``` + +## Unions + +A union schema accepts a value if it matches any one of its members. Unions are useful when a field can hold more than one type — for example, a value that is either a string or a number. + +By default, unions are _inclusive_: a value is accepted if it matches **any** of the union's members. + +The members are checked in order, and the first one that matches is used. + +### Excluding Incompatible Members + +If a union member is not compatible with the input, it is automatically excluded during validation. + +**Example** (Excluding incompatible members from the union) + +```ts +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)])) +``` + +If none of the union members match the input, the union fails with a message at the top level. + +**Example** (All members excluded) + +```ts +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)])) +``` + +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. + +**Example** (Validating against a set of literals) + +```ts +import { Schema } from "effect" + +const schema = Schema.Literals(["a", "b"]) + +console.log(String(Schema.decodeUnknownExit(schema)(null))) +// Failure(Cause([Fail(SchemaError: Expected "a" | "b")])) +``` + +### Exclusive Unions + +You can create an exclusive union, where the union matches if exactly one member matches, by passing the `{ mode: "oneOf" }` option. + +**Example** (Exclusive Union) + +```ts +import { Schema } from "effect" + +const schema = Schema.Union([Schema.Struct({ a: Schema.String }), Schema.Struct({ b: Schema.Number })], { + mode: "oneOf" +}) + +console.log(String(Schema.decodeUnknownExit(schema)({ a: "a", b: 1 }))) +// Failure(Cause([Fail(SchemaError: Expected exactly one member to match)])) +``` + +### Deriving Unions + +You can map the members of a union schema using the `mapMembers` static method on `Schema.Union`. The `mapMembers` static method accepts a function from `Union.members` to new members, and returns a new `Schema.Union` based on the result. + +#### Adding Members + +You can add members to a union schema using the `appendElement` and `appendElements` APIs of the `Tuple` module. + +**Example** (Adding members to a union) + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Union +*/ +const schema = Schema.Union([Schema.String, Schema.Number]) + .mapMembers(Tuple.appendElement(Schema.Boolean)) // adds a single member + .mapMembers(Tuple.appendElements([Schema.String, Schema.Number])) // adds multiple members +``` + +#### Mapping individual members + +You can evolve the members of a union schema using the `evolve` API of the `Tuple` module + +**Example** + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Union, + Schema.Number, + Schema.Array$ +]> +*/ +const schema = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]).mapMembers( + Tuple.evolve([ + (v) => Schema.Array(v), + undefined, // no change + (v) => Schema.Array(v) + ]) +) +``` + +#### Mapping all members at once + +You can map all members of a union schema using the `map` API of the `Tuple` module. + +**Example** + +```ts +import { Schema, Tuple } from "effect" + +/* +const schema: Schema.Union, + Schema.Array$, + Schema.Array$ +]> +*/ +const schema = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]).mapMembers(Tuple.map(Schema.Array)) +``` + +### Union of Literals + +You can create a union of literals using `Schema.Literals`. + +```ts +import { Schema } from "effect" + +const schema = Schema.Literals(["red", "green", "blue"]) +``` + +#### Deriving new literals + +You can map the members of a `Schema.Literals` schema using the `mapMembers` method. The `mapMembers` method accepts a function from `Literals.members` to new members, and returns a new `Schema.Union` based on the result. + +```ts +import { Schema, Tuple } from "effect" + +const schema = Schema.Literals(["red", "green", "blue"]).mapMembers( + Tuple.evolve([ + (a) => Schema.Struct({ _tag: a, a: Schema.String }), + (b) => Schema.Struct({ _tag: b, b: Schema.Number }), + (c) => Schema.Struct({ _tag: c, c: Schema.Boolean }) + ]) +) + +/* +type Type = { + readonly _tag: "red"; + readonly a: string; +} | { + readonly _tag: "green"; + readonly b: number; +} | { + readonly _tag: "blue"; + readonly c: boolean; +} +*/ +type Type = (typeof schema)["Type"] +``` + +### Tagged Unions + +You can define a tagged union using the `Schema.TaggedUnion` helper. This is useful when combining multiple tagged structs into a union. + +**Example** (Defining a tagged union with `Schema.TaggedUnion`) + +```ts +import { Schema } from "effect" + +// Create a union of two tagged structs +const schema = Schema.TaggedUnion({ + A: { a: Schema.String }, + B: { b: Schema.Finite } +}) +``` + +This is equivalent to writing: + +```ts +const schema = Schema.Union([ + Schema.TaggedStruct("A", { a: Schema.String }), + Schema.TaggedStruct("B", { b: Schema.Finite }) +]) +``` + +The result is a tagged union schema with built-in helpers based on the tag values. See the next section for more details. + +### Augmenting Tagged Unions + +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. + +**Example** (Adding tag-based helpers to a union) + +```ts +import { Schema } from "effect" + +const original = Schema.Union([ + Schema.Struct({ type: Schema.tag("A"), a: Schema.String }), + Schema.Struct({ type: Schema.tag("B"), b: Schema.Finite }), + Schema.Struct({ type: Schema.tag("C"), c: Schema.Boolean }) +]) + +// Enrich the union with tag-based utilities +const tagged = original.pipe(Schema.toTaggedUnion("type")) +``` + +This helper has some advantages over a dedicated constructor: + +- It does not require changes to the original schema, just call a helper. +- You can apply it to schemas from external sources. +- 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 + +The `cases` property gives direct access to each member schema of the union. + +**Example** (Getting a member schema from a tagged union) + +```ts +const A = tagged.cases.A +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. + +**Example** (Checking membership in a subset of union tags) + +```ts +console.log(tagged.isAnyOf(["A", "B"])({ type: "A", a: "a" })) // true +console.log(tagged.isAnyOf(["A", "B"])({ type: "B", b: 1 })) // true + +console.log(tagged.isAnyOf(["A", "B"])({ type: "C", c: true })) // false +``` + +#### Type Guards + +The `guards` property provides a type guard for each tag. + +**Example** (Using type guards for tagged members) + +```ts +console.log(tagged.guards.A({ type: "A", a: "a" })) // true +console.log(tagged.guards.B({ type: "B", b: 1 })) // true + +console.log(tagged.guards.A({ type: "B", b: 1 })) // false +``` + +#### Matching on a Tag + +You can define a matcher function using the `match` method. This is a concise way to handle each variant of the union. + +**Example** (Handling union members with `match`) + +```ts +const matcher = tagged.match({ + A: (a) => `This is an A: ${a.a}`, + B: (b) => `This is a B: ${b.b}`, + C: (c) => `This is a C: ${c.c}` +}) + +console.log(matcher({ type: "A", a: "a" })) // This is an A: a +console.log(matcher({ type: "B", b: 1 })) // This is a B: 1 +console.log(matcher({ type: "C", c: true })) // This is a C: true +``` + +## Recursive Schemas + +Use `Schema.suspend` when a schema needs to refer to itself (or to another schema that eventually refers back). `suspend` wraps a thunk, so the recursive reference is resolved lazily during decode / encode instead of eagerly during declaration. + +**Example** (Recursive Struct with Same Encoded and Type) + +```ts +import { Schema } from "effect" + +interface Category { + readonly name: string + readonly children: ReadonlyArray +} + +const Category: Schema.Codec = Schema.Struct({ + name: Schema.String, + children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) +}) +``` + +The explicit `Schema.Codec` annotation is important in recursive declarations because `Category` is referenced inside its own initializer. Without the annotation, TypeScript often cannot stabilize the self-referential type and falls back to an implicit `any` style error. + +**Example** (Recursive Struct with Different Encoded and Type) + +```ts +import { Schema } from "effect" + +interface Category { + readonly name: number + readonly children: ReadonlyArray +} + +interface CategoryEncoded { + readonly name: string + readonly children: ReadonlyArray +} + +const Category: Schema.Codec = Schema.Struct({ + name: Schema.FiniteFromString, + children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) +}) +``` + +Here the encoded shape differs from the runtime shape (`name` is `string` when encoded, `number` after decoding), so both type parameters must be explicit: `Schema.Codec`. + +Using only `Schema.Codec` would force encoded and decoded types to be the same, which does not describe this schema. + +**Example** (Recursive Union) + +```ts +import { Schema } from "effect" + +type U = A | B + +interface A { + readonly a: string + readonly next: U +} +interface B { + readonly b: number + readonly next: U +} + +const URef = Schema.suspend((): Schema.Codec => U) + +const A: Schema.Codec = Schema.Struct({ + a: Schema.String, + next: URef +}) + +const B: Schema.Codec = Schema.Struct({ + b: Schema.Number, + next: URef +}) + +const U: Schema.Codec = Schema.Union([A, B]) +``` + +`URef` factors the recursive edge (`U -> U`) into one shared `Schema.suspend` value. Reusing it across members avoids duplicating the lazy reference and makes the intent clear: every variant points back to the same union schema. + +# Declaring Custom Types + +When none of the built-in schema combinators fit your data type, use `Schema.declare` or `Schema.declareConstructor`. + +## `Schema.declare` (non-parametric types) + +`Schema.declare` creates a schema from a **type guard** — a function that checks whether an unknown value is of a given type. This is useful when you have a type that doesn't fit the built-in combinators (like `Struct`, `Array`, etc.) and you need to teach Schema how to recognize it. + +```ts +Schema.declare( + is: (u: unknown) => u is T, + annotations?: { expected?: string; toCodecJson?: ...; ... } +) +``` + +The first argument is your type guard. Schema will call it on any input value: if it returns `true`, decoding succeeds; if `false`, decoding fails. + +**Example** (Creating a schema for `URL`) + +```ts +import { Schema } from "effect" + +// The type guard tells Schema how to recognize a URL instance +const URLSchema = Schema.declare( + (u): u is URL => u instanceof URL +) + +console.log(String(Schema.decodeUnknownExit(URLSchema)(new URL("https://example.com")))) +// Success(https://example.com/) + +console.log(String(Schema.decodeUnknownExit(URLSchema)(null))) +// Failure(Cause([Fail(SchemaError(Expected ))])) +``` + +> **Tip**: For simple `instanceof` checks, prefer `Schema.instanceOf(URL)`, it wraps `Schema.declare` with an `instanceof` guard automatically. + +### Customizing the error message with `expected` + +The default error message `Expected ` is not very descriptive. Use the `expected` annotation (second argument) to provide a human-readable name for your type. + +**Example** (Adding an `expected` annotation) + +```ts +import { Schema } from "effect" + +const URLSchema = Schema.declare( + (u): u is URL => u instanceof URL, + { expected: "URL" } +) + +console.log(String(Schema.decodeUnknownExit(URLSchema)(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 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" + +const URLSchema = Schema.declare( + (u): u is URL => u instanceof URL, + { expected: "URL" } +) + +// Derive a JSON codec from the schema +const codec = Schema.toCodecJson(URLSchema) + +// 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")))) +// 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. + +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, Schema, SchemaIssue, SchemaTransformation } from "effect" + +const URLSchema = Schema.declare( + (u): u is URL => u instanceof URL, + { + expected: "URL", + // Teach Schema how to convert URL <-> JSON + toCodecJson: () => + Schema.link()( + // The JSON representation is a plain string + Schema.String, + // How to convert between URL and string + SchemaTransformation.transformOrFail({ + // JSON string -> URL (may fail if the string is not a valid URL) + decode: (s, options) => + Effect.try({ + try: () => new URL(s), + catch: () => new SchemaIssue.InvalidValue({ message: "Invalid URL string" }, s, options) + }), + // URL -> JSON string (always succeeds) + encode: (url) => Effect.succeed(url.href) + }) + ) + } +) + +const codec = Schema.toCodecJson(URLSchema) + +// Now encoding produces the URL's href string +console.log(String(Schema.encodeUnknownExit(codec)(new URL("https://example.com")))) +// Success("https://example.com/") + +// And decoding parses a string back into a URL +console.log(String(Schema.decodeUnknownExit(codec)("https://example.com"))) +// Success(https://example.com/) +``` + +## `Schema.declareConstructor` (parametric types) + +While `Schema.declare` works for fixed types like `URL` or `File`, some types are **generic** — they contain other types as parameters. Think of `Array`, `Option`, or a custom `Box`. The schema for `Box` is different from `Box` because the inner value has a different type. + +`Schema.declareConstructor` handles this by letting you define a **schema factory**: a function that takes schemas for the type parameters and returns a schema for the full type. + +> **Important:** `declareConstructor` is for types where the **container shape is the same** on both sides: only the inner type parameter changes (e.g. `Box` to `Box`). If you need to convert a structurally different type into your declared type (e.g. `T` to `Box`), first declare `Box` with `declareConstructor`, then define a separate transformation schema to express the conversion. + +### How the two-step call works + +`declareConstructor` uses a curried (two-step) call pattern: + +```ts +Schema.declareConstructor()( + typeParameters, // array of schemas, one per type parameter + run, // factory that produces the parsing function + annotations // optional metadata (same as Schema.declare) +) +``` + +1. **Outer call** `declareConstructor()` — fixes the TypeScript types. `Type` is the decoded type, `Encoded` is the encoded type. +2. **Inner call** `(typeParameters, run, annotations)` — provides the runtime behavior: + - `typeParameters` — an array of schemas, one for each type variable (e.g. `[itemSchema]` for `Box`) + - `run` — a function that receives **resolved codecs** for those type parameters and returns a **parsing function** `(input, ast, options) => Effect` + - `annotations` — optional metadata like `expected`, `toCodecJson`, etc. + +The parsing function you return from `run` is responsible for: + +1. Checking that the input has the right shape (e.g. is an object with a `value` property) +2. Recursively decoding inner values using the provided codecs +3. Returning an `Effect` that succeeds with the decoded value or fails with an issue + +**Example** (A generic `Box` container) + +```ts +import { Effect, Schema, SchemaIssue, SchemaParser } from "effect" + +// 1. Define the type +interface Box { + readonly value: A +} + +// 2. A type guard that checks the shape (ignoring the inner type) +const isBox = (u: unknown): u is Box => typeof u === "object" && u !== null && "value" in u + +// 3. Create a schema factory: given a schema for A, return a schema for Box +const Box = (item: A) => + Schema.declareConstructor, Box>()( + // Pass the inner schema as a type parameter + [item], + // `run` receives the resolved codec for `item` + ([itemCodec]) => + // Return the parsing function + (u, ast, options) => { + // First, check the outer shape + if (!isBox(u)) { + return Effect.fail(new SchemaIssue.InvalidType(ast, u, options)) + } + // Then, decode the inner value using the item codec + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(itemCodec)(u.value, options), + { + onSuccess: (value) => ({ value }), + // Wrap inner errors with a Pointer so the error path shows ["value"] + onFailure: (issue) => new SchemaIssue.Pointer(["value"], issue) + } + ) + } + ) + +// Use it: Box that decodes strings to finite numbers +const schema = Box(Schema.FiniteFromString) + +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 +// at ["value"]))])) +``` + +> `declareConstructor` accepts the same `annotations` as `declare` — including `expected` (for custom error messages) and `toCodecJson` (for JSON serialization). See the [`Schema.declare` section above](#schemadeclare-non-parametric-types) for details on how to use them. + +# Validation + +After defining a schema's shape, you can add validation rules called _filters_. Filters check runtime values against constraints like minimum length, numeric range, or custom predicates. Validation happens at runtime — Schema checks the actual value against the rules you define and reports any violations. + +You can apply filters with the `.check` method or the `Schema.check` function. + +Define custom filters with `Schema.makeFilter`. + +**Example** (Custom filter that checks minimum length) + +```ts +import { Schema } from "effect" + +// Filter: the string must have at least 3 characters +const schema = Schema.String.check(Schema.makeFilter((s) => s.length >= 3)) + +console.log(String(Schema.decodeUnknownExit(schema)(""))) +// Failure(Cause([Fail(SchemaError: Expected )])) +``` + +You can attach annotations and provide a custom error message when defining a filter. + +**Example** (Filter with annotations and a custom message) + +```ts +import { Schema } from "effect" + +// Filter with a title, description, and custom error message +const schema = Schema.String.check( + Schema.makeFilter((s) => s.length >= 3 || `length must be >= 3, got ${s.length}`, { + title: "length >= 3", + description: "a string with at least 3 characters" + }) +) + +console.log(String(Schema.decodeUnknownExit(schema)(""))) +// Failure(Cause([Fail(SchemaError: length must be >= 3, got 0)])) +``` + +### Filter error messages and schema identifiers + +The default formatter chooses the error label from the level that failed: + +- If the input does not match the base schema type, the formatter reports a + type-level failure. In that case, a schema `identifier` is used as the + expected label. +- If the base type matches but a filter fails, the formatter reports a filter + failure. In that case, the filter's `message` annotation is used first, then + its `expected` annotation, and finally `` if neither is provided. + +An `identifier` does not name a failed filter. Use `expected` to name the +filter in the default formatter, or `message` to replace the filter failure +message completely. + +**Example** (Schema identifier versus filter expected message) + +```ts +import { Schema } from "effect" + +const Username = Schema.NonEmptyString.annotate({ identifier: "Username" }) + +console.log(String(Schema.decodeUnknownExit(Username)(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)])) +``` + +### Filter return shapes + +A filter predicate can return any of the shapes described by `Schema.FilterOutput`: + +- `undefined` or `true` — success. +- `false` — generic failure (no custom message). +- `string` — failure with the string used as the error message. +- `SchemaIssue.Issue` — a fully-formed issue, returned as-is (escape hatch for `Composite`, `AnyOf`, etc.). +- `{ path, issue }` — failure attached to a nested path. `issue` can be a `string` (wrapped in an `InvalidValue`) or a full `SchemaIssue.Issue`. +- `ReadonlyArray` — several failures reported together. Empty arrays are success; a single element is unwrapped; multiple entries are grouped into an `Issue.Composite`. + +**Example** (Failure at a nested path) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ password: Schema.String, confirmPassword: Schema.String }).check( + Schema.makeFilter((o) => + o.password === o.confirmPassword + ? undefined + : { path: ["password"], issue: "password and confirmPassword must match" } + ) +) + +console.log(String(Schema.decodeUnknownExit(schema)({ password: "123456", confirmPassword: "1234567" }))) +// Failure(Cause([Fail(SchemaError: password and confirmPassword must match +// at ["password"])])) +``` + +**Example** (Reporting multiple failures at once) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ a: Schema.Finite, b: Schema.Finite, c: Schema.Finite }).check( + Schema.makeFilter((o) => { + const issues: Array = [] + if (o.a > 0) { + if (o.b <= 0) issues.push({ path: ["b"], issue: "b must be greater than 0" }) + if (o.c <= 0) issues.push({ path: ["c"], issue: "c must be greater than 0" }) + } + return issues + }) +) + +console.log(String(Schema.decodeUnknownExit(schema)({ a: 1, b: 0, c: 0 }))) +// Failure(Cause([Fail(SchemaError: b must be greater than 0 +// at ["b"] +// c must be greater than 0 +// at ["c"])])) +``` + +## Preserving Schema Type After Filtering + +Adding a filter does not change the schema's type. You can still use all schema-specific methods (like `.fields` on a struct or `.make`) after calling `.check(...)`. + +**Example** (Chaining filters and annotations without losing type information) + +```ts +import { Schema } from "effect" + +// ┌─── Schema.String +// ▼ +Schema.String + +// ┌─── Schema.String +// ▼ +const NonEmptyString = Schema.String.check(Schema.isNonEmpty()) + +// ┌─── Schema.String +// ▼ +const schema = NonEmptyString.annotate({}) +``` + +Even after adding a filter and an annotation, the schema is still a `Schema.String`. + +**Example** (Accessing struct fields after filtering) + +```ts +import { Schema } from "effect" + +// Define a struct and apply a (dummy) filter +const schema = Schema.Struct({ + name: Schema.String, + age: Schema.Number +}).check(Schema.makeFilter(() => true)) + +// The `.fields` property is still available +const fields = schema.fields +``` + +## Filters as First-Class + +Filters are standalone values that you can define once and reuse across different schemas. The same filter (for example, `Schema.isMinLength`) works on strings, arrays, or any type with a compatible shape. + +You can pass multiple filters to a single `.check(...)` call. + +**Example** (Combining filters on a string) + +```ts +import { Schema } from "effect" + +const schema = Schema.String.check( + Schema.isMinLength(3), // value must be at least 3 chars long + Schema.isTrimmed() // no leading/trailing whitespace +) + +console.log(String(Schema.decodeUnknownExit(schema)(" a"))) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) +``` + +**Example** (Using `isMinLength` with an object that has `length`) + +```ts +import { Schema } from "effect" + +// Object must have a numeric `length` field that is >= 3 +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)])) +``` + +**Example** (Validating array length) + +```ts +import { Schema } from "effect" + +// Array must contain at least 3 strings +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)])) +``` + +## Multiple Issues Reporting + +By default, when `{ errors: "all" }` is passed, all filters are evaluated, even if one fails. This allows multiple issues to be reported at once. + +**Example** (Collecting multiple validation issues) + +```ts +import { Schema } from "effect" + +const schema = Schema.String.check(Schema.isMinLength(3), Schema.isTrimmed()) + +console.log( + String( + Schema.decodeUnknownExit(schema)(" a", { + errors: "all" + }) + ) +) +/* +Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3 +Expected a string with no leading or trailing whitespace)])) +*/ +``` + +## Aborting Validation + +If you want to stop validation as soon as a filter fails, you can call the `abort` method on the filter. + +**Example** (Short-circuit on first failure) + +```ts +import { Schema } from "effect" + +const schema = Schema.String.check( + Schema.isMinLength(3).abort(), // Stop on failure here + Schema.isTrimmed() // This will not run if minLength fails +) + +console.log( + String( + Schema.decodeUnknownExit(schema)(" a", { + errors: "all" + }) + ) +) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) +``` + +## Filter Groups + +Group filters into a reusable unit with `Schema.makeFilterGroup`. This helps when the same set of checks appears in multiple places. + +**Example** (Reusable group for 32-bit integers) + +```ts +import { Schema } from "effect" + +// ┌─── FilterGroup +// ▼ +const isInt32 = Schema.makeFilterGroup( + [Schema.isInt(), Schema.isBetween({ minimum: -2147483648, maximum: 2147483647 })], + { + title: "isInt32", + description: "a 32-bit integer" + } +) +``` + +## Refinements + +Use `Schema.refine` to refine a schema to a more specific type. + +**Example** (Require at least two items in a string array) + +```ts +import { Schema } from "effect" + +// ┌─── refine> +// ▼ +const refined = Schema.Array(Schema.String).pipe( + Schema.refine((arr): arr is readonly [string, string, ...Array] => arr.length >= 2) +) +``` + +## Branding + +Use `Schema.brand` to add a brand to a schema. + +**Example** (Brand a string as a UserId) + +```ts +import { Schema } from "effect" + +// ┌─── Schema.brand +// ▼ +const branded = Schema.String.pipe(Schema.brand("UserId")) +``` + +## Structural Filters + +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**. + +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 + +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** (A nested failure prevents the structural filter from running) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + tags: Schema.Array(Schema.String.check(Schema.isNonEmpty())).check( + Schema.isMinLength(3) // structural filter + ) +}) + +console.log(String(Schema.decodeUnknownExit(schema)({ tags: ["a", ""] }, { errors: "all" }))) +/* +Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1 + at ["tags"][1])])) +*/ +``` + +## Effectful Filters + +Filters passed to `.check(...)` must be synchronous. When you need to call an API or use a service during validation, use an effectful filter instead. Effectful filters run inside an `Effect`, which means they can be asynchronous and access services. + +Define an effectful filter with `Getter.checkEffect` as part of a transformation. + +**Example** (Asynchronous validation of a numeric value) + +```ts +import { Effect, Result, Schema, SchemaGetter, SchemaIssue } from "effect" + +// Simulated API call that fails when userId is 0 +const myapi = (userId: number) => + Effect.gen(function*() { + if (userId === 0) { + return new Error("not found") + } + return { userId } + }).pipe(Effect.delay(100)) + +const schema = Schema.Finite.pipe( + Schema.decode({ + 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({ message: "not found" }, n, options) + : undefined // No issue, value is valid + }) + ), + encode: SchemaGetter.passthrough() + }) +) +``` + +## Filter Factories + +A filter factory is a function that returns a new filter each time you call it, letting you parameterize the constraint (for example, "greater than X" for any value of X). + +**Example** (Factory for a `isGreaterThan` filter on ordered values) + +```ts +import { Order, Schema } from "effect" + +// Create a filter factory for values greater than a given value +export const makeGreaterThan = (options: { + readonly order: Order.Order + readonly annotate?: ((exclusiveMinimum: T) => Schema.Annotations.Filter) | undefined + readonly format?: (value: T) => string | undefined +}) => { + const greaterThan = Order.isGreaterThan(options.order) + const format = options.format ?? globalThis.String + return (exclusiveMinimum: T, annotations?: Schema.Annotations.Filter) => { + return Schema.makeFilter((input) => greaterThan(input, exclusiveMinimum), { + title: `greaterThan(${format(exclusiveMinimum)})`, + description: `a value greater than ${format(exclusiveMinimum)}`, + ...options.annotate?.(exclusiveMinimum), + ...annotations + }) + } +} +``` + +# Constructors + +A constructor creates a value of the schema's type, running all validations at the time of creation. If the value does not satisfy the schema, the constructor throws an error. Every schema exposes a `make` method for this purpose. + +For an alternative that does not throw on schema validation failures, use `Schema.makeOption` (or `SchemaParser.makeOption`), which returns `Option.Some` on success and `Option.None` for schema issues. Non-schema failures, such as defects, still throw. + +```ts +import { Schema, SchemaParser } from "effect" + +const schema = Schema.Struct({ + a: Schema.Number.check(Schema.isGreaterThan(0)) +}) + +console.log(schema.makeOption({ a: 1 })) +// { _id: 'Option', _tag: 'Some', value: { a: 1 } } + +console.log(schema.makeOption({ a: -1 })) +// { _id: 'Option', _tag: 'None' } + +// Equivalent standalone usage: +const parse = SchemaParser.makeOption(schema) + +console.log(parse({ a: 1 })) +// { _id: 'Option', _tag: 'Some', value: { a: 1 } } +``` + +## Constructors in Composed Schemas + +To support constructing values from composed schemas, `make` is now available on all schemas, including unions. + +```ts +import { Schema } from "effect" + +const schema = Schema.Union([Schema.Struct({ a: Schema.String }), Schema.Struct({ b: Schema.Number })]) + +schema.make({ a: "hello" }) +schema.make({ b: 1 }) +``` + +## Branded Constructors + +Branding adds an invisible marker to a type so that values from different domains cannot be accidentally mixed — even when they have the same underlying shape (for example, both are `string`). For branded schemas, the default constructor accepts an unbranded input and returns a branded output. + +```ts +import { Schema } from "effect" + +const schema = Schema.String.pipe(Schema.brand<"a">()) + +// make(input: string, options?: Schema.MakeOptions): string & Brand<"a"> +schema.make +``` + +However, when a branded schema is part of a composite (such as a struct), you must pass a branded value. + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String.pipe(Schema.brand<"a">()), + b: Schema.Number +}) + +/* +make(input: { + readonly a: string & Brand<"a">; + readonly b: number; +}, options?: Schema.MakeOptions): { + readonly a: string & Brand<"a">; + readonly b: number; +} +*/ +schema.make +``` + +## Refined Constructors + +For refined schemas, the constructor accepts the unrefined type and returns the refined one. + +```ts +import { Option, Schema } from "effect" + +const schema = Schema.Option(Schema.String).pipe(Schema.refine(Option.isSome)) + +// make(input: Option.Option, options?: Schema.MakeOptions): Option.Some +schema.make +``` + +As with branding, when used in a composite schema, the refined value must be provided. + +```ts +import { Option, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.Option(Schema.String).pipe(Schema.refine(Option.isSome)), + b: Schema.Number +}) + +/* +make(input: { + readonly a: Option.Some; + readonly b: number; +}, options?: Schema.MakeOptions): { + readonly a: Option.Some; + readonly b: number; +} +*/ +schema.make +``` + +## Default Values in Constructors + +You can define a default value for a field using `Schema.withConstructorDefault`. If no value is provided at runtime (either the key is missing or the value is `undefined`), the constructor uses this default. + +**Example** (Providing a default number) + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.Number.pipe(Schema.withConstructorDefault(Effect.succeed(-1))) +}) + +console.log(schema.make({ a: 5 })) +// { a: 5 } + +console.log(schema.make({})) +// { a: -1 } +``` + +The Effect passed to `withConstructorDefault` will be executed each time a default value is needed. + +**Example** (Re-executing the default function) + +```ts +import { Effect, Schema } from "effect" + +let counter = 0 + +const schema = Schema.Struct({ + a: Schema.Date.pipe(Schema.withConstructorDefault(Effect.sync(() => new Date(counter++)))) +}) + +console.log(schema.make({})) +// { a: 1970-01-01T00:00:00.000Z } + +console.log(schema.make({})) +// { a: 1970-01-01T00:00:00.001Z } +``` + +### Nested Constructor Default Values + +Default values can be nested inside composed schemas. In this case, inner defaults are resolved first. + +**Example** (Nested default values) + +```ts +import { Effect, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.Struct({ + b: Schema.Number.pipe(Schema.withConstructorDefault(Effect.succeed(-1))) + }).pipe(Schema.withConstructorDefault(Effect.succeed({}))) +}) + +console.log(schema.make({})) +// { a: { b: -1 } } +console.log(schema.make({ a: {} })) +// { a: { b: -1 } } +``` + +## Effectful Defaults + +Default values can also come from an `Effect`, for example, reading from a configuration service or performing an asynchronous operation. The environment must be `never` (no required services). + +**Example** (Using an effect to provide a default) + +```ts +import { Effect, Schema, SchemaParser } from "effect" + +const schema = Schema.Struct({ + a: Schema.Number.pipe( + Schema.withConstructorDefault( + Effect.gen(function*() { + yield* Effect.sleep(100) + return -1 + }) + ) + ) +}) + +SchemaParser.makeEffect(schema)({}).pipe(Effect.runPromise).then(console.log) +// { a: -1 } +``` + +**Example** (Providing a default from an optional service) + +```ts +import { Context, Effect, Option, Schema, SchemaParser } from "effect" + +// Define a service that may provide a default value +class ConstructorService extends Context.Service }>()( + "ConstructorService" +) {} + +const schema = Schema.Struct({ + a: Schema.Number.pipe( + Schema.withConstructorDefault( + Effect.gen(function*() { + yield* Effect.sleep(100) + const oservice = yield* Effect.serviceOption(ConstructorService) + if (Option.isNone(oservice)) { + return -1 + } + return yield* oservice.value.defaultValue + }) + ) + ) +}) + +SchemaParser.makeEffect(schema)({}) + .pipe( + Effect.provideService(ConstructorService, ConstructorService.of({ defaultValue: Effect.succeed(0) })), + Effect.runPromise + ) + .then(console.log, console.error) +// { a: 0 } +``` + +# Transformations + +Transformations convert values from one type to another during decoding or encoding. They are standalone, reusable objects you compose with schemas. + +## Transformations as First-Class + +In previous versions, transformations were directly embedded in schemas. In the current version, they are defined as independent values that can be reused across schemas. + +**Example** (Previous approach: inline transformation) + +```ts +const Trim = transform( + String, + Trimmed, + // non re-usable transformation + { + decode: (i) => i.trim(), + encode: identity + } +) {} +``` + +This style made it difficult to reuse logic across different schemas. + +Now, transformations like `trim` are declared once and reused wherever needed. + +**Example** (The `trim` built-in transformation) + +```ts +import { SchemaTransformation } from "effect" + +// const t: Transformation +const t = SchemaTransformation.trim() +``` + +You can apply a transformation to any compatible schema. In this example, `trim` is applied to a string schema using `Schema.decode` (more on this later). + +**Example** (Applying `trim` to a string schema) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const schema = Schema.String.pipe(Schema.decode(SchemaTransformation.trim())) + +console.log(Schema.decodeUnknownSync(schema)(" 123")) +// 123 +``` + +## The Transformation Type + +A `Transformation` carries four type parameters: + +```ts +Transformation +``` + +- `T`: the decoded (output) type +- `E`: the encoded (input) type +- `RD`: the context used while decoding +- `RE`: the context used while encoding + +A `Transformation` consists of two `Getter` functions: + +- `decode: Getter` — transforms a value during decoding +- `encode: Getter` — transforms a value during encoding + +Each `Getter` receives an input and an optional context and returns either a value or an error. Getters can be composed to build more complex logic. + +**Example** (Implementation of `Transformation.trim`) + +```ts +/** + * @category String transformations + * @since 4.0.0 + */ +export function trim(): Transformation { + return new Transformation(Getter.trim(), Getter.passthrough()) +} +``` + +In this case: + +- The `decode` process uses `Getter.trim()` to remove leading and trailing whitespace. +- The `encode` process uses `Getter.passthrough()`, which returns the input as is. + +## Composing Transformations + +You can combine transformations using the `.compose` method. The resulting transformation applies the `decode` and `encode` logic of both transformations in sequence. + +**Example** (Trim and lowercase a string) + +```ts +import { Option, SchemaTransformation } from "effect" + +// Compose two transformations: trim followed by toLowerCase +const trimToLowerCase = SchemaTransformation.trim().compose(SchemaTransformation.toLowerCase()) + +// Run the decode logic manually to inspect the result +console.log(trimToLowerCase.decode.run(Option.some(" Abc"), {})) +/* +{ + _id: 'Exit', + _tag: 'Success', + value: { _id: 'Option', _tag: 'Some', value: 'abc' } +} +*/ +``` + +In this example: + +- The `decode` logic applies `Getter.trim()` followed by `Getter.toLowerCase()`, producing a string that is trimmed and lowercased. +- The `encode` logic is `Getter.passthrough()`, which simply returns the input as-is. + +## Transforming One Schema into Another + +To define how one schema transforms into another, you can use: + +- `Schema.decodeTo` (and its inverse `Schema.encodeTo`) +- `Schema.decode` (and its inverse `Schema.encode`) + +These functions let you attach transformations to schemas, defining how values should be converted during decoding or encoding. + +### decodeTo + +Use `Schema.decodeTo` when you want to transform a source schema into a different target schema. + +You must provide: + +1. The target schema +2. An optional transformation + +If no transformation is provided, the operation is called "schema composition" (see below). + +**Example** (Parsing a number from a string) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const NumberFromString = + // source schema: String + Schema.String.pipe( + Schema.decodeTo( + Schema.Number, // target schema: Number + SchemaTransformation.numberFromString // built-in transformation that coerce a string to a number (and back) + ) + ) + +console.log(Schema.decodeUnknownSync(NumberFromString)("123")) +// 123 +console.log(Schema.decodeUnknownSync(NumberFromString)("a")) +// NaN +``` + +### decode + +Use `Schema.decode` when the source and target schemas are the same and you only want to apply a transformation. + +This is a shorter version of `decodeTo`. + +**Example** (Trimming whitespace from a string) + +```ts +import { Schema, SchemaTransformation } from "effect" + +// Equivalent to decodeTo(Schema.String, Transformation.trim()) +const TrimmedString = Schema.String.pipe(Schema.decode(SchemaTransformation.trim())) +``` + +### Defining an Inline Transformation + +You can create a transformation directly using helpers from the `SchemaTransformation` module. + +For example, `SchemaTransformation.transform` lets you define a simple transformation by providing `decode` and `encode` functions. + +**Example** (Converting meters to kilometers and back) + +```ts +import { Schema, SchemaTransformation } from "effect" + +// Defines a transformation that converts meters (number) to kilometers (number) +// 1000 meters -> 1 kilometer (decode) +// 1 kilometer -> 1000 meters (encode) +const Kilometers = Schema.Finite.pipe( + Schema.decode( + SchemaTransformation.transform({ + decode: (meters) => meters / 1000, + encode: (kilometers) => kilometers * 1000 + }) + ) +) +``` + +You can define transformations that may fail during decoding or encoding using `SchemaTransformation.transformOrFail`. + +This is useful when you need to validate input or enforce rules that may not always succeed. + +**Example** (Converting a string URL into a `URL` object) + +```ts +import { Effect, Schema, SchemaIssue, SchemaTransformation } from "effect" + +const URLFromString = Schema.String.pipe( + Schema.decodeTo( + Schema.instanceOf(URL), + SchemaTransformation.transformOrFail({ + decode: (s, options) => + Effect.try({ + try: () => new URL(s), + catch: () => new SchemaIssue.InvalidValue({ message: "Invalid URL string" }, s, options) + }), + encode: (url) => Effect.succeed(url.href) + }) + ) +) +``` + +## Schema composition + +You can compose transformations, but you can also compose schemas with `Schema.decodeTo`. + +**Example** (Converting meters to miles via kilometers) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const KilometersFromMeters = Schema.Finite.pipe( + Schema.decode( + SchemaTransformation.transform({ + decode: (meters) => meters / 1000, + encode: (kilometers) => kilometers * 1000 + }) + ) +) + +const MilesFromKilometers = Schema.Finite.pipe( + Schema.decode( + SchemaTransformation.transform({ + decode: (kilometers) => kilometers * 0.621371, + encode: (miles) => miles / 0.621371 + }) + ) +) + +const MilesFromMeters = KilometersFromMeters.pipe(Schema.decodeTo(MilesFromKilometers)) +``` + +This approach does not require the source and target schemas to be type-compatible. If you need more control over type compatibility, you can use one of the `Transformation.passthrough*` helpers. + +## Passthrough Helpers + +The `passthrough`, `passthroughSubtype`, and `passthroughSupertype` helpers let you compose schemas by describing how their types relate. + +### passthrough + +Use `passthrough` when the encoded output of the target schema matches the type of the source schema. + +**Example** (When `To.Encoded === From.Type`) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const From = Schema.Struct({ + a: Schema.String +}) + +const To = Schema.Struct({ + a: Schema.FiniteFromString +}) + +// To.Encoded (string) = From.Type (string) +const schema = From.pipe(Schema.decodeTo(To, SchemaTransformation.passthrough())) +``` + +### passthroughSubtype + +Use `passthroughSubtype` when the source type is a subtype of the target's encoded output. + +**Example** (When `From.Type` is a subtype of `To.Encoded`) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const From = Schema.FiniteFromString + +const To = Schema.UndefinedOr(Schema.Number) + +// From.Type (number) extends To.Encoded (number | undefined) +const schema = From.pipe(Schema.decodeTo(To, SchemaTransformation.passthroughSubtype())) +``` + +### passthroughSupertype + +Use `passthroughSupertype` when the target's encoded output is a subtype of the source type. + +**Example** (When `To.Encoded` is a subtype of `From.Type`) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const From = Schema.UndefinedOr(Schema.String) + +const To = Schema.FiniteFromString + +// To.Encoded (string) extends From.Type (string | undefined) +const schema = From.pipe(Schema.decodeTo(To, SchemaTransformation.passthroughSupertype())) +``` + +### Turning off strict mode + +Strict mode ensures that decoding and encoding fully match. You can disable it by passing `{ strict: false }` to `passthrough`. + +**Example** (Turning off strict mode) + +```ts +import { Schema, SchemaTransformation } from "effect" + +const From = Schema.Struct({ + a: Schema.Literals(["a", "b"]), + b: 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 }))) +``` + +## Managing Optional Keys + +You can control how optional values are handled during transformations using the `SchemaTransformation.transformOptional` helper. + +This helper works with `Option` and returns an `Option`, where: + +- `E` is the encoded type +- `T` is the decoded type + +This function is useful when dealing with optional values that may be present or missing during decoding or encoding. + +If the input is `Option.none()`, it means the value is not provided. +If it is `Option.some(value)`, then the transformation logic is applied to `value`. + +You control the optionality of the output by returning an `Option`: + +- `Option.none()`: exclude the key from the output +- `Option.some(transformedValue)`: include the transformed value + +**Example** (Optional string key transformed to `Option`) + +```ts +import { Option, Schema, SchemaTransformation } from "effect" + +const OptionFromNonEmptyString = Schema.optionalKey(Schema.String).pipe( + Schema.decodeTo( + Schema.Option(Schema.NonEmptyString), + SchemaTransformation.transformOptional({ + // Convert empty strings to None, and non-empty strings to Some(value) + decode: (oe) => + Option.isSome(oe) && oe.value !== "" ? Option.some(Option.some(oe.value)) : Option.some(Option.none()), + + // Flatten nested Options back to a single optional string + encode: (ot) => Option.flatten(ot) + }) + ) +) + +const schema = Schema.Struct({ + foo: OptionFromNonEmptyString +}) + +// Decoding examples + +console.log(Schema.decodeUnknownSync(schema)({})) +// Output: { foo: None } + +console.log(Schema.decodeUnknownSync(schema)({ foo: "" })) +// Output: { foo: None } + +console.log(Schema.decodeUnknownSync(schema)({ foo: "hi" })) +// Output: { foo: Some("hi") } + +// Encoding examples + +console.log(Schema.encodeSync(schema)({ foo: Option.none() })) +// Output: {} + +console.log(Schema.encodeSync(schema)({ foo: Option.some("hi") })) +// Output: { foo: "hi" } +``` + +## Omitting a Key During Encoding + +Use `SchemaGetter.omit()` to exclude a field from the encoded output. At runtime, `omit()` returns `Option.none()`, which tells the struct parser to skip writing that key. + +For this to work, the encoded side must be marked as optional with `Schema.optionalKey`. Otherwise, producing `None` for a required field causes a `MissingKey` error. + +**Example** (Field present when decoded, omitted when encoded) + +```ts +import { Effect, Schema, SchemaGetter } from "effect" + +const schema = Schema.Struct({ + a: Schema.FiniteFromString, + b: Schema.String.pipe( + Schema.encodeTo(Schema.optionalKey(Schema.String), { + decode: SchemaGetter.withDefault(Effect.succeed("default_value")), + encode: SchemaGetter.omit() + }) + ) +}) + +// ┌─── { readonly a: string; readonly b?: string; } +// ▼ +type Encoded = typeof schema.Encoded + +// ┌─── { readonly a: number; readonly b: string; } +// ▼ +type Type = typeof schema.Type + +console.log(Schema.decodeUnknownSync(schema)({ a: "1", b: "value" })) +// Output: { a: 1, b: "value" } + +console.log(Schema.decodeUnknownSync(schema)({ a: "1" })) +// Output: { a: 1, b: "default_value" } + +console.log(Schema.encodeSync(schema)({ a: 1, b: "default_value" })) +// Output: { a: "1" } +``` + +For the common case of a discriminator tag that should be omitted during encoding, use `Schema.tagDefaultOmit`: + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + _tag: Schema.tagDefaultOmit("MyTag"), + a: Schema.FiniteFromString +}) + +console.log(Schema.decodeUnknownSync(schema)({ a: "1" })) +// Output: { a: 1, _tag: "MyTag" } + +console.log(Schema.encodeSync(schema)({ a: 1, _tag: "MyTag" })) +// Output: { a: "1" } +``` + +# Flipping Schemas + +Flipping a schema swaps its decoding and encoding directions. If a schema decodes a `string` into a `number`, the flipped version decodes a `number` into a `string`. This is useful when you want to reuse an existing schema but invert its direction. + +**Example** (Flipping a schema that parses a string into a number) + +```ts +import { Schema } from "effect" + +// Flips a schema that decodes a string into a number, +// turning it into one that decodes a number into a string +// +// ┌─── flip +// ▼ +const StringFromFinite = Schema.flip(Schema.FiniteFromString) +``` + +You can access the original schema using the `.schema` property: + +**Example** (Accessing the original schema) + +```ts +import { Schema } from "effect" + +const StringFromFinite = Schema.flip(Schema.FiniteFromString) + +// ┌─── FiniteFromString +// ▼ +StringFromFinite.schema +``` + +Flipping a schema twice returns a schema with the same structure and behavior as the original: + +**Example** (Double flipping restores the original schema) + +```ts +import { Schema } from "effect" + +// ┌─── FiniteFromString +// ▼ +const schema = Schema.flip(Schema.flip(Schema.FiniteFromString)) +``` + +## How it works + +All internal operations in the Schema AST are symmetrical. Encoding with a schema is equivalent to decoding with its flipped version: + +```ts +// Encoding with a schema is the same as decoding with its flipped version +encode(schema) = decode(flip(schema)) +``` + +This symmetry ensures that flipping works consistently across all schema types. + +## Flipped constructors + +A flipped schema also includes a constructor. It builds values of the **encoded** type from the original schema. + +**Example** (Using a flipped schema to construct an encoded value) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.FiniteFromString +}) + +/* +type Encoded = { + readonly a: string; +} +*/ +type Encoded = (typeof schema)["Encoded"] + +// make: { readonly a: string } ──▶ { readonly a: string } +Schema.flip(schema).make +``` + +# Classes and Opaque Types + +Schema supports two kinds of nominal types: _opaque structs_ for lightweight distinct types, and _classes_ for full-featured types with methods and prototype-backed instances. + +## Opaque Structs + +Goal: opaque typing without changing runtime behavior. + +`Schema.Opaque` lets you take an ordinary `Schema.Struct` and wrap it in a thin class shell whose **only** purpose is to create a distinct TypeScript type. + +Internally the value is **still the same plain struct schema**. + +Instance methods and custom constructors **are not allowed** in opaque structs (no `new ...`). +This is not enforced at the type level, but it may be enforced through a linter in the future. + +### How is this different from `Schema.Class`? + +`Schema.Class` also wraps a `Struct`, **but** it turns the wrapper into a proper class: + +- You can add instance methods, getters, setters, custom constructors. +- Instances compare structurally with `Equal.equals`, but they do not implement `Equal`. +- Instances carry the class prototype at runtime, so `instanceof` checks succeed and methods are callable. + +**Example** (Creating an Opaque Struct) + +```ts +import { Schema } from "effect" + +class Person extends Schema.Opaque()( + Schema.Struct({ + name: Schema.String + }) +) {} + +// ┌─── Codec +// ▼ +const codec = Schema.revealCodec(Person) + +// const person: Person +const person = Person.make({ name: "John" }) + +console.log(person.name) +// "John" + +// The class itself holds the original schema and its metadata +console.log(Person) +// -> [Function: Person] Struct$ + +// { readonly name: Schema.String } +Person.fields + +/* +const another: Schema.Struct<{ + readonly name: typeof Person; +}> +*/ +const another = Schema.Struct({ name: Person }) // You can use the opaque type inside other schemas + +/* +type Type = { + readonly name: Person; +} +*/ +type Type = (typeof another)["Type"] +``` + +Opaque structs can be used just like regular structs, with no other changes needed. + +**Example** (Retrieving Schema Fields) + +```ts +import { Schema } from "effect" + +// A function that takes a generic struct +const getFields = (struct: Schema.Struct) => struct.fields + +class Person extends Schema.Opaque()( + Schema.Struct({ + name: Schema.String + }) +) {} + +/* +const fields: { + readonly name: Schema.String; +} +*/ +const fields = getFields(Person) +``` + +### Static methods + +You can add static members to an opaque struct class to extend its behavior. + +**Example** (Custom serializer via static method) + +```ts +import { Schema } from "effect" + +class Person extends Schema.Opaque()( + Schema.Struct({ + name: Schema.String, + createdAt: Schema.Date + }) +) { + // Create a custom serializer using the class itself + static readonly serializer = Schema.toCodecJson(this) +} + +console.log( + Schema.encodeUnknownSync(Person)({ + name: "John", + createdAt: new Date() + }) +) +// { name: 'John', createdAt: 2025-05-02T13:49:29.926Z } + +console.log( + Schema.encodeUnknownSync(Person.serializer)({ + name: "John", + createdAt: new Date() + }) +) +// { name: 'John', createdAt: '2025-05-02T13:49:29.928Z' } +``` + +### Annotations and filters + +You can attach filters and annotations to the struct passed into `Opaque`. + +**Example** (Applying a filter and title annotation) + +```ts +import { Schema } from "effect" + +class Person extends Schema.Opaque()( + Schema.Struct({ + name: Schema.String + }).annotate({ identifier: "Person" }) +) {} + +console.log(String(Schema.decodeUnknownExit(Person)(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. + +```ts +import { Schema } from "effect" + +class Person extends Schema.Opaque()( + Schema.Struct({ + name: Schema.String + }) +) {} + +/* +const S: Schema.Struct<{ + readonly name: Schema.String; +}> +*/ +const S = Person.annotate({ title: "Person" }) // `annotate` returns the wrapped struct type +``` + +### Recursive Opaque Structs + +**Example** (Recursive Opaque Struct with Same Encoded and Type) + +```ts +import { Schema } from "effect" + +export class Category extends Schema.Opaque()( + Schema.Struct({ + name: Schema.String, + children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) + }) +) {} + +/* +type Encoded = { + readonly children: readonly Category[]; + readonly name: string; +} +*/ +export type Encoded = (typeof Category)["Encoded"] +``` + +**Example** (Recursive Opaque Struct with Different Encoded and Type) + +```ts +import { Schema } from "effect" + +interface CategoryEncoded extends Schema.Codec.Encoded {} + +export class Category extends Schema.Opaque()( + Schema.Struct({ + name: Schema.FiniteFromString, + children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) + }) +) {} + +/* +type Encoded = { + readonly children: readonly CategoryEncoded[]; + readonly name: string; +} +*/ +export type Encoded = (typeof Category)["Encoded"] +``` + +**Example** (Mutually Recursive Schemas) + +```ts +import { Schema } from "effect" + +class Expression extends Schema.Opaque()( + Schema.Struct({ + type: Schema.Literal("expression"), + value: Schema.Union([Schema.Number, Schema.suspend((): Schema.Codec => Operation)]) + }) +) {} + +class Operation extends Schema.Opaque()( + Schema.Struct({ + type: Schema.Literal("operation"), + operator: Schema.Literals(["+", "-"]), + left: Expression, + right: Expression + }) +) {} + +/* +type Encoded = { + readonly type: "operation"; + readonly operator: "+" | "-"; + readonly left: { + readonly type: "expression"; + readonly value: number | Operation; + }; + readonly right: { + readonly type: "expression"; + readonly value: number | Operation; + }; +} +*/ +export type Encoded = (typeof Operation)["Encoded"] +``` + +### Branded Opaque Structs + +You can brand an opaque struct using the `Brand` generic parameter. + +**Example** (Branded Opaque Struct) + +```ts +import { Schema } from "effect" + +class A extends Schema.Opaque()( + Schema.Struct({ + a: Schema.String + }) +) {} +class B extends Schema.Opaque()( + Schema.Struct({ + a: Schema.String + }) +) {} + +const f = (a: A) => a +const g = (b: B) => b + +f(A.make({ a: "a" })) // ok +g(B.make({ a: "a" })) // ok + +f(B.make({ a: "a" })) // error: Argument of type 'B' is not assignable to parameter of type 'A'. +g(A.make({ a: "a" })) // error: Argument of type 'A' is not assignable to parameter of type 'B'. +``` + +Like with branded classes, you can use the `Brand` module to create branded opaque structs. + +```ts +import { Schema } from "effect" +import type { Brand } from "effect" + +class A extends Schema.Opaque>()( + Schema.Struct({ + a: Schema.String + }) +) {} +class B extends Schema.Opaque>()( + Schema.Struct({ + a: Schema.String + }) +) {} + +const f = (a: A) => a +const g = (b: B) => b + +f(A.make({ a: "a" })) // ok +g(B.make({ a: "a" })) // ok + +f(B.make({ a: "a" })) // error: Argument of type 'B' is not assignable to parameter of type 'A'. +g(A.make({ a: "a" })) // error: Argument of type 'A' is not assignable to parameter of type 'B'. +``` + +## Schema as a Class + +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. + +### Wrapping a Primitive Schema + +```ts +import { Schema } from "effect" + +class MyString extends Schema.String { + static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) +} + +console.log(MyString.decodeUnknownSync("a")) +// "a" +``` + +### Wrapping a Struct Schema + +```ts +import { Schema } from "effect" + +class MyStruct extends Schema.Struct({ name: Schema.String }) { + static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) +} + +console.log(MyStruct.decodeUnknownSync({ name: "a" })) +// { name: "a" } +``` + +### Subclassing + +You can extend a schema class to layer on more static helpers: + +```ts +import { Schema } from "effect" + +class MyString extends Schema.FiniteFromString { + static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) +} + +class MyString2 extends MyString { + static readonly encodeSync = Schema.encodeSync(this) +} + +console.log(MyString2.decodeUnknownSync("1")) +// 1 +console.log(MyString2.encodeSync(1)) +// "1" +``` + +## Classes + +### Existing Classes + +#### Validating the Constructor + +**Use Case**: When you want to validate the constructor arguments of an existing class. + +**Example** (Using a tuple to validate the constructor arguments) + +```ts +import { Schema } from "effect" + +const PersonConstructorArguments = Schema.Tuple([Schema.String, Schema.Finite]) + +// Existing class +class Person { + constructor(readonly name: string, readonly age: number) { + PersonConstructorArguments.make([name, age]) + } +} + +try { + new Person("John", NaN) +} catch (error) { + if (error instanceof Error) { + console.log(error.message) + } +} +/* +Expected a finite number + at [1] +*/ +``` + +**Example** (Inheritance) + +```ts +import { Schema } from "effect" + +const PersonConstructorArguments = Schema.Tuple([Schema.String, Schema.Finite]) + +class Person { + constructor(readonly name: string, readonly age: number) { + PersonConstructorArguments.make([name, age]) + } +} + +const PersonWithEmailConstructorArguments = Schema.Tuple([Schema.String]) + +class PersonWithEmail extends Person { + constructor(name: string, age: number, readonly email: string) { + // Only validate the additional argument + PersonWithEmailConstructorArguments.make([email]) + super(name, age) + } +} +``` + +#### Defining a Schema + +```ts +import { Schema, SchemaTransformation } from "effect" + +class Person { + constructor(readonly name: string, readonly age: number) {} +} + +const PersonSchema = Schema.instanceOf(Person, { + title: "Person", + // optional: default JSON serialization + toCodecJson: () => + Schema.link()( + Schema.Tuple([Schema.String, Schema.Number]), + SchemaTransformation.transform({ + decode: (args) => new Person(...args), + encode: (instance) => [instance.name, instance.age] as const + }) + ) +}) + // optional: explicit encoding + .pipe( + Schema.encodeTo( + Schema.Struct({ + name: Schema.String, + age: Schema.Number + }), + SchemaTransformation.transform({ + decode: (args) => new Person(args.name, args.age), + encode: (instance) => instance + }) + ) + ) +``` + +**Example** (Inheritance) + +```ts +import { Schema, SchemaTransformation } from "effect" + +class Person { + constructor(readonly name: string, readonly age: number) {} +} + +const PersonSchema = Schema.instanceOf(Person, { + title: "Person", + // optional: default JSON serialization + toCodecJson: () => + Schema.link()( + Schema.Tuple([Schema.String, Schema.Number]), + SchemaTransformation.transform({ + decode: (args) => new Person(...args), + encode: (instance) => [instance.name, instance.age] as const + }) + ) +}) + // optional: explicit encoding + .pipe( + Schema.encodeTo( + Schema.Struct({ + name: Schema.String, + age: Schema.Number + }), + SchemaTransformation.transform({ + decode: (args) => new Person(args.name, args.age), + encode: (instance) => instance + }) + ) + ) + +class PersonWithEmail extends Person { + constructor(name: string, age: number, readonly email: string) { + super(name, age) + } +} + +// const PersonWithEmailSchema = ...repeat the pattern above... +``` + +#### Errors + +**Example** (Extending Data.Error) + +```ts +import { Data, Effect, identity, Schema, SchemaTransformation } from "effect" + +const Props = Schema.Struct({ + message: Schema.String +}) + +class Err extends Data.Error { + constructor(props: typeof Props.Type) { + super(Props.make(props)) + } +} + +const program = Effect.gen(function*() { + yield* new Err({ message: "Uh oh" }) +}) + +Effect.runPromiseExit(program).then((exit) => console.log(JSON.stringify(exit, null, 2))) +/* +{ + "_id": "Exit", + "_tag": "Failure", + "cause": { + "_id": "Cause", + "failures": [ + { + "_tag": "Fail", + "error": { + "message": "Uh oh" + } + } + ] + } +} +*/ + +const transformation = SchemaTransformation.transform({ + decode: (props) => new Err(props), + encode: identity +}) + +const schema = Schema.instanceOf(Err, { + title: "Err", + serialization: { + json: () => Schema.link()(Props, transformation) + } +}).pipe(Schema.encodeTo(Props, transformation)) +``` + +### Class API + +**Example** (Constructing and decoding a class) + +```ts +import { Schema } from "effect" + +// Define a class with a single string field "a" +class A extends Schema.Class("A")({ + a: Schema.String +}) { + // Regular class fields are allowed + readonly _a = 1 +} + +console.log(new A({ a: "a" })) +// A { a: 'a', _a: 1 } +console.log(A.make({ a: "a" })) +// A { a: 'a', _a: 1 } +console.log(Schema.decodeUnknownSync(A)({ a: "a" })) +// A { a: 'a', _a: 1 } +``` + +#### Filters + +To attach a filter to the whole class, pass a `Struct` instead of a field record and call `.check(...)` on it. + +**Example** (Validating a relationship between fields) + +```ts +import { Schema } from "effect" + +class A extends Schema.Class("A")( + Schema.Struct({ + a: Schema.String, + b: Schema.String + }).check(Schema.makeFilter(({ a, b }) => a === b, { title: "a === b" })) +) {} + +try { + new A({ a: "a", b: "b" }) +} catch (error: any) { + console.log(error.message) +} +// Expected a === b + +try { + Schema.decodeUnknownSync(A)({ a: "a", b: "b" }) +} catch (error: any) { + console.log(error.message) +} +// Expected a === b +``` + +#### Branded Classes + +Attach a brand to a class to avoid mixing values from different domains that share the same structure. + +**Example** (Unique brands block assignment) + +```ts +import { Schema } from "effect" + +// Brand the class using a unique symbol type parameter +class A extends Schema.Class("A")({ + a: Schema.String +}) {} + +class B extends Schema.Class("B")({ + a: Schema.String +}) {} + +// Even though A and B have the same fields, their brands are different, +// so they are not assignable to each other. + +// @ts-expect-error +export const a: A = B.make({ a: "a" }) +// @ts-expect-error +export const b: B = A.make({ a: "a" }) +``` + +**Example** (Using the Brand module) + +```ts +import type { Brand } from "effect" +import { Schema } from "effect" + +class A extends Schema.Class>("A")({ + a: Schema.String +}) {} + +class B extends Schema.Class>("B")({ + a: Schema.String +}) {} + +// Different named brands are still not assignable + +// @ts-expect-error +export const a: A = B.make({ a: "a" }) +// @ts-expect-error +export const b: B = A.make({ a: "a" }) +``` + +#### Annotations + +Attach metadata to a class schema. The metadata is stored as annotations on the schema AST and can be read at runtime. + +**Example** (Attaching and reading annotations) + +```ts +import { Schema } from "effect" + +export class A extends Schema.Class("A")( + { + a: Schema.String + }, + // Attach metadata (e.g., title) alongside the schema + { title: "my title" } +) {} + +console.log(A.ast.annotations?.title) +// "my title" +``` + +#### extend + +Use `extend` to create a subclass that adds fields to the base schema. Instance fields declared on the base class are also available on the subclass. + +**Example** (Extending a class with new fields) + +```ts +import { Schema } from "effect" + +// Base class with one schema field ("a") and one regular class field ("_a") +class A extends Schema.Class("A")( + Schema.Struct({ + a: Schema.String + }) +) { + readonly _a = 1 +} + +// Subclass adds a new schema field ("b") and its own regular field ("_b") +class B extends A.extend("B")({ + b: Schema.Number +}) { + readonly _b = 2 +} + +console.log(new B({ a: "a", b: 2 })) +// B { a: 'a', _a: 1, _b: 2 } +console.log(B.make({ a: "a", b: 2 })) +// B { a: 'a', _a: 1, _b: 2 } +console.log(Schema.decodeUnknownSync(B)({ a: "a", b: 2 })) +// B { a: 'a', _a: 1, _b: 2 } +``` + +#### extends and static members + +To keep static members from the base class, pass `typeof Base` as the second generic parameter when calling `extend`. + +**Example** (Preserving static members on subclasses) + +```ts +import { Schema } from "effect" + +class A extends Schema.Class("A")({ + a: Schema.String +}) { + static readonly foo = "foo" +} + +class B extends A.extend("B")({ + b: Schema.Number +}) {} + +console.log(B.foo) +// "foo" +``` + +#### Recursive Classes + +Use `Schema.suspend` to reference a class inside its own definition. This is common for tree-like data structures. + +**Example** (Self-referential tree structure) + +```ts +import { Schema } from "effect" + +// A simple tree of categories where each node can have child categories. +// Use Schema.suspend to refer to Category while it is being defined. +export class Category extends Schema.Class("Category")( + Schema.Struct({ + name: Schema.String, + children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) + }) +) {} + +/* +type Encoded = { + readonly children: readonly Category[]; + readonly name: string; +} +*/ +export type Encoded = (typeof Category)["Encoded"] +``` + +**Example** (Recursive schema with different Encoded and Type) + +```ts +import { Schema } from "effect" + +// Define the encoded representation for Category separately. +// This is useful when the Encoded type differs from the Type type. +interface CategoryEncoded extends Schema.Codec.Encoded {} + +// The runtime type is Category; the encoded form is CategoryEncoded. +// "name" is decoded from a string to a finite number to show that +// Type and Encoded types can differ. +export class Category extends Schema.Class("Category")( + Schema.Struct({ + name: Schema.FiniteFromString, + children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) + }) +) {} + +/* +type Encoded = { + readonly children: readonly CategoryEncoded[]; + readonly name: string; +} +*/ +export type Encoded = (typeof Category)["Encoded"] +``` + +**Example** (Mutually recursive expression language) + +```ts +import { Schema } from "effect" + +class Expression extends Schema.Class("Expression")( + Schema.Struct({ + type: Schema.Literal("expression"), + value: Schema.Union([Schema.Number, Schema.suspend((): Schema.Codec => Operation)]) + }) +) {} + +class Operation extends Schema.Class("Operation")( + Schema.Struct({ + type: Schema.Literal("operation"), + operator: Schema.Literals(["+", "-"]), + left: Expression, + right: Expression + }) +) {} + +/* +type Encoded = { + readonly type: "operation"; + readonly operator: "+" | "-"; + readonly left: { + readonly type: "expression"; + readonly value: number | Operation; + }; + readonly right: { + readonly type: "expression"; + readonly value: number | Operation; + }; +} +*/ +export type Encoded = (typeof Operation)["Encoded"] +``` + +### TaggedClass + +`TaggedClass` is a convenience over `Class` that automatically adds a `_tag` field using `Schema.tag`. This is useful for discriminated unions where each variant needs a tag. + +The tag value doubles as the identifier by default. Pass an explicit identifier as the first argument to override it. + +**Example** (Basic tagged class) + +```ts +import { Schema } from "effect" + +class Person extends Schema.TaggedClass()("Person", { + name: Schema.String +}) {} + +const mike = new Person({ name: "Mike" }) +console.log(mike) +// Person { _tag: 'Person', name: 'Mike' } +console.log(mike._tag) +// "Person" +``` + +**Example** (Custom identifier) + +```ts +import { Schema } from "effect" + +class Person extends Schema.TaggedClass("MyPerson")("Person", { + name: Schema.String +}) {} + +console.log(Person.identifier) +// "MyPerson" +console.log(new Person({ name: "Mike" })._tag) +// "Person" +``` + +**Example** (Discriminated union) + +```ts +import { Schema } from "effect" + +class Cat extends Schema.TaggedClass()("Cat", { + lives: Schema.Number +}) {} + +class Dog extends Schema.TaggedClass()("Dog", { + wagsTail: Schema.Boolean +}) {} + +const Animal = Schema.Union([Cat, Dog]) + +console.log(Schema.decodeUnknownSync(Animal)({ _tag: "Cat", lives: 9 })) +// Cat { _tag: 'Cat', lives: 9 } +``` + +All features from `Class` are available: `extend`, `annotate`, `check`, branded classes, and recursive definitions. + +### Error + +```ts +import { Schema } from "effect" + +class E extends Schema.Error("E")({ + id: Schema.Number +}) {} +``` + +### TaggedError + +`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. + +**Example** (Defining and catching a tagged error) + +```ts +import { Effect, Schema } from "effect" + +class HttpError extends Schema.TaggedError()("HttpError", { + status: Schema.Number, + message: Schema.String +}) {} + +const program = Effect.gen(function*() { + yield* new HttpError({ status: 404, message: "Not found" }) +}) + +const recovered = program.pipe( + Effect.catchTag("HttpError", (err) => Effect.succeed(`Caught: ${err.status} ${err.message}`)) +) +``` + +**Example** (Multiple tagged errors in a union) + +```ts +import { Effect, Schema } from "effect" + +class NotFound extends Schema.TaggedError()("NotFound", { + path: Schema.String +}) {} + +class Unauthorized extends Schema.TaggedError()("Unauthorized", { + reason: Schema.String +}) {} + +const program = Effect.gen(function*() { + if (Math.random() < 0.5) { + yield* new Unauthorized({ reason: "Unauthorized" }) + } else { + yield* new NotFound({ path: "/missing" }) + } +}) + +// Each error can be caught independently by its tag +const recovered = program.pipe( + Effect.catchTags({ + NotFound: (err) => Effect.succeed(`Not found: ${err.path}`), + Unauthorized: (err) => Effect.succeed(`Unauthorized: ${err.reason}`) + }) +) +``` + +All features from `Error` are available: `extend`, `annotate`, and `check`. + +# Serialization + +Serialization converts typed values into a format suitable for storage or transmission (such as JSON, FormData, or XML). Deserialization reverses the process, turning raw data back into typed values. Schema provides built-in support for several common formats. + +## JSON Support + +#### UnknownFromJsonString + +A schema that decodes a JSON-encoded string into an unknown value. + +This schema takes a string as input and attempts to parse it as JSON during decoding. If parsing succeeds, the result is passed along as an unknown value. If the string is not valid JSON, decoding fails. + +When encoding, any value is converted back into a JSON string using JSON.stringify. If the value is not a valid JSON value, encoding fails. + +**Example** + +```ts +import { Schema } from "effect" + +Schema.decodeUnknownSync(Schema.UnknownFromJsonString)(`{"a":1,"b":2}`) +// => { a: 1, b: 2 } +``` + +#### fromJsonString + +Returns a schema that decodes a JSON string and then decodes the parsed value using the given schema. + +This is useful when working with JSON-encoded strings where the actual structure of the value is known and described by an existing schema. + +The resulting schema first parses the input string as JSON, and then runs the provided schema on the parsed result. + +**Example** + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ a: Schema.Number }) +const schemaFromJsonString = Schema.fromJsonString(schema) + +Schema.decodeUnknownSync(schemaFromJsonString)(`{"a":1,"b":2}`) +// => { a: 1 } +``` + +## String Encoding Support + +Schema provides built-in schemas for common string encodings. Each one decodes an encoded string into a UTF-8 string (and encodes back). They can be composed with `fromJsonString` to decode structured data in a single pipeline. + +#### StringFromBase64 + +Decodes a Base64-encoded (RFC 4648) string into a UTF-8 string. + +```ts +import { Schema } from "effect" + +Schema.decodeUnknownSync(Schema.StringFromBase64)("aGVsbG8=") +// => "hello" +``` + +Compose with `fromJsonString` to decode Base64-encoded JSON into a validated struct: + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ a: Schema.Number }) + +// base64 string -> UTF-8 string -> parsed & validated struct +const schemaFromBase64 = Schema.StringFromBase64.pipe( + Schema.decodeTo(Schema.fromJsonString(schema)) +) +``` + +#### StringFromBase64Url + +Like `StringFromBase64`, but uses the URL-safe Base64 alphabet (RFC 4648 section 5). + +```ts +import { Schema } from "effect" + +Schema.decodeUnknownSync(Schema.StringFromBase64Url)("aGVsbG8") +// => "hello" +``` + +#### StringFromHex + +Decodes a hex-encoded string into a UTF-8 string. + +```ts +import { Schema } from "effect" + +Schema.decodeUnknownSync(Schema.StringFromHex)("68656c6c6f") +// => "hello" +``` + +#### StringFromUriComponent + +Decodes a URI-component-encoded string into a UTF-8 string. Useful for storing structured data in URL query parameters. + +```ts +import { Schema } from "effect" + +const PaginationSchema = Schema.Struct({ + maxItemPerPage: Schema.Number, + page: Schema.Number +}) + +const UrlSchema = Schema.StringFromUriComponent.pipe( + Schema.decodeTo(Schema.fromJsonString(PaginationSchema)) +) + +console.log(Schema.encodeSync(UrlSchema)({ maxItemPerPage: 10, page: 1 })) +// %7B%22maxItemPerPage%22%3A10%2C%22page%22%3A1%7D +``` + +#### Uint8Array variants + +For binary data, use the `Uint8Array` variants instead: + +- `Schema.Uint8ArrayFromBase64` - decodes Base64 into a `Uint8Array`. +- `Schema.Uint8ArrayFromBase64Url` - decodes URL-safe Base64 into a `Uint8Array`. +- `Schema.Uint8ArrayFromHex` - decodes hex into a `Uint8Array`. + +#### Low-level transformations + +The `SchemaTransformation` module exposes the underlying transformations (`stringFromBase64String`, `stringFromBase64UrlString`, `stringFromHexString`, `stringFromUriComponent`). Prefer the built-in `Schema.*` schemas above unless you need to build a custom pipeline. + +## FormData Support + +`Schema.fromFormData` returns a schema that reads a `FormData` instance, +converts it into a tree record using bracket notation, and then decodes the +resulting structure using the provided schema. + +The decoding process has two steps: + +1. Parse `FormData` into a nested tree record. +2. Decode the parsed value with the given schema. + +**Example** (Decoding a flat structure) + +```ts +import { Schema } from "effect" + +const schema = Schema.fromFormData( + Schema.Struct({ + a: Schema.String + }) +) + +const formData = new FormData() +formData.append("a", "1") +formData.append("b", "2") + +console.log(String(Schema.decodeUnknownExit(schema)(formData))) +// Success({"a":"1"}) +``` + +You can express nested values using bracket notation. + +**Example** (Nested fields) + +```ts +import { Schema } from "effect" + +const schema = Schema.fromFormData( + Schema.Struct({ + a: Schema.String, + b: Schema.Struct({ + c: Schema.String, + d: Schema.String + }) + }) +) + +const formData = new FormData() +formData.append("a", "1") +formData.append("b[c]", "2") +formData.append("b[d]", "3") + +console.log(String(Schema.decodeUnknownExit(schema)(formData))) +// Success({"a":"1","b":{"c":"2","d":"3"}}) +``` + +If you want to decode string fields into non-string primitive values, use `Schema.toCodecStringTree`. + +**Example** (Parsing non-string values) + +```ts +import { Schema } from "effect" + +const schema = Schema.fromFormData( + Schema.toCodecStringTree( + Schema.Struct({ + a: Schema.Int + }) + ) +) + +const formData = new FormData() +formData.append("a", "1") + +console.log(String(Schema.decodeUnknownExit(schema)(formData))) +// Success({"a":1}) // Note: the value is a number +``` + +## URLSearchParams Support + +`Schema.fromURLSearchParams` returns a schema that reads a `URLSearchParams` +instance, converts it into a tree record using bracket notation, and then decodes +the resulting structure using the provided schema. + +The decoding process has two steps: + +1. Parse `URLSearchParams` into a nested tree record. +2. Decode the parsed value with the given schema. + +**Example** (Decoding a flat structure) + +```ts +import { Schema } from "effect" + +const schema = Schema.fromURLSearchParams( + Schema.Struct({ + a: Schema.String + }) +) + +const urlSearchParams = new URLSearchParams("a=1&b=2") + +console.log(String(Schema.decodeUnknownExit(schema)(urlSearchParams))) +// Success({"a":"1"}) +``` + +You can express nested values using bracket notation. + +**Example** (Nested fields) + +```ts +import { Schema } from "effect" + +const schema = Schema.fromURLSearchParams( + Schema.Struct({ + a: Schema.String, + b: Schema.Struct({ + c: Schema.String, + d: Schema.String + }) + }) +) + +const urlSearchParams = new URLSearchParams("a=1&b[c]=2&b[d]=3") + +console.log(String(Schema.decodeUnknownExit(schema)(urlSearchParams))) +// Success({"a":"1","b":{"c":"2","d":"3"}}) +``` + +If you want to decode values that are not strings, use `Schema.toCodecStringTree`. This serializer preserves values such as numbers when compatible with the schema. + +**Example** (Parsing non-string values) + +```ts +import { Schema } from "effect" + +const schema = Schema.fromURLSearchParams( + Schema.toCodecStringTree( + Schema.Struct({ + a: Schema.Int + }) + ) +) + +const urlSearchParams = new URLSearchParams("a=1&b=2") + +console.log(String(Schema.decodeUnknownExit(schema)(urlSearchParams))) +// Success({"a":1}) // Note: the value is a number +``` + +## Canonical Codecs + +When sending data over the network or storing it on disk, you need to convert your domain types to a format like JSON. Schema provides built-in support for serializing values to JSON, strings, FormData, URLSearchParams, and XML. + +Canonical codecs turn one schema into another schema (a "codec") that can serialize and deserialize values using a specific format (JSON, strings, `URLSearchParams`, `FormData`, and so on). This helps you map your domain types to formats that can only represent a limited set of values. + +To keep things concrete, the rest of this page focuses on JSON. + +### JSON Canonical Codec + +Many JavaScript values cannot be serialized to JSON in a safe and reversible way: + +- `Date`: `JSON.stringify()` converts a date to an ISO string, but `JSON.parse()` does not restore a `Date` object +- `Uint8Array`, `ReadonlyMap`, `ReadonlySet`: `JSON.stringify()` converts them to `{}`, so the original data is lost +- `Symbol`, `BigInt`: `JSON.stringify()` throws errors +- Custom classes and Effect data types (`Option`, `Result`, and so on): `JSON.stringify()` does not know how to encode or decode them + +This can lead to data loss, runtime errors, or values that decode into the wrong shape when you try to round-trip complex data through JSON. + +**The solution** + +A canonical codec describes how values that match a schema should be converted to a specific format. In practice, canonical codecs work like this: + +1. **Annotation-based**: you choose a serialization strategy by adding annotations to your schema (for example `toCodecJson`, `toCodecIso`, `toCodecStringTree`, and others). +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) + +```ts +import { Schema } from "effect" + +class Point { + constructor(public readonly x: number, public readonly y: number) {} + + // Plain method on a class instance + distance(other: Point): number { + const dx = this.x - other.x + const dy = this.y - other.y + return Math.sqrt(dx * dx + dy * dy) + } +} + +const PointSchema = Schema.instanceOf(Point) +``` + +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 +const codecJson = Schema.toCodecJson(PointSchema) + +console.log(String(Schema.encodeUnknownExit(codecJson)(new Point(1, 2)))) +// Failure(Cause([Fail(SchemaError(Expected JSON value))])) + +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 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: {}, definitions: {} } +``` + +#### Configuring the Codec + +You configure the canonical JSON codec by adding a `toCodecJson` annotation to your schema. + +Then you call `Schema.toCodecJson(schema)` to produce a codec schema that can encode and decode values to and from JSON-compatible data. + +**Example** (Encoding a class as a JSON tuple) + +```ts +import { Schema, SchemaTransformation } from "effect" + +class Point { + constructor(public readonly x: number, public readonly y: number) {} + + distance(other: Point): number { + const dx = this.x - other.x + const dy = this.y - other.y + return Math.sqrt(dx * dx + dy * dy) + } +} + +const PointSchema = Schema.instanceOf(Point, { + toCodecJson: () => + Schema.link()( + // Pick a JSON representation for Point. + // Here we use a fixed-length tuple: [x, y]. + Schema.Tuple([Schema.Finite, Schema.Finite]), + SchemaTransformation.transform({ + // Decode: convert the JSON representation into a Point instance. + decode: (args) => new Point(...args), + + // Encode: convert a Point instance into the JSON representation. + encode: (instance) => [instance.x, instance.y] as const + }) + ) +}) + +// Convert the schema into a JSON codec schema. +const codecJson = Schema.toCodecJson(PointSchema) + +// Encoding produces JSON-safe data, so it can be stringified. +console.log(JSON.stringify(Schema.encodeUnknownSync(codecJson)(new Point(1, 2)))) +// "[1,2]" + +// Decoding rebuilds the Point instance from parsed JSON. +console.log(Schema.decodeUnknownSync(codecJson)(JSON.parse("[1,2]"))) +// Point { x: 1, y: 2 } + +// JSON Schema generation now has a real representation to work with. +console.dir(Schema.toJsonSchemaDocument(PointSchema), { depth: null }) +/* +{ + dialect: 'draft-2020-12', + schema: { + type: 'array', + prefixItems: [ { type: 'number' }, { type: 'number' } ], + maxItems: 2, + minItems: 2 + }, + definitions: {} +} +*/ +``` + +When you use `toCodecJson`, you describe the JSON shape once (in the schema), and Effect can reuse that description in two places: + +- `Schema.toCodecJson(...)` uses it to encode and decode JSON data at runtime. +- `Schema.toJsonSchemaDocument(...)` uses it to produce a JSON Schema document for the same JSON shape. + +Because both outputs come from the same annotation, they describe the same format (in this example, a two-item array `[x, y]`). If you change the JSON representation in `toCodecJson`, both the codec and the generated JSON Schema will change with it. + +You can use the JSON Schema to validate or describe the JSON data (for example in OpenAPI), and use the codec schema to encode and decode values in that same format. + +#### How `toCodecJson` Works + +When you call `Schema.toCodecJson(schema)`, the library: + +1. **Walks the AST**: it traverses the schema's abstract syntax tree (AST) recursively. For details, see the `SchemaAST` module. +2. **Finds annotations**: it looks for `toCodecJson` annotations on nodes. +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. + +**Example** (Custom encoding takes priority over default Date handling) + +```ts +import { Schema, SchemaTransformation } from "effect" + +// Custom Date encoding (Date -> number) +const DateFromEpochMillis = Schema.Date.pipe( + Schema.encodeTo( + Schema.Number, + SchemaTransformation.transform({ + decode: (epochMillis) => new Date(epochMillis), + encode: (date) => date.getTime() + }) + ) +) + +const schema = Schema.Struct({ + date1: DateFromEpochMillis, + date2: Schema.Date +}) + +const toCodecJson = Schema.toCodecJson(schema) + +const data = { date1: new Date("2021-01-01"), date2: new Date("2021-01-01") } + +const serialized = Schema.encodeUnknownSync(toCodecJson)(data) +console.log(serialized) +// { date1: 1609459200000, date2: "2021-01-01T00:00:00.000Z" } +// date1 uses your custom number format, date2 uses the default ISO string format +``` + +### StringTree Canonical Codec + +The `StringTree` codec converts all values to strings, keeping the structure but not the original types. + +```ts +type StringTree = string | undefined | { readonly [key: string]: StringTree } | ReadonlyArray +``` + +A StringTree codec turns any value into a structure made only of: + +- strings +- `undefined` +- plain objects containing other `StringTree` values +- arrays of `StringTree` values + +#### toCodecJson vs toCodecStringTree + +**Example** (Comparing JSON and StringTree codecs) + +```ts +import { Schema, SchemaTransformation } from "effect" + +class Point { + constructor(public readonly x: number, public readonly y: number) {} + + distance(other: Point): number { + const dx = this.x - other.x + const dy = this.y - other.y + return Math.sqrt(dx * dx + dy * dy) + } +} + +const PointSchema = Schema.instanceOf(Point, { + toCodecJson: () => + Schema.link()( + Schema.Tuple([Schema.Finite, Schema.Finite]), + SchemaTransformation.transform({ + decode: (args) => new Point(...args), + encode: (instance) => [instance.x, instance.y] as const + }) + ) +}) + +const point = new Point(1, 2) + +const toCodecJson = Schema.toCodecJson(PointSchema) + +const json = Schema.encodeUnknownSync(toCodecJson)(point) + +// keeps numbers as numbers +console.log(json) +// [1, 2] + +const toCodecStringTree = Schema.toCodecStringTree(PointSchema) + +const stringTree = Schema.encodeUnknownSync(toCodecStringTree)(point) + +// every leaf value becomes a string +console.log(stringTree) +// [ '1', '2' ] +``` + +### ISO Canonical Codec + +The ISO canonical codec (`toCodecIso`) converts schemas to their `Iso` representation. This is useful when you want to build isomorphic transformations or optics. + +**Example** (Using the ISO canonical codec with a Class) + +```ts +import { Schema } from "effect" + +// Define a class schema +class Person extends Schema.Class("Person")({ + name: Schema.String, + age: Schema.Number +}) {} + +const codecIso = Schema.toCodecIso(Person) + +// The Iso type represents the "focus" of the schema. +// For Class schemas, the Iso type is the struct representation +// of the class fields: { readonly name: string; readonly age: number } +// This allows you to convert between the class instance and a plain object +// with the same shape, which is useful for optics and transformations. + +const person = new Person({ name: "John", age: 30 }) + +const serialized = Schema.encodeUnknownSync(codecIso)(person) +console.log(serialized) +// { name: 'John', age: 30 } + +const deserialized = Schema.decodeUnknownSync(codecIso)(serialized) +console.log(deserialized) +// Person { name: 'John', age: 30 } +``` + +ISO serializers are mainly used internally for building optics and reusable transformations. + +## XML Encoder + +`Schema.toEncoderXml` lets you serialize values to XML. +It uses the `toCodecStringTree` serializer internally. + +**Example** + +```ts +import { Effect, Option, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Array(Schema.NullOr(Schema.String)), + c: Schema.Struct({ + d: Schema.Option(Schema.String), + e: Schema.Date + }), + f: Schema.optional(Schema.String) +}) + +// const encoder: (t: {...}) => Effect +const xmlEncoder = Schema.toEncoderXml(schema) + +console.log( + Effect.runSync( + xmlEncoder({ + a: "", + b: ["bar", "baz", null], + c: { d: Option.some("qux"), e: new Date("2021-01-01") }, + f: undefined + }) + ) +) +/* + + + + bar + baz + + + + + <_tag>Some + qux + + 2021-01-01T00:00:00.000Z + + + +*/ +``` + +**Note**. Schemas representing custom types are encoded as `undefined`: + +# Schema Generation and Tooling + +Schema can derive JSON Schemas, test data generators (Arbitraries), equivalence checks, optics, and more from a single schema definition. + +### Generating a JSON Schema from a Schema + +#### Basic Conversion + +By default, a schema produces a draft-2020-12 JSON Schema. + +The result is a data structure including: + +- the source of the JSON Schema (e.g. `draft-2020-12`, `draft-07`, etc...) +- the JSON Schema itself +- any definitions referenced by `$ref` (if any) + +**Example** (Tuple to draft-2020-12 JSON Schema) + +```ts +import { Schema } from "effect" + +// Define a tuple: [string, number] +const schema = Schema.Tuple([Schema.String, Schema.Finite]) + +// Generate a draft-2020-12 JSON Schema +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document, null, 2)) +/* +Output: +{ + "source": "draft-2020-12", + "schema": { + "type": "array", + "prefixItems": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "definitions": {} +} +*/ +``` + +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 +import { JsonSchema, Schema } from "effect" + +const schema = Schema.Tuple([Schema.String, Schema.Finite]) + +const doc2020_12 = Schema.toJsonSchemaDocument(schema) +const doc07 = JsonSchema.toDocumentDraft07(doc2020_12) + +console.log(JSON.stringify(doc07, null, 2)) +/* +Output: +{ + "source": "draft-07", + "schema": { + "type": "array", + "maxItems": 2, + "minItems": 2, + "items": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "definitions": {} +} +*/ +``` + +#### Attaching Standard Metadata + +Use `.annotate(...)` to attach standard JSON Schema annotations: + +- `title` +- `description` +- `default` +- `examples` +- `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 +import { Schema } from "effect" + +const schema = Schema.NonEmptyString.annotate({ + title: "Username", + description: "A non-empty user name string", + default: "anonymous", + examples: ["alice", "bob"] +}) + +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document, null, 2)) +/* +{ + "source": "draft-2020-12", + "schema": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "title": "Username", + "description": "A non-empty user name string", + "default": "anonymous", + "examples": [ + "alice", + "bob" + ] + } + ] + }, + "definitions": {} +} +*/ +``` + +#### Annotating the Encoded Side of a Transformation + +When a schema includes a transformation (e.g. `Schema.Trim`), the generated JSON Schema corresponds to the encoded side. Calling `.annotate(...)` on a transformation annotates the decoded side, so the annotations won't appear in the JSON Schema output. + +To annotate the encoded side, use `Schema.annotateEncoded`. + +**Example** (Annotating the encoded side of `Trim`) + +```ts +import { Schema } from "effect" + +const schema = Schema.Trim.pipe( + Schema.annotateEncoded({ + description: "my description", + title: "my title" + }) +) + +console.log(JSON.stringify(Schema.toJsonSchemaDocument(schema), null, 2)) +/* +{ + "dialect": "draft-2020-12", + "schema": { + "type": "string", + "title": "my title", + "description": "my description" + }, + "definitions": {} +} +*/ +``` + +Alternatively, build a custom transformation using `Schema.decodeTo`: + +```ts +import { Schema, SchemaTransformation } from "effect" + +const schema = Schema.String.annotate({ + description: "my description", + title: "my title" +}).pipe(Schema.decodeTo(Schema.Trimmed, SchemaTransformation.trim())) + +console.log(JSON.stringify(Schema.toJsonSchemaDocument(schema), null, 2)) +/* +{ + "dialect": "draft-2020-12", + "schema": { + "type": "string", + "title": "my title", + "description": "my description" + }, + "definitions": {} +} +*/ +``` + +#### Optional fields / elements + +Optional fields are converted to optional fields or elements in the JSON Schema. + +**Example** + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optionalKey(Schema.String) +}) + +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document, null, 2)) +/* +{ + "source": "draft-2020-12", + "schema": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + }, + "additionalProperties": false + }, + "definitions": {} +} +*/ +``` + +Fields including `undefined` (such as those defined unsing `Schema.optional` or `Schema.UndefinedOr`) are converted to optional fields or elements in the JSON Schema with a union with the `null` type. + +**Example** + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.optional(Schema.String) +}) + +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document, null, 2)) +/* +{ + "source": "draft-2020-12", + "schema": { + "type": "object", + "properties": { + "a": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "definitions": {} +} +*/ +``` + +#### Defining a JSON-safe representation for custom types + +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. + +**Example** (Align a JSON serializer and JSON Schema for `Headers`) + +```ts +import { Schema, SchemaGetter } from "effect" + +const data = new Headers({ a: "b" }) + +// `Headers` does not serialize to JSON in a useful way by default. +console.log(JSON.stringify(data)) +// {} + +// Define a schema with a `toCodecJson` annotation. +// The JSON form will be: [ [name, value], ... ]. +const MyHeaders = Schema.instanceOf(Headers, { + toCodecJson: () => + Schema.link()( + // JSON-safe representation: array of [key, value] pairs + Schema.Array(Schema.Tuple([Schema.String, Schema.String])), + { + decode: SchemaGetter.transform((headers) => new Headers(headers.map(([key, value]) => [key, value]))), + encode: SchemaGetter.transform((headers) => [...headers.entries()]) + } + ) +}) + +const schema = Schema.Struct({ + headers: MyHeaders +}) + +// Build a serializer that produces JSON-safe values using the `toCodecJson` annotation. +const serializer = Schema.toCodecJson(schema) + +const json = Schema.encodeUnknownSync(serializer)({ + headers: data +}) + +// The JSON-encoded value: +console.log(json) +// { headers: [ [ 'a', 'b' ] ] } + +// Generate a JSON Schema that matches the JSON-safe shape produced by the serializer. +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document.schema, null, 2)) +/* +{ + "type": "object", + "properties": { + "headers": { + "type": "array", + "items": { + "type": "array", + "prefixItems": [ + { + "type": "string" + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "required": [ + "headers" + ], + "additionalProperties": false +} +*/ + +// Example (Decode a JSON-safe value using the same serializer) +// If a value matches the JSON Schema above, you can decode it with the serializer. +console.log(String(Schema.decodeUnknownExit(serializer)(json))) +// Success({"headers":Headers([["a","b"]])}) +``` + +#### Validation Constraints + +**Example** + +```ts +import { Schema } from "effect" + +const schema = Schema.String.check(Schema.isMinLength(1)) + +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document, null, 2)) +/* +{ + "source": "draft-2020-12", + "schema": { + "type": "string", + "allOf": [ + { + "minLength": 1 + } + ] + }, + "definitions": {} +} +*/ +``` + +**Example** (Multiple filters) + +```ts +import { Schema } from "effect" + +const schema = Schema.String.check( + Schema.isMinLength(1, { description: "description1" }), + Schema.isMaxLength(2, { description: "description2" }) +) + +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document, null, 2)) +/* +{ + "source": "draft-2020-12", + "schema": { + "type": "string", + "allOf": [ + { + "minLength": 1, + "description": "description1" + }, + { + "maxLength": 2, + "description": "description2" + } + ] + }, + "definitions": {} +} +*/ +``` + +#### The fromJsonString combinator + +With `fromJsonString`, the generated schema uses `contentSchema` to embed the JSON Schema of the decoded value. + +**Example** (Embedding `contentSchema` for JSON string content) + +```ts +import { Schema } from "effect" + +// Original value is an object with a string field 'a' +const original = Schema.Struct({ a: Schema.String }) + +// fromJsonString: the outer value is a string, +// but its content must be valid JSON matching 'original' +const schema = Schema.fromJsonString(original) + +const document = Schema.toJsonSchemaDocument(schema) + +console.log(JSON.stringify(document, null, 2)) +/* +{ + "source": "draft-2020-12", + "schema": { + "type": "string", + "contentMediaType": "application/json", + "contentSchema": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + }, + "required": [ + "a" + ], + "additionalProperties": false + } + }, + "definitions": {} +} +*/ +``` + +### Generating an Arbitrary from a Schema + +Property-based tests need generators. `Schema.toArbitrary` derives a factory +that accepts the `fast-check` module and returns an `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)(FastCheck) + +console.log(FastCheck.sample(PersonArbitrary, 3)) +``` + +`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. + +#### 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, 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, input, options)) + } + 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)(FastCheck), 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. + +**Example** (Deriving equivalence for a basic schema) + +```ts +import { Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}) + +const equivalence = Schema.toEquivalence(schema) +``` + +#### Declarations + +**Example** (Providing a custom equivalence for a class) + +```ts +import { Schema } from "effect" + +class MyClass { + constructor(readonly a: string) {} +} + +const schema = Schema.instanceOf(MyClass, { + toEquivalence: () => (x, y) => x.a === y.a +}) + +const equivalence = Schema.toEquivalence(schema) +``` + +#### Overrides + +You can override the derived equivalence for a schema using `overrideToEquivalence`. This is useful when the default derivation does not fit your requirements. + +**Example** (Overriding equivalence for a struct) + +```ts +import { Equivalence, Schema } from "effect" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}).pipe(Schema.overrideToEquivalence(() => Equivalence.make((x, y) => x.a === y.a))) + +const equivalence = Schema.toEquivalence(schema) +``` + +### Generating an Optic from a Schema + +Optics provide a composable way to read and update deeply nested values without mutating the original object. Schema can derive optics automatically from your schema definition. + +#### Problem + +The `Optic` module only works with plain JavaScript objects and collections (structs, records, tuples, and arrays). +This can feel restrictive when working with custom types. + +To work around this, you can define an `Iso` between your custom type and a plain JavaScript object. + +**Example** (Defining an `Iso` manually between a custom type and a plain JavaScript object) + +```ts +import { Optic, Schema } from "effect" + +// Define custom schema-based classes +class A extends Schema.Class("A")({ s: Schema.String }) {} +class B extends Schema.Class("B")({ a: A }) {} + +// Create an Iso that converts between B and a plain object +const iso = Optic.makeIso( + (s) => ({ a: { s: s.a.s } }), // forward transformation + (a) => new B({ a: new A({ s: a.a.s }) }) // backward transformation +) + +// Build an optic that drills down to the "s" field inside "a" +const _s = iso.key("a").key("s") + +console.log(_s.replace("b", new B({ a: new A({ s: "a" }) }))) +// B { a: A { s: 'b' } } +``` + +#### Solution + +Manually creating `Iso` instances is repetitive and error-prone. +To simplify this, the library provides a helper function that generates an `Iso` directly from a schema. + +This allows you to keep working with plain JavaScript objects and collections while still benefiting from schema definitions. + +**Example** (Generating an `Iso` automatically from a schema) + +```ts +import { Schema } from "effect" + +class A extends Schema.Class("A")({ s: Schema.String }) {} +class B extends Schema.Class("B")({ a: A }) {} + +// Automatically generate an Iso from the schema of B +// const iso: Iso +const iso = Schema.toIso(B) + +const _s = iso.key("a").key("s") + +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. + +**Example** (Compare two values and apply the patch) + +```ts +import { Schema } from "effect" + +// Describe the shape of your data +const schema = Schema.Struct({ + id: Schema.Number, + name: Schema.String, + price: Schema.Number +}) + +// Build a differ tied to the schema +const differ = Schema.toDifferJsonPatch(schema) + +// Prepare two values to compare +const oldValue = { id: 1, name: "a", price: 1 } +const newValue = { id: 1, name: "b", price: 2 } + +// Compute a JSON Patch document (an array of operations) +const jsonPatch = differ.diff(oldValue, newValue) +console.log(jsonPatch) +/* +[ + { op: 'replace', path: '/name', value: 'b' }, + { op: 'replace', path: '/price', value: 2 } +] +*/ + +// Apply the patch to the old value to get the new value +const patched = differ.patch(oldValue, jsonPatch) +console.log(patched) +// { id: 1, name: 'b', price: 2 } +``` + +#### Works with custom types too + +**Example** (Compare two custom types) + +```ts +import { Schema } from "effect" + +class A extends Schema.Class("A")({ n: Schema.Number }) {} +class B extends Schema.Class("B")({ a: A }) {} + +const differ = Schema.toDifferJsonPatch(B) + +const oldValue = new B({ a: new A({ n: 0 }) }) +const newValue = new B({ a: new A({ n: 1 }) }) + +const patch = differ.diff(oldValue, newValue) +console.log(patch) +// [ { op: 'replace', path: '/a/n', value: 1 } ] + +console.log(differ.patch(oldValue, patch)) +// B { a: A { n: 1 } } +``` + +#### How it works + +The idea is simple: if you have a `Schema` for a type `T`, you can serialize any `T` to JSON and back. That lets us compute and apply JSON Patch on the JSON view, while keeping the public API typed as `T`. + +- `diff(oldValue, newValue)` + + 1. Encode `oldValue: T` and `newValue: T` to JSON with the schema serializer. + 2. Compute a JSON Patch document between the two JSON values. + 3. Return that patch (an array of `"add" | "remove" | "replace"` operations). + +- `patch(oldValue, patch)` + 1. Encode `oldValue: T` to JSON. + 2. Apply the JSON Patch to the JSON value. + 3. Decode the patched JSON back to `T` using the schema. + +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 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: + +- 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: + +- `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] -->|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 one +structural side of a schema. Named or recursive nodes use `Reference` values instead of duplicating their definitions. + +### `Document` + +A `Document` has: + +- `representation`: the root `Representation` +- `references`: a map of named definitions used by the root representation + +References let the representation share definitions and support recursion. + +### `MultiDocument` + +A `MultiDocument` stores multiple root representations that share the same `references` table. + +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. + +## Projection and persistence boundaries + +### Representations use the encoded side + +`toRepresentation` follows a schema's encoding chain and represents its last encoded side. It does not serialize the +transformation functions. + +```ts +import { Schema } from "effect" + +const encoded = Schema.toRepresentation(Schema.NumberFromString) +console.log(encoded.representation._tag) +// "String" + +const decoded = Schema.toRepresentation(Schema.toType(Schema.NumberFromString)) +console.log(decoded.representation._tag) +// "Number" +``` + +Consequently, rebuilding `encoded` produces a schema for the string representation; it does not recreate the original +string-to-number transformation. + +### Live and persisted documents + +A live `Document` can contain functions in its ordinary annotations. These callbacks allow compilers to handle custom +behavior: + +- a check can provide `toJsonSchema` +- a declaration or check can provide `toCode` + +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. + +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. + +### Persistence identities + +Opaque declarations and checks need a stable identity before they can be persisted: + +```ts +interface RepresentationAnnotation { + readonly id: string + readonly payload: Schema.Json +} + +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. + +### Reference policies + +`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: + +```ts +interface ReferencePolicyInput { + readonly ast: SchemaAST.AST + readonly occurrences: number + readonly identifier: string | undefined +} +``` + +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: + +```ts +import { Schema, SchemaRepresentation } from "effect" + +const Item = Schema.Struct({ name: Schema.String }) + +const document = SchemaRepresentation.toRepresentations([Item.ast, Item.ast], { + referencePolicy: ({ ast, identifier, occurrences }) => identifier ?? (occurrences > 1 ? `${ast._tag}_` : undefined) +}) + +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)` 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. + +The multi-root equivalents are `toJsonMultiDocument` and `fromJsonMultiDocument`. + +```ts +import { Schema, SchemaRepresentation } from "effect" + +const live = Schema.toRepresentation( + Schema.String.check(Schema.isMinLength(3)) +) + +const json = SchemaRepresentation.toJson(live) +const persisted = SchemaRepresentation.fromJson(json) +``` + +Persisted `Declaration` and `Filter` nodes must contain a representation identity. `fromJson` validates the document but +does not require the corresponding revivers until reconstruction. + +## Rebuilding runtime schemas + +### `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 +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: [Schema.isMinLengthReviver] +}) + +console.log(Schema.is(rebuilt)("abc")) +// true +console.log(Schema.is(rebuilt)("a")) +// false +``` + +Effect exports individual revivers next to the built-in declarations and checks they reconstruct, such as +`Schema.OptionReviver`, `Schema.DateReviver`, and `Schema.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. + +`fromRepresentations` rebuilds the ordered roots of a `MultiDocument` in a shared reference environment. Only references +reachable from those roots are revived. + +### Custom revivers + +There are separate reviver contracts for opaque declarations, leaf filters, and opaque filter groups: + +- `DeclarationReviver

` +- `FilterReviver

` +- `FilterGroupReviver

` + +Use `makeDeclarationReviver`, `makeFilterReviver`, and `makeFilterGroupReviver` 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.makeFilterReviver( + 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` compiles a live `MultiDocument` into runtime and TypeScript source fragments. It: + +- 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 symbol, enum, and import artifacts + +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 + +The StandardSchemaV1 formatter is used by `Schema.toStandardSchemaV1` and will return a `StandardSchemaV1.FailureResult` object: + +```ts +export interface FailureResult { + /** The issues of failed validation. */ + readonly issues: ReadonlyArray +} + +export interface Issue { + /** The error message of the issue. */ + readonly message: string + /** The path of the issue. */ + readonly path: ReadonlyArray +} +``` + +You can customize the messages of the `Issue` object in two main ways: + +- By passing formatter hooks +- By annotating schemas with `message` or `messageMissingKey` or `messageUnexpectedKey` + +For the exact rule used by the default formatter for identifiers, filter +`expected`, and `message` annotations, see +[Filter error messages and schema identifiers](#filter-error-messages-and-schema-identifiers). + +##### Hooks + +Formatter hooks let you define custom messages in one place and apply them across different schemas. This can help avoid repeating message definitions and makes it easier to update them later. + +Hooks are **required**. There is a default implementation that can be overridden only for demo purposes. This design helps keep the bundle size smaller by avoiding unused message formatting logic. + +There are two kinds of hooks: + +- `LeafHook` — for issues that occur at leaf nodes in the schema. +- `CheckHook` — for custom validation checks. + +`LeafHook` handles these issue types: + +- `InvalidType` +- `InvalidValue` +- `MissingKey` +- `UnexpectedKey` +- `Forbidden` +- `OneOf` + +`CheckHook` handles `Check` issues, such as failed filters / refinements. + +**Example** (Default hooks) + +Default hooks are just for demo purposes: + +- LeafHook: returns the issue tag +- CheckHook: returns the meta infos of the check as a string + +```ts +import { Effect, Schema, SchemaIssue } from "effect" + +const schema = Schema.Struct({ + a: Schema.NonEmptyString, + b: Schema.NonEmptyString +}) + +Schema.decodeUnknownEffect(schema)({ b: "" }, { errors: "all" }) + .pipe( + Effect.mapError((error) => SchemaIssue.makeFormatterStandardSchemaV1()(error.issue)), + Effect.runPromise + ) + .then(console.log, (a) => console.dir(a, { depth: null })) +/* +Output: +{ + issues: [ + { path: [ 'a' ], message: 'Missing key' }, + { path: [ 'b' ], message: 'Expected a value with a length of at least 1' } + ] +} +*/ +``` + +##### Customizing messages + +If a schema has a `message` annotation, it will take precedence over any formatter hook. + +To make the examples easier to follow, we define a helper function that prints formatted validation messages using `SchemaFormatter`. + +**Example utilities** + +```ts +// utils.ts +import { Exit, Schema, SchemaIssue } from "effect" +import i18next from "i18next" + +i18next.init({ + lng: "en", + resources: { + en: { + translation: { + "string.mismatch": "Please enter a valid string", + "string.minLength": "Please enter at least {{minLength}} character(s)", + "struct.missingKey": "This field is required", + "struct.mismatch": "Please enter a valid object", + "default.mismatch": "Invalid type", + "default.invalidValue": "Invalid value", + "default.forbidden": "Forbidden operation", + "default.oneOf": "Too many successful values", + "default.check": "The value does not match the check" + } + } + } +}) + +export const t = i18next.t + +export function getLogIssues(options?: { + readonly leafHook?: SchemaIssue.LeafHook | undefined + readonly checkHook?: SchemaIssue.CheckHook | undefined +}) { + return >(schema: S, input: unknown) => { + console.log( + String( + Schema.decodeUnknownExit(schema)(input, { errors: "all" }).pipe( + Exit.mapError((err) => SchemaIssue.makeFormatterStandardSchemaV1(options)(err.issue).issues) + ) + ) + ) + } +} +``` + +**Example** (Using hooks to translate common messages) + +```ts +import { Schema } from "effect" +import { getLogIssues, t } from "./utils.js" + +const Person = Schema.Struct({ + name: Schema.String.check(Schema.isNonEmpty()) +}) + +// Configure hooks to customize how issues are rendered +const logIssues = getLogIssues({ + // Format leaf-level issues (missing key, wrong type, etc.) + leafHook: (issue) => { + switch (issue._tag) { + case "InvalidType": { + if (issue.ast._tag === "String") { + return t("string.mismatch") // Wrong type for a string + } else if (issue.ast._tag === "Objects") { + return t("struct.mismatch") // Value is not an object + } + return t("default.mismatch") // Fallback for other types + } + case "InvalidValue": { + return t("default.invalidValue") + } + case "MissingKey": + return t("struct.missingKey") + case "UnexpectedKey": + return t("struct.unexpectedKey") + case "Forbidden": + return t("default.forbidden") + case "OneOf": + return t("default.oneOf") + } + }, + // Format custom check errors (like isMinLength or user-defined validations) + checkHook: (issue) => { + const meta = issue.filter.annotations?.meta + if (meta) { + switch (meta._tag) { + case "isMinLength": { + return t("string.minLength", { minLength: meta.minLength }) + } + } + } + return t("default.check") + } +}) + +// Invalid object (not even a struct) +logIssues(Person, null) +// Failure(Cause([Fail([{"path":[],"message":"Please enter a valid object"}])])) + +// Missing "name" key +logIssues(Person, {}) +// Failure(Cause([Fail([{"path":["name"],"message":"This field is required"}])])) + +// "name" has the wrong type +logIssues(Person, { name: 1 }) +// Failure(Cause([Fail([{"path":["name"],"message":"Please enter a valid string"}])])) + +// "name" is an empty string +logIssues(Person, { name: "" }) +// Failure(Cause([Fail([{"path":["name"],"message":"Please enter at least 1 character(s)"}])])) +``` + +##### Inline custom messages + +You can attach custom error messages directly to a schema using annotations. These messages can either be plain strings or functions that return strings. This is useful when you want to provide field-specific wording or localization without relying on formatter hooks. + +**Example** (Attaching custom messages to a struct field) + +```ts +import { Schema } from "effect" +import { getLogIssues, t } from "./utils.js" + +const Person = Schema.Struct({ + name: Schema.String + // Message for invalid type (e.g., number instead of string) + .annotate({ message: t("string.mismatch") }) + // Message to show when the key is missing + .annotateKey({ messageMissingKey: t("struct.missingKey") }) + // Message to show when the string is empty + .check(Schema.isNonEmpty({ message: t("string.minLength", { minLength: 1 }) })) +}) + // Message to show when the whole object has the wrong shape + .annotate({ message: t("struct.mismatch") }) + +// Use defaults for leaf and check hooks +const logIssues = getLogIssues() + +// Invalid object (not even a struct) +logIssues(Person, null) +// Failure(Cause([Fail([{"path":[],"message":"Please enter a valid object"}])])) + +// Missing "name" key +logIssues(Person, {}) +// Failure(Cause([Fail([{"path":["name"],"message":"This field is required"}])])) + +// "name" has the wrong type +logIssues(Person, { name: 1 }) +// Failure(Cause([Fail([{"path":["name"],"message":"Please enter a valid string"}])])) + +// "name" is an empty string +logIssues(Person, { name: "" }) +// Failure(Cause([Fail([{"path":["name"],"message":"Please enter at least 1 character(s)"}])])) +``` + +##### Sending a FailureResult over the wire + +You can use the `Schema.StandardSchemaV1FailureResult` schema to send a `StandardSchemaV1.FailureResult` over the wire. + +**Example** (Sending a FailureResult over the wire) + +```ts +import { Schema, SchemaIssue, SchemaParser } from "effect" + +const b = Symbol.for("b") + +const schema = Schema.Struct({ + a: Schema.NonEmptyString, + [b]: Schema.Finite, + c: Schema.Tuple([Schema.String]) +}) + +const r = SchemaParser.decodeUnknownExit(schema)({ a: "", c: [] }, { errors: "all" }) + +if (r._tag === "Failure") { + const failures = r.cause.failures + if (failures[0]?._tag === "Fail") { + const failureResult = SchemaIssue.makeFormatterStandardSchemaV1()(failures[0].error) + const serializer = Schema.toCodecJson(Schema.StandardSchemaV1FailureResult) + console.dir(Schema.encodeSync(serializer)(failureResult), { depth: null }) + } +} +/* +{ + issues: [ + { + message: 'Expected a value with a length of at least 1', + path: [ 'a' ] + }, + { message: 'Missing key', path: [ 'c', 0 ] }, + { message: 'Missing key', path: [ 'Symbol(b)' ] } + ] +} +*/ +``` + +# Middlewares + +A middleware wraps around the decoding or encoding process, letting you intercept errors, provide fallback values, or inject services. The most common use case is returning a default value when decoding fails. + +## Fallbacks + +You can use `Schema.catchDecoding` to return a fallback value when decoding fails. +This API uses an Effect without a context. If you need a fallback value that depends on a service, use `Schema.catchDecodingWithContext`. + +**Example** (Returning a simple fallback value) + +```ts +import { Effect, Schema } from "effect" + +// Provide a fallback string when decoding does not succeed +const schema = Schema.String.pipe(Schema.catchDecoding(() => Effect.succeedSome("b"))) + +console.log(String(Schema.decodeUnknownExit(schema)(null))) +// Success("b") +``` + +You can also return `Option.none()` to omit a field from the output. +This is useful when working with optional fields. + +**Example** (Omitting a field when decoding fails) + +```ts +import { Effect, Schema } from "effect" + +// Omit the field when decoding does not succeed +const schema = Schema.Struct({ + a: Schema.optionalKey(Schema.String).pipe(Schema.catchDecoding(() => Effect.succeedNone)) +}) + +console.log(String(Schema.decodeUnknownExit(schema)({ a: null }))) +// Success({}) +``` + +### Using a Service to provide a fallback value + +You can use `Schema.catchDecodingWithContext` to get a fallback value from a service. + +**Example** (Retrieving a fallback value from a service) + +```ts +import { Context, Effect, Option, Schema } from "effect" + +// Define a service that provides a fallback value +class Service extends Context.Service }>()("Service") {} + +// ┌─── Codec +// ▼ +const schema = Schema.revealCodec( + Schema.revealCodec( + Schema.String.pipe( + Schema.catchDecodingWithContext(() => + Effect.gen(function*() { + const service = yield* Service + return Option.some(yield* service.fallback) + }) + ) + ) + ) +) + +// Provide the service during decoding +// ┌─── Codec +// ▼ +const provided = Schema.revealCodec( + schema.pipe(Schema.middlewareDecoding(Effect.provideService(Service, { fallback: Effect.succeed("b") }))) +) + +console.log(String(Schema.decodeUnknownExit(provided)(null))) +// Success("b") +``` + +# Advanced Topics + +This section covers Schema's internal type machinery and advanced features. You don't need this to use Schema — it's here for library authors and advanced users who want to understand or extend the type system. + +## Model + +A "schema" is a strongly typed wrapper around an untyped AST (abstract syntax tree) node. + +The base interface is `Bottom`, which sits at the bottom of the schema type hierarchy. In Schema v4, the number of tracked type parameters has increased to 15, allowing for more precise and flexible schema definitions. + +```ts +export interface Bottom< + out T, + out E, + out RD, + out RE, + out Ast extends AST.AST, + out RebuildOut 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 Pipeable.Pipeable { + readonly [TypeId]: typeof TypeId + + readonly ast: Ast + readonly "Rebuild": RebuildOut + 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.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 [AST.Check, ...Array>]): this["Rebuild"] + rebuild(ast: this["ast"]): this["Rebuild"] + /** + * @throws {Error} The issue is contained in the error cause. + */ + make(input: this["~type.make.in"], options?: MakeOptions): this["Type"] +} +``` + +### Parameter Overview + +- `T`: the decoded output type +- `E`: the encoded representation +- `RD`: the type of the services required for decoding +- `RE`: the type of the services required for encoding +- `Ast`: the AST node type +- `RebuildOut`: the type returned when modifying the schema (namely when you add annotations or checks) +- `TypeMakeIn`: the type of the input to the `make` constructor +- `Iso`: the type of the focus of the default `Optic.Iso` +- `TypeParameters`: the type of the type parameters + +Contextual information about the schema (when the schema is used in a composite schema such as a struct or a tuple): + +- `TypeMake`: the type used to construct the value +- `TypeReadonly`: whether the schema is readonly on the type side +- `TypeIsOptional`: whether the schema is optional on the type side +- `TypeDefault`: whether the constructor has a default value +- `EncodedIsReadonly`: whether the schema is readonly on the encoded side +- `EncodedIsOptional`: whether the schema is optional on the encoded side + +### AST Node Structure + +Every schema is based on an AST node with a consistent internal shape: + +```mermaid +classDiagram + class ASTNode { + + annotations + + checks + + encoding + + context + + ...specific node fields... + } +``` + +- `annotations`: metadata attached to the schema node +- `checks`: an array of validation rules +- `encoding`: a list of transformations that describe how to encode the value +- `context`: includes details used when the schema appears inside composite schemas such as structs or tuples (e.g., whether the field is optional or mutable) + +## Type Hierarchy + +The `Bottom` type is the foundation of the schema system. It carries all internal type parameters used by the library. + +Higher-level schema types build on this base by narrowing those parameters. Common derived types include: + +- `Top`: a generic schema with no fixed shape +- `Schema`: represents the TypeScript type `T` +- `Codec`: a schema that decodes `E` to `T` and encodes `T` to `E`, possibly requiring services `RD` and `RE` + +```mermaid +flowchart TD + T[Top] --> S["Schema[T]"] + S --> C["Codec[T, E, RD, RE]"] + S --> O["Optic[T, Iso]"] + C --> B["Bottom[T, E, RD, RE, Ast, RebuildOut, TypeMakeIn, Iso, TypeParameters, TypeMake, TypeMutability, TypeOptionality, TypeConstructorDefault, EncodedMutability, EncodedOptionality]"] +``` + +### Best Practices + +Use `Top`, `Schema`, and `Codec` as _constraints_ only. Do not use them as explicit annotations or return types. + +**Example** (Prefer constraints over wide annotations) + +```ts +import { Schema } from "effect" + +// ✅ Use as a constraint. S can be any schema that extends Top. +declare function foo(schema: S) + +// ❌ Do not return Codec directly. It erases useful type information. +declare function bar(): Schema.Codec + +// ❌ Avoid wide annotations that lose details baked into a specific schema. +const schema: Schema.Codec = Schema.FiniteFromString +``` + +These wide types reset other internal parameters to defaults, which removes useful information: + +- `Top`: all type parameters are set to defaults +- `Schema`: all type parameters except `Type` are set to defaults +- `Codec`: all type parameters except `Type`, `Encoded`, `DecodingServices`, `EncodingServices` are set to defaults + +**Example** (How wide annotations erase information) + +```ts +import { Schema } from "effect" + +// Read a hidden type-level property from a concrete schema +type TypeMutability = (typeof Schema.FiniteFromString)["~type.mutability"] // "readonly" + +const schema: Schema.Codec = Schema.FiniteFromString + +// After widening to Codec<...>, the mutability info is broadened +type TypeMutability2 = (typeof schema)["~type.mutability"] // "readonly" | "mutable" +``` + +## Typed Annotations + +You can retrieve typed annotations with the `Schema.resolveAnnotations` function. The function is called "resolve" rather than "get" because it performs a lookup: if the schema has checks, the annotations are taken from the last check; otherwise they are taken from the base schema instance. This means annotations placed on a check (e.g. via `.check(myCheck.annotate({ ... }))`) take precedence over annotations on the schema itself. + +**Example** (Resolving annotations from a base schema) + +```ts +import { Schema } from "effect" + +const schema = Schema.String.annotate({ title: "my string" }) + +console.log(Schema.resolveAnnotations(schema)) +// Output: { title: "my string" } +``` + +**Example** (Annotations on the last check take precedence) + +```ts +import { Schema } from "effect" + +const schema = Schema.String + .annotate({ title: "base" }) + .check(Schema.isNonEmpty().annotate({ title: "from check" })) + +console.log(Schema.resolveAnnotations(schema)?.title) +// Output: "from check" +``` + +You can also extend the available annotations by adding your own in a module declaration file. + +**Example** (Adding a custom annotation for versioning) + +```ts +import { Schema } from "effect" + +// Extend the Annotations interface with a custom `version` annotation +declare module "effect/Schema" { + namespace Annotations { + interface Augment { + readonly version?: readonly [major: number, minor: number, patch: number] | undefined + } + } +} + +// The `version` annotation is now recognized by the TypeScript compiler +const schema = Schema.String.annotate({ version: [1, 2, 0] }) + +// const version: readonly [major: number, minor: number, patch: number] | undefined +const version = Schema.resolveAnnotations(schema)?.["version"] + +if (version) { + // Access individual parts of the version + console.log(version[1]) + // Output: 2 +} +``` + +### Key-level Annotations + +Key-level annotations are attached via `annotateKey` and apply to a field's position inside a `Struct` or `Tuple` rather than to the field's value type. Use `Schema.resolveAnnotationsKey` to retrieve them. + +**Example** (Resolving key-level annotations) + +```ts +import { Schema } from "effect" + +const schema = Schema.String.annotateKey({ messageMissingKey: "required" }) + +console.log(Schema.resolveAnnotationsKey(schema)) +// Output: { messageMissingKey: "required" } +``` + +## Generics Improvements + +Using generics in schema composition and filters can be difficult. + +The plan is to make generics **covariant** and easier to use. + +## Separate Requirement Type Parameters + +In real-world applications, decoding and encoding often have different dependencies. For example, decoding may require access to a database, while encoding does not. + +To support this, schemas now have two separate requirement parameters: + +```ts +interface Codec { + // ... +} +``` + +- `RD`: services required **only for decoding** +- `RE`: services required **only for encoding** + +This makes it easier to work with schemas in contexts where one direction has no external dependencies. + +**Example** (Decoding requirements are ignored during encoding) + +```ts +import type { Effect } from "effect" +import { Context, Schema } from "effect" + +// A service that retrieves full user info from an ID +class UserDatabase extends Context.Service< + UserDatabase, + { + getUserById: (id: string) => Effect.Effect<{ readonly id: string; readonly name: string }> + } +>()("UserDatabase") {} + +// Schema that decodes from an ID to a user object using the database, +// but encodes just the ID +declare const User: Schema.Codec< + { id: string; name: string }, + string, + UserDatabase, // Decoding requires the database + never // Encoding does not require any services +> + +// ┌─── Effect<{ readonly id: string; readonly name: string; }, Schema.SchemaError, UserDatabase> +// ▼ +const decoding = Schema.decodeEffect(User)("user-123") + +// ┌─── Effect +// ▼ +const encoding = Schema.encodeEffect(User)({ id: "user-123", name: "John Doe" }) +``` + +# Integrations + +Schema integrates with popular frameworks and libraries. This section shows working examples for forms (TanStack Form) and web servers (Elysia). + +### Forms + +#### TanStack Form + +Features: + +- Errors are formatted with the `StandardSchemaV1` formatter. +- Fields are validated **and parsed** (not just strings). +- You can add form-level validation by attaching filters to the struct. +- Schemas may include async transformations. + +**Example** (Parse user input and surface form-level errors) + +```tsx +import { useForm } from "@tanstack/react-form" +import type { AnyFieldApi } from "@tanstack/react-form" +import { Effect, Schema, SchemaGetter, SchemaTransformation } from "effect" +import React from "react" + +// ---------------------------------------------------- +// Toolkit +// ---------------------------------------------------- + +// Treat an empty string from the UI as `undefined` for optional fields, +// and encode `undefined` back to an empty string when showing it. +const UndefinedFromEmptyString = Schema.Undefined.pipe( + Schema.encodeTo(Schema.Literal(""), { + decode: SchemaGetter.transform(() => undefined), + encode: SchemaGetter.transform(() => "" as const) + }) +) + +// Helper to make any schema "UI-optional": +// - empty string -> undefined +// - otherwise validate/parse with the given schema +function optional(schema: S) { + return Schema.Union([UndefinedFromEmptyString, schema]) +} + +// Decode helper that returns a `Promise` with either a typed value +// or a human-friendly error message string. +function decode(schema: Schema.Codec) { + return function(value: unknown) { + return Schema.decodeUnknownEffect(schema)(value).pipe( + Effect.mapError((error) => error.message), + Effect.result, + Effect.runPromise + ) + } +} + +// ---------------------------------------------------- +// Schemas +// ---------------------------------------------------- + +const FirstName = Schema.String.check( + Schema.isMinLength(3, { + message: "must be at least 3 characters" + }) +) +const Age = Schema.Number.check( + Schema.isInt({ message: "must be an integer" }).abort(), + Schema.isBetween( + { minimum: 18, maximum: 100 }, + { + message: "must be between 18 and 100" + } + ) +).pipe(Schema.encodeTo(Schema.String, SchemaTransformation.numberFromString)) + +// Whole-form schema with a form-level rule: +// If firstName is "John", age is required. +const schema = Schema.Struct({ + firstName: FirstName, + age: optional(Age) +}).check( + Schema.makeFilter(({ firstName, age }) => { + if (firstName === "John" && age === undefined) return "Age is required for John" + }) +) + +function FieldInfo({ field }: { field: AnyFieldApi }) { + return ( + <> + {field.state.meta.isTouched && !field.state.meta.isValid ? + {field.state.meta.errors.map((error) => error.message).join(", ")} : + null} + {field.state.meta.isValidating ? "Validating..." : null} + + ) +} + +export default function App() { + // We parse the whole form on submit and keep the typed value here + const parsedRef = React.useRef(undefined) + + const form = useForm({ + defaultValues: { + firstName: "John", + age: "" + } satisfies (typeof schema)["Encoded"], + validators: { + onChangeAsync: Schema.toStandardSchemaV1(schema), + + // Final guard before submit: + // - decode the entire form + // - on failure: return a string (form-level error) + // - on success: stash the typed value for `onSubmit` + onSubmitAsync: async ({ value }) => { + const r = await decode(schema)(value) + if (r._tag === "Failure") return r.failure + parsedRef.current = r.success + } + }, + + // Submit runs only if validators pass. + // At this point `parsedRef.current` holds the fully typed value. + onSubmit: async () => { + // get the parsed value from the ref + const parsed = parsedRef.current + if (!parsed) throw new Error("Unexpected submit without parsed data") + // Use the typed data here (no post-processing needed) + console.log(parsed) + } + }) + + return ( +

+ ) +} +``` + +### Integrations + +#### Elysia + +```ts +import { node } from "@elysiajs/node" +import { openapi } from "@elysiajs/openapi" +import { Schema } from "effect" +import { Elysia } from "elysia" + +// ---------------------------------------------------- +// Utilities +// ---------------------------------------------------- + +function encodingJsonSchema(schema: Schema.Codec) { + return Schema.toStandardSchemaV1( + Schema.flip(Schema.toCodecJson(schema)).annotate({ + direction: "encoding" + }) + ) +} + +function decodingJsonSchema(schema: Schema.Codec) { + return Schema.toStandardSchemaV1(Schema.toCodecJson(schema)) +} + +function decodingStringSchema(schema: Schema.Codec) { + return Schema.toStandardSchemaV1(Schema.toCodecStringTree(schema)) +} + +function mapJsonSchema(schema: Schema.Top) { + return Schema.toJsonSchema(schema.ast.annotations?.direction === "encoding" ? Schema.flip(schema) : schema, { + target: "draft-2020-12", // or "draft-07" + referenceStrategy: "skip" + }).schema +} + +// ---------------------------------------------------- +// Application +// ---------------------------------------------------- + +new Elysia({ adapter: node() }) + .use( + openapi({ + mapJsonSchema: { + effect: mapJsonSchema + } + }) + ) + .get( + "/id/:id", + async ({ status, params, query }) => { + console.log(`params: ${JSON.stringify(params)}`) + console.log(`query: ${JSON.stringify(query)}`) + return status(200, { date: new Date() }) + }, + { + params: decodingStringSchema( + Schema.Struct({ + id: Schema.Int + }) + ), + query: decodingStringSchema( + Schema.Struct({ + required: Schema.String, + optional: Schema.optionalKey(Schema.String), + array: Schema.Array(Schema.String), + tuple: Schema.Tuple([Schema.String, Schema.Int]) + }) + ), + response: { + 200: encodingJsonSchema( + Schema.Struct({ + date: Schema.ValidDate + }) + ) + } + } + ) + .post( + "/body", + ({ body }) => { + console.log(body) + return { bigint: body.bigint + 1n } + }, + { + body: decodingJsonSchema( + Schema.Struct({ + bigint: Schema.BigInt + }) + ), + response: { + 200: encodingJsonSchema( + Schema.Struct({ + bigint: Schema.BigInt + }) + ) + } + } + ) + .listen(3000) +``` diff --git a/.repos/effect/packages/effect/benchmark/Pool.ts b/.repos/effect/packages/effect/benchmark/Pool.ts new file mode 100644 index 000000000..094b50eb4 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/benchmark/http/multipart.ts b/.repos/effect/packages/effect/benchmark/http/multipart.ts new file mode 100644 index 000000000..22ef22378 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/benchmark/http/tracer.ts b/.repos/effect/packages/effect/benchmark/http/tracer.ts new file mode 100644 index 000000000..ef9b4bbaf --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/benchmark/httpapi/sseClientDecoder.ts b/.repos/effect/packages/effect/benchmark/httpapi/sseClientDecoder.ts new file mode 100644 index 000000000..0b41490a8 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/benchmark/rpc/RpcSerialization.ts b/.repos/effect/packages/effect/benchmark/rpc/RpcSerialization.ts new file mode 100644 index 000000000..f5bc5e047 --- /dev/null +++ b/.repos/effect/packages/effect/benchmark/rpc/RpcSerialization.ts @@ -0,0 +1,299 @@ +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: "Msgpack", serialization: RpcSerialization.msgPack }, + { 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( + "Msgpack uses RpcSerialization.msgPack defaults, including records. SchemaBinary fingerprints envelopes only and shares one string dictionary across the frames of a connection." +) +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 msgpackThroughput = new Map() +for (const task of bench.tasks) { + const label = labels.get(task.name)! + if (label.formatName === "Msgpack" && task.result?.state === "completed") { + msgpackThroughput.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 = msgpackThroughput.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 Msgpack": `${(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/.repos/effect/packages/effect/benchmark/schema/Optic.ts b/.repos/effect/packages/effect/benchmark/schema/Optic.ts new file mode 100644 index 000000000..5d4741772 --- /dev/null +++ b/.repos/effect/packages/effect/benchmark/schema/Optic.ts @@ -0,0 +1,104 @@ +import { Optic, Schema } from "effect" +import { Bench } from "tinybench" + +// 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 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")({ + id: Schema.Number, + profile: Schema.Struct({ + name: Schema.String, + email: Schema.String, + address: Schema.Struct({ + street: Schema.String, + city: Schema.String, + country: Schema.String + }) + }) +}) {} + +// Create a user instance +const user = User.make({ + id: 1, + profile: { + name: "John Doe", + email: "john@example.com", + address: { + street: "123 Main St", + city: "New York", + country: "USA" + } + } +}) + +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", 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() + +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/.repos/effect/packages/effect/benchmark/schema/SchemaBinary.md b/.repos/effect/packages/effect/benchmark/schema/SchemaBinary.md new file mode 100644 index 000000000..a337b718f --- /dev/null +++ b/.repos/effect/packages/effect/benchmark/schema/SchemaBinary.md @@ -0,0 +1,155 @@ +# SchemaBinary benchmark + +Run from the repository root: + +```sh +nix develop -c pnpm --dir packages/effect exec node benchmark/schema/SchemaBinary.ts +``` + +To repeat the raw msgpackr cases without its native string extractor: + +```sh +nix develop -c env MSGPACKR_NATIVE_ACCELERATION_DISABLED=true pnpm --dir packages/effect exec node benchmark/schema/SchemaBinary.ts +``` + +These results are from one full run with fingerprint-mode row runs on Linux x86_64 (AMD EPYC 4344P) with Node 26.7.0. Codec and schema construction are excluded. One-shot tasks use 100 warmups and 1,000 measured samples; streaming tasks use 25 warmups and 250 measured samples. Throughput is machine-local and should only be compared within this run. + +Both modes pack each field id with a wire kind, so varint numbers, decimals, and booleans skip the per-field length prefix and booleans ride in the tag itself. Short decimals such as `12.5` encode as a varint mantissa plus a scale instead of an eight-byte f64, and index-signature record pairs pack the value kind into the key-length varint. An array of structs is written as a row run: each distinct set of present fields declares its shape once, and later rows reference that shape and any string already written in the same slot. Fingerprint mode omits field identifiers entirely: single frames use positional layouts and its row shapes are presence masks instead of field id lists. JSON, Msgpack, and NDJSON use the same `Schema.toCodecJson` representation. + +Protobuf uses `protobufjs` reflection types parsed once before timing. `Schema.Number` maps to proto3 `double`, records map to `map`, and top-level arrays and records use wrapper messages. Tuple samples and nested arrays use messages because protobuf does not support either shape directly. The timed decode paths include `toObject` and the case adapters, so every format produces its final application value. Descriptor construction and Protobuf encode adapters are excluded. + +Every format is timed through its public API, so the SchemaBinary and JSON / Msgpack numbers include the Schema pass that produces or validates the application value. Where the binary layer already validates a schema on its own, `toCodec` skips that pass in both directions rather than repeating the work: encoding runs the binary encoder directly, and decoding hands the value it just produced straight through. Any input the binary layer did not produce, `Schema.is` included, still runs the real check. + +The raw serializer section deliberately relaxes that rule. It compares the public SchemaBinary codec with raw msgpackr and JSON calls that do not validate the application value. `Effect Msgpack schema` remains in those tables as the public-API comparison. The benchmark prints whether msgpackr's native string extractor is active. + +A static `.proto` must pick one numeric type for `Schema.Number`, so integral values pay eight bytes where SchemaBinary picks a varint per value. Typing the known-integer fields as `uint32` would reduce the 200-row Protobuf payload from 24,480 to 20,600 bytes and the small record from 42 to 35, but would no longer cover the full `Schema.Number` domain. The reported sizes also reflect `protobufjs` 7.6.5 encoding default-valued scalars such as `verified: false`; an encoder that applies proto3 implicit presence would omit them. + +The streaming setup compares the closest public decode paths. SchemaBinary reuses one synchronous parser for each feed shape. Protobuf calls `decodeDelimited` across the batch and materializes every message with `toObject` plus the case adapter. Msgpack synchronously calls `unpackMultiple` on the batch, then validates each value. NDJSON runs `Ndjson.decodeSchema` through Effect Stream and Channel for every operation, including UTF-8 decoding, line splitting, `JSON.parse`, schema validation, and runtime scheduling. A batch is one Channel run over 32 lines, or 200 lines for the per-frame case. Single and fragmented measurements each run a complete Channel for one line, so their scheduling cost is not amortized. This makes batch the closest throughput comparison while preserving the cost of each public API. + +SchemaBinary and Protobuf use length-prefixed frames, NDJSON includes one newline byte per frame, and Msgpack concatenates self-delimiting values. Fragmented inputs split after the first byte. Stream compression is applied to the complete concatenated stream. + +The `200-row array payload` case uses `Schema.Array(LargeRow)`, so one value is an array containing 200 rows. A one-shot operation encodes or decodes that entire array. Its streaming batch contains 32 frames with the same 200-row array, or 6,400 row occurrences in total. The `200 single-row frames` case uses `LargeRow` directly and sends the 200 distinct rows as 200 frames. Streaming throughput is decoded values per second: arrays per second for the first case and rows per second for the second. Multiply the array rate by 200 to compare decoded row throughput. + +## Payload size + +Cells contain raw / gzip -6 / zstd bytes. + +| Case | SchemaBinary | Fingerprint | JSON | Msgpack | Protobuf | +| ---------------------- | -----------------: | -----------------: | ------------------: | ------------------: | ------------------: | +| small record | 47 / 70 / 56 | 30 / 50 / 39 | 89 / 100 / 92 | 69 / 88 / 78 | 42 / 51 / 44 | +| nested payload | 280 / 298 / 290 | 200 / 210 / 209 | 453 / 303 / 309 | 385 / 304 / 299 | 241 / 220 / 214 | +| collections | 1079 / 710 / 687 | 1065 / 692 / 669 | 1828 / 671 / 660 | 1462 / 788 / 805 | 2932 / 799 / 754 | +| index signatures / 128 | 1673 / 646 / 603 | 1680 / 652 / 612 | 2235 / 573 / 559 | 2203 / 676 / 629 | 2834 / 586 / 476 | +| index signatures / 512 | 7145 / 2278 / 2298 | 7152 / 2286 / 2310 | 9531 / 2266 / 2074 | 9287 / 2544 / 2304 | 11666 / 1978 / 1737 | +| 200-row array payload | 7771 / 2441 / 2224 | 7741 / 2398 / 2182 | 57529 / 3230 / 3008 | 51283 / 3516 / 3320 | 24480 / 3111 / 2839 | + +Streaming cells contain total raw / gzip -6 / zstd bytes for the complete stream. + +| Case | Frames | SchemaBinary | Fingerprint | Msgpack | Protobuf | NDJSON | +| ---------------------- | -----: | -------------------: | -------------------: | --------------------: | --------------------: | ---------------------: | +| small record | 32 | 1504 / 83 / 66 | 960 / 60 / 48 | 2240 / 111 / 87 | 1376 / 66 / 51 | 2880 / 123 / 98 | +| nested payload | 32 | 8960 / 380 / 295 | 6400 / 256 / 209 | 10880 / 369 / 306 | 7776 / 271 / 220 | 14528 / 399 / 315 | +| collections | 32 | 34528 / 968 / 729 | 34080 / 935 / 711 | 47424 / 1130 / 813 | 93888 / 2074 / 774 | 58528 / 1079 / 676 | +| index signatures / 128 | 32 | 53536 / 1007 / 615 | 53760 / 1017 / 624 | 71008 / 1252 / 649 | 90752 / 1503 / 492 | 71552 / 1073 / 540 | +| index signatures / 512 | 32 | 228640 / 4538 / 2320 | 228864 / 4557 / 2332 | 297696 / 5775 / 2037 | 373376 / 5956 / 1782 | 305024 / 5831 / 1829 | +| 200-row array payload | 32 | 248672 / 4598 / 2261 | 247712 / 4547 / 2220 | 623488 / 12335 / 2736 | 783456 / 12374 / 2909 | 1840960 / 87481 / 3226 | +| 200 single-row frames | 200 | 27040 / 3079 / 3173 | 21240 / 2876 / 2731 | 51520 / 2956 / 2888 | 24280 / 3104 / 2788 | 57528 / 3230 / 3019 | + +## One-shot throughput + +Average encode operations per second: + +| Case | Default | Fingerprint | JSON | Msgpack | Protobuf | +| ---------------------- | --------: | ----------: | ------: | ------: | --------: | +| small record | 1,319,592 | 1,507,529 | 436,487 | 704,398 | 2,000,847 | +| nested payload | 588,799 | 641,251 | 259,634 | 285,917 | 562,044 | +| collections | 146,285 | 153,447 | 22,636 | 23,006 | 75,809 | +| index signatures / 128 | 138,629 | 139,601 | 36,497 | 35,532 | 63,231 | +| index signatures / 512 | 36,813 | 36,699 | 6,999 | 6,358 | 15,699 | +| 200-row array payload | 19,622 | 19,671 | 7,182 | 4,192 | 12,040 | + +Average decode operations per second: + +| Case | Default | Fingerprint | JSON | Msgpack | Protobuf | +| ---------------------- | --------: | ----------: | ------: | ------: | --------: | +| small record | 1,560,001 | 1,613,600 | 398,999 | 733,207 | 2,281,446 | +| nested payload | 586,239 | 689,588 | 216,563 | 270,625 | 560,920 | +| collections | 113,511 | 116,816 | 21,138 | 22,045 | 77,912 | +| index signatures / 128 | 63,122 | 63,161 | 28,565 | 30,062 | 44,545 | +| index signatures / 512 | 16,412 | 16,338 | 5,432 | 4,593 | 6,029 | +| 200-row array payload | 23,071 | 23,094 | 5,624 | 4,809 | 15,663 | + +## Raw serializer adversarial cases + +These cases show where SchemaBinary loses. The shallow record makes fixed per-call costs visible. The clinical fixture is msgpackr's [`tests/example4.json`](https://github.com/kriszyp/msgpackr/blob/e3c852df383059b9ea8a8d3e5517d6e5527bf756/tests/example4.json), the input used by its own general benchmark. It has many nested, heterogeneous object shapes, which suit msgpackr's dynamic record cache. + +The clinical Schema is inferred once before timing. Objects at the same array path are merged, missing fields become optional, and mixed leaf types become unions. Schema inference and codec construction are excluded. The shared-structure Packr is primed once, matching msgpackr's steady-state benchmark setup. + +Raw / gzip -6 / zstd bytes: + +| Case | SchemaBinary | Fingerprint | Effect Msgpack schema | msgpackr shared | msgpackr plain | JSON raw | +| ------------------------- | -----------------: | -----------------: | --------------------: | -----------------: | -----------------: | -----------------: | +| shallow record | 47 / 70 / 56 | 30 / 50 / 39 | 69 / 88 / 78 | 24 / 42 / 33 | 69 / 88 / 78 | 89 / 100 / 92 | +| msgpackr clinical fixture | 4433 / 2282 / 2317 | 3513 / 1623 / 1672 | 6357 / 2364 / 2438 | 3821 / 1604 / 1681 | 6357 / 2364 / 2435 | 7569 / 2201 / 2288 | + +Average operations per second with msgpackr native acceleration enabled: + +| Case | Direction | SchemaBinary | Fingerprint | Effect Msgpack schema | msgpackr shared | msgpackr plain | JSON raw | +| ------------------------- | --------- | -----------: | ----------: | --------------------: | --------------: | -------------: | --------: | +| shallow record | encode | 1,704,527 | 1,842,472 | 798,150 | 2,463,086 | 2,806,880 | 2,928,575 | +| shallow record | decode | 2,039,262 | 2,202,957 | 891,250 | 6,906,994 | 4,324,485 | 2,280,053 | +| msgpackr clinical fixture | encode | 52,186 | 58,306 | 17,531 | 67,371 | 61,381 | 131,325 | +| msgpackr clinical fixture | decode | 48,462 | 58,404 | 16,928 | 183,899 | 53,859 | 56,427 | + +Clinical-fixture decode operations per second with native acceleration toggled: + +| Format | Enabled | Disabled | +| --------------------- | ------: | -------: | +| SchemaBinary | 48,462 | 48,476 | +| Fingerprint | 58,404 | 58,441 | +| Effect Msgpack schema | 16,928 | 15,124 | +| msgpackr shared | 183,899 | 118,799 | +| msgpackr plain | 53,859 | 40,537 | +| JSON raw | 56,427 | 57,388 | + +Shared-structure msgpackr still leads the clinical fixture: 1.29x on encode and 3.79x on decode against default SchemaBinary with native extraction enabled, and 2.45x on decode with it disabled. Fingerprint mode narrows that to 1.16x and 3.15x while staying 8% smaller than the shared-structure payload. + +Against everything that does not generate code, SchemaBinary is at or ahead of the field on the clinical fixture. Fingerprint decode beats plain msgpackr by 1.08x with native extraction enabled and 1.44x with it disabled, and beats raw `JSON.parse` by 1.03x. Fingerprint encode is within 5% of plain msgpackr, though `JSON.stringify` is 2.3x ahead of both. Against the schema-validating Effect Msgpack API, default SchemaBinary is 3.0x faster to encode and 2.9x faster to decode. + +The shallow record is where the remaining fixed cost shows. Both directions carry the parse pipeline around the codec, roughly a fifth of a shallow decode, which msgpackr does not pay because `unpack` is one function call. + +The rest of the shared-structure gap is code generation. msgpackr builds one reader per record structure with `new Function`, so a decoded object is an object literal: about 1 ns per property against 8 to 9 ns for the keyed store this codec has to use. That is worth roughly 2.3 us of the clinical fixture's 20 us decode. Removing the layout dispatch on top of it would leave around 13 us, still short of the 5.4 us shared-structure msgpackr reaches with its native string extractor. Closing that gap is a `new Function` decision, not a tuning one. + +## Streaming decode throughput + +Average decoded values per second for batched input: + +| Case | Default | Fingerprint | Msgpack | Protobuf | NDJSON | +| ---------------------- | --------: | ----------: | ------: | --------: | ------: | +| small record | 5,116,343 | 5,956,797 | 967,638 | 4,170,440 | 831,994 | +| nested payload | 821,034 | 1,042,459 | 284,246 | 586,806 | 312,906 | +| collections | 121,413 | 123,472 | 21,071 | 77,768 | 20,555 | +| index signatures / 128 | 65,080 | 65,325 | 28,163 | 38,953 | 28,743 | +| index signatures / 512 | 15,774 | 16,146 | 4,117 | 5,135 | 5,071 | +| 200-row array payload | 23,238 | 23,247 | 6,573 | 7,455 | 4,988 | +| 200 single-row frames | 2,257,102 | 2,682,446 | 849,319 | 1,692,708 | 950,570 | + +Average decoded values per second for single and first-byte-fragmented input: + +| Case | Default single | Default fragmented | Fingerprint single | Fingerprint fragmented | NDJSON single | NDJSON fragmented | +| ---------------------- | -------------: | -----------------: | -----------------: | ---------------------: | ------------: | ----------------: | +| small record | 3,020,534 | 2,168,195 | 3,695,255 | 2,720,058 | 131,636 | 133,208 | +| nested payload | 721,200 | 677,292 | 890,654 | 833,766 | 104,947 | 103,084 | +| collections | 119,996 | 118,256 | 121,800 | 119,050 | 18,668 | 18,652 | +| index signatures / 128 | 64,295 | 64,417 | 64,764 | 64,364 | 24,908 | 24,547 | +| index signatures / 512 | 16,424 | 16,396 | 16,581 | 16,488 | 5,195 | 5,098 | +| 200-row array payload | 23,468 | 23,205 | 23,418 | 23,052 | 5,251 | 5,218 | +| 200 single-row frames | 1,505,519 | 1,473,846 | 1,915,049 | 1,644,030 | 131,373 | 126,835 | + +## Analysis + +- Fingerprint mode is now the smallest SchemaBinary format for every case except the two index-signature maps, where the two modes are within 7 bytes: row runs apply in both modes, and fingerprint shapes are presence masks with no id list, so the `200-row array payload` dropped from 19,454 to 7,741 bytes. Its decode rate rose from 14,905 to 23,321 ops/s, matching the default mode. +- The default mode has the smallest raw payload of any non-fingerprint format in every case except the small record, where only Protobuf's one-byte field numbers beat its hashed five-byte field tags (42 vs 47 bytes). Fingerprint mode wins there too (30 bytes). +- Compression still changes the map ranking: Protobuf has the smallest zstd output for both index-signature cases, and JSON wins gzip at 128 keys. +- Protobuf keeps the small record in both directions, by about 1.5x. SchemaBinary leads the other five one-shot cases in both directions, from 1.05x on the nested payload up to 2.7x on index-signature decode, while returning schema-validated application values. Against Msgpack through the same public API it leads every case, by 1.9x to 6.4x. +- Single-frame and fragmented streaming rates for the `200 single-row frames` case carry 20% or worse RME at 250 samples, so only their batch column is worth comparing across runs. diff --git a/.repos/effect/packages/effect/benchmark/schema/SchemaBinary.ts b/.repos/effect/packages/effect/benchmark/schema/SchemaBinary.ts new file mode 100644 index 000000000..1b1d93126 --- /dev/null +++ b/.repos/effect/packages/effect/benchmark/schema/SchemaBinary.ts @@ -0,0 +1,930 @@ +import { Effect, Schema, Stream } from "effect" +import { Msgpack, Ndjson, SchemaBinary } from "effect/unstable/encoding" +import { isNativeAccelerationEnabled, pack, Packr, unpack, Unpackr } from "msgpackr" +import assert from "node:assert/strict" +import { Buffer } from "node:buffer" +import { readFileSync } from "node:fs" +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])) + +type InferredNode = + | { readonly _tag: "never" } + | { readonly _tag: "null" | "string" | "number" | "boolean" } + | { readonly _tag: "array"; readonly element: InferredNode } + | { + readonly _tag: "object" + readonly count: number + readonly fields: ReadonlyMap + } + | { readonly _tag: "union"; readonly members: ReadonlyMap } + +const inferredNever: InferredNode = { _tag: "never" } + +const inferredMembers = (node: InferredNode): ReadonlyMap => + node._tag === "union" ? node.members : new Map([[node._tag, node]]) + +const mergeInferred = (left: InferredNode, right: InferredNode): InferredNode => { + if (left._tag === "never") return right + if (right._tag === "never") return left + if (left._tag === right._tag) { + if (left._tag === "array" && right._tag === "array") { + return { _tag: "array", element: mergeInferred(left.element, right.element) } + } + if (left._tag === "object" && right._tag === "object") { + const fields = new Map(left.fields) + for (const [key, field] of right.fields) { + const previous = fields.get(key) + fields.set( + key, + previous === undefined + ? field + : { seen: previous.seen + field.seen, node: mergeInferred(previous.node, field.node) } + ) + } + return { _tag: "object", count: left.count + right.count, fields } + } + if (left._tag === "union" && right._tag === "union") { + const members = new Map(left.members) + for (const [tag, member] of right.members) { + members.set(tag, members.has(tag) ? mergeInferred(members.get(tag)!, member) : member) + } + return { _tag: "union", members } + } + return left + } + const members = new Map(inferredMembers(left)) + for (const [tag, member] of inferredMembers(right)) { + members.set(tag, members.has(tag) ? mergeInferred(members.get(tag)!, member) : member) + } + return { _tag: "union", members } +} + +const inferNode = (value: unknown): InferredNode => { + if (value === null) return { _tag: "null" } + if (typeof value === "string") return { _tag: "string" } + if (typeof value === "number") return { _tag: "number" } + if (typeof value === "boolean") return { _tag: "boolean" } + if (Array.isArray(value)) { + return { _tag: "array", element: value.reduce((node, item) => mergeInferred(node, inferNode(item)), inferredNever) } + } + if (typeof value === "object") { + return { + _tag: "object", + count: 1, + fields: new Map(Object.entries(value).map(([key, field]) => [key, { seen: 1, node: inferNode(field) }])) + } + } + throw new Error(`Cannot infer a benchmark schema for ${typeof value}`) +} + +const inferredSchema = (node: InferredNode): Schema.ConstraintCodec => { + switch (node._tag) { + case "never": + return Schema.Never + case "null": + return Schema.Null + case "string": + return Schema.String + case "number": + return Schema.Number + case "boolean": + return Schema.Boolean + case "array": + return Schema.Array(inferredSchema(node.element)) + case "union": + return Schema.Union(Array.from(node.members.values(), inferredSchema)) + case "object": + return Schema.Struct(Object.fromEntries(Array.from(node.fields, ([key, field]) => [ + key, + field.seen === node.count + ? inferredSchema(field.node) + : Schema.optionalKey(inferredSchema(field.node)) + ]))) + } +} + +// From msgpackr's benchmark corpus at e3c852d: https://github.com/kriszyp/msgpackr/blob/e3c852df383059b9ea8a8d3e5517d6e5527bf756/tests/example4.json +const msgpackrClinicalValue: unknown = JSON.parse( + readFileSync(new URL("./fixtures/msgpackr-example4.json", import.meta.url), "utf8") +) +const MsgpackrClinical = inferredSchema(inferNode(msgpackrClinicalValue)) + +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 + +const rawCases = [ + { + name: "small record / raw serializers", + schema: SmallRecord, + value: cases[0].value + }, + { + name: "msgpackr clinical fixture / raw serializers", + schema: MsgpackrClinical, + value: msgpackrClinicalValue + } +] 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 msgpackCodec = Msgpack.schema(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 msgpackEncode = Schema.encodeUnknownSync(msgpackCodec) + const msgpackDecode = Schema.decodeUnknownSync(msgpackCodec) + const protobufValue = protobufFixture.encodeInput(value) + + const binary = binaryEncode(value) + const fingerprint = fingerprintEncode(value).slice() + const json = jsonEncode(value) + const jsonBytes = textEncoder.encode(json) + const msgpack = msgpackEncode(value) + 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(msgpackDecode(msgpack), 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: "Msgpack", + ...sizes(msgpack), + encode: () => msgpackEncode(value), + decode: () => msgpackDecode(msgpack) + }, + { + name: "Protobuf", + ...sizes(protobufBytes), + encode: () => protobufFixture.type.encode(protobufValue).finish(), + decode: protobufDecode + } + ] + } +} + +const prepareRaw = >( + schema: S, + value: S["Type"] +): { readonly formats: ReadonlyArray } => { + const binaryCodec = SchemaBinary.toCodec(schema) + const fingerprintCodec = SchemaBinary.toCodec(schema, { fingerprint: true }) + const msgpackCodec = Msgpack.schema(Schema.toCodecJson(schema)) + const binaryEncode = Schema.encodeUnknownSync(binaryCodec) + const binaryDecode = Schema.decodeUnknownSync(binaryCodec) + const fingerprintEncode = Schema.encodeUnknownSync(fingerprintCodec) + const fingerprintDecode = Schema.decodeUnknownSync(fingerprintCodec) + const msgpackEncode = Schema.encodeUnknownSync(msgpackCodec) + const msgpackDecode = Schema.decodeUnknownSync(msgpackCodec) + const sharedPackr = new Packr({ structures: [] }) + + sharedPackr.pack(value) + const binary = binaryEncode(value).slice() + const fingerprint = fingerprintEncode(value).slice() + const effectMsgpack = msgpackEncode(value) + const sharedMsgpack = sharedPackr.pack(value).slice() + const plainMsgpack = pack(value).slice() + const json = Buffer.from(JSON.stringify(value)) + + assert.deepStrictEqual(binaryDecode(binary), value) + assert.deepStrictEqual(fingerprintDecode(fingerprint), value) + assert.deepStrictEqual(msgpackDecode(effectMsgpack), value) + assert.deepStrictEqual(sharedPackr.unpack(sharedMsgpack), value) + assert.deepStrictEqual(unpack(plainMsgpack), value) + assert.deepStrictEqual(JSON.parse(json.toString()), 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: "Effect Msgpack schema", + ...sizes(effectMsgpack), + encode: () => msgpackEncode(value), + decode: () => msgpackDecode(effectMsgpack) + }, + { + name: "msgpackr raw / shared structures", + ...sizes(sharedMsgpack), + encode: () => sharedPackr.pack(value), + decode: () => sharedPackr.unpack(sharedMsgpack) + }, + { + name: "msgpackr raw / plain", + ...sizes(plainMsgpack), + encode: () => pack(value), + decode: () => unpack(plainMsgpack) + }, + { + name: "JSON raw", + ...sizes(json), + encode: () => Buffer.from(JSON.stringify(value)), + decode: () => JSON.parse(json.toString()) + } + ] + } +} + +const prepared = cases.map((testCase) => ({ + name: testCase.name, + ...prepare(testCase.schema, testCase.value, testCase.protobuf) +})) + +const preparedRaw = rawCases.map((testCase) => ({ + name: testCase.name, + ...prepareRaw(testCase.schema, testCase.value) +})) + +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 encodeMsgpackValue = Schema.encodeUnknownSync(jsonSchema) + const decodeMsgpackValue = Schema.decodeUnknownSync(jsonSchema) + const msgpackPackr = new Packr() + const msgpackStream = concatFrames(values.map((value) => msgpackPackr.pack(encodeMsgpackValue(value)).slice())) + const msgpackUnpackr = new Unpackr() + + 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(encodeMsgpackValue(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 + const decodeMsgpackStream = () => + msgpackUnpackr.unpackMultiple(msgpackStream).map((value) => decodeMsgpackValue(value)) + + 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(decodeMsgpackStream(), 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: "Msgpack unpackMultiple / batch", framesPerOp: values.length, decode: decodeMsgpackStream }, + { 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: "Msgpack", frames: values.length, ...sizes(msgpackStream) }, + { 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 and Msgpack use the same Schema.toCodecJson representation; JSON sizes are UTF-8 bytes.") +console.log(`msgpackr native acceleration enabled: ${isNativeAccelerationEnabled}.`) +console.log( + "Cases suffixed with / raw serializers compare SchemaBinary's public codec with unvalidated raw serializers." +) +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.table(preparedRaw.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() + }) + } + } +} + +for (const testCase of preparedRaw) { + 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/.repos/effect/packages/effect/benchmark/schema/SchemaError.ts b/.repos/effect/packages/effect/benchmark/schema/SchemaError.ts new file mode 100644 index 000000000..ba2b450c7 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/benchmark/schema/fixtures/msgpackr-example4.json b/.repos/effect/packages/effect/benchmark/schema/fixtures/msgpackr-example4.json new file mode 100644 index 000000000..585c88e55 --- /dev/null +++ b/.repos/effect/packages/effect/benchmark/schema/fixtures/msgpackr-example4.json @@ -0,0 +1,284 @@ +{ + "metadata": { + "Designs": ["Randomized Controlled Trial"], + "Types": [], + "BriefSummary": "To determine the efficacy, long-term safety, and tolerability of alirocumab 300 mg every 4\n weeks (Q4W), in comparison with placebo, as well as its potential as a starting regimen. The\n dose regimen of 75 mg every 2 weeks (Q2W), as used in other studies, was added as a\n calibrator.", + "Abstract": "To determine the efficacy, long-term safety, and tolerability of alirocumab 300 mg every 4\n weeks (Q4W), in comparison with placebo, as well as its potential as a starting regimen. The\n dose regimen of 75 mg every 2 weeks (Q2W), as used in other studies, was added as a\n calibrator.", + "Acronym": null, + "ArticleId": "Qy3gwKWSoaWRmbmFEQA", + "Authors": null, + "CochraneID": null, + "Confidential": false, + "CorporateAuthor": null, + "Country": "Bulgaria, Canada, Hungary, Israel, Norway, Slovakia, United Kingdom, United States", + "CustomData": null, + "DatabaseType": "ClinicalTrials.gov", + "DOI": null, + "EmbaseAccessionNumber": null, + "Emtree": null, + "ErrataText": null, + "FullTextURL": null, + "Institution": null, + "ISSN": null, + "Issue": null, + "JournalTitle": null, + "MedlineID": null, + "MeSH": "Hypercholesterolemia|Antibodies, Monoclonal", + "Pages": null, + "ParentChildStatus": null, + "ParentID": null, + "PublicationDate": "March 21, 2017", + "PublicationYear": 2017, + "PubType": null, + "ReferenceStudy": null, + "SecondarySourceID": null, + "Source": "Regeneron Pharmaceuticals", + "SourceReferenceId": "NCT01926782", + "TaStudyDesign": "Randomized", + "Title": "A Randomized, Double-Blind, Placebo-Controlled Study to Evaluate the Efficacy and Safety of an Every Four Weeks Treatment Regimen of Alirocumab in Patients With Primary Hypercholesterolemia", + "TrialOutcome": null, + "Volume": null, + "Id": 179246831, + "Created": "2020-04-10T14:48:20.4384957Z", + "VersionNo": 2, + "ExtractData": null, + "Digitized": true, + "IsRapidExtract": false, + "IsUploaded": false + }, + "design": "Randomized Controlled Trial", + "conditions": [{ "label": "Cholesterol Total Increased", "id": "SUE_c" }], + "phase": 3, + "name": "NCT01926782", + "trialIds": ["NCT01926782"], + "acronyms": [], + "outcomeCount": 156, + "id": 179246831, + "groups": [ + { + "Id": "4r", + "RefId": "B5|O2~Alirocumab 75 mg Q2W/Up 150 mg Q2W Without Concomitant Statin", + "OriginalName": "Alirocumab 75 mg Q2W/Up 150 mg Q2W Without Concomitant Statin", + "N": 37, + "age": 59.3, + "ageSD": 11.3, + "male": 37.83783783783784, + "Interventions": [{ "termIds": [["SUBYEL", "SUB_Oc"], ["SUNUVb"]] }], + "analyzeAs": "Alirocumab", + "analyzableScore": 1.0717734625362931, + "matchingScore": 0 + }, + { + "Id": "zB", + "RefId": "B6|O3~Alirocumab 300 mg Q4W/Up 150 mg Q2W Without Concomitant Statin", + "OriginalName": "Alirocumab 300 mg Q4W/Up 150 mg Q2W Without Concomitant Statin", + "N": 146, + "age": 59.2, + "ageSD": 10.8, + "male": 45.205479452054796, + "Interventions": [{ "termIds": [["SUBYEL", "SUB_Oc"]] }], + "analyzeAs": "Statins", + "analyzableScore": 1.0717734625362931, + "matchingScore": 0 + }, + { + "Id": "3!", + "RefId": "B4|O1~Placebo Q2W Without Concomitant Statin", + "OriginalName": "Placebo Q2W Without Concomitant Statin", + "N": 73, + "age": 59.4, + "ageSD": 10.2, + "male": 54.794520547945204, + "Interventions": [{ "termIds": [["SUGeLS"], ["SUBYEL", "SUB_Oc"]] }], + "analyzeAs": "Control", + "analyzableScore": 1.2020833333333334, + "matchingScore": 0 + }, + { + "Id": "tv", + "RefId": "E3", + "OriginalName": "Alirocumab 300 mg Q4W/Up 150 mg Q2W", + "Interventions": [{ "termIds": [["SUCO54", "SUNUVb"]] }] + }, + { + "Id": "jt", + "RefId": "B3|O3~Alirocumab 300 mg Q4W/Up 150 mg Q2W With Concomitant Statin", + "OriginalName": "Alirocumab 300 mg Q4W/Up 150 mg Q2W With Concomitant Statin", + "N": 312, + "age": 61.6, + "ageSD": 10, + "male": 60.8974358974359, + "Interventions": [{ "termIds": [["SUBYEL", "SUB_Oc"]] }] + }, + { + "Id": "5!", + "RefId": "E2", + "OriginalName": "Alirocumab 75 mg Q2W/Up 150 mg Q2W", + "Interventions": [{ "termIds": [["SUNUVb"]] }] + }, + { + "Id": "4E", + "RefId": "B2|O2~Alirocumab 75 mg Q2W/Up 150 mg Q2W With Concomitant Statin", + "OriginalName": "Alirocumab 75 mg Q2W/Up 150 mg Q2W With Concomitant Statin", + "N": 78, + "age": 60.7, + "ageSD": 9.1, + "male": 65.38461538461539, + "Interventions": [{ "termIds": [["SUBYEL", "SUB_Oc"], ["SUNUVb"]] }] + }, + { + "Id": "i4", + "Interventions": [ + { + "Id": "Ya", + "Name": 178613599, + "Treatments": [{ "Id": "((", "Phase": "k)" }], + "Type": "Drug", + "termIds": [["SUGeLS"], ["SUNUVb"]], + "terms": [["Placebo"], ["Alirocumab"]] + }, + { + "Id": "o)", + "Name": 2159990, + "Treatments": [{ "Id": "1$", "Phase": "k)" }], + "Type": "Drug", + "termIds": [["SUBYEL"]], + "terms": [["Statins"]] + } + ], + "RefId": "E1|Placebo Q2W", + "OriginalName": "Placebo Q2W" + }, + { + "Id": "Ls", + "RefId": "B1|O1~Placebo Q2W With Concomitant Statin", + "OriginalName": "Placebo Q2W With Concomitant Statin", + "N": 157, + "age": 61.6, + "ageSD": 9.7, + "male": 64.3312101910828, + "Interventions": [{ "termIds": [["SUGeLS"], ["SUBYEL", "SUB_Oc"]] }] + } + ], + "hasDocData": true, + "hasRapidExtract": false, + "N": 803, + "queryScore": 1.4868329805051381, + "matchingScore": 7.960635921410255, + "score": 22.084654254966498, + "outcomes": [ + { + "id": "179246387", + "type": "Change", + "unit": "%", + "termIds": [["SUF0R", "SUBskP"]], + "quantifiers": [], + "name": "Calculated LDL-C in Not Receiving Concomitant Statin Therapy - On-Treatment Analysis", + "cells": [ + { "number": -0.4, "unit": "%", "group": "3!", "varType": "se", "N": 70, "se": 2, "sd": 16.73 }, + { "number": -54.6, "unit": "%", "group": "4r", "varType": "se", "N": 37, "se": 2.8, "sd": 17.03 }, + { "number": -59.4, "unit": "%", "group": "zB", "varType": "se", "N": 141, "se": 1.4, "sd": 16.62 } + ], + "time": { + "Id": 67122072, + "Low": { "Value": "Baseline" }, + "High": { "Number": 24, "Unit": "wk" }, + "Type": "Total", + "days": 168, + "description": "24wk" + }, + "score": 2.08, + "matchingTerm": "SUF0R", + "suggestedPositive": false, + "sourceUnit": "%" + }, + { + "id": "179246389", + "type": "Change", + "unit": "%", + "termIds": [["SUF0R", "SUBskP"]], + "quantifiers": [], + "name": "Calculated LDL-C in Receiving Concomitant Statin Therapy - On-Treatment Analysis", + "cells": [ + { "number": -0.3, "unit": "%", "group": "Ls", "varType": "se", "N": 151, "se": 2.1, "sd": 25.81 }, + { "number": -55.1, "unit": "%", "group": "4E", "varType": "se", "N": 75, "se": 3, "sd": 25.98 }, + { "number": -62.3, "unit": "%", "group": "jt", "varType": "se", "N": 302, "se": 1.5, "sd": 26.07 } + ], + "time": { + "Id": 67122072, + "Low": { "Value": "Baseline" }, + "High": { "Number": 24, "Unit": "wk" }, + "Type": "Total", + "days": 168, + "description": "24wk" + }, + "score": 2.08, + "matchingTerm": "SUF0R", + "suggestedPositive": false, + "sourceUnit": "%" + }, + { + "id": "179246393", + "type": "Change", + "unit": "%", + "termIds": [["SUF0R", "SUBskP"]], + "quantifiers": [], + "name": "Calculated LDL-C in Not Receiving Concomitant Statin Therapy - On-Treatment Analysis", + "cells": [ + { "number": -0.5, "unit": "%", "group": "3!", "varType": "se", "N": 70, "se": 2, "sd": 16.73 }, + { "number": -53.9, "unit": "%", "group": "4r", "varType": "se", "N": 37, "se": 2.7, "sd": 16.42 }, + { "number": -60, "unit": "%", "group": "zB", "varType": "se", "N": 141, "se": 1.4, "sd": 16.62 } + ], + "time": { + "Id": 67122069, + "Low": { "Value": "Baseline" }, + "High": { "Number": 12, "Unit": "wk" }, + "Type": "Total", + "days": 84, + "description": "12wk" + }, + "score": 2.08, + "matchingTerm": "SUF0R", + "suggestedPositive": false, + "sourceUnit": "%" + }, + { + "id": "179246394", + "type": "Change", + "unit": "%", + "termIds": [["SUF0R", "SUBskP"]], + "quantifiers": [], + "name": "Calculated LDL-C in Receiving Concomitant Statin Therapy - On-Treatment Analysis", + "cells": [ + { "number": 1.4, "unit": "%", "group": "Ls", "varType": "se", "N": 151, "se": 1.9, "sd": 23.35 }, + { "number": -47.3, "unit": "%", "group": "4E", "varType": "se", "N": 75, "se": 2.8, "sd": 24.25 }, + { "number": -58, "unit": "%", "group": "jt", "varType": "se", "N": 302, "se": 1.4, "sd": 24.33 } + ], + "time": { + "Id": 67122069, + "Low": { "Value": "Baseline" }, + "High": { "Number": 12, "Unit": "wk" }, + "Type": "Total", + "days": 84, + "description": "12wk" + }, + "score": 2.08, + "matchingTerm": "SUF0R", + "suggestedPositive": false, + "sourceUnit": "%" + } + ], + "characteristics": [ + { + "id": "179246354", + "type": "Binary", + "isCharacteristic": true, + "termIds": [["SUE_c", "SUCbN", "SUyJj"]], + "quantifiers": [], + "name": "Patients not having adequate control of their hypercholesterolemia based on their individual level of CVD risk", + "cells": [], + "number": 100 + } + ], + "outcomesScore": 18.97947630112307 +} diff --git a/.repos/effect/packages/effect/benchmark/stream/splitLines.ts b/.repos/effect/packages/effect/benchmark/stream/splitLines.ts new file mode 100644 index 000000000..c85569e2c --- /dev/null +++ b/.repos/effect/packages/effect/benchmark/stream/splitLines.ts @@ -0,0 +1,46 @@ +import { Effect, Stream } from "effect" +import { Bench } from "tinybench" + +const bench = new Bench() + +// ~100 short lines, all in one chunk +const singleChunk = Array.from({ length: 100 }, (_, i) => `line ${i}`).join("\n") + "\n" + +// same content split into many small chunks (simulates streaming I/O) +const manyChunks: Array = [] +for (let i = 0; i < singleChunk.length; i += 8) { + manyChunks.push(singleChunk.substring(i, i + 8)) +} + +// mixed line endings +const mixedEndings = "alpha\r\nbravo\rcharlie\ndelta\r\necho\rfoxtrot\n" +const mixedChunks: Array = [] +for (let i = 0; i < mixedEndings.length; i += 5) { + mixedChunks.push(mixedEndings.substring(i, i + 5)) +} + +const run = (chunks: Array) => + Effect.runPromise( + Stream.fromIterable(chunks).pipe( + Stream.splitLines, + Stream.runCollect + ) + ) + +bench + .add("single chunk (100 lines)", async function() { + await run([singleChunk]) + }) + .add("many small chunks (100 lines)", async function() { + await run(manyChunks) + }) + .add("mixed line endings (single chunk)", async function() { + await run([mixedEndings]) + }) + .add("mixed line endings (small chunks)", async function() { + await run(mixedChunks) + }) + +await bench.run() + +console.table(bench.table()) diff --git a/.repos/effect/packages/effect/package.json b/.repos/effect/packages/effect/package.json new file mode 100644 index 000000000..21bcb291e --- /dev/null +++ b/.repos/effect/packages/effect/package.json @@ -0,0 +1,121 @@ +{ + "name": "effect", + "type": "module", + "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.git", + "directory": "packages/effect" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "typescript", + "error-handling", + "concurrency", + "observability" + ], + "keywords": [ + "typescript", + "error-handling", + "concurrency", + "observability" + ], + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./testing": "./src/testing/index.ts", + "./unstable/ai": "./src/unstable/ai/index.ts", + "./unstable/cli": "./src/unstable/cli/index.ts", + "./unstable/cluster": "./src/unstable/cluster/index.ts", + "./unstable/devtools": "./src/unstable/devtools/index.ts", + "./unstable/encoding": "./src/unstable/encoding/index.ts", + "./unstable/eventlog": "./src/unstable/eventlog/index.ts", + "./unstable/http": "./src/unstable/http/index.ts", + "./unstable/httpapi": "./src/unstable/httpapi/index.ts", + "./unstable/observability": "./src/unstable/observability/index.ts", + "./unstable/persistence": "./src/unstable/persistence/index.ts", + "./unstable/process": "./src/unstable/process/index.ts", + "./unstable/reactivity": "./src/unstable/reactivity/index.ts", + "./unstable/rpc": "./src/unstable/rpc/index.ts", + "./unstable/schema": "./src/unstable/schema/index.ts", + "./unstable/socket": "./src/unstable/socket/index.ts", + "./unstable/sql": "./src/unstable/sql/index.ts", + "./unstable/workflow": "./src/unstable/workflow/index.ts", + "./unstable/workers": "./src/unstable/workers/index.ts", + "./*": "./src/*.ts", + "./unstable/cli/internal/*": null, + "./unstable/cluster/internal/*": null, + "./internal/*": null, + "./index": null, + "./*/index": null + }, + "files": [ + "src/**/*.ts", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "exports": { + "./package.json": "./package.json", + ".": "./dist/index.js", + "./testing": "./dist/testing/index.js", + "./unstable/ai": "./dist/unstable/ai/index.js", + "./unstable/cli": "./dist/unstable/cli/index.js", + "./unstable/cluster": "./dist/unstable/cluster/index.js", + "./unstable/devtools": "./dist/unstable/devtools/index.js", + "./unstable/encoding": "./dist/unstable/encoding/index.js", + "./unstable/eventlog": "./dist/unstable/eventlog/index.js", + "./unstable/http": "./dist/unstable/http/index.js", + "./unstable/httpapi": "./dist/unstable/httpapi/index.js", + "./unstable/observability": "./dist/unstable/observability/index.js", + "./unstable/persistence": "./dist/unstable/persistence/index.js", + "./unstable/process": "./dist/unstable/process/index.js", + "./unstable/reactivity": "./dist/unstable/reactivity/index.js", + "./unstable/rpc": "./dist/unstable/rpc/index.js", + "./unstable/schema": "./dist/unstable/schema/index.js", + "./unstable/socket": "./dist/unstable/socket/index.js", + "./unstable/sql": "./dist/unstable/sql/index.js", + "./unstable/workflow": "./dist/unstable/workflow/index.js", + "./unstable/workers": "./dist/unstable/workers/index.js", + "./*": "./dist/*.js", + "./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", + "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", + "check": "tsc -b tsconfig.json" + }, + "devDependencies": { + "@types/node": "^26.2.0", + "ajv": "^8.20.0", + "ajv-draft-04": "^1.0.0", + "ast-types": "^0.14.2", + "immer": "^11.1.18", + "protobufjs": "^7.6.5", + "tinybench": "^6.1.3", + "valibot": "^1.4.2" + }, + "dependencies": { + "fast-check": "^4.9.0", + "msgpackr": "^2.0.5" + } +} diff --git a/.repos/effect/packages/effect/runtimeperf/README.md b/.repos/effect/packages/effect/runtimeperf/README.md new file mode 100644 index 000000000..8ff0458bb --- /dev/null +++ b/.repos/effect/packages/effect/runtimeperf/README.md @@ -0,0 +1,135 @@ +# Effect Runtime Performance + +This harness measures focused synchronous runtime paths in fresh Node +processes. It supports: + +- focused Effect Schema diagnostics; +- 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 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. + +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/.repos/effect/packages/effect/runtimeperf/compare.mts b/.repos/effect/packages/effect/runtimeperf/compare.mts new file mode 100644 index 000000000..7c2cf346e --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/config.json b/.repos/effect/packages/effect/runtimeperf/config.json new file mode 100644 index 000000000..92d07ddf0 --- /dev/null +++ b/.repos/effect/packages/effect/runtimeperf/config.json @@ -0,0 +1,1000 @@ +{ + "defaults": { + "rounds": 5, + "timeMs": 300, + "warmupTimeMs": 100, + "targetBatchTimeNs": 100000, + "maxBatchSize": 1048576, + "bootstrapIterations": 10000, + "bootstrapSeed": 1592594996, + "minImprovementPercent": 2, + "maxRegressionPercent": 5 + }, + "suites": [ + { + "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/.repos/effect/packages/effect/runtimeperf/materialize.mts b/.repos/effect/packages/effect/runtimeperf/materialize.mts new file mode 100644 index 000000000..5635ebe8d --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/run.mts b/.repos/effect/packages/effect/runtimeperf/run.mts new file mode 100644 index 000000000..e1f38f163 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/stats.mts b/.repos/effect/packages/effect/runtimeperf/stats.mts new file mode 100644 index 000000000..4f4be2068 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/data.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/data.ts new file mode 100644 index 000000000..af9d40583 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/effect-beta.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/effect-beta.ts new file mode 100644 index 000000000..b21c0dd2d --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/valibot.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/valibot.ts new file mode 100644 index 000000000..fdff6a1b5 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/zod.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/zod.ts new file mode 100644 index 000000000..81e56f482 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts new file mode 100644 index 000000000..2a84cab07 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/behavior.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/behavior.ts new file mode 100644 index 000000000..8ed02deaf --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts new file mode 100644 index 000000000..2f62d5ac0 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/comparison.ts b/.repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/comparison.ts new file mode 100644 index 000000000..252635985 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/test/materialize.test.mts b/.repos/effect/packages/effect/runtimeperf/test/materialize.test.mts new file mode 100644 index 000000000..e4331c510 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/test/registry.test.mts b/.repos/effect/packages/effect/runtimeperf/test/registry.test.mts new file mode 100644 index 000000000..e43831bc8 --- /dev/null +++ b/.repos/effect/packages/effect/runtimeperf/test/registry.test.mts @@ -0,0 +1,112 @@ +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", "valibot", "zod4"].includes(fixture.implementation)) + } + }) + + 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/.repos/effect/packages/effect/runtimeperf/test/stats.test.mts b/.repos/effect/packages/effect/runtimeperf/test/stats.test.mts new file mode 100644 index 000000000..4114ab6f1 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/test/worker.test.mts b/.repos/effect/packages/effect/runtimeperf/test/worker.test.mts new file mode 100644 index 000000000..72cc79f52 --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/runtimeperf/utils.mts b/.repos/effect/packages/effect/runtimeperf/utils.mts new file mode 100644 index 000000000..a146ac9db --- /dev/null +++ b/.repos/effect/packages/effect/runtimeperf/utils.mts @@ -0,0 +1,252 @@ +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, + 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/.repos/effect/packages/effect/runtimeperf/worker.mts b/.repos/effect/packages/effect/runtimeperf/worker.mts new file mode 100644 index 000000000..19bc6d1ae --- /dev/null +++ b/.repos/effect/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/.repos/effect/packages/effect/src/Array.ts b/.repos/effect/packages/effect/src/Array.ts new file mode 100644 index 000000000..9e594cb80 --- /dev/null +++ b/.repos/effect/packages/effect/src/Array.ts @@ -0,0 +1,4973 @@ +/** + * Works with JavaScript arrays, readonly arrays, and non-empty arrays. + * + * The helpers cover common collection work such as creating arrays, reading + * elements, transforming values, sorting, grouping, splitting, combining, and + * reducing many values to one result. Helpers that change contents return new + * arrays and preserve non-empty array types when the result is guaranteed to + * contain values. + * + * @since 2.0.0 + */ +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 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" +import type * as Predicate from "./Predicate.ts" +import * as Record from "./Record.ts" +import * as Reducer from "./Reducer.ts" +import * as Result from "./Result.ts" +import * as Tuple from "./Tuple.ts" +import type { NoInfer, TupleOf } from "./Types.ts" + +/** + * Exposes the global array constructor. + * + * **When to use** + * + * Use to access native JavaScript array constructor methods such as `isArray` + * or `from` from the Effect module namespace. + * + * **Example** (Accessing the Array constructor) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.Array === globalThis.Array // => true + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const Array = globalThis.Array + +/** + * Type lambda for `ReadonlyArray`, used for higher-kinded type operations. + * + * @category utility types + * @since 2.0.0 + */ +export interface ReadonlyArrayTypeLambda extends TypeLambda { + readonly type: ReadonlyArray +} + +/** + * A readonly array guaranteed to have at least one element. + * + * **When to use** + * + * Use when non-emptiness must be tracked at the type level while preventing mutation. + * Many Array module functions accept or return this type. + * + * **Example** (Typing a non-empty array) + * + * ```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 + * @see {@link isReadonlyArrayNonEmpty} — narrow a `ReadonlyArray` to this type + * + * @category models + * @since 2.0.0 + */ +export type NonEmptyReadonlyArray = readonly [A, ...Array] + +/** + * A mutable array guaranteed to have at least one element. + * + * **When to use** + * + * Use when mutation is acceptable and non-emptiness must be tracked at the type + * level. + * + * **Details** + * + * This is the mutable counterpart of {@link NonEmptyReadonlyArray}. Most Array + * module functions return `NonEmptyArray` when the result is guaranteed + * non-empty. + * + * **Example** (Typing a mutable non-empty array) + * + * ```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 + * @see {@link isArrayNonEmpty} — narrow an `Array` to this type + * + * @category models + * @since 2.0.0 + */ +export type NonEmptyArray = [A, ...Array] + +/** + * Creates a `NonEmptyArray` from one or more elements. + * + * **When to use** + * + * Use when you need to create a typed non-empty array from literal values. + * + * **Details** + * + * The element type is inferred as the union of all arguments. Because at least + * one argument is required, this always returns a `NonEmptyArray`. + * + * **Example** (Creating an array from values) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.make(1, 2, 3) // => [1, 2, 3] + * ``` + * + * @see {@link of} — create a single-element array + * @see {@link fromIterable} — create from any iterable + * + * @category constructors + * @since 2.0.0 + */ +export const make = >( + ...elements: Elements +): NonEmptyArray => elements + +/** + * Creates a new `Array` of the specified length with all slots uninitialized. + * + * **When to use** + * + * Use when you need a pre-sized array that will be filled imperatively. + * + * **Details** + * + * Elements are typed as `A | undefined` because the slots are empty. + * + * **Example** (Allocating a fixed-size array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.allocate(3).length // => 3 + * ``` + * + * @see {@link makeBy} — create an array by computing each element + * + * @category constructors + * @since 2.0.0 + */ +export const allocate = (n: number): Array => new Array(n) + +/** + * Creates a `NonEmptyArray` of length `n` where element `i` is computed by `f(i)`. + * + * **When to use** + * + * Use when you need to compute each array element from its index. + * + * **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. + * + * **Example** (Generating values from indices) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.makeBy(5, (n) => n * 2) // => [0, 2, 4, 6, 8] + * ``` + * + * @see {@link range} — create a range of integers + * @see {@link replicate} — repeat a single value + * + * @category constructors + * @since 2.0.0 + */ +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 out = new Array(max) + for (let i = 0; i < max; i++) { + out[i] = f(i) + } + return out as NonEmptyArray +}) + +/** + * Creates a `NonEmptyArray` containing a range of integers, inclusive on both + * ends. + * + * **When to use** + * + * Use when you need a non-empty sequence of consecutive integers. + * + * **Details** + * + * If `start > end`, returns `[start]`. + * + * **Example** (Creating a range) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.range(1, 3) // => [1, 2, 3] + * ``` + * + * @see {@link makeBy} — generate values from a function + * + * @category constructors + * @since 2.0.0 + */ +export const range = (start: number, end: number): NonEmptyArray => + start <= end ? makeBy(end - start + 1, (i) => start + i) : [start] + +/** + * Creates a `NonEmptyArray` containing a value repeated `n` times. + * + * **When to use** + * + * Use when you need a non-empty array containing repeated copies of one value. + * + * **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. + * + * **Example** (Repeating a value) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.replicate("a", 3) // => ["a", "a", "a"] + * ``` + * + * @see {@link makeBy} — vary values based on index + * + * @category constructors + * @since 2.0.0 + */ +export const replicate: { + (n: number): (a: A) => NonEmptyArray + (a: A, n: number): NonEmptyArray +} = dual(2, (a: A, n: number): NonEmptyArray => makeBy(n, () => a)) + +/** + * Converts an `Iterable` to an `Array`. + * + * **When to use** + * + * Use to convert any `Iterable` (Set, Generator, etc.) into an array. + * + * **Details** + * + * If the input is already an array, this returns it by reference without + * copying. Otherwise, it creates a new array from the iterable. Use `copy` if + * you need a fresh array even when the input is already an array. + * + * **Example** (Converting a Set to an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.fromIterable(new Set([1, 2, 3])) // => [1, 2, 3] + * ``` + * + * @see {@link ensure} — wrap a single value or return an existing array + * @see {@link copy} — create a shallow copy of an array + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = (collection: Iterable): Array => + Array.isArray(collection) ? collection : Array.from(collection) + +/** + * Normalizes a value that is either a single element or an array into an array. + * + * **When to use** + * + * Use to normalize input that may be a single value or an array into a consistent + * array. + * + * **Details** + * + * If the input is already an array, this returns it by reference. If the input + * is a single value, this wraps it in a one-element array. This is useful for + * APIs that accept `A | Array`. + * + * **Example** (Normalizing input) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.ensure("a") // => ["a"] + * Array.ensure(["a", "b", "c"]) // => ["a", "b", "c"] + * ``` + * + * @see {@link of} — always wrap in a single-element array + * @see {@link fromIterable} — convert any iterable + * + * @category constructors + * @since 3.3.0 + */ +export const ensure = (self: ReadonlyArray | A): Array => Array.isArray(self) ? self : [self as A] + +/** + * Converts a record into an array of `[key, value]` tuples. + * + * **When to use** + * + * Use to convert a record into an array of key-value tuples for iteration or + * transformation. + * + * **Details** + * + * Key order follows `Object.entries` semantics. Empty records produce an empty + * array. + * + * **Example** (Converting a record to entries) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * @see {@link Record.fromEntries} to build a record from an array of tuples + * + * @category converting + * @since 2.0.0 + */ +export const fromRecord: (self: Readonly>) => Array<[K, A]> = Record.toEntries + +/** + * Converts an `Option` to an array: `Some(a)` becomes `[a]`, `None` becomes `[]`. + * + * **When to use** + * + * Use to convert a single `Option` into an array for downstream array operations. + * + * **Example** (Converting an Option to an array) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * Array.fromOption(Option.some(1)) // => [1] + * Array.fromOption(Option.none()) // => [] + * ``` + * + * @see {@link getSomes} — extract `Some` values from an array of Options + * + * @category converting + * @since 2.0.0 + */ +export const fromOption: (self: Option.Option) => Array = Option.toArray + +/** + * Pattern-matches on an array, handling empty and non-empty cases separately. + * + * **When to use** + * + * Use when you need to branch on whether an array is empty. + * + * **Details** + * + * `onNonEmpty` receives a `NonEmptyReadonlyArray`. Supports both data-first and + * data-last usage. + * + * **Example** (Branching on emptiness) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const describe = Array.match({ + * onEmpty: () => "empty", + * onNonEmpty: ([head, ...tail]) => `head: ${head}, tail: ${tail.length}` + * }) + * + * describe([]) // => "empty" + * describe([1, 2, 3]) // => "head: 1, tail: 2" + * ``` + * + * @see {@link matchLeft} — destructures into head + tail + * @see {@link matchRight} — destructures into init + last + * + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + ( + options: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (self: NonEmptyReadonlyArray) => C + } + ): (self: ReadonlyArray) => B | C + ( + self: ReadonlyArray, + options: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (self: NonEmptyReadonlyArray) => C + } + ): B | C +} = dual(2, ( + self: ReadonlyArray, + { onEmpty, onNonEmpty }: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (self: NonEmptyReadonlyArray) => C + } +): B | C => isReadonlyArrayNonEmpty(self) ? onNonEmpty(self) : onEmpty()) + +/** + * Pattern-matches on an array from the left, providing the first element and + * the remaining elements separately. + * + * **When to use** + * + * Use when you need to branch on an array and handle the non-empty case as the + * first element plus the remaining elements. + * + * **Details** + * + * `onNonEmpty` receives `(head, tail)` where `tail` is the rest of the array. + * + * **Example** (Destructuring head and tail) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const matchLeft = Array.matchLeft({ + * onEmpty: () => "empty", + * onNonEmpty: (head, tail) => `head: ${head}, tail: ${tail.length}` + * }) + * + * matchLeft([]) // => "empty" + * matchLeft([1, 2, 3]) // => "head: 1, tail: 2" + * ``` + * + * @see {@link match} — receives the full non-empty array + * @see {@link matchRight} — destructures into init + last + * + * @category pattern matching + * @since 2.0.0 + */ +export const matchLeft: { + ( + options: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (head: A, tail: Array) => C + } + ): (self: ReadonlyArray) => B | C + ( + self: ReadonlyArray, + options: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (head: A, tail: Array) => C + } + ): B | C +} = dual(2, ( + self: ReadonlyArray, + { onEmpty, onNonEmpty }: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (head: A, tail: Array) => C + } +): B | C => isReadonlyArrayNonEmpty(self) ? onNonEmpty(headNonEmpty(self), tailNonEmpty(self)) : onEmpty()) + +/** + * Pattern-matches on an array from the right, providing all elements except the + * last and the last element separately. + * + * **When to use** + * + * Use when you need to branch on an array and handle the non-empty case as the + * elements before the last plus the last element. + * + * **Details** + * + * `onNonEmpty` receives `(init, last)` where `init` is everything but the last element. + * + * **Example** (Destructuring init and last) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const matchRight = Array.matchRight({ + * onEmpty: () => "empty", + * onNonEmpty: (init, last) => `init: ${init.length}, last: ${last}` + * }) + * + * matchRight([]) // => "empty" + * matchRight([1, 2, 3]) // => "init: 2, last: 3" + * ``` + * + * @see {@link match} — receives the full non-empty array + * @see {@link matchLeft} — destructures into head + tail + * + * @category pattern matching + * @since 2.0.0 + */ +export const matchRight: { + ( + options: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (init: Array, last: A) => C + } + ): (self: ReadonlyArray) => B | C + ( + self: ReadonlyArray, + options: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (init: Array, last: A) => C + } + ): B | C +} = dual(2, ( + self: ReadonlyArray, + { onEmpty, onNonEmpty }: { + readonly onEmpty: LazyArg + readonly onNonEmpty: (init: Array, last: A) => C + } +): B | C => + isReadonlyArrayNonEmpty(self) ? + onNonEmpty(initNonEmpty(self), lastNonEmpty(self)) : + onEmpty()) + +/** + * Adds a single element to the front of an iterable, returning a `NonEmptyArray`. + * + * **When to use** + * + * Use when you need to guarantee a non-empty result after adding a required + * leading value. + * + * **Example** (Prepending an element) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.prepend([2, 3, 4], 1) // => [1, 2, 3, 4] + * ``` + * + * @see {@link append} — add to the end + * @see {@link prependAll} — prepend multiple elements + * + * @category combining + * @since 2.0.0 + */ +export const prepend: { + (head: B): (self: Iterable) => NonEmptyArray + (self: Iterable, head: B): NonEmptyArray +} = dual(2, (self: Iterable, head: B): NonEmptyArray => [head, ...self]) + +/** + * Prepends all elements from a prefix iterable to the front of an array. + * + * **When to use** + * + * Use to prepend multiple elements from an iterable to the front of an array. + * + * **Details** + * + * If either input is non-empty, the result is a `NonEmptyArray`. + * + * **Example** (Prepending multiple elements) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.prependAll([2, 3], [0, 1]) // => [0, 1, 2, 3] + * ``` + * + * @see {@link prepend} — add a single element to the front + * @see {@link appendAll} — add elements to the end + * + * @category combining + * @since 2.0.0 + */ +export const prependAll: { + , T extends Iterable>( + that: T + ): (self: S) => ReadonlyArray.OrNonEmpty | ReadonlyArray.Infer> + (self: Iterable, that: NonEmptyReadonlyArray): NonEmptyArray + (self: NonEmptyReadonlyArray, that: Iterable): NonEmptyArray + (self: Iterable, that: Iterable): Array +} = dual( + 2, + (self: Iterable, that: Iterable): Array => fromIterable(that).concat(fromIterable(self)) +) + +/** + * Adds a single element to the end of an iterable, returning a `NonEmptyArray`. + * + * **When to use** + * + * Use when you need to guarantee a non-empty result after adding a required + * trailing value. + * + * **Example** (Appending an element) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.append([1, 2, 3], 4) // => [1, 2, 3, 4] + * ``` + * + * @see {@link prepend} — add to the front + * @see {@link appendAll} — append multiple elements + * + * @category combining + * @since 2.0.0 + */ +export const append: { + (last: B): (self: Iterable) => NonEmptyArray + (self: Iterable, last: B): NonEmptyArray +} = dual(2, (self: Iterable, last: B): Array => [...self, last]) + +/** + * Concatenates two iterables into a single array. + * + * **When to use** + * + * Use to combine two iterable inputs into a new array with the second input's + * elements after the first. + * + * **Details** + * + * If either input is non-empty, the result is a `NonEmptyArray`. + * + * **Example** (Concatenating arrays) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.appendAll([1, 2], [3, 4]) // => [1, 2, 3, 4] + * ``` + * + * @see {@link append} — add a single element to the end + * @see {@link prependAll} — add elements to the front + * + * @category combining + * @since 2.0.0 + */ +export const appendAll: { + , T extends Iterable>( + that: T + ): (self: S) => ReadonlyArray.OrNonEmpty | ReadonlyArray.Infer> + (self: Iterable, that: NonEmptyReadonlyArray): NonEmptyArray + (self: NonEmptyReadonlyArray, that: Iterable): NonEmptyArray + (self: Iterable, that: Iterable): Array +} = dual( + 2, + (self: Iterable, that: Iterable): Array => fromIterable(self).concat(fromIterable(that)) +) + +/** + * Folds left-to-right while keeping every intermediate accumulator value. + * + * **When to use** + * + * Use to compute a running accumulator where each intermediate value is needed. + * + * **Details** + * + * The output length is `input.length + 1` because it starts with the initial + * value. The result is always a `NonEmptyArray`. Use `reduce` if you only need + * the final accumulated value. + * + * **Example** (Running totals) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.scan([1, 2, 3, 4], 0, (acc, value) => acc + value) // => [0, 1, 3, 6, 10] + * ``` + * + * @see {@link scanRight} — right-to-left scan + * @see {@link reduce} — fold without intermediate values + * + * @category folding + * @since 2.0.0 + */ +export const scan: { + (b: B, f: (b: B, a: A) => B): (self: Iterable) => NonEmptyArray + (self: Iterable, b: B, f: (b: B, a: A) => B): NonEmptyArray +} = dual(3, (self: Iterable, b: B, f: (b: B, a: A) => B): NonEmptyArray => { + const out: NonEmptyArray = [b] + let i = 0 + for (const a of self) { + out[i + 1] = f(out[i], a) + i++ + } + return out +}) + +/** + * Folds right-to-left while keeping every intermediate accumulator value. + * + * **When to use** + * + * Use to compute a running accumulator from right to left where each intermediate + * value is needed. + * + * **Details** + * + * The output length is `input.length + 1` because it ends with the initial + * value. The result is always a `NonEmptyArray`. + * + * **Example** (Scanning running totals in reverse) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.scanRight([1, 2, 3, 4], 0, (acc, value) => acc + value) // => [10, 9, 7, 4, 0] + * ``` + * + * @see {@link scan} — left-to-right scan + * @see {@link reduceRight} — fold without intermediate values + * + * @category folding + * @since 2.0.0 + */ +export const scanRight: { + (b: B, f: (b: B, a: A) => B): (self: Iterable) => NonEmptyArray + (self: Iterable, b: B, f: (b: B, a: A) => B): NonEmptyArray +} = dual(3, (self: Iterable, b: B, f: (b: B, a: A) => B): NonEmptyArray => { + const input = fromIterable(self) + const out: NonEmptyArray = new Array(input.length + 1) as any + out[input.length] = b + for (let i = input.length - 1; i >= 0; i--) { + out[i] = f(out[i + 1], input[i]) + } + return out +}) + +/** + * Checks whether a value is an `Array`. + * + * **When to use** + * + * Use to verify a value is a mutable array, narrowing its type to `Array`. + * + * **Details** + * + * Acts as a type guard narrowing the input to `Array` and delegates to + * `globalThis.Array.isArray`. + * + * **Example** (Type-guarding an unknown value) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.isArray(null) // => false + * Array.isArray([1, 2, 3]) // => true + * ``` + * + * @see {@link isArrayEmpty} — check for an empty array + * @see {@link isArrayNonEmpty} — check for a non-empty array + * + * @category guards + * @since 2.0.0 + */ +export const isArray: { + (self: unknown): self is Array + (self: T): self is Extract> +} = Array.isArray + +/** + * Checks whether a mutable `Array` is empty, narrowing the type to `[]`. + * + * **Example** (Checking for an empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.isArrayEmpty([]) // => true + * Array.isArrayEmpty([1, 2, 3]) // => false + * ``` + * + * @see {@link isReadonlyArrayEmpty} — readonly variant + * @see {@link isArrayNonEmpty} — opposite check + * + * @category guards + * @since 4.0.0 + */ +export const isArrayEmpty = (self: Array): self is [] => self.length === 0 + +/** + * Checks whether a `ReadonlyArray` is empty, narrowing the type to `readonly []`. + * + * **Example** (Checking for an empty readonly array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.isReadonlyArrayEmpty([]) // => true + * Array.isReadonlyArrayEmpty([1, 2, 3]) // => false + * ``` + * + * @see {@link isArrayEmpty} — mutable variant + * @see {@link isReadonlyArrayNonEmpty} — opposite check + * + * @category guards + * @since 4.0.0 + */ +export const isReadonlyArrayEmpty: (self: ReadonlyArray) => self is readonly [] = isArrayEmpty as any + +/** + * Checks whether a mutable `Array` is non-empty, narrowing the type to + * `NonEmptyArray`. + * + * **When to use** + * + * Use when you need the narrowed value to remain a mutable `Array` after proving + * it has at least one element. + * + * **Example** (Checking for a non-empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.isArrayNonEmpty([]) // => false + * Array.isArrayNonEmpty([1, 2, 3]) // => true + * ``` + * + * @see {@link isReadonlyArrayNonEmpty} — readonly variant + * @see {@link isArrayEmpty} — opposite check + * + * @category guards + * @since 4.0.0 + */ +export const isArrayNonEmpty: (self: Array) => self is NonEmptyArray = internalArray.isArrayNonEmpty + +/** + * Checks whether a `ReadonlyArray` is non-empty, narrowing the type to + * `NonEmptyReadonlyArray`. + * + * **When to use** + * + * Use when you need to prove a readonly array has at least one element without + * requiring mutable array methods afterward. + * + * **Example** (Checking for a non-empty readonly array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.isReadonlyArrayNonEmpty([]) // => false + * Array.isReadonlyArrayNonEmpty([1, 2, 3]) // => true + * ``` + * + * @see {@link isArrayNonEmpty} — mutable variant + * @see {@link isReadonlyArrayEmpty} — opposite check + * + * @category guards + * @since 4.0.0 + */ +export const isReadonlyArrayNonEmpty: (self: ReadonlyArray) => self is NonEmptyReadonlyArray = + internalArray.isArrayNonEmpty + +/** + * Returns the number of elements in a `ReadonlyArray`. + * + * **When to use** + * + * Use when you need length as a composable function rather than a property access. + * + * **Example** (Getting the length) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.length([1, 2, 3]) // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const length = (self: ReadonlyArray): number => self.length + +/** @internal */ +export function isOutOfBounds(i: number, as: ReadonlyArray): boolean { + 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. + * + * **When to use** + * + * Use when you need to read an array element by index and handle an + * out-of-bounds index as `Option.none`. + * + * **Details** + * + * The index is floored to an integer. This never throws. + * + * **Example** (Accessing indexes safely) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 + * @see {@link head} for reading the first element as an `Option` + * @see {@link last} for reading the last element as an `Option` + * + * @category getters + * @since 2.0.0 + */ +export const get: { + (index: number): (self: ReadonlyArray) => Option.Option + (self: ReadonlyArray, index: number): Option.Option +} = dual(2, (self: ReadonlyArray, index: number): Option.Option => { + const i = Math.floor(index) + return isOutOfBounds(i, self) ? Option.none() : Option.some(self[i]) +}) + +/** + * Reads an element at the given index, throwing if the index is out of bounds. + * + * **When to use** + * + * Use to read an array element at a known valid index when out-of-bounds would + * be a programming error. + * + * **Details** + * + * Throws an `Error` with the message `"Index out of bounds: "`. Prefer + * `get` for safe access. + * + * **Example** (Accessing indexes unsafely) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.getUnsafe([1, 2, 3], 1) // => 2 + * // Array.getUnsafe([1, 2, 3], 10) // throws Error + * ``` + * + * @see {@link get} — safe version returning `Option` + * + * @category unsafe + * @since 4.0.0 + */ +export const getUnsafe: { + (index: number): (self: ReadonlyArray) => A + (self: ReadonlyArray, index: number): A +} = dual(2, (self: ReadonlyArray, index: number): A => { + const i = Math.floor(index) + if (isOutOfBounds(i, self)) { + throw new Error(`Index out of bounds: ${i}`) + } + return self[i] +}) + +/** + * Splits a non-empty array into its first element and the remaining elements. + * + * **When to use** + * + * Use when you have a `NonEmptyReadonlyArray` and need both its first element + * and the remaining elements as separate values. + * + * **Details** + * + * Returns a tuple `[head, tail]` and requires a `NonEmptyReadonlyArray`. + * + * **Example** (Destructuring head and tail) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.unprepend([1, 2, 3, 4]) // => [1, [2, 3, 4]] + * ``` + * + * @see {@link unappend} for splitting a non-empty array into init and last + * @see {@link headNonEmpty} for getting only the first element + * @see {@link tailNonEmpty} for getting only the elements after the first + * + * @category splitting + * @since 2.0.0 + */ +export const unprepend = ( + self: NonEmptyReadonlyArray +): [firstElement: A, remainingElements: Array] => [headNonEmpty(self), tailNonEmpty(self)] + +/** + * Splits a non-empty array into all elements except the last, and the last + * element. + * + * **When to use** + * + * Use when you need to split a non-empty array into the elements before the + * last element and the last element. + * + * **Details** + * + * Returns a tuple `[init, last]` and requires a `NonEmptyReadonlyArray`. + * + * **Example** (Destructuring init and last) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.unappend([1, 2, 3, 4]) // => [[1, 2, 3], 4] + * ``` + * + * @see {@link unprepend} for splitting a non-empty array into head and tail + * @see {@link initNonEmpty} for getting only the elements before the last + * @see {@link lastNonEmpty} for getting only the last element + * + * @category splitting + * @since 2.0.0 + */ +export const unappend = ( + self: NonEmptyReadonlyArray +): [arrayWithoutLastElement: Array, lastElement: A] => [initNonEmpty(self), lastNonEmpty(self)] + +/** + * Returns the first element of an array safely wrapped in `Option.some`, or + * `Option.none` if the array is empty. + * + * **When to use** + * + * Use to safely get the first element of an array that may be empty. + * + * **Example** (Getting the first element) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * Array.head([1, 2, 3]) // => Option.some(1) + * Array.head([]) // => Option.none() + * ``` + * + * @see {@link headNonEmpty} — direct access when array is known non-empty + * @see {@link last} — get the last element + * + * @category getters + * @since 2.0.0 + */ +export const head: (self: ReadonlyArray) => Option.Option = get(0) + +/** + * Returns the first element of a `NonEmptyReadonlyArray` directly (no `Option` + * wrapper). + * + * **When to use** + * + * Use to get the first element without `Option` wrapping when the array is known + * to be non-empty. + * + * **Example** (Getting the head of a non-empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.headNonEmpty([1, 2, 3, 4]) // => 1 + * ``` + * + * @see {@link head} — safe version for possibly-empty arrays + * + * @category getters + * @since 2.0.0 + */ +export const headNonEmpty: (self: NonEmptyReadonlyArray) => A = getUnsafe(0) + +/** + * Returns the last element of an array safely wrapped in `Option.some`, or + * `Option.none` if the array is empty. + * + * **When to use** + * + * Use to safely get the last element of an array that may be empty. + * + * **Example** (Getting the last element) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * Array.last([1, 2, 3]) // => Option.some(3) + * Array.last([]) // => Option.none() + * ``` + * + * @see {@link lastNonEmpty} — direct access when array is known non-empty + * @see {@link head} — get the first element + * + * @category getters + * @since 2.0.0 + */ +export const last = (self: ReadonlyArray): Option.Option => + isReadonlyArrayNonEmpty(self) ? Option.some(lastNonEmpty(self)) : Option.none() + +/** + * Returns the last element of a `NonEmptyReadonlyArray` directly (no `Option` + * wrapper). + * + * **When to use** + * + * Use to get the last element without `Option` wrapping when the array is known + * to be non-empty. + * + * **Example** (Getting the last of a non-empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.lastNonEmpty([1, 2, 3, 4]) // => 4 + * ``` + * + * @see {@link last} — safe version for possibly-empty arrays + * + * @category getters + * @since 2.0.0 + */ +export const lastNonEmpty = (self: NonEmptyReadonlyArray): A => self[self.length - 1] + +/** + * Returns all elements except the first safely, wrapped in an `Option`. + * + * **When to use** + * + * Use to safely get all elements after the first when the iterable may be empty. + * + * **Details** + * + * Allocates a new array via `slice(1)`. Empty inputs return `Option.none()`. + * + * **Example** (Getting the tail) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 + * @see {@link init} — all elements except the last + * + * @category getters + * @since 2.0.0 + */ +export function tail(self: Iterable): Option.Option> { + const as = fromIterable(self) + return isReadonlyArrayNonEmpty(as) ? Option.some(tailNonEmpty(as)) : Option.none() +} + +/** + * Returns all elements except the first of a `NonEmptyReadonlyArray`. + * + * **When to use** + * + * Use to get all elements after the first when the array is known to be non-empty. + * + * **Example** (Getting the tail of a non-empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.tailNonEmpty([1, 2, 3, 4]) // => [2, 3, 4] + * ``` + * + * @see {@link tail} — safe version for possibly-empty arrays + * @see {@link initNonEmpty} — all elements except the last + * + * @category getters + * @since 2.0.0 + */ +export const tailNonEmpty = (self: NonEmptyReadonlyArray): Array => self.slice(1) + +/** + * Returns all elements except the last safely, wrapped in an `Option`. + * + * **When to use** + * + * Use to safely get all elements before the last when the iterable may be empty. + * + * **Details** + * + * Allocates a new array via `slice(0, -1)`. Empty inputs return + * `Option.none()`. + * + * **Example** (Getting init) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 + * @see {@link tail} — all elements except the first + * + * @category getters + * @since 2.0.0 + */ +export function init(self: Iterable): Option.Option> { + const as = fromIterable(self) + return isReadonlyArrayNonEmpty(as) ? Option.some(initNonEmpty(as)) : Option.none() +} + +/** + * Returns all elements except the last of a `NonEmptyReadonlyArray`. + * + * **When to use** + * + * Use to get all elements before the last when the array is known to be non-empty. + * + * **Example** (Getting init of a non-empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.initNonEmpty([1, 2, 3, 4]) // => [1, 2, 3] + * ``` + * + * @see {@link init} — safe version for possibly-empty arrays + * @see {@link tailNonEmpty} — all elements except the first + * + * @category getters + * @since 2.0.0 + */ +export const initNonEmpty = (self: NonEmptyReadonlyArray): Array => self.slice(0, -1) + +/** + * Keeps the first `n` elements, creating a new array. + * + * **When to use** + * + * Use to keep up to the first `n` elements from an iterable as a new array. + * + * **Details** + * + * `n` is clamped to `[0, length]`. Returns an empty array when `n <= 0`. + * + * **Example** (Taking from the start) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.take([1, 2, 3, 4, 5], 3) // => [1, 2, 3] + * ``` + * + * @see {@link takeRight} for keeping elements from the end + * @see {@link takeWhile} for keeping an initial prefix while a predicate holds + * @see {@link drop} for removing elements from the start + * + * @category getters + * @since 2.0.0 + */ +export const take: { + (n: number): (self: Iterable) => Array + (self: Iterable, n: number): Array +} = dual(2, (self: Iterable, n: number): Array => { + const input = fromIterable(self) + return input.slice(0, clamp(n, input)) +}) + +/** + * Keeps the last `n` elements, creating a new array. + * + * **When to use** + * + * Use to keep the last `n` elements of an iterable. + * + * **Details** + * + * `n` is clamped to `[0, length]`. Returns an empty array when `n <= 0`. + * + * **Example** (Taking from the end) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.takeRight([1, 2, 3, 4, 5], 3) // => [3, 4, 5] + * ``` + * + * @see {@link take} — keep from the start + * @see {@link dropRight} — remove from the end + * + * @category getters + * @since 2.0.0 + */ +export const takeRight: { + (n: number): (self: Iterable) => Array + (self: Iterable, n: number): Array +} = dual(2, (self: Iterable, n: number): Array => { + const input = fromIterable(self) + const i = clamp(n, input) + return i === 0 ? [] : input.slice(-i) +}) + +/** + * Takes elements from the start while the predicate holds, stopping at the + * first element that fails. + * + * **When to use** + * + * Use to keep the leading elements of an iterable while each element satisfies + * a predicate, returning the retained prefix as an array. + * + * **Details** + * + * Supports refinements for type narrowing. The predicate receives + * `(element, index)`. + * + * **Example** (Taking while condition holds) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * @see {@link dropWhile} for removing the matching prefix and keeping the rest + * @see {@link span} for splitting the matching prefix from the remaining elements + * + * @category getters + * @since 2.0.0 + */ +export const takeWhile: { + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Array + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Array + (self: Iterable, refinement: (a: A, i: number) => a is B): Array + (self: Iterable, predicate: (a: A, i: number) => boolean): Array +} = dual(2, (self: Iterable, predicate: (a: A, i: number) => boolean): Array => { + let i = 0 + const out: Array = [] + for (const a of self) { + if (!predicate(a, i)) { + break + } + out.push(a) + i++ + } + return out +}) + +/** + * Takes elements from the start while a `Filter` succeeds, collecting transformed values. + * + * **When to use** + * + * Use when you need to take a prefix from an iterable while a function can + * successfully extract or transform elements, stopping at the first element + * that produces a failure result. + * + * **Details** + * + * The filter receives `(element, index)` and processing stops at the first + * filter failure. + * + * @see {@link takeWhile} for taking a prefix based on a boolean predicate + * + * @category getters + * @since 4.0.0 + */ +export const takeWhileFilter: { + (f: (input: NoInfer, i: number) => Result.Result): (self: Iterable) => Array + (self: Iterable, f: (input: NoInfer, i: number) => Result.Result): Array +} = dual(2, (self: Iterable, f: (input: NoInfer, i: number) => Result.Result): Array => { + let i = 0 + const out: Array = [] + for (const a of self) { + const result = f(a, i) + if (Result.isFailure(result)) { + break + } + out.push(result.success) + i++ + } + return out +}) + +const spanIndex = (self: Iterable, predicate: (a: A, i: number) => boolean): number => { + let i = 0 + for (const a of self) { + if (!predicate(a, i)) { + break + } + i++ + } + return i +} + +/** + * Splits an iterable into two arrays: the longest prefix where the predicate + * holds, and the remaining elements. + * + * **When to use** + * + * Use when you need both the longest predicate-matching prefix and the + * remaining elements. + * + * **Details** + * + * Equivalent to `[takeWhile(pred), dropWhile(pred)]`, but more efficient + * because it runs in a single pass. Supports refinements for type narrowing of + * the prefix. + * + * **Example** (Splitting at predicate boundary) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * @see {@link dropWhile} for keeping only the elements after the matching prefix + * @see {@link splitWhere} for splitting at the first element that satisfies a predicate + * + * @category splitting + * @since 2.0.0 + */ +export const span: { + ( + refinement: (a: NoInfer, i: number) => a is B + ): (self: Iterable) => [init: Array, rest: Array>] + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => [init: Array, rest: Array] + ( + self: Iterable, + refinement: (a: A, i: number) => a is B + ): [init: Array, rest: Array>] + (self: Iterable, predicate: (a: A, i: number) => boolean): [init: Array, rest: Array] +} = dual( + 2, + (self: Iterable, predicate: (a: A, i: number) => boolean): [init: Array, rest: Array] => { + const input = fromIterable(self) + return splitAt(input, spanIndex(input, predicate)) + } +) + +/** + * Removes the first `n` elements, creating a new array. + * + * **When to use** + * + * Use to keep the suffix of an iterable after skipping a fixed number of + * leading elements. + * + * **Details** + * + * `n` is clamped to `[0, length]`. When `n <= 0`, this returns a copy of the + * full array. + * + * **Example** (Dropping from the start) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.drop([1, 2, 3, 4, 5], 2) // => [3, 4, 5] + * ``` + * + * @see {@link dropRight} for removing a fixed number of elements from the end + * @see {@link dropWhile} for removing a prefix based on a predicate instead of a fixed count + * @see {@link take} for keeping a fixed number of elements from the start + * + * @category getters + * @since 2.0.0 + */ +export const drop: { + (n: number): (self: Iterable) => Array + (self: Iterable, n: number): Array +} = dual(2, (self: Iterable, n: number): Array => { + const input = fromIterable(self) + return input.slice(clamp(n, input), input.length) +}) + +/** + * Removes the last `n` elements, creating a new array. + * + * **When to use** + * + * Use to remove the last `n` elements from an iterable. + * + * **Details** + * + * `n` is clamped to `[0, length]`. + * + * **Example** (Dropping from the end) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.dropRight([1, 2, 3, 4, 5], 2) // => [1, 2, 3] + * ``` + * + * @see {@link drop} — remove from the start + * @see {@link takeRight} — keep from the end + * + * @category getters + * @since 2.0.0 + */ +export const dropRight: { + (n: number): (self: Iterable) => Array + (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)) +}) + +/** + * Drops elements from the start while the predicate holds, returning the rest. + * + * **When to use** + * + * Use to remove a leading prefix of elements that satisfy a predicate. + * + * **Details** + * + * The predicate receives `(element, index)`. + * + * **Example** (Dropping while condition holds) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.dropWhile([1, 2, 3, 4, 5], (x) => x < 4) // => [4, 5] + * ``` + * + * @see {@link takeWhile} — keep the matching prefix instead + * @see {@link drop} — drop a fixed count + * + * @category getters + * @since 2.0.0 + */ +export const dropWhile: { + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Array + (self: Iterable, predicate: (a: A, i: number) => boolean): Array +} = dual(2, (self: Iterable, predicate: (a: A, i: number) => boolean): Array => { + const input = fromIterable(self) + let i = 0 + while (i < input.length) { + if (!predicate(input[i], i)) { + break + } + i++ + } + return input.slice(i) +}) + +/** + * Drops elements from the start while a `Filter` succeeds. + * + * **When to use** + * + * Use when you need to drop a prefix from an iterable by computing a `Result` + * per element instead of using a simple boolean predicate. + * + * **Details** + * + * The filter receives `(element, index)`. The result contains the remaining + * original elements after the first filter failure. + * + * @see {@link dropWhile} for dropping a prefix with a simple boolean predicate + * @see {@link takeWhileFilter} for keeping only the matching prefix + * + * @category getters + * @since 4.0.0 + */ +export const dropWhileFilter: { + (f: (input: NoInfer, i: number) => Result.Result): (self: Iterable) => Array + (self: Iterable, f: (input: A, i: number) => Result.Result): Array +} = dual( + 2, + (self: Iterable, f: (input: A, i: number) => Result.Result): Array => { + const input = fromIterable(self) + let i = 0 + while (i < input.length) { + if (Result.isFailure(f(input[i], i))) { + break + } + i++ + } + return input.slice(i) + } +) + +/** + * Returns the index of the first element matching the predicate, wrapped in an + * `Option`. + * + * **When to use** + * + * Use to find the index of the first matching element from the start of an + * iterable. + * + * **Example** (Finding an index) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 searching + * @since 2.0.0 + */ +export const findFirstIndex: { + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option.Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option.Option +} = dual(2, (self: Iterable, predicate: (a: A, i: number) => boolean): Option.Option => { + let i = 0 + for (const a of self) { + if (predicate(a, i)) { + return Option.some(i) + } + i++ + } + return Option.none() +}) + +/** + * Returns the index of the last element matching the predicate, wrapped in an + * `Option`. + * + * **When to use** + * + * Use to find the index of the last matching element from the end of an array. + * + * **Example** (Finding the last matching index) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 searching + * @since 2.0.0 + */ +export const findLastIndex: { + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option.Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option.Option +} = dual(2, (self: Iterable, predicate: (a: A, i: number) => boolean): Option.Option => { + const input = fromIterable(self) + for (let i = input.length - 1; i >= 0; i--) { + if (predicate(input[i], i)) { + return Option.some(i) + } + } + return Option.none() +}) + +/** + * Returns the first element matching a predicate, refinement, or mapping + * function, wrapped in `Option`. + * + * **When to use** + * + * Use to scan an iterable in iteration order and return the first selected + * element or mapped value as an `Option`. + * + * **Details** + * + * Accepts a predicate `(a, i) => boolean`, a refinement, or a function + * `(a, i) => Option` for simultaneous find-and-transform. If no element + * matches, this returns `Option.none()`. + * + * **Example** (Finding the first match) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 searching + * @since 2.0.0 + */ +export const findFirst: { + (f: (a: NoInfer, i: number) => Option.Option): (self: Iterable) => Option.Option + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Option.Option + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option.Option + (self: Iterable, f: (a: A, i: number) => Option.Option): Option.Option + (self: Iterable, refinement: (a: A, i: number) => a is B): Option.Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option.Option +} = moduleIterable.findFirst + +/** + * Returns the first selected value together with its index, wrapped in an + * `Option`. + * + * **When to use** + * + * Use to find both the first matching element and its index in one pass. + * + * **Details** + * + * Accepts a predicate, a refinement, or a function returning `Option`. For an + * `Option`-returning function, returns `[mappedValue, index]` for the first + * `Some`, or `Option.none()` if no element is selected. + * + * **Example** (Finding element with its index) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 searching + * @since 3.17.0 + */ +export const findFirstWithIndex: { + (f: (a: NoInfer, i: number) => Option.Option): (self: Iterable) => Option.Option<[B, number]> + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Option.Option<[B, number]> + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option.Option<[A, number]> + (self: Iterable, f: (a: A, i: number) => Option.Option): Option.Option<[B, number]> + (self: Iterable, refinement: (a: A, i: number) => a is B): Option.Option<[B, number]> + (self: Iterable, predicate: (a: A, i: number) => boolean): Option.Option<[A, number]> +} = dual( + 2, + ( + self: Iterable, + f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option.Option) + ): Option.Option<[A, number]> => { + let i = 0 + for (const a of self) { + const o = f(a, i) + if (typeof o === "boolean") { + if (o) { + return Option.some([a, i]) + } + } else { + if (Option.isSome(o)) { + return Option.some([o.value, i]) + } + } + i++ + } + return Option.none() + } +) + +/** + * Returns the last element matching a predicate, refinement, or mapping + * function, wrapped in `Option`. + * + * **When to use** + * + * Use to find the last matching element from the end of an array. + * + * **Details** + * + * Searches from the end of the array. If no element matches, this returns + * `Option.none()`. + * + * **Example** (Finding the last match) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 searching + * @since 2.0.0 + */ +export const findLast: { + (f: (a: NoInfer, i: number) => Option.Option): (self: Iterable) => Option.Option + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Option.Option + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option.Option + (self: Iterable, f: (a: A, i: number) => Option.Option): Option.Option + (self: Iterable, refinement: (a: A, i: number) => a is B): Option.Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option.Option +} = dual( + 2, + ( + self: Iterable, + f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option.Option) + ): Option.Option => { + const input = fromIterable(self) + for (let i = input.length - 1; i >= 0; i--) { + const a = input[i] + const o = f(a, i) + if (typeof o === "boolean") { + if (o) { + return Option.some(a) + } + } else { + if (Option.isSome(o)) { + return o + } + } + } + return Option.none() + } +) + +/** + * Inserts an element at the specified index safely, returning a new `NonEmptyArray` + * wrapped in an `Option`. + * + * **When to use** + * + * Use to insert a single element at a specific position in an array. + * + * **Details** + * + * Valid indices are `0` to `length`, inclusive. Inserting at `length` appends. + * + * **Example** (Inserting at an index) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 transforming + * @since 2.0.0 + */ +export const insertAt: { + (i: number, b: B): (self: Iterable) => Option.Option> + (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 + const index = Math.floor(i) + if (index !== out.length && isOutOfBounds(index, out)) { + return Option.none() + } + out.splice(index, 0, b) + return Option.some(out as any) +}) + +/** + * Replaces the element at the specified index safely with a new value, returning the + * updated array in `Option.some`. + * + * **When to use** + * + * Use to set a fixed replacement value at a specific index. + * + * **Details** + * + * Returns `Option.none()` when the index is out of bounds. + * + * **Example** (Replacing an element) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * 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 transforming + * @since 2.0.0 + */ +export const replace: { + (i: number, b: B): = Iterable>( + self: S + ) => Option.Option | B>> + = Iterable>( + self: S, + i: number, + b: B + ): Option.Option | B>> +} = dual( + 3, + (self: Iterable, i: number, b: B): Option.Option> => modify(self, i, () => b) +) + +/** + * Applies a function to the element at the specified index safely, returning the + * updated array in `Option.some`. + * + * **When to use** + * + * Use to derive a replacement value from an array element at a specific index + * while leaving the other elements unchanged. + * + * **Details** + * + * Returns `Option.none()` when the index is out of bounds. + * + * **Example** (Modifying an element) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * const values = [1, 2, 3, 4] + * const double = (n: number) => n * 2 + * + * 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 transforming + * @since 2.0.0 + */ +export const modify: { + = Iterable>( + i: number, + f: (a: ReadonlyArray.Infer) => B + ): (self: S) => Option.Option | B>> + = Iterable>( + self: S, + i: number, + f: (a: ReadonlyArray.Infer) => B + ): Option.Option | B>> +} = dual(3, (self: Iterable, i: number, f: (a: A) => B): Option.Option> => { + const arr = Array.from(self) + const index = Math.floor(i) + if (isOutOfBounds(index, arr)) { + return Option.none() + } + const out: Array = arr + const b = f(arr[index]) + out[index] = b + return Option.some(out) +}) + +/** + * Removes the element at the specified index, returning a new array. If the + * index is out of bounds, returns a copy of the original. + * + * **When to use** + * + * Use when you want a missing index to be a no-op and need a fresh array result + * instead of an optional failure. + * + * **Example** (Removing an element) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 transforming + * @since 2.0.0 + */ +export const remove: { + (i: number): (self: Iterable) => Array + (self: Iterable, i: number): Array +} = dual(2, (self: Iterable, i: number): Array => { + const out = Array.from(self) + const index = Math.floor(i) + if (isOutOfBounds(index, out)) { + return out + } + out.splice(index, 1) + return out +}) + +/** + * Reverses an iterable into a new array. + * + * **When to use** + * + * Use to reverse an iterable into a new array without mutating the original + * input. + * + * **Details** + * + * Preserves `NonEmptyArray` in the return type. + * + * **Example** (Reversing an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.reverse([1, 2, 3, 4]) // => [4, 3, 2, 1] + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const reverse = >( + self: S +): S extends NonEmptyReadonlyArray ? NonEmptyArray : S extends Iterable ? Array : never => + Array.from(self).reverse() as any + +/** + * Sorts an array by the given `Order`, returning a new array. + * + * **When to use** + * + * Use to sort an array using a single `Order` comparator. + * + * **Details** + * + * Preserves `NonEmptyArray` in the return type. Use `sortWith` to sort by a + * derived key, or `sortBy` for multi-key sorting. + * + * **Example** (Sorting numbers) + * + * ```ts import.meta.vitest + * import { Array, Order } from "effect" + * + * Array.sort([3, 1, 4, 1, 5], Order.Number) // => [1, 1, 3, 4, 5] + * ``` + * + * @see {@link sortWith} — sort by a mapping function + * @see {@link sortBy} — sort by multiple orders + * + * @category sorting + * @since 2.0.0 + */ +export const sort: { + ( + O: Order.Order + ): >(self: S) => ReadonlyArray.With> + (self: NonEmptyReadonlyArray, O: Order.Order): NonEmptyArray + (self: Iterable, O: Order.Order): Array +} = dual(2, (self: Iterable, O: Order.Order): Array => { + const out = Array.from(self) + out.sort(O) + return out +}) + +/** + * Sorts an array by a derived key using a mapping function and an `Order` for + * that key. + * + * **When to use** + * + * Use when you need to sort values by a derived key, such as a string length or + * object field, while keeping the original values. + * + * **Details** + * + * Equivalent to `sort(Order.mapInput(order, f))`, but more convenient. + * + * **Example** (Sorting strings by length) + * + * ```ts import.meta.vitest + * import { Array, Order } from "effect" + * + * 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 sorting + * @since 2.0.0 + */ +export const sortWith: { + , B>( + f: (a: ReadonlyArray.Infer) => B, + order: Order.Order + ): (self: S) => ReadonlyArray.With> + (self: NonEmptyReadonlyArray, f: (a: A) => B, O: Order.Order): NonEmptyArray + (self: Iterable, f: (a: A) => B, order: Order.Order): Array +} = dual( + 3, + (self: Iterable, f: (a: A) => B, order: Order.Order): Array => + Array.from(self).map((a) => [a, f(a)] as const).sort(([, a], [, b]) => order(a, b)).map(([_]) => _) +) + +/** + * Sorts an array by multiple `Order`s applied in sequence: the first order is + * used first; ties are broken by the second order, and so on. + * + * **When to use** + * + * Use to sort by multiple criteria where later orders break ties from earlier + * ones. + * + * **Details** + * + * This is data-last only and returns a function. The return type preserves + * `NonEmptyArray`. + * + * **Example** (Sorting by multiple keys) + * + * ```ts import.meta.vitest + * import { Array, Order, pipe } from "effect" + * + * const users = [ + * { name: "Alice", age: 30 }, + * { name: "Bob", age: 25 }, + * { name: "Charlie", age: 30 } + * ] + * + * 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) + * ) + * ) + * + * sortedUsers.map((user) => user.name).join(",") // => "Bob,Alice,Charlie" + * ``` + * + * @see {@link sort} — sort by a single `Order` + * @see {@link sortWith} — sort by a derived key + * + * @category sorting + * @since 2.0.0 + */ +export const sortBy = >( + ...orders: ReadonlyArray>> +) => { + const sortByAll = sort(Order.combineAll(orders)) + return ( + self: S + ): S extends NonEmptyReadonlyArray ? NonEmptyArray : S extends Iterable ? Array : never => { + const input = fromIterable(self) + if (isReadonlyArrayNonEmpty(input)) { + return sortByAll(input) as any + } + return [] as any + } +} + +/** + * Pairs elements from two iterables by position. If the iterables differ in + * length, the extra elements from the longer one are discarded. + * + * **When to use** + * + * Use when you need simple pairs of corresponding elements from two iterables. + * + * **Details** + * + * Returns `NonEmptyArray` when both inputs are non-empty. + * + * **Example** (Zipping two arrays) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.zip([1, 2, 3], ["a", "b"]) // => [[1, "a"], [2, "b"]] + * ``` + * + * @see {@link zipWith} — zip with a combiner function + * @see {@link unzip} — inverse operation + * + * @category zipping + * @since 2.0.0 + */ +export const zip: { + (that: NonEmptyReadonlyArray): (self: NonEmptyReadonlyArray) => NonEmptyArray<[A, B]> + (that: Iterable): (self: Iterable) => Array<[A, B]> + (self: NonEmptyReadonlyArray, that: NonEmptyReadonlyArray): NonEmptyArray<[A, B]> + (self: Iterable, that: Iterable): Array<[A, B]> +} = dual( + 2, + (self: Iterable, that: Iterable): Array<[A, B]> => zipWith(self, that, Tuple.make) +) + +/** + * Combines elements from two iterables pairwise using a function. If the + * iterables differ in length, extra elements are discarded. + * + * **When to use** + * + * Use when zipping two iterables in an array pipeline and each pair should + * become a computed array element instead of a tuple. + * + * **Example** (Zipping with addition) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.zipWith([1, 2, 3], [4, 5, 6], (a, b) => a + b) // => [5, 7, 9] + * ``` + * + * @see {@link zip} — zip into tuples + * + * @category zipping + * @since 2.0.0 + */ +export const zipWith: { + (that: NonEmptyReadonlyArray, f: (a: A, b: B) => C): (self: NonEmptyReadonlyArray) => NonEmptyArray + (that: Iterable, f: (a: A, b: B) => C): (self: Iterable) => Array + (self: NonEmptyReadonlyArray, that: NonEmptyReadonlyArray, f: (a: A, b: B) => C): NonEmptyArray + (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Array +} = dual(3, (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Array => { + const as = fromIterable(self) + const bs = fromIterable(that) + if (isReadonlyArrayNonEmpty(as) && isReadonlyArrayNonEmpty(bs)) { + const out: NonEmptyArray = [f(headNonEmpty(as), headNonEmpty(bs))] + const len = Math.min(as.length, bs.length) + for (let i = 1; i < len; i++) { + out[i] = f(as[i], bs[i]) + } + return out + } + return [] +}) + +/** + * Splits an array of pairs into two arrays. Inverse of {@link zip}. + * + * **Example** (Unzipping pairs) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.unzip([[1, "a"], [2, "b"], [3, "c"]]) // => [[1, 2, 3], ["a", "b", "c"]] + * ``` + * + * @see {@link zip} — combine two arrays into pairs + * + * @category zipping + * @since 2.0.0 + */ +export const unzip: >( + self: S +) => S extends NonEmptyReadonlyArray ? [NonEmptyArray, NonEmptyArray] + : S extends Iterable ? [Array, Array] + : never = ((self: Iterable): [Array, Array] => { + const input = fromIterable(self) + if (isReadonlyArrayNonEmpty(input)) { + const fa: NonEmptyArray = [input[0][0]] + const fb: NonEmptyArray = [input[0][1]] + for (let i = 1; i < input.length; i++) { + fa[i] = input[i][0] + fb[i] = input[i][1] + } + return [fa, fb] + } + return [[], []] + }) as any + +/** + * Places a separator element between every pair of elements. + * + * **When to use** + * + * Use to insert a separator between elements, for example when preparing data for display or concatenation. + * + * **Details** + * + * The return type preserves `NonEmptyArray`. Empty inputs produce an empty + * result. + * + * **Example** (Interspersing a separator) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.intersperse([1, 2, 3], 0) // => [1, 0, 2, 0, 3] + * ``` + * + * @see {@link join} — intersperse and join into a string + * + * @category transforming + * @since 2.0.0 + */ +export const intersperse: { + ( + middle: B + ): >(self: S) => ReadonlyArray.With | B> + (self: NonEmptyReadonlyArray, middle: B): NonEmptyArray + (self: Iterable, middle: B): Array +} = dual(2, (self: Iterable, middle: B): Array => { + const input = fromIterable(self) + if (isReadonlyArrayNonEmpty(input)) { + const out: NonEmptyArray = [headNonEmpty(input)] + const tail = tailNonEmpty(input) + for (let i = 0; i < tail.length; i++) { + if (i < tail.length) { + out.push(middle) + } + out.push(tail[i]) + } + return out + } + return [] +}) + +/** + * Applies a function to the first element of a non-empty array, returning a + * new array. + * + * **When to use** + * + * Use to transform the first element of a non-empty array while preserving the rest. + * + * **Example** (Modifying the head) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 transforming + * @since 4.0.0 + */ +export const modifyHeadNonEmpty: { + (f: (a: A) => B): (self: NonEmptyReadonlyArray) => NonEmptyArray + (self: NonEmptyReadonlyArray, f: (a: A) => B): NonEmptyArray +} = dual( + 2, + ( + self: NonEmptyReadonlyArray, + f: (a: A) => B + ): NonEmptyArray => [f(headNonEmpty(self)), ...tailNonEmpty(self)] +) + +/** + * Replaces the first element of a non-empty array with a new value. + * + * **When to use** + * + * Use when you already know the array is non-empty and the replacement value + * does not depend on the current first element. + * + * **Example** (Setting the head) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 transforming + * @since 4.0.0 + */ +export const setHeadNonEmpty: { + (b: B): (self: NonEmptyReadonlyArray) => NonEmptyArray + (self: NonEmptyReadonlyArray, b: B): NonEmptyArray +} = dual( + 2, + (self: NonEmptyReadonlyArray, b: B): NonEmptyArray => modifyHeadNonEmpty(self, () => b) +) + +/** + * Applies a function to the last element of a non-empty array, returning a + * new array. + * + * **When to use** + * + * Use when you already know the array is non-empty and the new last element + * depends on the current last element. + * + * **Example** (Modifying the last element) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 transforming + * @since 4.0.0 + */ +export const modifyLastNonEmpty: { + (f: (a: A) => B): (self: NonEmptyReadonlyArray) => NonEmptyArray + (self: NonEmptyReadonlyArray, f: (a: A) => B): NonEmptyArray +} = dual( + 2, + (self: NonEmptyReadonlyArray, f: (a: A) => B): NonEmptyArray => + append(initNonEmpty(self), f(lastNonEmpty(self))) +) + +/** + * Replaces the last element of a non-empty array with a new value. + * + * **When to use** + * + * Use when you already know the array is non-empty and the replacement value + * does not depend on the current last element. + * + * **Example** (Setting the last element) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 transforming + * @since 4.0.0 + */ +export const setLastNonEmpty: { + (b: B): (self: NonEmptyReadonlyArray) => NonEmptyArray + (self: NonEmptyReadonlyArray, b: B): NonEmptyArray +} = dual( + 2, + (self: NonEmptyReadonlyArray, b: B): NonEmptyArray => modifyLastNonEmpty(self, () => b) +) + +/** + * Transforms an array by rotating it `n` steps. Positive `n` rotates right; negative `n` + * rotates left. + * + * **When to use** + * + * Use when elements should wrap around the end of the array rather than being + * dropped. + * + * **Details** + * + * `n` is rounded to the nearest integer before rotating. The return type + * preserves `NonEmptyArray`. Empty arrays, or rotations normalized to `0`, + * return a copy. + * + * **Example** (Rotating elements) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 transforming + * @since 2.0.0 + */ +export const rotate: { + (n: number): >(self: S) => ReadonlyArray.With> + (self: NonEmptyReadonlyArray, n: number): NonEmptyArray + (self: Iterable, n: number): Array +} = dual(2, (self: Iterable, n: number): Array => { + const input = fromIterable(self) + if (isReadonlyArrayNonEmpty(input)) { + const len = input.length + const m = Math.round(n) % len + if (isOutOfBounds(Math.abs(m), input) || m === 0) { + return copy(input) + } + if (m < 0) { + const [f, s] = splitAtNonEmpty(input, -m) + return appendAll(s, f) + } else { + return rotate(input, m - len) + } + } + return [] +}) + +/** + * Returns a membership-test function using a custom equivalence. + * + * **When to use** + * + * Use when checking membership with caller-provided equality instead of + * `Equal.equivalence()`. + * + * **Example** (Checking with custom equality) + * + * ```ts import.meta.vitest + * import { Array, pipe } from "effect" + * + * const containsNumber = Array.containsWith((a: number, b: number) => a === b) + * + * pipe([1, 2, 3, 4], containsNumber(3)) // => true + * ``` + * + * @see {@link contains} for the `Equal.equivalence()` variant + * + * @category predicates + * @since 2.0.0 + */ +export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { + (a: A): (self: Iterable) => boolean + (self: Iterable, a: A): boolean +} => + dual(2, (self: Iterable, a: A): boolean => { + for (const i of self) { + if (isEquivalent(a, i)) { + return true + } + } + return false + }) + +/** + * Checks whether an array contains a value, using `Equal.equivalence()` for + * comparison. + * + * **When to use** + * + * Use to check whether an iterable contains a value using Effect's default + * equality instead of providing a comparison function. + * + * **Example** (Checking membership) + * + * ```ts import.meta.vitest + * import { Array, pipe } from "effect" + * + * pipe(["a", "b", "c", "d"], Array.contains("c")) // => true + * ``` + * + * @see {@link containsWith} — use custom equality + * + * @category predicates + * @since 2.0.0 + */ +export const contains: { + (a: A): (self: Iterable) => boolean + (self: Iterable, a: A): boolean +} = containsWith(Equal.asEquivalence()) + +/** + * Applies a function repeatedly to consume prefixes of the array and collect + * the values it produces. + * + * **When to use** + * + * Use when you need custom grouping logic where each step returns both a value + * and the remaining input. + * + * **Details** + * + * The function receives a `NonEmptyReadonlyArray` and returns `[value, rest]`. + * Processing continues until the remaining array is empty. + * + * **Example** (Chopping an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 splitting + * @since 2.0.0 + */ +export const chop: { + , B>( + f: (as: NonEmptyReadonlyArray>) => readonly [B, ReadonlyArray>] + ): (self: S) => ReadonlyArray.With> + ( + self: NonEmptyReadonlyArray, + f: (as: NonEmptyReadonlyArray) => readonly [B, ReadonlyArray] + ): NonEmptyArray + ( + self: Iterable, + f: (as: NonEmptyReadonlyArray) => readonly [B, ReadonlyArray] + ): Array +} = dual(2, ( + self: Iterable, + f: (as: NonEmptyReadonlyArray) => readonly [B, ReadonlyArray] +): Array => { + const input = fromIterable(self) + if (isReadonlyArrayNonEmpty(input)) { + const [b, rest] = f(input) + const out: NonEmptyArray = [b] + let next: ReadonlyArray = rest + while (internalArray.isArrayNonEmpty(next)) { + const [b, rest] = f(next) + out.push(b) + next = rest + } + return out + } + return [] +}) + +/** + * Splits an iterable into two arrays at the given index. + * + * **When to use** + * + * Use to divide an array into a prefix and suffix at a specific position. + * + * **Details** + * + * `n` can be `0`, in which case all elements are placed in the second array. + * The index is floored to an integer. + * + * **Example** (Splitting at an index) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.splitAt([1, 2, 3, 4, 5], 3) // => [[1, 2, 3], [4, 5]] + * ``` + * + * @see {@link splitAtNonEmpty} — for non-empty arrays + * @see {@link splitWhere} — split at a predicate boundary + * + * @category splitting + * @since 2.0.0 + */ +export const splitAt: { + (n: number): (self: Iterable) => [beforeIndex: Array, fromIndex: Array] + (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) + if (isReadonlyArrayNonEmpty(input)) { + if (_n >= 1) { + return splitAtNonEmpty(input, _n) + } + return [[], input] + } + return [input, []] +}) + +/** + * Splits a non-empty array into two parts at the given index. The first part + * is guaranteed to be non-empty (`n` is clamped to >= 1). + * + * **When to use** + * + * Use when downstream code requires the left side of the split to contain at + * least one element. + * + * **Example** (Splitting a non-empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.splitAtNonEmpty(["a", "b", "c", "d", "e"], 3) // => [["a", "b", "c"], ["d", "e"]] + * ``` + * + * @see {@link splitAt} — for possibly-empty arrays + * + * @category splitting + * @since 4.0.0 + */ +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)) + return _n >= self.length ? + [copy(self), []] : + [prepend(self.slice(1, _n), headNonEmpty(self)), self.slice(_n)] +}) + +/** + * Splits an iterable into `n` roughly equal-sized chunks. + * + * **When to use** + * + * Use to distribute elements across a fixed number of groups, such as when splitting work across threads. + * + * **Details** + * + * Uses `chunksOf(ceil(length / n))` internally. The last chunk may be shorter. + * + * **Example** (Splitting into groups) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * + * @category splitting + * @since 2.0.0 + */ +export const split: { + (n: number): (self: Iterable) => Array> + (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))) +}) + +/** + * Splits an iterable at the first element matching the predicate. The matching + * element is included in the second array. + * + * **When to use** + * + * Use when you need to split an array at the first element that marks a + * condition boundary. + * + * **Example** (Splitting at a condition) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * @see {@link splitAt} — split at a fixed index + * + * @category splitting + * @since 2.0.0 + */ +export const splitWhere: { + ( + predicate: (a: NoInfer, i: number) => boolean + ): (self: Iterable) => [beforeMatch: Array, fromMatch: Array] + (self: Iterable, predicate: (a: A, i: number) => boolean): [beforeMatch: Array, fromMatch: Array] +} = dual( + 2, + (self: Iterable, predicate: (a: A, i: number) => boolean): [beforeMatch: Array, fromMatch: Array] => + span(self, (a: A, i: number) => !predicate(a, i)) +) + +/** + * Creates a shallow copy of an array. + * + * **When to use** + * + * Use to create a distinct array reference for an existing array, for example + * before mutating the returned array. + * + * **Details** + * + * The return type preserves `NonEmptyArray`. Use this when you need a distinct + * reference, for example before mutating the returned array. + * + * **Example** (Copying an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const original = [1, 2, 3] + * const copied = Array.copy(original) + * + * copied // => [1, 2, 3] + * original === copied // => false + * ``` + * + * @see {@link fromIterable} — returns the same reference for arrays + * + * @category transforming + * @since 2.0.0 + */ +export const copy: { + (self: NonEmptyReadonlyArray): NonEmptyArray + (self: ReadonlyArray): Array +} = ((self: ReadonlyArray): Array => self.slice()) as any + +/** + * Pads or truncates an array to exactly `n` elements, filling with `fill` + * if the array is shorter, or slicing if longer. + * + * **When to use** + * + * Use to ensure an array has a specific length, padding with a fill value or truncating as needed. + * + * **Details** + * + * Returns an empty array when `n <= 0`. + * + * **Example** (Padding an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 transforming + * @since 3.8.4 + */ +export const pad: { + ( + n: number, + fill: T + ): ( + self: Array + ) => 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) + } + return appendAll( + self, + makeBy(n - self.length, () => fill) + ) +}) + +/** + * Splits an iterable into chunks of length `n`. The last chunk may be shorter + * if `n` does not evenly divide the length. + * + * **When to use** + * + * Use to divide an iterable into a new array of non-overlapping chunks with a + * maximum chunk size. + * + * **Details** + * + * `chunksOf(n)([])` is `[]`, not `[[]]`. Each chunk is a `NonEmptyArray`, and + * the outer return type preserves `NonEmptyArray`. + * + * **Example** (Chunking an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.chunksOf([1, 2, 3, 4, 5], 2) // => [[1, 2], [3, 4], [5]] + * ``` + * + * @see {@link split} — split into a given number of groups + * @see {@link window} — sliding windows + * + * @category splitting + * @since 2.0.0 + */ +export const chunksOf: { + ( + n: number + ): >( + self: S + ) => ReadonlyArray.With>> + (self: NonEmptyReadonlyArray, n: number): NonEmptyArray> + (self: Iterable, n: number): Array> +} = dual(2, (self: Iterable, n: number): Array> => { + const input = fromIterable(self) + if (isReadonlyArrayNonEmpty(input)) { + return chop(input, splitAtNonEmpty(n)) + } + return [] +}) + +/** + * Creates overlapping sliding windows of size `n`. + * + * **When to use** + * + * Use to process sequences with a moving window, such as for computing running averages or detecting patterns. + * + * **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. + * + * **Example** (Creating sliding windows) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * + * @category splitting + * @since 3.13.2 + */ +export const window: { + (n: N): (self: Iterable) => Array> + (self: Iterable, n: N): Array> +} = dual(2, (self: Iterable, n: number): Array> => { + const input = fromIterable(self) + if (n > 0 && isReadonlyArrayNonEmpty(input)) { + return Array.from( + { length: input.length - (n - 1) }, + (_, index) => input.slice(index, index + n) + ) + } + return [] +}) + +/** + * Groups consecutive equal elements using a custom equivalence function. + * + * **When to use** + * + * Use when you already have a non-empty array arranged so matching elements are + * adjacent and need a custom equivalence function. + * + * **Details** + * + * Only adjacent elements are grouped. Non-adjacent duplicates stay separate. + * Requires a `NonEmptyReadonlyArray`. + * + * **Example** (Grouping consecutive equal elements) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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()` + * @see {@link groupBy} for grouping all elements into a record by key, regardless of adjacency + * + * @category grouping + * @since 2.0.0 + */ +export const groupWith: { + (isEquivalent: (self: A, that: A) => boolean): (self: NonEmptyReadonlyArray) => NonEmptyArray> + (self: NonEmptyReadonlyArray, isEquivalent: (self: A, that: A) => boolean): NonEmptyArray> +} = dual( + 2, + (self: NonEmptyReadonlyArray, isEquivalent: (self: A, that: A) => boolean): NonEmptyArray> => + chop(self, (as) => { + const h = headNonEmpty(as) + const out: NonEmptyArray = [h] + let i = 1 + for (; i < as.length; i++) { + const a = as[i] + if (isEquivalent(a, h)) { + out.push(a) + } else { + break + } + } + return [out, as.slice(i)] + }) +) + +/** + * Groups consecutive equal elements using `Equal.equivalence()`. + * + * **When to use** + * + * Use when you already have adjacent equal values and Effect's default equality + * is the right comparison. + * + * **Details** + * + * Only adjacent elements are grouped. + * + * **Example** (Grouping adjacent equal elements) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.group([1, 1, 2, 2, 2, 3, 1]) // => [[1, 1], [2, 2, 2], [3], [1]] + * ``` + * + * @see {@link groupWith} — use custom equality + * @see {@link groupBy} — group by a key function into a record + * + * @category grouping + * @since 2.0.0 + */ +export const group: (self: NonEmptyReadonlyArray) => NonEmptyArray> = groupWith( + Equal.asEquivalence() +) + +/** + * Groups elements into a record by a key-returning function. Each key maps + * to a `NonEmptyArray` of elements that produced that key. + * + * **When to use** + * + * Use to build buckets of elements indexed by a computed string or symbol key. + * + * **Details** + * + * Unlike `group` and `groupWith`, elements do not need to be adjacent to be + * grouped together. The key function must return a `string` or `symbol`. + * + * **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 property) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const people = [ + * { name: "Alice", group: "A" }, + * { name: "Bob", group: "B" }, + * { name: "Charlie", group: "A" } + * ] + * + * Object.keys(Array.groupBy(people, (person) => person.group)).join(",") // => "A,B" + * ``` + * + * @see {@link group} — group adjacent equal elements + * @see {@link groupWith} — group adjacent elements by custom equality + * + * @category grouping + * @since 2.0.0 + */ +export const groupBy: { + ( + f: (a: A) => K + ): (self: Iterable) => Record.ReadonlyRecord.GroupByResult> + ( + self: Iterable, + f: (a: A) => K + ): Record.ReadonlyRecord.GroupByResult> +} = dual(2, ( + self: Iterable, + f: (a: A) => K +): 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 { + 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. + * + * **When to use** + * + * Use when you need the union of two arrays but duplicate detection must use a + * custom equivalence instead of the default `Equal.equivalence()`. + * + * **Example** (Computing unions with custom equality) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 set operations + * @since 2.0.0 + */ +export const unionWith: { + , T extends Iterable>( + that: T, + isEquivalent: (self: ReadonlyArray.Infer, that: ReadonlyArray.Infer) => boolean + ): (self: S) => ReadonlyArray.OrNonEmpty | ReadonlyArray.Infer> + ( + self: NonEmptyReadonlyArray, + that: Iterable, + isEquivalent: (self: A, that: B) => boolean + ): NonEmptyArray + ( + self: Iterable, + that: NonEmptyReadonlyArray, + isEquivalent: (self: A, that: B) => boolean + ): NonEmptyArray + (self: Iterable, that: Iterable, isEquivalent: (self: A, that: B) => boolean): Array +} = dual(3, (self: Iterable, that: Iterable, isEquivalent: (self: A, that: A) => boolean): Array => { + const a = fromIterable(self) + const b = fromIterable(that) + if (isReadonlyArrayNonEmpty(a)) { + if (isReadonlyArrayNonEmpty(b)) { + const dedupe = dedupeWith(isEquivalent) + return dedupe(appendAll(a, b)) + } + return a + } + return b +}) + +/** + * Computes the union of two arrays, removing duplicates using + * `Equal.equivalence()`. + * + * **Example** (Computing array unions) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 set operations + * @since 2.0.0 + */ +export const union: { + >( + that: T + ): >( + self: S + ) => ReadonlyArray.OrNonEmpty | ReadonlyArray.Infer> + (self: NonEmptyReadonlyArray, that: ReadonlyArray): NonEmptyArray + (self: ReadonlyArray, that: NonEmptyReadonlyArray): NonEmptyArray + (self: Iterable, that: Iterable): Array +} = dual( + 2, + (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 + } +) + +/** + * Computes the intersection of two arrays using a custom equivalence. Order is + * determined by the first array. + * + * **When to use** + * + * Use when you need to keep only values present in both arrays and equality + * must be defined by a custom comparator, such as matching objects by id. + * + * **Example** (Computing intersections with custom equality) + * + * ```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 + * + * 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 set operations + * @since 2.0.0 + */ +export const intersectionWith = (isEquivalent: (self: A, that: A) => boolean): { + (that: Iterable): (self: Iterable) => Array + (self: Iterable, that: Iterable): Array +} => { + const has = containsWith(isEquivalent) + return dual( + 2, + (self: Iterable, that: Iterable): Array => { + const thatArray = fromIterable(that) + return fromIterable(self).filter((a) => has(thatArray, a)) + } + ) +} + +/** + * Computes the intersection of two arrays using `Equal.equivalence()`. Order is + * determined by the first array. + * + * **When to use** + * + * Use when Effect equality is the right membership test and you want to keep + * values present in both inputs while preserving the first input's order. + * + * **Example** (Computing array intersections) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 set operations + * @since 2.0.0 + */ +export const intersection: { + (that: Iterable): (self: Iterable) => Array + (self: Iterable, that: Iterable): Array +} = 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 + * custom equivalence. + * + * **When to use** + * + * Use when you need to keep only values from the first array and equality must + * be defined by a custom comparator, such as matching objects by id. + * + * **Example** (Computing differences with custom equality) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 set operations + * @since 2.0.0 + */ +export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): { + (that: Iterable): (self: Iterable) => Array + (self: Iterable, that: Iterable): Array +} => { + const has = containsWith(isEquivalent) + return dual( + 2, + (self: Iterable, that: Iterable): Array => { + const thatArray = fromIterable(that) + return fromIterable(self).filter((a) => !has(thatArray, a)) + } + ) +} + +/** + * Computes elements in the first array that are not in the second, using + * `Equal.equivalence()`. + * + * **When to use** + * + * Use when you need to keep values from the first array that are absent from + * the second and the default `Equal.equivalence()` comparison is appropriate. + * + * **Example** (Computing array differences) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 set operations + * @since 2.0.0 + */ +export const difference: { + (that: Iterable): (self: Iterable) => Array + (self: Iterable, that: Iterable): Array +} = 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. + * + * **When to use** + * + * Use to create a typed empty array without allocating placeholder elements. + * + * **Example** (Creating an empty array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.empty() // => [] + * ``` + * + * @see {@link of} — create a single-element array + * @see {@link make} — create from multiple values + * + * @category constructors + * @since 2.0.0 + */ +export const empty: () => Array = () => [] + +/** + * Wraps a single value in a `NonEmptyArray`. + * + * **Example** (Creating a single-element array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.of(1) // => [1] + * ``` + * + * @see {@link make} — create from multiple values + * @see {@link empty} — create an empty array + * + * @category constructors + * @since 2.0.0 + */ +export const of = (a: A): NonEmptyArray => [a] + +/** + * Utility types for working with `ReadonlyArray` at the type level. Use these + * to infer element types, preserve non-emptiness, and flatten nested arrays. + * + * @since 2.0.0 + */ +export declare namespace ReadonlyArray { + /** + * Infers the element type of an iterable. + * + * **Example** (Inferring an element type) + * + * ```ts import.meta.vitest + * import type { Array } from "effect" + * + * type StringArrayType = Array.ReadonlyArray.Infer> + * // StringArrayType is string + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type Infer> = S extends ReadonlyArray ? A + : S extends Iterable ? A + : never + + /** + * Constructs an array type preserving non-emptiness. + * + * **Example** (Preserving non-emptiness) + * + * ```ts import.meta.vitest + * import type { Array } from "effect" + * + * type Result = Array.ReadonlyArray.With + * // Result is NonEmptyArray + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type With, A> = S extends NonEmptyReadonlyArray ? NonEmptyArray + : Array + + /** + * Creates a non-empty array if either input is non-empty. + * + * **Example** (Preserving non-emptiness from either input) + * + * ```ts import.meta.vitest + * import type { Array } from "effect" + * + * type Result = Array.ReadonlyArray.OrNonEmpty< + * readonly [number], + * ReadonlyArray, + * number + * > + * // Result is NonEmptyArray + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type OrNonEmpty< + S extends Iterable, + T extends Iterable, + A + > = S extends NonEmptyReadonlyArray ? NonEmptyArray + : T extends NonEmptyReadonlyArray ? NonEmptyArray + : Array + + /** + * Creates a non-empty array only if both inputs are non-empty. + * + * **Example** (Preserving non-emptiness from both inputs) + * + * ```ts import.meta.vitest + * import type { Array } from "effect" + * + * type Result = Array.ReadonlyArray.AndNonEmpty< + * readonly [number], + * readonly [string], + * boolean + * > + * // Result is NonEmptyArray + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type AndNonEmpty< + S extends Iterable, + T extends Iterable, + A + > = S extends NonEmptyReadonlyArray ? T extends NonEmptyReadonlyArray ? NonEmptyArray + : Array + : Array + + /** + * Flattens a nested array type. + * + * **Example** (Flattening nested array types) + * + * ```ts import.meta.vitest + * import type { Array } from "effect" + * + * type Nested = ReadonlyArray> + * type Flattened = Array.ReadonlyArray.Flatten + * // Flattened is Array + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type Flatten>> = T extends + NonEmptyReadonlyArray> ? NonEmptyArray + : Array +} + +/** + * Transforms each element using a function, returning a new array. + * + * **When to use** + * + * Use to transform each element independently while preserving the array shape. + * + * **Details** + * + * The function receives `(element, index)`. The return type preserves + * `NonEmptyArray`. + * + * **Example** (Doubling values) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.map([1, 2, 3], (x) => x * 2) // => [2, 4, 6] + * ``` + * + * @see {@link flatMap} — map and flatten + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + , B>( + f: (a: ReadonlyArray.Infer, i: number) => B + ): (self: S) => ReadonlyArray.With + , B>(self: S, f: (a: ReadonlyArray.Infer, i: number) => B): ReadonlyArray.With +} = dual(2, (self: ReadonlyArray, f: (a: A, i: number) => B): Array => self.map(f)) + +/** + * Maps each element to an array and flattens the results into a single array. + * + * **When to use** + * + * Use to map each array element to zero or more values and concatenate the + * results in one pass. + * + * **Details** + * + * The function receives `(element, index)`. This returns `NonEmptyArray` when + * both the input and mapped arrays are non-empty. + * + * **Example** (Flat mapping an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.flatMap([1, 2, 3], (x) => [x, x * 2]) // => [1, 2, 2, 4, 3, 6] + * ``` + * + * @see {@link map} — transform without flattening + * @see {@link flatten} — flatten without mapping + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + , T extends ReadonlyArray>( + f: (a: ReadonlyArray.Infer, i: number) => T + ): (self: S) => ReadonlyArray.AndNonEmpty> + (self: NonEmptyReadonlyArray, f: (a: A, i: number) => NonEmptyReadonlyArray): NonEmptyArray + (self: ReadonlyArray, f: (a: A, i: number) => ReadonlyArray): Array +} = dual( + 2, + (self: ReadonlyArray, f: (a: A, i: number) => ReadonlyArray): Array => { + if (isReadonlyArrayEmpty(self)) { + return [] + } + const out: Array = [] + for (let i = 0; i < self.length; i++) { + const inner = f(self[i], i) + for (let j = 0; j < inner.length; j++) { + out.push(inner[j]) + } + } + return out + } +) + +/** + * Flattens a nested array of arrays into a single array. + * + * **When to use** + * + * Use to collapse one level of nested arrays when no per-element mapping is + * needed. + * + * **Example** (Flattening nested arrays) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.flatten([[1, 2], [], [3, 4], [], [5, 6]]) // => [1, 2, 3, 4, 5, 6] + * ``` + * + * @see {@link flatMap} — map then flatten in one step + * + * @category sequencing + * @since 2.0.0 + */ +export const flatten: >>(self: S) => ReadonlyArray.Flatten = + flatMap(identity) as any + +/** + * Extracts all `Some` values from an iterable of `Option`s, discarding `None`s. + * + * **When to use** + * + * Use to collect only present values from an iterable of `Option` values while + * discarding `None` values. + * + * **Example** (Extracting Some values) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * Array.getSomes([Option.some(1), Option.none(), Option.some(2)]) // => [1, 2] + * ``` + * + * @see {@link fromOption} — convert a single Option + * @see {@link getSuccesses} — extract successes from Results + * + * @category filtering + * @since 2.0.0 + */ + +export const getSomes: >, X = any>( + self: T +) => Array>> = (self: any) => { + const out: Array = [] + for (const a of self) { + if (Option.isSome(a)) { + out.push(a.value) + } + } + return out +} + +/** + * Extracts all failure values from an iterable of `Result`s, discarding + * successes. + * + * **When to use** + * + * Use when you can drop the success channel and only need the failure + * payloads, not the original result wrappers. + * + * **Example** (Extracting failures) + * + * ```ts import.meta.vitest + * import { Array, Result } from "effect" + * + * Array.getFailures([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) // => ["err"] + * ``` + * + * @see {@link getSuccesses} — extract success values + * @see {@link separate} — split into failures and successes + * + * @category filtering + * @since 4.0.0 + */ +export const getFailures = >>( + self: T +): Array>> => { + const out: Array = [] + for (const a of self) { + if (Result.isFailure(a)) { + out.push(a.failure) + } + } + + return out +} + +/** + * Extracts all success values from an iterable of `Result`s, discarding + * failures. + * + * **When to use** + * + * Use when you can drop the failure channel and only need the success + * payloads, not the original result wrappers. + * + * **Example** (Extracting successes) + * + * ```ts import.meta.vitest + * import { Array, Result } from "effect" + * + * Array.getSuccesses([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) // => [1, 2] + * ``` + * + * @see {@link getFailures} — extract failure values + * @see {@link separate} — split into failures and successes + * + * @category filtering + * @since 4.0.0 + */ +export const getSuccesses = >>( + self: T +): Array>> => { + const out: Array = [] + for (const a of self) { + if (Result.isSuccess(a)) { + out.push(a.success) + } + } + + return out +} + +/** + * Keeps transformed values for elements where a `Filter` succeeds. + * + * **When to use** + * + * Use to filter an iterable with a `Result`-returning transformation while + * discarding failures. + * + * **Details** + * + * The filter receives `(element, index)`. Failures are discarded. + * + * **Example** (Filtering and transforming) + * + * ```ts import.meta.vitest + * import { Array, Result } from "effect" + * + * 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 + * @see {@link partition} for keeping both failures and successes + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + (f: (input: NoInfer, i: number) => Result.Result): (self: Iterable) => Array + (self: Iterable, f: (input: A, i: number) => Result.Result): Array +} = dual(2, (self: Iterable, f: (input: A, i: number) => Result.Result): Array => { + const as = fromIterable(self) + const out: Array = [] + for (let i = 0; i < as.length; i++) { + const result = f(as[i], i) + if (Result.isSuccess(result)) { + out.push(result.success) + } + } + return out +}) + +/** + * Keeps only elements satisfying a predicate (or refinement). + * + * **When to use** + * + * Use to filter an iterable into a new array of original elements that satisfy + * a boolean predicate or refinement. + * + * **Details** + * + * The predicate receives `(element, index)`. Refinements are supported for type + * narrowing. + * + * **Example** (Filtering even numbers) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.filter([1, 2, 3, 4], (x) => x % 2 === 0) // => [2, 4] + * ``` + * + * @see {@link partition} — split into matching and non-matching + * @see {@link filterMap} for transforming while filtering + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Array + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Array + (self: Iterable, refinement: (a: A, i: number) => a is B): Array + (self: Iterable, predicate: (a: A, i: number) => boolean): Array +} = dual( + 2, + (self: Iterable, predicate: (a: A, i: number) => boolean): Array => { + const as = fromIterable(self) + const out: Array = [] + for (let i = 0; i < as.length; i++) { + if (predicate(as[i], i)) { + out.push(as[i]) + } + } + return out + } +) + +/** + * Splits an iterable using a `Filter` into failures and successes. + * + * **When to use** + * + * Use to partition an iterable by evaluating each element with a + * `Result`-returning filter and keeping both failure and success values. + * + * **Details** + * + * Returns `[excluded, satisfying]`. The filter receives `(element, index)`. + * + * **Example** (Partitioning with a filter) + * + * ```ts import.meta.vitest + * import { Array, Result } from "effect" + * + * Array.partition([1, -2, 3], (n, i) => + * n > 0 ? Result.succeed(n + i) : Result.fail(`negative:${n}`) + * ) // => [["negative:-2"], [1, 5]] + * ``` + * + * @see {@link filter} — keep only matching elements + * @see {@link filterMap} for discarding failures + * @see {@link separate} — split an iterable of `Result` values + * + * @category filtering + * @since 2.0.0 + */ +export const partition: { + ( + f: (input: NoInfer, i: number) => Result.Result + ): (self: Iterable) => [excluded: Array, satisfying: Array] + ( + self: Iterable, + f: (input: A, i: number) => Result.Result + ): [excluded: Array, satisfying: Array] +} = dual( + 2, + ( + self: Iterable, + f: (input: A, i: number) => Result.Result + ): [excluded: Array, satisfying: Array] => { + const excluded: Array = [] + const satisfying: Array = [] + let i = 0 + for (const a of self) { + const result = f(a, i++) + if (Result.isSuccess(result)) { + satisfying.push(result.success) + } else { + excluded.push(result.failure) + } + } + return [excluded, satisfying] + } +) + +/** + * Separates an iterable of `Result`s into failure values and success values. + * + * **When to use** + * + * Use to split an iterable of `Result` values into failure and success arrays. + * + * **Details** + * + * Returns `[failures, successes]`. This is equivalent to + * `partition(identity)`. + * + * **Example** (Separating Results) + * + * ```ts import.meta.vitest + * import { Array, Result } from "effect" + * + * Array.separate([Result.succeed(1), Result.fail("error"), Result.succeed(2)]) // => [["error"], [1, 2]] + * ``` + * + * @see {@link getFailures} — extract only failures + * @see {@link getSuccesses} — extract only successes + * @see {@link partition} for computing `Result` values while splitting + * + * @category filtering + * @since 2.0.0 + */ +export const separate: >>( + self: T +) => [ + failures: Array>>, + successes: Array>> +] = partition(identity) + +/** + * Folds an iterable from left to right into a single value. + * + * **When to use** + * + * Use to combine all elements into one accumulated value from left to right. + * + * **Details** + * + * The function receives `(accumulator, element, index)`. + * + * **Example** (Summing an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.reduce([1, 2, 3], 0, (acc, n) => acc + n) // => 6 + * ``` + * + * @see {@link reduceRight} — fold from right to left + * @see {@link scan} — fold keeping intermediate values + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + (b: B, f: (b: B, a: A, i: number) => B): (self: Iterable) => B + (self: Iterable, b: B, f: (b: B, a: A, i: number) => B): B +} = dual( + 3, + (self: Iterable, b: B, f: (b: B, a: A, i: number) => B): B => + fromIterable(self).reduce((b, a, i) => f(b, a, i), b) +) + +/** + * Folds an iterable from right to left into a single value. + * + * **When to use** + * + * Use when you need to fold values from right to left. + * + * **Details** + * + * The function receives `(accumulator, element, index)`. + * + * **Example** (Folding from right to left) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.reduceRight([1, 2, 3], 0, (acc, n) => acc + n) // => 6 + * ``` + * + * @see {@link reduce} — fold from left to right + * @see {@link scanRight} — fold keeping intermediate values + * + * @category folding + * @since 2.0.0 + */ +export const reduceRight: { + (b: B, f: (b: B, a: A, i: number) => B): (self: Iterable) => B + (self: Iterable, b: B, f: (b: B, a: A, i: number) => B): B +} = dual( + 3, + (self: Iterable, b: B, f: (b: B, a: A, i: number) => B): B => + fromIterable(self).reduceRight((b, a, i) => f(b, a, i), b) +) + +/** + * Lifts a predicate into an array: returns `[value]` if the predicate holds, + * `[]` otherwise. + * + * **Example** (Wrapping values conditionally) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const fromEven = Array.liftPredicate((n: number) => n % 2 === 0) + * + * fromEven(1) // => [] + * fromEven(2) // => [2] + * ``` + * + * @see {@link liftOption} — lift an Option-returning function + * + * @category lifting + * @since 2.0.0 + */ +export const liftPredicate: { // Note: I intentionally avoid using the NoInfer pattern here. + (refinement: Predicate.Refinement): (a: A) => Array + (predicate: Predicate.Predicate): (b: B) => Array +} = (predicate: Predicate.Predicate) => (b: B): Array => predicate(b) ? [b] : [] + +/** + * Lifts an `Option`-returning function into one that returns an array: + * `Some(a)` becomes `[a]`, `None` becomes `[]`. + * + * **When to use** + * + * Use when an optional parser or lookup should participate in array pipelines + * as zero-or-one results. + * + * **Example** (Lifting an Option function) + * + * ```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) + * }) + * + * parseNumber("123") // => [123] + * parseNumber("abc") // => [] + * ``` + * + * @see {@link liftPredicate} — lift a boolean predicate + * @see {@link liftResult} — lift a Result-returning function + * + * @category lifting + * @since 2.0.0 + */ +export const liftOption = , B>( + f: (...a: A) => Option.Option +) => +(...a: A): Array => fromOption(f(...a)) + +/** + * Converts a nullable value to an array: `null`/`undefined` becomes `[]`, + * anything else becomes `[value]`. + * + * **When to use** + * + * Use to treat a nullable single value as zero or one array element. + * + * **Example** (Converting nullable values to an array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.fromNullishOr(1) // => [1] + * Array.fromNullishOr(null) // => [] + * Array.fromNullishOr(undefined) // => [] + * ``` + * + * @see {@link liftNullishOr} — lift a nullable-returning function + * @see {@link fromOption} — convert from Option + * + * @category converting + * @since 4.0.0 + */ +export const fromNullishOr = (a: A): Array> => a == null ? empty() : [a as NonNullable] + +/** + * Lifts a nullable-returning function into one that returns an array: + * `null`/`undefined` becomes `[]`, anything else becomes `[value]`. + * + * **Example** (Lifting a nullable function) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const parseNumber = Array.liftNullishOr((s: string) => { + * const n = Number(s) + * return isNaN(n) ? null : n + * }) + * + * parseNumber("123") // => [123] + * parseNumber("abc") // => [] + * ``` + * + * @see {@link fromNullishOr} — convert a single nullable value + * @see {@link liftOption} — lift an Option-returning function + * + * @category lifting + * @since 4.0.0 + */ +export const liftNullishOr = , B>( + f: (...a: A) => B +): (...a: A) => Array> => +(...a) => fromNullishOr(f(...a)) + +/** + * Maps each element with a nullable-returning function, keeping only non-null / + * non-undefined results. + * + * **When to use** + * + * Use when you need to map and filter in one step, where the mapper can return + * `null` or `undefined` to skip elements. + * + * **Example** (Flat mapping with nullable values) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * @see {@link fromNullishOr} for converting a single nullable value to an array + * + * @category sequencing + * @since 4.0.0 + */ +export const flatMapNullishOr: { + (f: (a: A) => B): (self: ReadonlyArray) => Array> + (self: ReadonlyArray, f: (a: A) => B): Array> +} = dual( + 2, + (self: ReadonlyArray, f: (a: A) => B): Array> => flatMap(self, (a) => fromNullishOr(f(a))) +) + +/** + * Lifts a `Result`-returning function into one that returns an array: failures + * produce `[]`, successes produce `[value]`. + * + * **When to use** + * + * Use when a fallible parser or lookup should participate in array pipelines as + * zero-or-one results and the failure value should be discarded. + * + * **Example** (Lifting a Result function) + * + * ```ts import.meta.vitest + * import { Array, Result } from "effect" + * + * const parseNumber = (s: string): Result.Result => + * isNaN(Number(s)) + * ? Result.fail(new Error("Not a number")) + * : Result.succeed(Number(s)) + * + * const liftedParseNumber = Array.liftResult(parseNumber) + * + * liftedParseNumber("42") // => [42] + * liftedParseNumber("not a number") // => [] + * ``` + * + * @see {@link liftOption} — lift an Option-returning function + * @see {@link liftPredicate} — lift a boolean predicate + * + * @category lifting + * @since 4.0.0 + */ +export const liftResult = , E, B>( + f: (...a: A) => Result.Result +) => +(...a: A): Array => { + const e = f(...a) + return Result.isFailure(e) ? [] : [e.success] +} + +/** + * Checks whether all elements satisfy the predicate. Supports refinements for + * type narrowing. + * + * **When to use** + * + * Use to check whether every array element satisfies a predicate, including + * refinement-based type narrowing. + * + * **Example** (Testing all elements) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 guards + * @since 2.0.0 + */ +export const every: { + ( + refinement: (a: NoInfer, i: number) => a is B + ): (self: ReadonlyArray) => self is ReadonlyArray + (predicate: (a: NoInfer, i: number) => boolean): (self: ReadonlyArray) => boolean + (self: ReadonlyArray, refinement: (a: A, i: number) => a is B): self is ReadonlyArray + (self: ReadonlyArray, predicate: (a: A, i: number) => boolean): boolean +} = dual( + 2, + (self: ReadonlyArray, refinement: (a: A, i: number) => a is B): self is ReadonlyArray => + self.every(refinement) +) + +/** + * Checks whether at least one element satisfies the predicate. Narrows the type + * to `NonEmptyReadonlyArray` on success. + * + * **Example** (Testing for any match) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 guards + * @since 2.0.0 + */ +export const some: { + ( + predicate: (a: NoInfer, i: number) => boolean + ): (self: ReadonlyArray) => self is NonEmptyReadonlyArray + (self: ReadonlyArray, predicate: (a: A, i: number) => boolean): self is NonEmptyReadonlyArray +} = dual( + 2, + (self: ReadonlyArray, predicate: (a: A, i: number) => boolean): self is NonEmptyReadonlyArray => + self.some(predicate) +) + +/** + * Applies a function to each suffix of the array (starting from each index), + * collecting the results. + * + * **When to use** + * + * Use when you need to compute a result from every suffix of an array, such as + * cumulative aggregations from each position. + * + * **Details** + * + * For index `i`, the function receives `self.slice(i)`. + * + * **Example** (Computing suffix lengths) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.extend([1, 2, 3], (as) => as.length) // => [3, 2, 1] + * ``` + * + * @see {@link scan} for keeping intermediate accumulator values during a fold + * + * @category mapping + * @since 2.0.0 + */ +export const extend: { + (f: (as: ReadonlyArray) => B): (self: ReadonlyArray) => Array + (self: ReadonlyArray, f: (as: ReadonlyArray) => B): Array +} = dual( + 2, + (self: ReadonlyArray, f: (as: ReadonlyArray) => B): Array => self.map((_, i, as) => f(as.slice(i))) +) + +/** + * Returns the minimum element of a non-empty array according to the given + * `Order`. + * + * **Example** (Finding the minimum) + * + * ```ts import.meta.vitest + * import { Array, Order } from "effect" + * + * Array.min([3, 1, 2], Order.Number) // => 1 + * ``` + * + * @see {@link max} — find the maximum + * @see {@link sort} — sort the entire array + * + * @category getters + * @since 2.0.0 + */ +export const min: { + (O: Order.Order): (self: NonEmptyReadonlyArray) => A + (self: NonEmptyReadonlyArray, O: Order.Order): A +} = dual(2, (self: NonEmptyReadonlyArray, O: Order.Order): A => self.reduce(Order.min(O))) + +/** + * Returns the maximum element of a non-empty array according to the given + * `Order`. + * + * **Example** (Finding the maximum) + * + * ```ts import.meta.vitest + * import { Array, Order } from "effect" + * + * Array.max([3, 1, 2], Order.Number) // => 3 + * ``` + * + * @see {@link min} — find the minimum + * @see {@link sort} — sort the entire array + * + * @category getters + * @since 2.0.0 + */ +export const max: { + (O: Order.Order): (self: NonEmptyReadonlyArray) => A + (self: NonEmptyReadonlyArray, O: Order.Order): A +} = dual(2, (self: NonEmptyReadonlyArray, O: Order.Order): A => self.reduce(Order.max(O))) + +/** + * Builds an array by repeatedly applying a function to a seed value. The + * function returns `Option.some([element, nextSeed])` to continue, or + * `Option.none()` to stop. + * + * **Example** (Generating a sequence) + * + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * Array.unfold(1, (n) => n <= 5 ? Option.some([n, n + 1]) : Option.none()) // => [1, 2, 3, 4, 5] + * ``` + * + * @see {@link makeBy} — generate from index + * @see {@link range} — generate a numeric range + * + * @category constructors + * @since 2.0.0 + */ +export const unfold = (b: B, f: (b: B) => Option.Option): Array => { + const out: Array = [] + let next: B = b + while (true) { + const o = f(next) + if (Option.isNone(o)) { + break + } + const [a, b] = o.value + out.push(a) + next = b + } + return out +} + +/** + * Creates an `Order` for arrays based on an element `Order`. Arrays are + * compared element-wise; if all compared elements are equal, shorter arrays + * come first. + * + * **Example** (Comparing arrays) + * + * ```ts import.meta.vitest + * import { Array, Order } from "effect" + * + * const arrayOrder = Array.makeOrder(Order.Number) + * + * arrayOrder([1, 2], [1, 3]) // => -1 + * ``` + * + * @see {@link makeEquivalence} — create an equivalence for arrays + * + * @category instances + * @since 4.0.0 + */ +export const makeOrder: (O: Order.Order) => Order.Order> = Order.Array + +/** + * Creates an `Equivalence` for arrays based on an element `Equivalence`. Two + * arrays are equivalent when they have the same length and all elements are + * pairwise equivalent. + * + * **Example** (Comparing arrays for equality) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * const eq = Array.makeEquivalence((a, b) => a === b) + * + * eq([1, 2, 3], [1, 2, 3]) // => true + * ``` + * + * @see {@link makeOrder} — create an ordering for arrays + * + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence: ( + isEquivalent: Equivalence.Equivalence +) => Equivalence.Equivalence> = Equivalence.Array + +/** + * Runs a side-effect for each element. The callback receives `(element, index)`. + * + * **When to use** + * + * Use to iterate over an array for side-effects only, when no transformed + * result is needed. + * + * **Example** (Iterating with side-effects) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 traversing + * @since 2.0.0 + */ +export const forEach: { + (f: (a: A, i: number) => void): (self: Iterable) => void + (self: Iterable, f: (a: A, i: number) => void): void +} = dual(2, (self: Iterable, f: (a: A, i: number) => void): void => fromIterable(self).forEach((a, i) => f(a, i))) + +/** + * Removes duplicates using a custom equivalence, preserving the order of the + * first occurrence. + * + * **When to use** + * + * Use to remove all duplicate elements with a custom equivalence when default + * equality is not appropriate. + * + * **Example** (Deduplicating with custom equality) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 deduplication + * @since 2.0.0 + */ +export const dedupeWith: { + >( + isEquivalent: (self: ReadonlyArray.Infer, that: ReadonlyArray.Infer) => boolean + ): (self: S) => ReadonlyArray.With> + (self: NonEmptyReadonlyArray, isEquivalent: (self: A, that: A) => boolean): NonEmptyArray + (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Array +} = dual( + 2, + (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Array => { + const input = fromIterable(self) + if (isReadonlyArrayNonEmpty(input)) { + const out: NonEmptyArray = [headNonEmpty(input)] + const rest = tailNonEmpty(input) + for (const r of rest) { + if (out.every((a) => !isEquivalent(r, a))) { + out.push(r) + } + } + return out + } + return [] + } +) + +/** + * Removes duplicates using `Equal.equivalence()`, preserving the order of the + * first occurrence. + * + * **When to use** + * + * Use to remove repeated values from an iterable when Effect's default equality + * is the right comparison, preserving the first occurrence. + * + * **Example** (Removing duplicates) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 deduplication + * @since 2.0.0 + */ +export const dedupe = >( + self: S +): 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. + * + * **When to use** + * + * Use when consecutive duplicates should be collapsed using a custom + * equivalence, while equivalent values that appear later should remain in the + * result. + * + * **Details** + * + * Non-adjacent duplicates are preserved. + * + * **Example** (Deduplicating adjacent elements) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 deduplication + * @since 2.0.0 + */ +export const dedupeAdjacentWith: { + (isEquivalent: (self: A, that: A) => boolean): (self: Iterable) => Array + (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Array +} = dual(2, (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Array => { + const out: Array = [] + let lastA: Option.Option = Option.none() + for (const a of self) { + if (Option.isNone(lastA) || !isEquivalent(a, lastA.value)) { + out.push(a) + lastA = Option.some(a) + } + } + return out +}) + +/** + * Removes consecutive duplicate elements using `Equal.equivalence()`. + * + * **When to use** + * + * Use when you need to collapse consecutive duplicates while preserving later + * non-consecutive repeats, and the default equality is sufficient. + * + * **Example** (Removing adjacent duplicates) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.dedupeAdjacent([1, 1, 2, 2, 3, 3]) // => [1, 2, 3] + * ``` + * + * @see {@link dedupeAdjacentWith} — use custom equality + * @see {@link dedupe} — remove all duplicates + * + * @category deduplication + * @since 2.0.0 + */ +export const dedupeAdjacent: (self: Iterable) => Array = dedupeAdjacentWith(Equal.asEquivalence()) + +/** + * Joins string elements with a separator. + * + * **Example** (Joining strings) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.join(["a", "b", "c"], "-") // => "a-b-c" + * ``` + * + * @see {@link intersperse} — insert separator elements without joining + * + * @category folding + * @since 2.0.0 + */ +export const join: { + (sep: string): (self: Iterable) => string + (self: Iterable, sep: string): string +} = dual(2, (self: Iterable, sep: string): string => fromIterable(self).join(sep)) + +/** + * Maps over an array while threading an accumulator through each step, returning both the final state and the mapped array. + * + * **When to use** + * + * Use when you need to map while threading state through each element and keep + * the final state. + * + * **Details** + * + * Combines `map` and `reduce` in a single pass. The callback receives the + * current state, element, and index, and returns `[nextState, mappedValue]`. + * The result is `[finalState, mappedArray]`. This can be used in both + * data-first and data-last style. + * + * **Example** (Running sum alongside mapped values) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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) + * @see {@link reduce} — when you only need the final accumulated value + * + * @category folding + * @since 2.0.0 + */ +export const mapAccum: { + = Iterable>( + s: S, + f: (s: S, a: ReadonlyArray.Infer, i: number) => readonly [S, B] + ): (self: I) => [state: S, mappedArray: ReadonlyArray.With] + = Iterable>( + self: I, + s: S, + f: (s: S, a: ReadonlyArray.Infer, i: number) => readonly [S, B] + ): [state: S, mappedArray: ReadonlyArray.With] +} = dual( + 3, + (self: Iterable, s: S, f: (s: S, a: A, i: number) => [S, B]): [state: S, mappedArray: Array] => { + let i = 0 + let s1 = s + const out: Array = [] + for (const a of self) { + const r = f(s1, a, i) + s1 = r[0] + out.push(r[1]) + i++ + } + return [s1, out] + } +) + +/** + * Computes the cartesian product of two arrays, applying a combiner to each pair. + * + * **When to use** + * + * Use to compute every combination from two arrays and immediately transform + * each pair into a custom result. + * + * **Details** + * + * Produces every combination of an element from `self` with an element from + * `that`, so the result length is `self.length * that.length`. Iteration visits + * every element of `that` for each element of `self`. + * + * **Example** (Combining numbers and letters) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 combining + * @since 2.0.0 + */ +export const cartesianWith: { + (that: ReadonlyArray, f: (a: A, b: B) => C): (self: ReadonlyArray) => Array + (self: ReadonlyArray, that: ReadonlyArray, f: (a: A, b: B) => C): Array +} = dual( + 3, + (self: ReadonlyArray, that: ReadonlyArray, f: (a: A, b: B) => C): Array => + flatMap(self, (a) => map(that, (b) => f(a, b))) +) + +/** + * Computes the cartesian product of two arrays, returning all pairs as tuples. + * + * **When to use** + * + * Use when you need every `[a, b]` pair from two arrays as tuples. + * + * **Details** + * + * Produces every `[a, b]` combination of an element from `self` with an element + * from `that`, so the result length is `self.length * that.length`. + * + * **Example** (Generating all pairs from two arrays) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * Array.cartesian([1, 2], ["a", "b"]) // => [[1, "a"], [1, "b"], [2, "a"], [2, "b"]] + * ``` + * + * @see {@link cartesianWith} — apply a combiner to each pair + * + * @category combining + * @since 2.0.0 + */ +export const cartesian: { + (that: ReadonlyArray): (self: ReadonlyArray) => Array<[A, B]> + (self: ReadonlyArray, that: ReadonlyArray): Array<[A, B]> +} = dual( + 2, + (self: ReadonlyArray, that: ReadonlyArray): Array<[A, B]> => cartesianWith(self, that, (a, b) => [a, b]) +) + +// ------------------------------------------------------------------------------------- +// do notation +// ------------------------------------------------------------------------------------- + +/** + * Provides the starting point for the "do simulation" — an array comprehension pattern. + * + * **When to use** + * + * Use when you want array-comprehension style code with do notation. + * + * **Details** + * + * Use {@link bind} to introduce array variables and {@link let_ let} for plain + * values. Each `bind` produces the cartesian product of all bound variables, + * like nested loops. Use `filter` and `map` in the pipeline to add conditions + * and transformations. + * + * **Example** (Building array comprehensions with do notation) + * + * ```ts import.meta.vitest + * import { Array, pipe } from "effect" + * + * 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) + * ) // => [[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 constructors + * @since 3.2.0 + */ +export const Do: ReadonlyArray<{}> = of({}) + +/** + * Adds a new array variable to a do-notation scope, producing the cartesian product with all previous bindings. + * + * **When to use** + * + * Use to add another array-producing binding to an `Array.Do` pipeline, pairing + * each existing scope with every value returned by the callback. + * + * **Details** + * + * Each `bind` call adds a named property to the accumulated object. The + * callback receives the current scope and must return an array. This is + * equivalent to `flatMap` plus merging the new value into the scope object. + * + * **Example** (Binding two arrays) + * + * ```ts import.meta.vitest + * import { Array, pipe } from "effect" + * + * pipe( + * Array.Do, + * Array.bind("x", () => [1, 2]), + * Array.bind("y", () => ["a", "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 sequencing + * @since 3.2.0 + */ +export const bind: { + ( + tag: Exclude, + f: (a: NoInfer) => ReadonlyArray + ): ( + self: ReadonlyArray + ) => Array<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> + ( + self: ReadonlyArray, + tag: Exclude, + f: (a: NoInfer) => ReadonlyArray + ): Array<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> +} = internalDoNotation.bind(map, flatMap) as any + +/** + * Wraps each array element in an object with the given key, starting a do-notation scope. + * + * **When to use** + * + * Use when you already have an array and want to start a do-notation pipeline + * by naming each element. + * + * **Details** + * + * Equivalent to `Array.map(self, (a) => ({ [tag]: a }))`. This is an + * alternative to starting with `Do` plus `bind` when you already have an array. + * + * **Example** (Naming an existing array) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 mapping + * @since 3.2.0 + */ +export const bindTo: { + (tag: N): (self: ReadonlyArray) => Array<{ [K in N]: A }> + (self: ReadonlyArray, tag: N): Array<{ [K in N]: A }> +} = internalDoNotation.bindTo(map) as any + +const let_: { + ( + tag: Exclude, + f: (a: NoInfer) => B + ): (self: ReadonlyArray) => Array<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> + ( + self: ReadonlyArray, + tag: Exclude, + f: (a: NoInfer) => B + ): Array<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> +} = internalDoNotation.let_(map) as any + +export { + /** + * Adds a computed plain value to the do-notation scope without introducing a new array dimension. + * + * **When to use** + * + * Use when each do-notation branch needs a derived field from the current + * bindings without multiplying the number of branches. + * + * **Details** + * + * Unlike `bind`, the callback returns a single value instead of an array, so + * no cartesian product occurs. Use this for derived or intermediate values + * that depend on previously bound variables. + * + * **Example** (Adding a computed value) + * + * ```ts import.meta.vitest + * import { Array, pipe } from "effect" + * + * pipe( + * Array.Do, + * Array.bind("x", () => [1, 2, 3]), + * Array.let("doubled", ({ x }) => x * 2) + * ) // => [{ 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 mapping + * @since 3.2.0 + */ + let_ as let +} + +const reducer = Reducer.make((a, b) => a.concat(b), [] as any) + +/** + * Returns a `Reducer` that combines `ReadonlyArray` values by concatenation. + * + * @see {@link makeReducerConcat} — mutable `Array` variant + * + * @category folding + * @since 4.0.0 + */ +export function getReadonlyReducerConcat(): Reducer.Reducer> { + return reducer +} + +/** + * Returns a `Reducer` that combines `Array` values by concatenation. + * + * @see {@link getReadonlyReducerConcat} — readonly variant + * + * @category folding + * @since 4.0.0 + */ +export function makeReducerConcat(): Reducer.Reducer> { + return reducer +} + +/** + * Computes the number of elements in an iterable that satisfy a predicate. + * + * **When to use** + * + * Use when you need to count how many elements of an iterable satisfy a + * predicate. + * + * **Details** + * + * The predicate receives both the element and its index. Empty iterables return + * `0`. + * + * **Example** (Counting even numbers) + * + * ```ts import.meta.vitest + * import { Array } from "effect" + * + * 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 + * + * @category folding + * @since 3.16.0 + */ +export const countBy: { + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => number + (self: Iterable, predicate: (a: A, i: number) => boolean): number +} = dual( + 2, + ( + self: Iterable, + f: (a: A, i: number) => boolean + ): number => { + let count = 0 + const as = fromIterable(self) + for (let i = 0; i < as.length; i++) { + const a = as[i] + if (f(a, i)) { + count++ + } + } + return count + } +) diff --git a/.repos/effect/packages/effect/src/BigDecimal.ts b/.repos/effect/packages/effect/src/BigDecimal.ts new file mode 100644 index 000000000..865db4449 --- /dev/null +++ b/.repos/effect/packages/effect/src/BigDecimal.ts @@ -0,0 +1,1818 @@ +/** + * Decimal numbers and arithmetic for cases where JavaScript `number` rounding + * is not precise enough. A `BigDecimal` stores digits as a `bigint` plus a + * decimal scale, which lets the module parse, compare, add, subtract, multiply, + * divide, round, and format decimal values such as money, quantities, and + * measurements. + * + * @since 2.0.0 + */ + +import * as Equal from "./Equal.ts" +import * as Equ from "./Equivalence.ts" +import { dual } from "./Function.ts" +import * as Hash from "./Hash.ts" +import { type Inspectable, NodeInspectSymbol } from "./Inspectable.ts" +import * as Option from "./Option.ts" +import * as order from "./Order.ts" +import type { Ordering } from "./Ordering.ts" +import { type Pipeable, pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" + +const DEFAULT_PRECISION = 100 +const FINITE_INT_REGEXP = /^[+-]?\d+$/ + +const TypeId = "~effect/BigDecimal" + +/** + * Represents an arbitrary precision decimal number. + * + * **When to use** + * + * Use when decimal arithmetic needs to avoid JavaScript floating point + * representation errors. + * + * **Example** (Inspecting BigDecimal storage) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const d = BigDecimal.fromStringUnsafe("123.45") + * + * d.value // => 12345n + * d.scale // => 2 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface BigDecimal extends Equal.Equal, Pipeable, Inspectable { + readonly [TypeId]: typeof TypeId + readonly value: bigint + readonly scale: number + /** @internal */ + normalized?: BigDecimal +} + +const BigDecimalProto: Omit = { + [TypeId]: TypeId, + [Hash.symbol](this: BigDecimal): number { + const normalized = normalize(this) + return Hash.combine(Hash.hash(normalized.value), Hash.number(normalized.scale)) + }, + [Equal.symbol](this: BigDecimal, that: unknown): boolean { + return isBigDecimal(that) && equals(this, that) + }, + toString(this: BigDecimal) { + return `BigDecimal(${format(this)})` + }, + toJSON(this: BigDecimal) { + return { + _id: "BigDecimal", + value: String(this.value), + scale: this.scale + } + }, + [NodeInspectSymbol](this: BigDecimal) { + return this.toJSON() + }, + pipe() { + return pipeArguments(this, arguments) + } +} as const + +/** + * Checks whether a given value is a `BigDecimal`. + * + * **When to use** + * + * Use to validate unknown input and narrow it to `BigDecimal`. + * + * **Example** (Checking BigDecimal values) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const decimal = BigDecimal.fromNumber(123.45) + * BigDecimal.isBigDecimal(decimal) // => false + * BigDecimal.isBigDecimal(BigDecimal.fromStringUnsafe("123.45")) // => true + * BigDecimal.isBigDecimal(123.45) // => false + * BigDecimal.isBigDecimal("123.45") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isBigDecimal = (u: unknown): u is BigDecimal => hasProperty(u, TypeId) + +/** + * Creates a `BigDecimal` from a `bigint` value and a scale. + * + * **When to use** + * + * Use to construct a decimal directly from its unscaled integer value and + * decimal scale. + * + * **Example** (Creating decimals from bigint and scale) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * // Create 123.45 (12345 with scale 2) + * const decimal = BigDecimal.make(12345n, 2) + * decimal // => BigDecimal.fromStringUnsafe("123.45") + * + * // Create 42 (42 with scale 0) + * const integer = BigDecimal.make(42n, 0) + * integer // => BigDecimal.fromBigInt(42n) + * ``` + * + * @see {@link fromBigInt} for constructing an integer decimal from a `bigint` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (value: bigint, scale: number): BigDecimal => { + const o = Object.create(BigDecimalProto) + o.value = value + o.scale = scale + return o +} + +/** + * Internal function used to create pre-normalized `BigDecimal`s. + * + * @internal + */ +export const makeNormalizedUnsafe = (value: bigint, scale: number): BigDecimal => { + if (value !== bigint0 && value % bigint10 === bigint0) { + throw new RangeError("Value must be normalized") + } + + const o = make(value, scale) + o.normalized = o + return o +} + +const bigint0 = BigInt(0) +const bigint1 = BigInt(1) +const bigint_1 = BigInt(-1) +const bigint2 = BigInt(2) +const bigint5 = BigInt(5) +const bigint_5 = BigInt(-5) +const bigint10 = BigInt(10) +const zero = makeNormalizedUnsafe(bigint0, 0) +const one = makeNormalizedUnsafe(bigint1, 0) + +/** + * Normalizes a given `BigDecimal` by removing trailing zeros. + * + * **When to use** + * + * Use to canonicalize decimals that have equivalent values but different + * internal scales. + * + * **Example** (Normalizing trailing zeros) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * + * @category scaling + * @since 2.0.0 + */ +export const normalize = (self: BigDecimal): BigDecimal => { + if (self.normalized === undefined) { + if (self.value === bigint0) { + self.normalized = zero + } else { + const digits = `${self.value}` + + let trail = 0 + for (let i = digits.length - 1; i >= 0; i--) { + if (digits[i] === "0") { + trail++ + } else { + break + } + } + + if (trail === 0) { + self.normalized = self + } + + const value = BigInt(digits.substring(0, digits.length - trail)) + const scale = self.scale - trail + self.normalized = makeNormalizedUnsafe(value, scale) + } + } + + return self.normalized +} + +/** + * Changes a `BigDecimal` to the specified scale. + * + * **When to use** + * + * Use to change how many decimal places are represented by a `BigDecimal`. + * + * **Details** + * + * Increasing the scale appends decimal zeros. Decreasing the scale discards + * digits beyond the target scale by `bigint` division, which truncates toward + * zero. + * + * **Example** (Scaling decimal precision) + * + * ```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) + * const scaledStorage = [scaled.value, scaled.scale] // => [1234500n, 4] + * + * // Decrease scale (reduce precision, truncating toward zero) + * const reduced = BigDecimal.scale(decimal, 1) + * reduced // => BigDecimal.fromStringUnsafe("123.4") + * ``` + * + * @see {@link round} for changing scale with configurable rounding + * + * @category scaling + * @since 2.0.0 + */ +export const scale: { + (scale: number): (self: BigDecimal) => BigDecimal + (self: BigDecimal, scale: number): BigDecimal +} = dual(2, (self: BigDecimal, scale: number): BigDecimal => { + if (scale > self.scale) { + return make(self.value * bigint10 ** BigInt(scale - self.scale), scale) + } + + if (scale < self.scale) { + return make(self.value / bigint10 ** BigInt(self.scale - scale), scale) + } + + return self +}) + +/** + * Provides an addition operation on `BigDecimal`s. + * + * **When to use** + * + * Use when you need a decimal addition function for piping or higher-order APIs + * while preserving decimal precision. + * + * **Example** (Adding decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const result = BigDecimal.sum( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(5n) + * ``` + * + * @see {@link sumAll} for summing an iterable of `BigDecimal` values + * + * @category math + * @since 2.0.0 + */ +export const sum: { + (that: BigDecimal): (self: BigDecimal) => BigDecimal + (self: BigDecimal, that: BigDecimal): BigDecimal +} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => { + if (that.value === bigint0) { + return self + } + + if (self.value === bigint0) { + return that + } + + if (self.scale > that.scale) { + return make(scale(that, self.scale).value + self.value, self.scale) + } + + if (self.scale < that.scale) { + return make(scale(self, that.scale).value + that.value, that.scale) + } + + return make(self.value + that.value, self.scale) +}) + +/** + * Takes an `Iterable` of `BigDecimal`s and returns their sum as a single `BigDecimal`. + * + * **When to use** + * + * Use when you need to aggregate decimal quantities with decimal precision + * instead of converting through JavaScript numbers. + * + * **Example** (Adding multiple decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const result = BigDecimal.sumAll([ + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("4") + * ]) // => BigDecimal.fromBigInt(9n) + * ``` + * + * @see {@link sum} for adding two `BigDecimal` values + * + * @category math + * @since 3.16.0 + */ +export const sumAll = (collection: Iterable): BigDecimal => { + let out: BigDecimal = zero + for (const n of collection) { + out = sum(out, n) + } + return out +} + +/** + * Provides a multiplication operation on `BigDecimal`s. + * + * **When to use** + * + * Use to multiply two `BigDecimal` values. + * + * **Example** (Multiplying decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const result = BigDecimal.multiply( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(6n) + * ``` + * + * @see {@link multiplyAll} for multiplying an iterable of `BigDecimal` values + * + * @category math + * @since 2.0.0 + */ +export const multiply: { + (that: BigDecimal): (self: BigDecimal) => BigDecimal + (self: BigDecimal, that: BigDecimal): BigDecimal +} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => { + if (that.value === bigint0 || self.value === bigint0) { + return zero + } + + return make(self.value * that.value, self.scale + that.scale) +}) + +/** + * Takes an `Iterable` of `BigDecimal`s and returns their multiplication as a single `BigDecimal`. + * + * **When to use** + * + * Use to multiply all `BigDecimal` values in an iterable. + * + * **Example** (Multiplying multiple decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const result = BigDecimal.multiplyAll([ + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("4") + * ]) // => BigDecimal.fromBigInt(24n) + * ``` + * + * @see {@link multiply} for multiplying two `BigDecimal` values + * + * @category math + * @since 4.0.0 + */ +export const multiplyAll = (collection: Iterable): BigDecimal => { + let out: BigDecimal = one + for (const n of collection) { + if (n.value === bigint0) { + return zero + } + out = multiply(out, n) + } + return out +} + +/** + * Provides a subtraction operation on `BigDecimal`s. + * + * **When to use** + * + * Use to subtract one `BigDecimal` value from another. + * + * **Example** (Subtracting decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const result = BigDecimal.subtract( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(-1n) + * ``` + * + * @category math + * @since 2.0.0 + */ +export const subtract: { + (that: BigDecimal): (self: BigDecimal) => BigDecimal + (self: BigDecimal, that: BigDecimal): BigDecimal +} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => { + if (that.value === bigint0) { + return self + } + + if (self.value === bigint0) { + return make(-that.value, that.scale) + } + + if (self.scale > that.scale) { + return make(self.value - scale(that, self.scale).value, self.scale) + } + + if (self.scale < that.scale) { + return make(scale(self, that.scale).value - that.value, that.scale) + } + + return make(self.value - that.value, self.scale) +}) + +/** + * Internal function used for arbitrary precision division. + */ +const divideWithPrecision = ( + num: bigint, + den: bigint, + scale: number, + precision: number +): BigDecimal => { + const numNegative = num < bigint0 + const denNegative = den < bigint0 + const negateResult = numNegative !== denNegative + + num = numNegative ? -num : num + den = denNegative ? -den : den + + // Shift digits until numerator is larger than denominator (set scale appropriately). + while (num < den) { + num *= bigint10 + scale++ + } + + // First division. + let quotient = num / den + let remainder = num % den + + if (remainder === bigint0) { + // No remainder, return immediately. + return make(negateResult ? -quotient : quotient, scale) + } + + // The quotient is guaranteed to be non-negative at this point. No need to consider sign. + let count = `${quotient}`.length + + // Shift the remainder by 1 decimal; The quotient will be 1 digit upon next division. + remainder *= bigint10 + while (remainder !== bigint0 && count < precision) { + const q = remainder / den + const r = remainder % den + quotient = quotient * bigint10 + q + remainder = r * bigint10 + + count++ + scale++ + } + + if (remainder !== bigint0) { + // Round final number with remainder. + quotient += roundTerminal(remainder / den) + } + + return make(negateResult ? -quotient : quotient, scale) +} + +/** + * Internal function used for rounding. + * + * Returns 1 if the most significant digit is >= 5, otherwise 0. + * + * This is used after dividing a number by a power of ten and rounding the last digit. + * + * @internal + */ +export const roundTerminal = (n: bigint): bigint => { + const pos = n >= bigint0 ? 0 : 1 + return Number(`${n}`[pos]) < 5 ? bigint0 : bigint1 +} + +/** + * Divides `BigDecimal`s safely. + * + * **When to use** + * + * Use to divide `BigDecimal` values while representing division by zero as + * `Option.none`. + * + * **Details** + * + * If the dividend is not a multiple of the divisor, the result will be a `BigDecimal` value + * with up to the default division precision. If the divisor is `0`, the result + * will be `Option.none()`. + * + * **Example** (Dividing decimals safely) + * + * ```ts import.meta.vitest + * import { BigDecimal, Option } from "effect" + * + * 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 + * @see {@link remainder} for the decimal remainder operation + * + * @category math + * @since 2.0.0 + */ +export const divide: { + (that: BigDecimal): (self: BigDecimal) => Option.Option + (self: BigDecimal, that: BigDecimal): Option.Option +} = dual(2, (self: BigDecimal, that: BigDecimal): Option.Option => { + if (that.value === bigint0) { + return Option.none() + } + + if (self.value === bigint0) { + return Option.some(zero) + } + + const scale = self.scale - that.scale + if (self.value === that.value) { + return Option.some(make(bigint1, scale)) + } + + return Option.some(divideWithPrecision(self.value, that.value, scale, DEFAULT_PRECISION)) +}) + +/** + * Provides an unsafe division operation on `BigDecimal`s. + * + * **When to use** + * + * Use when you need to divide `BigDecimal` values where the divisor is known + * to be non-zero, so division by zero should be a thrown exception. + * + * **Details** + * + * If the dividend is not a multiple of the divisor, the result will be a `BigDecimal` value + * with up to the default division precision. + * + * **Gotchas** + * + * Throws a `RangeError` if the divisor is `0`. + * + * **Example** (Dividing decimals unsafely) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * + * @category math + * @since 4.0.0 + */ +export const divideUnsafe: { + (that: BigDecimal): (self: BigDecimal) => BigDecimal + (self: BigDecimal, that: BigDecimal): BigDecimal +} = dual(2, (self: BigDecimal, that: BigDecimal): BigDecimal => { + if (that.value === bigint0) { + throw new RangeError("Division by zero") + } + + if (self.value === bigint0) { + return zero + } + + const scale = self.scale - that.scale + if (self.value === that.value) { + return make(bigint1, scale) + } + return divideWithPrecision(self.value, that.value, scale, DEFAULT_PRECISION) +}) + +/** + * Provides an `Order` instance for `BigDecimal` that allows comparing and sorting BigDecimal values. + * + * **When to use** + * + * Use when you need to sort or compare decimal values through APIs that accept + * an ordering instance. + * + * **Example** (Comparing decimals) + * + * ```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) + * + * 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) +}) + +/** + * Returns `true` if the first argument is less than the second, otherwise `false`. + * + * **When to use** + * + * Use to test whether one `BigDecimal` is strictly less than another. + * + * **Example** (Checking less-than comparisons) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const isLessThan: { + (that: BigDecimal): (self: BigDecimal) => boolean + (self: BigDecimal, that: BigDecimal): boolean +} = order.isLessThan(Order) + +/** + * Checks whether a given `BigDecimal` is less than or equal to the provided one. + * + * **When to use** + * + * Use to test whether one `BigDecimal` is less than or equal to another. + * + * **Example** (Checking less-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const isLessThanOrEqualTo: { + (that: BigDecimal): (self: BigDecimal) => boolean + (self: BigDecimal, that: BigDecimal): boolean +} = order.isLessThanOrEqualTo(Order) + +/** + * Returns `true` if the first argument is greater than the second, otherwise `false`. + * + * **When to use** + * + * Use to test whether one `BigDecimal` is strictly greater than another. + * + * **Example** (Checking greater-than comparisons) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const isGreaterThan: { + (that: BigDecimal): (self: BigDecimal) => boolean + (self: BigDecimal, that: BigDecimal): boolean +} = order.isGreaterThan(Order) + +/** + * Checks whether a given `BigDecimal` is greater than or equal to the provided one. + * + * **When to use** + * + * Use to test whether one `BigDecimal` is greater than or equal to another. + * + * **Example** (Checking greater-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo: { + (that: BigDecimal): (self: BigDecimal) => boolean + (self: BigDecimal, that: BigDecimal): boolean +} = order.isGreaterThanOrEqualTo(Order) + +/** + * Checks whether a `BigDecimal` is between a `minimum` and `maximum` value (inclusive). + * + * **When to use** + * + * Use to test whether a `BigDecimal` falls inside an inclusive range. + * + * **Example** (Checking decimal ranges) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const between = BigDecimal.between({ + * minimum: BigDecimal.fromStringUnsafe("1"), + * maximum: BigDecimal.fromStringUnsafe("5") + * }) + * + * 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 + * + * @category predicates + * @since 2.0.0 + */ +export const between: { + (options: { + minimum: BigDecimal + maximum: BigDecimal + }): (self: BigDecimal) => boolean + (self: BigDecimal, options: { + minimum: BigDecimal + maximum: BigDecimal + }): boolean +} = order.isBetween(Order) + +/** + * Restricts the given `BigDecimal` to be within the range specified by the `minimum` and `maximum` values. + * + * **When to use** + * + * Use to force a `BigDecimal` into an inclusive range. + * + * **Details** + * + * If the `BigDecimal` is less than the `minimum` value, the function returns + * the `minimum` value. If it is greater than the `maximum` value, the function + * returns the `maximum` value. Otherwise, it returns the original `BigDecimal`. + * + * **Example** (Clamping decimals to a range) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const clamp = BigDecimal.clamp({ + * minimum: BigDecimal.fromStringUnsafe("1"), + * maximum: 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 + * + * @category math + * @since 2.0.0 + */ +export const clamp: { + (options: { + minimum: BigDecimal + maximum: BigDecimal + }): (self: BigDecimal) => BigDecimal + (self: BigDecimal, options: { + minimum: BigDecimal + maximum: BigDecimal + }): BigDecimal +} = order.clamp(Order) + +/** + * Returns the minimum between two `BigDecimal`s. + * + * **When to use** + * + * Use to select the smaller of two `BigDecimal` values. + * + * **Example** (Selecting the smaller decimal) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const result = BigDecimal.min( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(2n) + * ``` + * + * @see {@link max} for selecting the larger value + * + * @category math + * @since 2.0.0 + */ +export const min: { + (that: BigDecimal): (self: BigDecimal) => BigDecimal + (self: BigDecimal, that: BigDecimal): BigDecimal +} = order.min(Order) + +/** + * Returns the maximum between two `BigDecimal`s. + * + * **When to use** + * + * Use to select the larger of two `BigDecimal` values. + * + * **Example** (Selecting the larger decimal) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const result = BigDecimal.max( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(3n) + * ``` + * + * @see {@link min} for selecting the smaller value + * + * @category math + * @since 2.0.0 + */ +export const max: { + (that: BigDecimal): (self: BigDecimal) => BigDecimal + (self: BigDecimal, that: BigDecimal): BigDecimal +} = order.max(Order) + +/** + * Determines the sign of a given `BigDecimal`. + * + * **When to use** + * + * Use to classify a `BigDecimal` as negative, zero, or positive. + * + * **Example** (Reading decimal signs) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.sign(BigDecimal.fromStringUnsafe("-5")) // => -1 + * BigDecimal.sign(BigDecimal.fromStringUnsafe("0")) // => 0 + * BigDecimal.sign(BigDecimal.fromStringUnsafe("5")) // => 1 + * ``` + * + * @category math + * @since 2.0.0 + */ +export const sign = (n: BigDecimal): Ordering => n.value === bigint0 ? 0 : n.value < bigint0 ? -1 : 1 + +/** + * Determines the absolute value of a given `BigDecimal`. + * + * **When to use** + * + * Use to remove the sign from a `BigDecimal` while preserving its magnitude. + * + * **Example** (Calculating absolute values) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @since 2.0.0 + */ +export const abs = (n: BigDecimal): BigDecimal => n.value < bigint0 ? make(-n.value, n.scale) : n + +/** + * Provides a negate operation on `BigDecimal`s. + * + * **When to use** + * + * Use to flip the sign of a `BigDecimal`. + * + * **Example** (Negating decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.negate(BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(-3n) + * BigDecimal.negate(BigDecimal.fromStringUnsafe("-6")) // => BigDecimal.fromBigInt(6n) + * ``` + * + * @category math + * @since 2.0.0 + */ +export const negate = (n: BigDecimal): BigDecimal => make(-n.value, n.scale) + +/** + * Computes the decimal remainder safely when one operand is divided by a second + * operand. + * + * **When to use** + * + * Use to compute a decimal remainder while representing division by zero as + * `Option.none`. + * + * **Details** + * + * If the divisor is `0`, the result will be `Option.none()`. + * + * **Example** (Computing remainders safely) + * + * ```ts import.meta.vitest + * import { BigDecimal, Option } from "effect" + * + * 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 + * @see {@link divide} for decimal quotient calculation + * + * @category math + * @since 2.0.0 + */ +export const remainder: { + (divisor: BigDecimal): (self: BigDecimal) => Option.Option + (self: BigDecimal, divisor: BigDecimal): Option.Option +} = dual(2, (self: BigDecimal, divisor: BigDecimal): Option.Option => { + if (divisor.value === bigint0) { + return Option.none() + } + + const max = Math.max(self.scale, divisor.scale) + return Option.some(make(scale(self, max).value % scale(divisor, max).value, max)) +}) + +/** + * Returns the decimal remainder left over when one operand is divided by a + * non-zero second operand. + * + * **When to use** + * + * Use when you need to compute a `BigDecimal` remainder with a divisor known to + * be non-zero and want a plain `BigDecimal` result instead of an `Option`. + * + * **Gotchas** + * + * Throws a `RangeError` if the divisor is `0`. + * + * **Example** (Computing remainders unsafely) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.remainderUnsafe( + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("2") + * ) // => BigDecimal.fromBigInt(1n) + * ``` + * + * @see {@link remainder} for returning `Option.none` when the divisor is zero + * + * @category math + * @since 4.0.0 + */ +export const remainderUnsafe: { + (divisor: BigDecimal): (self: BigDecimal) => BigDecimal + (self: BigDecimal, divisor: BigDecimal): BigDecimal +} = dual(2, (self: BigDecimal, divisor: BigDecimal): BigDecimal => { + if (divisor.value === bigint0) { + throw new RangeError("Division by zero") + } + + const max = Math.max(self.scale, divisor.scale) + return make(scale(self, max).value % scale(divisor, max).value, max) +}) + +/** + * Provides an `Equivalence` instance for `BigDecimal` that determines equality between BigDecimal values. + * + * **When to use** + * + * Use when comparing decimal values through APIs that accept an equivalence + * relation. + * + * **Example** (Checking decimal equivalence) + * + * ```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") + * + * 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 +}) + +/** + * Checks whether two `BigDecimal`s are equal. + * + * **When to use** + * + * Use to compare two `BigDecimal` values for numeric equality. + * + * **Example** (Checking decimal equality) + * + * ```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") + * + * BigDecimal.equals(a, b) // => true + * BigDecimal.equals(a, c) // => false + * ``` + * + * @see {@link Equivalence} for passing decimal equality to APIs that require an `Equivalence` + * + * @category predicates + * @since 2.0.0 + */ +export const equals: { + (that: BigDecimal): (self: BigDecimal) => boolean + (self: BigDecimal, that: BigDecimal): boolean +} = dual(2, (self: BigDecimal, that: BigDecimal): boolean => Equivalence(self, that)) + +/** + * Creates a `BigDecimal` from a `bigint` value. + * + * **When to use** + * + * Use to construct an integer `BigDecimal` from a `bigint`. + * + * **Example** (Creating decimals from bigint) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * const decimal = BigDecimal.fromBigInt(123n) + * decimal // => BigDecimal.fromStringUnsafe("123") + * + * const largeBigInt = BigDecimal.fromBigInt(9007199254740991n) + * largeBigInt // => BigDecimal.fromStringUnsafe("9007199254740991") + * ``` + * + * @see {@link make} for constructing a decimal with an explicit scale + * + * @category constructors + * @since 2.0.0 + */ +export const fromBigInt = (n: bigint): BigDecimal => make(n, 0) + +/** + * Creates a `BigDecimal` from a finite `number`. + * + * **When to use** + * + * Use when you need to convert a trusted finite JavaScript number to a + * `BigDecimal` and want a plain result instead of an `Option`. + * + * **Gotchas** + * + * It is not recommended to convert a floating point number to a decimal + * directly, as the floating point representation may be unexpected. Throws a + * `RangeError` if the number is not finite (`NaN`, `+Infinity` or `-Infinity`). + * + * **Example** (Creating decimals from finite numbers) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * + * @category constructors + * @since 4.0.0 + */ +export const fromNumberUnsafe = (n: number): BigDecimal => { + return Option.getOrThrowWith(fromNumber(n), () => new RangeError(`Number must be finite, got ${n}`)) +} + +/** + * Creates a `BigDecimal` safely from a finite `number`. + * + * **When to use** + * + * Use to convert a finite JavaScript number to a `BigDecimal` without throwing + * on invalid input. + * + * **Details** + * + * Returns `Option.none()` for `NaN`, `+Infinity` or `-Infinity`. + * + * **Gotchas** + * + * It is not recommended to convert a floating point number to a decimal + * directly, as the floating point representation may be unexpected. + * + * **Example** (Creating decimals from numbers safely) + * + * ```ts import.meta.vitest + * import { BigDecimal, Option } from "effect" + * + * 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 + * @see {@link fromString} for parsing decimal strings directly + * + * @category constructors + * @since 2.0.0 + */ +export const fromNumber = (n: number): Option.Option => { + if (!Number.isFinite(n)) { + return Option.none() + } + + const string = `${n}` + if (string.includes("e")) { + return fromString(string) + } + + const [lead, trail = ""] = string.split(".") + return Option.some(make(BigInt(`${lead}${trail}`), trail.length)) +} + +/** + * Parses a decimal string into a `BigDecimal` safely. + * + * **When to use** + * + * Use to parse external decimal text without throwing on invalid input. + * + * **Details** + * + * Returns `Option.some` for valid decimal or exponent notation and + * `Option.none` when the string cannot be parsed or would produce an unsafe + * scale. The empty string parses as zero. + * + * **Example** (Parsing decimal strings safely) + * + * ```ts import.meta.vitest + * import { BigDecimal, Option } from "effect" + * + * 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 + * @see {@link fromNumber} for converting finite JavaScript numbers + * + * @category constructors + * @since 2.0.0 + */ +export const fromString = (s: string): Option.Option => { + if (s === "") { + return Option.some(zero) + } + + let base: string + let exp: number + const seperator = s.search(/[eE]/) + if (seperator !== -1) { + const trail = s.slice(seperator + 1) + base = s.slice(0, seperator) + exp = Number(trail) + if (base === "" || !Number.isSafeInteger(exp) || !FINITE_INT_REGEXP.test(trail)) { + return Option.none() + } + } else { + base = s + exp = 0 + } + + let digits: string + let offset: number + const dot = base.search(/\./) + if (dot !== -1) { + const lead = base.slice(0, dot) + const trail = base.slice(dot + 1) + digits = `${lead}${trail}` + offset = trail.length + } else { + digits = base + offset = 0 + } + + if (!FINITE_INT_REGEXP.test(digits)) { + return Option.none() + } + + const scale = offset - exp + if (!Number.isSafeInteger(scale)) { + return Option.none() + } + + return Option.some(make(BigInt(digits), scale)) +} + +/** + * Parses a decimal string into a `BigDecimal`, throwing if the string is + * invalid. + * + * **When to use** + * + * Use when you expect decimal text to be valid and want parse errors to throw. + * + * **Details** + * + * Accepts the same syntax as `fromString`. Use `fromString` when invalid input + * should be represented as `Option.none` instead of throwing. + * + * **Example** (Parsing decimal strings unsafely) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.fromStringUnsafe("123") // => BigDecimal.fromBigInt(123n) + * BigDecimal.fromStringUnsafe("123.456") // => BigDecimal.make(123456n, 3) + * ``` + * + * @see {@link fromString} for returning `Option.none` on invalid input + * + * @category constructors + * @since 4.0.0 + */ +export const fromStringUnsafe = (s: string): BigDecimal => { + return Option.getOrThrowWith(fromString(s), () => new Error(`Invalid numerical string: ${s}`)) +} + +/** + * Formats a `BigDecimal` as a string. + * + * **When to use** + * + * Use to render a `BigDecimal` as plain decimal text when possible. + * + * **Details** + * + * The value is normalized before formatting. Scientific notation is used when + * the absolute value of the normalized scale is at least `16`; otherwise plain + * decimal notation is used. + * + * **Example** (Formatting decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * + * @category converting + * @since 2.0.0 + */ +export const format = (n: BigDecimal): string => { + const normalized = normalize(n) + if (Math.abs(normalized.scale) >= 16) { + return toExponential(normalized) + } + + const negative = normalized.value < bigint0 + const absolute = negative ? `${normalized.value}`.substring(1) : `${normalized.value}` + + let before: string + let after: string + + if (normalized.scale >= absolute.length) { + before = "0" + after = "0".repeat(normalized.scale - absolute.length) + absolute + } else { + const location = absolute.length - normalized.scale + if (location > absolute.length) { + const zeros = location - absolute.length + before = `${absolute}${"0".repeat(zeros)}` + after = "" + } else { + after = absolute.slice(location) + before = absolute.slice(0, location) + } + } + + const complete = after === "" ? before : `${before}.${after}` + return negative ? `-${complete}` : complete +} + +/** + * Formats a given `BigDecimal` as a `string` in scientific notation. + * + * **When to use** + * + * Use to render a `BigDecimal` in scientific notation. + * + * **Example** (Formatting decimals exponentially) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.toExponential(BigDecimal.make(123456n, -5)) // => "1.23456e+10" + * ``` + * + * @see {@link format} for plain decimal formatting when possible + * + * @category converting + * @since 3.11.0 + */ +export const toExponential = (n: BigDecimal): string => { + if (isZero(n)) { + return "0e+0" + } + + const normalized = normalize(n) + const digits = `${abs(normalized).value}` + const head = digits.slice(0, 1) + const tail = digits.slice(1) + + let output = `${isNegative(normalized) ? "-" : ""}${head}` + if (tail !== "") { + output += `.${tail}` + } + + const exp = tail.length - normalized.scale + return `${output}e${exp >= 0 ? "+" : ""}${exp}` +} + +/** + * Converts a `BigDecimal` to a JavaScript `number`. + * + * **When to use** + * + * Use when you need a JavaScript number at an interop boundary where precision + * loss is acceptable. + * + * **Gotchas** + * + * This conversion is unsafe because the result can lose integer or fractional + * precision, round to a nearby representable value, or become `Infinity` when + * the decimal cannot be represented as a finite JavaScript `number`. + * + * **Example** (Converting decimals to numbers) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.toNumberUnsafe(BigDecimal.fromStringUnsafe("123.456")) // => 123.456 + * ``` + * + * @see {@link format} for preserving decimal precision as text + * + * @category converting + * @since 4.0.0 + */ +export const toNumberUnsafe = (n: BigDecimal): number => Number(format(n)) + +/** + * Checks whether a given `BigDecimal` is an integer. + * + * **When to use** + * + * Use to test whether a `BigDecimal` has no fractional decimal part. + * + * **Example** (Checking integer decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("0")) // => true + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1")) // => true + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1.1")) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isInteger = (n: BigDecimal): boolean => normalize(n).scale <= 0 + +/** + * Checks whether a given `BigDecimal` is `0`. + * + * **When to use** + * + * Use to test whether a `BigDecimal` is exactly zero. + * + * **Example** (Checking zero decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.isZero(BigDecimal.fromStringUnsafe("0")) // => true + * BigDecimal.isZero(BigDecimal.fromStringUnsafe("1")) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isZero = (n: BigDecimal): boolean => n.value === bigint0 + +/** + * Checks whether a given `BigDecimal` is negative. + * + * **When to use** + * + * Use to test whether a `BigDecimal` is less than zero. + * + * **Example** (Checking negative decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("-1")) // => true + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("0")) // => false + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("1")) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isNegative = (n: BigDecimal): boolean => n.value < bigint0 + +/** + * Checks whether a given `BigDecimal` is positive. + * + * **When to use** + * + * Use to test whether a `BigDecimal` is greater than zero. + * + * **Example** (Checking positive decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("-1")) // => false + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("0")) // => false + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("1")) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isPositive = (n: BigDecimal): boolean => n.value > bigint0 + +const isBigDecimalArgs = (args: IArguments) => isBigDecimal(args[0]) + +/** + * Rounding modes for `BigDecimal`. + * + * **When to use** + * + * Use with `round` to choose how discarded digits affect a `BigDecimal` + * rounded to a target scale. + * + * **Details** + * + * - `ceil`: round towards positive infinity + * - `floor`: round towards negative infinity + * - `to-zero`: round towards zero + * - `from-zero`: round away from zero + * - `half-ceil`: round to the nearest neighbor; if equidistant round towards positive infinity + * - `half-floor`: round to the nearest neighbor; if equidistant round towards negative infinity + * - `half-to-zero`: round to the nearest neighbor; if equidistant round towards zero + * - `half-from-zero`: round to the nearest neighbor; if equidistant round away from zero + * - `half-even`: round to the nearest neighbor; if equidistant round to the neighbor with an even digit + * - `half-odd`: round to the nearest neighbor; if equidistant round to the neighbor with an odd digit + * + * @see {@link round} for configurable rounding with a `RoundingMode` + * @see {@link ceil} for fixed rounding toward positive infinity + * @see {@link floor} for fixed rounding toward negative infinity + * @see {@link truncate} for fixed rounding toward zero + * + * @category math + * @since 3.16.0 + */ +export type RoundingMode = + | "ceil" + | "floor" + | "to-zero" + | "from-zero" + | "half-ceil" + | "half-floor" + | "half-to-zero" + | "half-from-zero" + | "half-even" + | "half-odd" + +/** + * Computes a rounded `BigDecimal` at the given scale with the specified rounding mode. + * + * **When to use** + * + * Use to round a decimal at a requested scale with an explicit rounding mode. + * + * **Example** (Rounding decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @see {@link floor} for fixed rounding toward negative infinity + * @see {@link truncate} for fixed rounding toward zero + * + * @category math + * @since 3.16.0 + */ +export const round: { + (options: { scale?: number; mode?: RoundingMode }): (self: BigDecimal) => BigDecimal + (n: BigDecimal, options?: { scale?: number; mode?: RoundingMode }): BigDecimal +} = dual(isBigDecimalArgs, (self: BigDecimal, options?: { scale?: number; mode?: RoundingMode }): BigDecimal => { + const mode = options?.mode ?? "half-from-zero" + const scale = options?.scale ?? 0 + + switch (mode) { + case "ceil": + return ceil(self, scale) + + case "floor": + return floor(self, scale) + + case "to-zero": + return truncate(self, scale) + + case "from-zero": + return (isPositive(self) ? ceil(self, scale) : floor(self, scale)) + + case "half-ceil": + return floor(sum(self, make(bigint5, scale + 1)), scale) + + case "half-floor": + return ceil(sum(self, make(bigint_5, scale + 1)), scale) + + case "half-to-zero": + return isNegative(self) + ? floor(sum(self, make(bigint5, scale + 1)), scale) + : ceil(sum(self, make(bigint_5, scale + 1)), scale) + + case "half-from-zero": + return isNegative(self) + ? ceil(sum(self, make(bigint_5, scale + 1)), scale) + : floor(sum(self, make(bigint5, scale + 1)), scale) + } + + const halfCeil = floor(sum(self, make(bigint5, scale + 1)), scale) + const halfFloor = ceil(sum(self, make(bigint_5, scale + 1)), scale) + const digit = digitAt(halfCeil, scale) + + switch (mode) { + case "half-even": + return equals(halfCeil, halfFloor) ? halfCeil : (digit % bigint2 === bigint0) ? halfCeil : halfFloor + + case "half-odd": + return equals(halfCeil, halfFloor) ? halfCeil : (digit % bigint2 === bigint0) ? halfFloor : halfCeil + } +}) + +/** + * Computes a truncated `BigDecimal` at the given scale. This removes fractional digits beyond the scale, + * rounding toward zero. + * + * **When to use** + * + * Use when you need to discard fractional digits beyond a scale rather than + * round half up, half down, or toward an infinity. + * + * **Example** (Truncating decimals) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @see {@link ceil} for rounding toward positive infinity + * @see {@link floor} for rounding toward negative infinity + * + * @category math + * @since 3.16.0 + */ +export const truncate: { + (scale: number): (self: BigDecimal) => BigDecimal + (self: BigDecimal, scale?: number): BigDecimal +} = dual(isBigDecimalArgs, (self: BigDecimal, scale: number = 0): BigDecimal => { + if (self.scale <= scale) { + return self + } + + // BigInt division truncates towards zero + return make(self.value / (bigint10 ** BigInt(self.scale - scale)), scale) +}) + +/** + * Computes the ceiling of a `BigDecimal` at the given scale. + * + * **When to use** + * + * Use to round a decimal toward positive infinity at a requested scale. + * + * **Details** + * + * The default scale is `0`. Positive scales keep digits to the right of the + * decimal point, and negative scales round positions to the left of the decimal + * point. + * + * @see {@link floor} for rounding toward negative infinity + * @see {@link truncate} for rounding toward zero + * @see {@link round} for configurable rounding modes + * + * **Example** (Rounding decimals up) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * BigDecimal.ceil(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(150n) + * BigDecimal.ceil(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-14n) + * ``` + * + * @category math + * @since 3.16.0 + */ +export const ceil: { + (scale: number): (self: BigDecimal) => BigDecimal + (self: BigDecimal, scale?: number): BigDecimal +} = dual(isBigDecimalArgs, (self: BigDecimal, scale: number = 0): BigDecimal => { + const truncated = truncate(self, scale) + + if (isPositive(self) && isLessThan(truncated, self)) { + return sum(truncated, make(bigint1, scale)) + } + + return truncated +}) + +/** + * Internal function used by `round` for `half-even` and `half-odd` rounding modes. + * + * Returns the digit at the position of the given `scale` within the `BigDecimal`. + * + * @internal + */ +export const digitAt: { + (scale: number): (self: BigDecimal) => bigint + (self: BigDecimal, scale: number): bigint +} = dual(2, (self: BigDecimal, scale: number): bigint => { + if (self.scale < scale) { + return bigint0 + } + + const scaled = self.value / (bigint10 ** BigInt(self.scale - scale)) + return scaled % bigint10 +}) + +/** + * Computes the floor of a `BigDecimal` at the given scale. + * + * **When to use** + * + * Use to round a decimal toward negative infinity at a requested scale. + * + * **Example** (Rounding decimals down) + * + * ```ts import.meta.vitest + * import { BigDecimal } from "effect" + * + * 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 + * @see {@link truncate} for rounding toward zero + * @see {@link round} for configurable rounding modes + * + * @category math + * @since 3.16.0 + */ +export const floor: { + (scale: number): (self: BigDecimal) => BigDecimal + (self: BigDecimal, scale?: number): BigDecimal +} = dual(isBigDecimalArgs, (self: BigDecimal, scale: number = 0): BigDecimal => { + const truncated = truncate(self, scale) + + if (isNegative(self) && isGreaterThan(truncated, self)) { + return sum(truncated, make(bigint_1, scale)) + } + + return truncated +}) diff --git a/.repos/effect/packages/effect/src/BigInt.ts b/.repos/effect/packages/effect/src/BigInt.ts new file mode 100644 index 000000000..18ac6ba68 --- /dev/null +++ b/.repos/effect/packages/effect/src/BigInt.ts @@ -0,0 +1,1005 @@ +/** + * Works with JavaScript `bigint` values. + * + * This module exposes the native `BigInt` constructor together with helpers for + * checking, arithmetic, comparison, range checks, safe parsing and conversions + * that return `Option`, integer square roots, aggregation, ordering, + * equivalence, reducers, and combiners. + * + * @since 2.0.0 + */ + +import * as Combiner from "./Combiner.ts" +import * as Equ from "./Equivalence.ts" +import { dual } from "./Function.ts" +import * as Option from "./Option.ts" +import * as order from "./Order.ts" +import type { Ordering } from "./Ordering.ts" +import * as predicate from "./Predicate.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Exposes the global bigint constructor for JavaScript bigint coercion. + * + * **When to use** + * + * Use to access native JavaScript bigint constructor coercion from the Effect + * module namespace. + * + * **Gotchas** + * + * This follows native `BigInt` coercion rules. It throws for invalid strings or + * non-integral numbers, and whitespace-only strings coerce to `0n`. + * + * @see {@link fromString} for parsing strings into an `Option` + * @see {@link fromNumber} for converting safe integers into an `Option` + * + * **Example** (Constructing bigints) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.BigInt(123) // => 123n + * BigInt.BigInt("456") // => 456n + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const BigInt = globalThis.BigInt + +const bigint0 = BigInt(0) +const bigint1 = BigInt(1) +const bigint2 = BigInt(2) + +/** + * Checks whether a value is a `bigint`. + * + * **When to use** + * + * Use to validate unknown input and narrow it to `bigint`. + * + * **Example** (Checking for bigints) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.isBigInt(1n) // => true + * BigInt.isBigInt(1) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isBigInt: (u: unknown) => u is bigint = predicate.isBigInt + +/** + * Provides an addition operation on `bigint`s. + * + * **When to use** + * + * Use when you need a binary addition function for piping or higher-order APIs + * instead of the infix addition operator. + * + * **Example** (Adding bigints) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.sum(2n, 3n) // => 5n + * ``` + * + * @see {@link sumAll} for summing an iterable of `bigint` values + * + * @category math + * @since 2.0.0 + */ +export const sum: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = dual(2, (self: bigint, that: bigint): bigint => self + that) + +/** + * Provides a multiplication operation on `bigint`s. + * + * **When to use** + * + * Use to multiply two `bigint` values. + * + * **Example** (Multiplying bigints) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.multiply(2n, 3n) // => 6n + * ``` + * + * @see {@link multiplyAll} for multiplying an iterable of `bigint` values + * + * @category math + * @since 2.0.0 + */ +export const multiply: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = dual(2, (self: bigint, that: bigint): bigint => self * that) + +/** + * Provides a subtraction operation on `bigint`s. + * + * **When to use** + * + * Use to subtract one `bigint` value from another. + * + * **Example** (Subtracting bigints) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.subtract(2n, 3n) // => -1n + * ``` + * + * @category math + * @since 2.0.0 + */ +export const subtract: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = dual(2, (self: bigint, that: bigint): bigint => self - that) + +/** + * Divides one `bigint` by another safely. + * + * **When to use** + * + * Use to divide `bigint` values while representing division by zero as + * `Option.none`. + * + * **Details** + * + * Uses JavaScript `bigint` division, so non-exact quotients are truncated + * toward zero. Returns `Option.none()` when the divisor is `0n`. + * + * **Example** (Dividing bigints safely) + * + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" + * + * 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` + * @see {@link remainder} for the JavaScript remainder operation + * + * @category math + * @since 2.0.0 + */ +export const divide: { + (that: bigint): (self: bigint) => Option.Option + (self: bigint, that: bigint): Option.Option +} = dual( + 2, + (self: bigint, that: bigint): Option.Option => that === bigint0 ? Option.none() : Option.some(self / that) +) + +/** + * Divides one `bigint` by another, throwing if the divisor is zero. + * + * **When to use** + * + * Use to divide `bigint` values where the divisor is known to be non-zero and + * division by zero should be a thrown exception. + * + * **Details** + * + * Uses JavaScript `bigint` division, so non-exact quotients are truncated + * toward zero. + * + * **Gotchas** + * + * Throws a `RangeError` when the divisor is `0n`. + * + * **Example** (Dividing bigints unsafely) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.divideUnsafe(6n, 3n) // => 2n + * BigInt.divideUnsafe(6n, 4n) // => 1n + * ``` + * + * @see {@link divide} for division that returns `Option.none` when the divisor is `0n` + * + * @category math + * @since 4.0.0 + */ +export const divideUnsafe: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = dual(2, (self: bigint, that: bigint): bigint => self / that) + +/** + * Returns the result of adding `1n` to a `bigint`. + * + * **When to use** + * + * Use to increment a `bigint` counter by one. + * + * **Example** (Incrementing a bigint) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.increment(2n) // => 3n + * ``` + * + * @category math + * @since 2.0.0 + */ +export const increment = (n: bigint): bigint => n + bigint1 + +/** + * Returns the result of subtracting `1n` from a `bigint`. + * + * **When to use** + * + * Use to decrement a `bigint` counter by one. + * + * **Example** (Decrementing a bigint) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.decrement(3n) // => 2n + * ``` + * + * @category math + * @since 2.0.0 + */ +export const decrement = (n: bigint): bigint => n - bigint1 + +/** + * Provides an `Order` instance for `bigint` that allows comparing and sorting BigInt values. + * + * **When to use** + * + * Use when you need to sort or compare bigint values through APIs that accept + * an ordering instance. + * + * **Example** (Comparing bigints with Order) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * const a = 123n + * const b = 456n + * const c = 123n + * + * BigInt.Order(a, b) // => -1 + * BigInt.Order(b, a) // => 1 + * BigInt.Order(a, c) // => 0 + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Order: order.Order = order.BigInt + +/** + * Equivalence instance for bigints using strict equality (`===`). + * + * **When to use** + * + * Use when checking bigint equality through APIs that accept an equivalence + * relation. + * + * **Example** (Comparing bigints for equivalence) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.Equivalence(1n, 1n) // => true + * BigInt.Equivalence(1n, 2n) // => false + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Equivalence: Equ.Equivalence = Equ.BigInt + +/** + * Returns `true` if the first argument is less than the second, otherwise `false`. + * + * **When to use** + * + * Use to test whether one `bigint` is strictly less than another. + * + * **Example** (Checking less-than comparisons) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.isLessThan(2n, 3n) // => true + * BigInt.isLessThan(3n, 3n) // => false + * BigInt.isLessThan(4n, 3n) // => false + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThan: { + (that: bigint): (self: bigint) => boolean + (self: bigint, that: bigint): boolean +} = order.isLessThan(Order) + +/** + * Returns a function that checks if a given `bigint` is less than or equal to the provided one. + * + * **When to use** + * + * Use to test whether one `bigint` is less than or equal to another. + * + * **Example** (Checking less-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.isLessThanOrEqualTo(2n, 3n) // => true + * BigInt.isLessThanOrEqualTo(3n, 3n) // => true + * BigInt.isLessThanOrEqualTo(4n, 3n) // => false + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThanOrEqualTo: { + (that: bigint): (self: bigint) => boolean + (self: bigint, that: bigint): boolean +} = order.isLessThanOrEqualTo(Order) + +/** + * Returns `true` if the first argument is greater than the second, otherwise `false`. + * + * **When to use** + * + * Use to test whether one `bigint` is strictly greater than another. + * + * **Example** (Checking greater-than comparisons) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.isGreaterThan(2n, 3n) // => false + * BigInt.isGreaterThan(3n, 3n) // => false + * BigInt.isGreaterThan(4n, 3n) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThan: { + (that: bigint): (self: bigint) => boolean + (self: bigint, that: bigint): boolean +} = order.isGreaterThan(Order) + +/** + * Returns a function that checks if a given `bigint` is greater than or equal to the provided one. + * + * **When to use** + * + * Use to test whether one `bigint` is greater than or equal to another. + * + * **Example** (Checking greater-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.isGreaterThanOrEqualTo(2n, 3n) // => false + * BigInt.isGreaterThanOrEqualTo(3n, 3n) // => true + * BigInt.isGreaterThanOrEqualTo(4n, 3n) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo: { + (that: bigint): (self: bigint) => boolean + (self: bigint, that: bigint): boolean +} = order.isGreaterThanOrEqualTo(Order) + +/** + * Checks whether a `bigint` is between a `minimum` and `maximum` value (inclusive). + * + * **When to use** + * + * Use to test whether a `bigint` falls inside an inclusive range. + * + * **Example** (Checking whether a bigint is within bounds) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * const between = BigInt.between({ minimum: 0n, maximum: 5n }) + * + * between(3n) // => true + * between(-1n) // => false + * between(6n) // => false + * ``` + * + * @see {@link clamp} for forcing a `bigint` into an inclusive range + * + * @category predicates + * @since 2.0.0 + */ +export const between: { + (options: { + minimum: bigint + maximum: bigint + }): (self: bigint) => boolean + (self: bigint, options: { + minimum: bigint + maximum: bigint + }): boolean +} = order.isBetween(Order) + +/** + * Restricts the given `bigint` to be within the range specified by the `minimum` and `maximum` values. + * + * **When to use** + * + * Use to force a `bigint` into an inclusive range. + * + * **Details** + * + * If the `bigint` is less than the minimum, the function returns the minimum. + * If the `bigint` is greater than the maximum, the function returns the + * maximum. Otherwise, it returns the original `bigint`. + * + * **Example** (Clamping a bigint to bounds) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * const clamp = BigInt.clamp({ minimum: 1n, maximum: 5n }) + * + * clamp(3n) // => 3n + * clamp(0n) // => 1n + * clamp(6n) // => 5n + * ``` + * + * @see {@link between} for checking whether a `bigint` is already inside a range + * + * @category math + * @since 2.0.0 + */ +export const clamp: { + (options: { + minimum: bigint + maximum: bigint + }): (self: bigint) => bigint + (self: bigint, options: { + minimum: bigint + maximum: bigint + }): bigint +} = order.clamp(Order) + +/** + * Returns the minimum between two `bigint`s. + * + * **When to use** + * + * Use to select the smaller of two `bigint` values. + * + * **Example** (Finding the minimum bigint) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.min(2n, 3n) // => 2n + * ``` + * + * @see {@link max} for selecting the larger value + * + * @category math + * @since 2.0.0 + */ +export const min: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = order.min(Order) + +/** + * Returns the maximum between two `bigint`s. + * + * **When to use** + * + * Use to select the larger of two `bigint` values. + * + * **Example** (Finding the maximum bigint) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.max(2n, 3n) // => 3n + * ``` + * + * @see {@link min} for selecting the smaller value + * + * @category math + * @since 2.0.0 + */ +export const max: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = order.max(Order) + +/** + * Determines the sign of a given `bigint`. + * + * **When to use** + * + * Use to classify a `bigint` as negative, zero, or positive. + * + * **Example** (Determining bigint signs) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.sign(-5n) // => -1 + * BigInt.sign(0n) // => 0 + * BigInt.sign(5n) // => 1 + * ``` + * + * @category math + * @since 2.0.0 + */ +export const sign = (n: bigint): Ordering => order.BigInt(n, bigint0) + +/** + * Determines the absolute value of a given `bigint`. + * + * **When to use** + * + * Use to remove the sign from a `bigint` while preserving its magnitude. + * + * **Example** (Calculating absolute values) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.abs(-5n) // => 5n + * BigInt.abs(0n) // => 0n + * BigInt.abs(5n) // => 5n + * ``` + * + * @category math + * @since 2.0.0 + */ +export const abs = (n: bigint): bigint => (n < bigint0 ? -n : n) + +/** + * Determines the greatest common divisor of two `bigint`s. + * + * **When to use** + * + * Use to compute the greatest common divisor of two integer values. + * + * **Example** (Calculating greatest common divisors) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.gcd(2n, 3n) // => 1n + * BigInt.gcd(2n, 4n) // => 2n + * BigInt.gcd(16n, 24n) // => 8n + * ``` + * + * @see {@link lcm} for computing the least common multiple + * + * @category math + * @since 2.0.0 + */ +export const gcd: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = dual(2, (self: bigint, that: bigint): bigint => { + while (that !== bigint0) { + const t = that + that = self % that + self = t + } + return abs(self) +}) + +/** + * Determines the least common multiple of two `bigint`s. + * + * **When to use** + * + * Use to compute the least common multiple of two integer values. + * + * **Example** (Calculating least common multiples) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.lcm(2n, 3n) // => 6n + * BigInt.lcm(2n, 4n) // => 4n + * BigInt.lcm(16n, 24n) // => 48n + * ``` + * + * @see {@link gcd} for computing the greatest common divisor + * + * @category math + * @since 2.0.0 + */ +export const lcm: { + (that: bigint): (self: bigint) => bigint + (self: bigint, that: bigint): bigint +} = 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`. + * + * **When to use** + * + * Use when you need to compute an integer square root for a `bigint` that has + * already been validated as non-negative, and you want negative input to throw + * instead of returning `Option.none`. + * + * **Details** + * + * For non-perfect squares, returns the largest `bigint` whose square is less + * than or equal to the input. + * + * **Gotchas** + * + * Throws a `RangeError` if the input is negative. + * + * **Example** (Calculating square roots unsafely) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.sqrtUnsafe(4n) // => 2n + * BigInt.sqrtUnsafe(9n) // => 3n + * BigInt.sqrtUnsafe(16n) // => 4n + * ``` + * + * @see {@link sqrt} for returning `Option.none` when the input is negative + * + * @category math + * @since 4.0.0 + */ +export const sqrtUnsafe = (n: bigint): bigint => { + if (n < bigint0) { + throw new RangeError("Cannot take the square root of a negative number") + } + if (n < bigint2) { + return n + } + let x = n / bigint2 + while (x * x > n) { + x = ((n / x) + x) / bigint2 + } + return x +} + +/** + * Computes the integer square root of a `bigint` safely. + * + * **When to use** + * + * Use to compute an integer square root while representing negative input as + * `Option.none`. + * + * **Details** + * + * For non-perfect squares, returns the largest `bigint` whose square is less + * than or equal to the input. Returns `Option.none()` when the input is + * negative. + * + * **Example** (Calculating square roots safely) + * + * ```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() + * ``` + * + * @see {@link sqrtUnsafe} for square root computation that throws on negative input + * + * @category math + * @since 2.0.0 + */ +export const sqrt = (n: bigint): Option.Option => + isGreaterThanOrEqualTo(n, bigint0) ? Option.some(sqrtUnsafe(n)) : Option.none() + +/** + * Takes an `Iterable` of `bigint`s and returns their sum as a single `bigint`. Returns `0n` for an empty iterable. + * + * **When to use** + * + * Use when you want an immediate aggregate from an iterable instead of a + * folding reducer owned by another API. + * + * **Example** (Summing iterable bigints) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.sumAll([2n, 3n, 4n]) // => 9n + * ``` + * + * @see {@link sum} for adding two `bigint` values + * @see {@link ReducerSum} for summing through APIs that consume a `Reducer` + * + * @category math + * @since 2.0.0 + */ +export const sumAll = (collection: Iterable): bigint => { + let out = bigint0 + for (const n of collection) { + out += n + } + return out +} + +/** + * Takes an `Iterable` of `bigint`s and returns their product as a single `bigint`. Returns `1n` for an empty iterable. + * + * **When to use** + * + * Use to multiply all `bigint` values in an iterable. + * + * **Example** (Multiplying iterable bigints) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.multiplyAll([2n, 3n, 4n]) // => 24n + * ``` + * + * @see {@link multiply} for multiplying two `bigint` values + * @see {@link ReducerMultiply} for multiplying through APIs that consume a `Reducer` + * + * @category math + * @since 2.0.0 + */ +export const multiplyAll = (collection: Iterable): bigint => { + let out = bigint1 + for (const n of collection) { + if (n === bigint0) { + return bigint0 + } + out *= n + } + return out +} + +/** + * Converts a `bigint` to a `number` safely. + * + * **When to use** + * + * Use to convert a `bigint` to a JavaScript number only when it is a safe + * integer. + * + * **Details** + * + * If the `bigint` is outside the safe integer range for JavaScript (`Number.MAX_SAFE_INTEGER` + * and `Number.MIN_SAFE_INTEGER`), it returns `Option.none()`. + * + * **Example** (Converting bigints to numbers) + * + * ```ts import.meta.vitest + * import { BigInt as BI, Option } from "effect" + * + * 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` + * + * @category converting + * @since 2.0.0 + */ +export const toNumber = (b: bigint): Option.Option => { + if (b > BigInt(Number.MAX_SAFE_INTEGER) || b < BigInt(Number.MIN_SAFE_INTEGER)) { + return Option.none() + } + return Option.some(Number(b)) +} + +/** + * Parses a string into a `bigint` safely. + * + * **When to use** + * + * Use to parse a string as a `bigint` without throwing on invalid input. + * + * **Details** + * + * If the string is empty or contains characters that cannot be converted into a + * `bigint`, it returns `Option.none()`. + * + * **Example** (Parsing strings as bigints) + * + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" + * + * 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 + * + * @category converting + * @since 2.4.12 + */ +export const fromString = (s: string): Option.Option => { + try { + return s.trim() === "" + ? Option.none() + : Option.some(BigInt(s)) + } catch { + return Option.none() + } +} + +/** + * Converts a number to a `bigint`. + * + * **When to use** + * + * Use to convert a JavaScript number to `bigint` only when it is a safe integer. + * + * **Details** + * + * If the number is outside the safe integer range for JavaScript + * (`Number.MAX_SAFE_INTEGER` and `Number.MIN_SAFE_INTEGER`) or if the number is + * not a valid `bigint`, it returns `Option.none()`. + * + * **Example** (Converting numbers to bigints) + * + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" + * + * 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 + * @see {@link BigInt} for native constructor coercion + * + * @category converting + * @since 2.4.12 + */ +export function fromNumber(n: number): Option.Option { + if (n > Number.MAX_SAFE_INTEGER || n < Number.MIN_SAFE_INTEGER) { + return Option.none() + } + + try { + return Option.some(BigInt(n)) + } catch { + return Option.none() + } +} + +/** + * Returns the JavaScript remainder of dividing one `bigint` by another. + * + * **When to use** + * + * Use when you want native remainder semantics, including signed remainders and + * a thrown division-by-zero error. + * + * **Gotchas** + * + * Throws a `RangeError` when the divisor is `0n`. + * + * **Example** (Calculating remainders) + * + * ```ts import.meta.vitest + * import { BigInt } from "effect" + * + * BigInt.remainder(10n, 3n) // => 1n + * BigInt.remainder(15n, 4n) // => 3n + * ``` + * + * @see {@link divide} for quotient calculation with division-by-zero represented as `Option.none` + * + * @category math + * @since 4.0.0 + */ +export const remainder: { + (divisor: bigint): (self: bigint) => bigint + (self: bigint, divisor: bigint): bigint +} = dual(2, (self: bigint, divisor: bigint): bigint => self % divisor) + +/** + * Reducer for combining `bigint`s using addition. + * + * **When to use** + * + * Use to sum many `bigint` values through APIs that consume a `Reducer`. + * + * **Details** + * + * The initial value is `0n`, so `combineAll([])` returns `0n`. + * + * @see {@link sumAll} for summing an iterable directly + * @see {@link ReducerMultiply} for multiplying `bigint` values + * + * @category math + * @since 4.0.0 + */ +export const ReducerSum: Reducer.Reducer = Reducer.make((a, b) => a + b, bigint0) + +/** + * Reducer for combining `bigint`s using multiplication. + * + * **When to use** + * + * Use to multiply many `bigint` values through APIs that consume a `Reducer`. + * + * **Details** + * + * The initial value is `1n`, so `combineAll([])` returns `1n`. + * + * @see {@link multiplyAll} for multiplying an iterable directly + * @see {@link ReducerSum} for summing `bigint` values + * + * @category math + * @since 4.0.0 + */ +export const ReducerMultiply: Reducer.Reducer = Reducer.make((a, b) => a * b, bigint1, (collection) => { + let acc = bigint1 + for (const n of collection) { + if (n === bigint0) return bigint0 + acc *= n + } + return acc +}) + +/** + * Combiner that returns the maximum `bigint`. + * + * **When to use** + * + * Use to keep the largest `bigint` when an API consumes a `Combiner`. + * + * @see {@link CombinerMin} for keeping the smallest `bigint` + * @see {@link max} for comparing two `bigint` values directly + * + * @category math + * @since 4.0.0 + */ +export const CombinerMax: Combiner.Combiner = Combiner.max(Order) + +/** + * Combiner that returns the minimum `bigint`. + * + * **When to use** + * + * Use to keep the smallest `bigint` through APIs that consume a `Combiner`. + * + * @see {@link CombinerMax} for keeping the largest `bigint` + * @see {@link min} for comparing two `bigint` values directly + * + * @category math + * @since 4.0.0 + */ +export const CombinerMin: Combiner.Combiner = Combiner.min(Order) diff --git a/.repos/effect/packages/effect/src/Boolean.ts b/.repos/effect/packages/effect/src/Boolean.ts new file mode 100644 index 000000000..405e7b35a --- /dev/null +++ b/.repos/effect/packages/effect/src/Boolean.ts @@ -0,0 +1,461 @@ +/** + * Works with TypeScript `boolean` values. + * + * This module exposes the native `Boolean` constructor together with helpers + * for checking values, choosing between lazy branches, combining booleans with + * logical operations, checking collections with `every` or `some`, ordering + * booleans, and reducing boolean values. + * + * @since 2.0.0 + */ +import * as Equ from "./Equivalence.ts" +import type { LazyArg } from "./Function.ts" +import { dual } from "./Function.ts" +import * as order from "./Order.ts" +import * as predicate from "./Predicate.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Exposes the global boolean constructor for JavaScript truthiness + * coercion. + * + * **When to use** + * + * Use to access native JavaScript truthiness coercion from the Effect module + * namespace. + * + * **Gotchas** + * + * This follows native truthiness rules. For example, non-empty strings such as + * `"false"` coerce to `true`. + * + * **Example** (Coercing values to booleans) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.Boolean(1) // => true + * Boolean.Boolean("false") // => true + * Boolean.Boolean(0) // => false + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const Boolean = globalThis.Boolean + +/** + * Checks whether a value is a `boolean`. + * + * **When to use** + * + * Use to validate unknown input and narrow it to `boolean`. + * + * **Example** (Checking for booleans) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.isBoolean(true) // => true + * Boolean.isBoolean("true") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isBoolean: (input: unknown) => input is boolean = predicate.isBoolean + +/** + * Chooses between two lazy branches based on a boolean value. + * + * **When to use** + * + * Use to choose between two lazy branches based on a boolean value. + * + * **Example** (Pattern matching on booleans) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.match(true, { + * onFalse: () => "It's false!", + * onTrue: () => "It's true!" + * }) // => "It's true!" + * ``` + * + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + (options: { + readonly onFalse: LazyArg + readonly onTrue: LazyArg + }): (value: boolean) => A | B + (value: boolean, options: { + readonly onFalse: LazyArg + readonly onTrue: LazyArg + }): A | B +} = dual(2, (value: boolean, options: { + readonly onFalse: LazyArg + readonly onTrue: LazyArg +}): A | B => value ? options.onTrue() : options.onFalse()) + +/** + * Provides an `Order` instance for `boolean` that allows comparing and sorting boolean values. + * In this ordering, `false` is considered less than `true`. + * + * **When to use** + * + * Use when you need to sort or compare boolean values through APIs that accept + * an ordering instance where `false` comes before `true`. + * + * **Example** (Comparing booleans) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.Order(false, true) // => -1 + * Boolean.Order(true, false) // => 1 + * Boolean.Order(true, true) // => 0 + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Order: order.Order = order.Boolean + +/** + * Equivalence instance for booleans using strict equality (`===`). + * + * **When to use** + * + * Use when checking boolean equality through APIs that accept an equivalence + * relation. + * + * **Example** (Comparing booleans for equivalence) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.Equivalence(true, true) // => true + * Boolean.Equivalence(true, false) // => false + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Equivalence: Equ.Equivalence = Equ.Boolean + +/** + * Negates the given boolean: `!self` + * + * **When to use** + * + * Use to invert a boolean value. + * + * **Example** (Negating booleans) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.not(true) // => false + * Boolean.not(false) // => true + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const not = (self: boolean): boolean => !self + +/** + * Combines two booleans using logical AND: `self && that`. + * + * **When to use** + * + * Use to require both boolean operands to be `true`. + * + * **Details** + * + * Supports both data-first and data-last forms. + * + * **Example** (Combining booleans with AND) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.and(true, true) // => true + * Boolean.and(true, false) // => false + * Boolean.and(false, true) // => false + * Boolean.and(false, false) // => false + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const and: { + (that: boolean): (self: boolean) => boolean + (self: boolean, that: boolean): boolean +} = dual(2, (self: boolean, that: boolean): boolean => self && that) + +/** + * Combines two booleans using NAND: `!(self && that)`. + * + * **When to use** + * + * Use to negate a logical AND result. + * + * **Example** (Combining booleans with NAND) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.nand(true, true) // => false + * Boolean.nand(true, false) // => true + * Boolean.nand(false, true) // => true + * Boolean.nand(false, false) // => true + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const nand: { + (that: boolean): (self: boolean) => boolean + (self: boolean, that: boolean): boolean +} = dual(2, (self: boolean, that: boolean): boolean => !(self && that)) + +/** + * Combines two booleans using OR: `self || that`. + * + * **When to use** + * + * Use to accept when either boolean operand is `true`. + * + * **Example** (Combining booleans with OR) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.or(true, true) // => true + * Boolean.or(true, false) // => true + * Boolean.or(false, true) // => true + * Boolean.or(false, false) // => false + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const or: { + (that: boolean): (self: boolean) => boolean + (self: boolean, that: boolean): boolean +} = dual(2, (self: boolean, that: boolean): boolean => self || that) + +/** + * Combines two booleans using NOR: `!(self || that)`. + * + * **When to use** + * + * Use to accept only when both boolean operands are `false`. + * + * **Example** (Combining booleans with NOR) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.nor(true, true) // => false + * Boolean.nor(true, false) // => false + * Boolean.nor(false, true) // => false + * Boolean.nor(false, false) // => true + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const nor: { + (that: boolean): (self: boolean) => boolean + (self: boolean, that: boolean): boolean +} = dual(2, (self: boolean, that: boolean): boolean => !(self || that)) + +/** + * Combines two booleans using XOR: `(!self && that) || (self && !that)`. + * + * **When to use** + * + * Use to accept when exactly one boolean operand is `true`. + * + * **Example** (Combining booleans with XOR) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.xor(true, true) // => false + * Boolean.xor(true, false) // => true + * Boolean.xor(false, true) // => true + * Boolean.xor(false, false) // => false + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const xor: { + (that: boolean): (self: boolean) => boolean + (self: boolean, that: boolean): boolean +} = dual(2, (self: boolean, that: boolean): boolean => (!self && that) || (self && !that)) + +/** + * Combines two booleans using EQV (aka XNOR): `!xor(self, that)`. + * + * **When to use** + * + * Use to accept when both boolean operands have the same truth value. + * + * **Example** (Checking boolean equivalence) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.eqv(true, true) // => true + * Boolean.eqv(true, false) // => false + * Boolean.eqv(false, true) // => false + * Boolean.eqv(false, false) // => true + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const eqv: { + (that: boolean): (self: boolean) => boolean + (self: boolean, that: boolean): boolean +} = dual(2, (self: boolean, that: boolean): boolean => !xor(self, that)) + +/** + * Combines two booleans using an implication: `(!self || that)`. + * + * **When to use** + * + * Use to model logical implication between a condition and a consequence. + * + * **Example** (Checking boolean implication) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.implies(true, true) // => true + * Boolean.implies(true, false) // => false + * Boolean.implies(false, true) // => true + * Boolean.implies(false, false) // => true + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const implies: { + (that: boolean): (self: boolean) => boolean + (self: boolean, that: boolean): boolean +} = dual(2, (self, that) => self ? that : true) + +/** + * Checks whether every boolean in a collection is `true`. + * + * **When to use** + * + * Use to check that every boolean in an iterable is `true`. + * + * **Example** (Checking every boolean) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.every([true, true, true]) // => true + * Boolean.every([true, false, true]) // => false + * ``` + * + * @see {@link some} for checking whether at least one value is `true` + * @see {@link ReducerAnd} for reducing booleans with AND through a `Reducer` + * + * @category predicates + * @since 2.0.0 + */ +export const every = (collection: Iterable): boolean => { + for (const b of collection) { + if (!b) { + return false + } + } + return true +} + +/** + * Checks whether at least one boolean in a collection is `true`. + * + * **When to use** + * + * Use to check that at least one boolean in an iterable is `true`. + * + * **Example** (Checking some booleans) + * + * ```ts import.meta.vitest + * import { Boolean } from "effect" + * + * Boolean.some([true, false, true]) // => true + * Boolean.some([false, false, false]) // => false + * ``` + * + * @see {@link every} for checking whether all values are `true` + * @see {@link ReducerOr} for reducing booleans with OR through a `Reducer` + * + * @category predicates + * @since 2.0.0 + */ +export const some = (collection: Iterable): boolean => { + for (const b of collection) { + if (b) { + return true + } + } + return false +} + +/** + * Reducer for combining `boolean`s using AND. + * + * **When to use** + * + * Use to require every accumulated boolean to be `true` through APIs that + * consume a `Reducer`. + * + * **Details** + * + * The `initialValue` is `true`, so `combineAll([])` returns `true`. + * + * **Gotchas** + * + * `combineAll` uses the default left-to-right `Reducer.make` fold and does not + * short-circuit on `false`. + * + * @see {@link ReducerOr} for reducing with OR semantics + * @see {@link every} for checking an iterable directly + * + * @category math + * @since 4.0.0 + */ +export const ReducerAnd: Reducer.Reducer = Reducer.make((a, b) => a && b, true) + +/** + * Reducer for combining `boolean`s using OR. + * + * **When to use** + * + * Use to reduce boolean values where the result should be `true` if any + * combined value is `true`. + * + * **Details** + * + * The `initialValue` is `false`. + * + * @see {@link ReducerAnd} for reducing with AND semantics + * @see {@link some} for checking an iterable directly + * + * @category math + * @since 4.0.0 + */ +export const ReducerOr: Reducer.Reducer = Reducer.make((a, b) => a || b, false) diff --git a/.repos/effect/packages/effect/src/Brand.ts b/.repos/effect/packages/effect/src/Brand.ts new file mode 100644 index 000000000..1cbba59fd --- /dev/null +++ b/.repos/effect/packages/effect/src/Brand.ts @@ -0,0 +1,318 @@ +/** + * The `Brand` module adds compile-time names to ordinary TypeScript values so + * structurally identical values cannot be mixed accidentally. A branded value + * has the same runtime representation as its unbranded value; the extra + * information lives in the type system unless you choose a validating + * constructor. + * + * @since 2.0.0 + */ +import * as Arr from "./Array.ts" +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 * as SchemaIssue from "./SchemaIssue.ts" +import type * as Types from "./Types.ts" + +const TypeId = "~effect/Brand" + +/** + * A generic interface that defines a branded type. + * + * **When to use** + * + * Use to define a branded type such as `number & Brand<"Positive">` when + * TypeScript should keep structurally identical values separate without + * changing their runtime value. + * + * @see {@link Branded} for applying a brand key to a base type + * @see {@link Constructor} for validating or constructing branded values + * + * @category models + * @since 2.0.0 + */ +export interface Brand { + readonly [TypeId]: { + readonly [K in Keys]: Keys + } +} + +/** + * A constructor for a branded type that provides validation and safe + * construction methods. + * + * **When to use** + * + * Use as the shared callable interface for branded values when an API accepts + * or returns a brand constructor and callers need throwing, `Option`, `Result`, + * or type-guard validation forms. + * + * @see {@link nominal} for a constructor without runtime validation + * @see {@link make} for creating a constructor from a validation predicate + * @see {@link check} for creating a constructor from schema checks + * @see {@link all} for combining brand constructors + * + * @category models + * @since 2.0.0 + */ +export interface Constructor> { + /** + * Constructs a branded type from a value of type `Unbranded`, throwing an + * error if the provided value is not valid. + */ + (unbranded: Brand.Unbranded): B + /** + * Constructs a branded type from a value of type `Unbranded`, returning + * `Some` if the provided value is valid, `None` otherwise. + */ + option(unbranded: Brand.Unbranded): Option.Option + /** + * Constructs a branded type from a value of type `Unbranded`, returning + * `Success` if the provided value is valid, `Failure` + * otherwise. + */ + result(unbranded: Brand.Unbranded): Result.Result + /** + * Attempts to refine the provided value of type `Unbranded`, returning + * `true` if the provided value is a valid branded type, `false` otherwise. + */ + is(unbranded: Brand.Unbranded): unbranded is Brand.Unbranded & B + + /** + * The checks that are applied to the branded type. + * + * @internal + */ + checks?: readonly [SchemaAST.Check>, ...Array>>] | undefined +} + +/** + * Error returned when a branded type is constructed from an invalid value. + * + * **Details** + * + * The error wraps a `SchemaIssue.Issue`, renders `message` with the default + * schema issue formatter, and formats as `BrandError()`. + * + * **Gotchas** + * + * `BrandError` is an error-like model with `_tag`, `name`, `message`, and + * `toString`; it does not extend JavaScript `Error`. + * + * @category errors + * @since 4.0.0 + */ +export class BrandError { + constructor(issue: SchemaIssue.Issue) { + this.issue = issue + } + /** + * Discriminant used to identify brand construction failures. + * + * @since 4.0.0 + */ + readonly _tag = "BrandError" + /** + * Error name used by tools that inspect JavaScript error-like objects. + * + * @since 4.0.0 + */ + readonly name: string = "BrandError" + /** + * Schema issue describing why brand validation failed. + * + * @since 4.0.0 + */ + readonly issue: SchemaIssue.Issue + /** + * Human-readable rendering of the validation issue. + * + * @since 4.0.0 + */ + get message() { + return SchemaIssue.defaultFormatter(this.issue) + } + /** + * Formats the brand error together with its validation message. + * + * @since 4.0.0 + */ + toString() { + return `BrandError(${this.message})` + } +} + +/** + * Namespace containing type-level helpers for working with branded types and + * brand constructors. + * + * @since 2.0.0 + */ +export declare namespace Brand { + /** + * A utility type to extract a branded type from a `Constructor`. + * + * @category utility types + * @since 2.0.0 + */ + export type FromConstructor = C extends Constructor ? B : never + + /** + * A utility type to extract the unbranded value type from a brand. + * + * @category utility types + * @since 2.0.0 + */ + export type Unbranded> = B extends infer U & Brands ? U : B + + /** + * A utility type to extract the keys of a branded type. + * + * @category utility types + * @since 4.0.0 + */ + export type Keys> = keyof B[typeof TypeId] + + /** + * A utility type to extract the brands from a branded type. + * + * @category utility types + * @since 2.0.0 + */ + export type Brands> = Types.UnionToIntersection< + { [K in Keys]: K extends string ? Brand : never }[Keys] + > + + /** + * A utility type that checks that all brands have the same base type. + * + * @category utility types + * @since 2.0.0 + */ + export type EnsureCommonBase< + Brands extends readonly [Constructor, ...Array>] + > = { + [B in keyof Brands]: Brand.Unbranded> extends + Brand.Unbranded> + ? Brand.Unbranded> extends Brand.Unbranded> + ? Brands[B] + : Brands[B] + : "ERROR: All brands should have the same base type" + } +} + +/** + * A type alias for creating branded types more concisely. + * + * @category utility types + * @since 2.0.0 + */ +export type Branded = A & Brand + +/** + * Returns a `Constructor` that **does not apply any runtime checks** and just + * returns the provided value. + * + * **When to use** + * + * Use to create nominal types that allow distinguishing between two values + * of the same type but with different meanings. + * + * @see {@link make} for constructing branded values with validation. + * @see {@link check} for constructing branded values from schema checks. + * + * @category constructors + * @since 2.0.0 + */ +export function nominal>(): Constructor { + return Object.assign((input: Brand.Unbranded) => input as A, { + option: (input: Brand.Unbranded) => Option.some(input as A), + result: (input: Brand.Unbranded) => Result.succeed(input as A), + is: (_: Brand.Unbranded): _ is Brand.Unbranded & A => true + }) +} + +/** + * Returns a `Constructor` that can construct a branded type from an unbranded + * value using the provided `filter` predicate as validation of the input data. + * + * **When to use** + * + * Use when you want validation while constructing the branded type. + * + * @see {@link nominal} for a brand constructor that performs no validation. + * + * @category constructors + * @since 4.0.0 + */ +export function make>( + filter: (unbranded: Brand.Unbranded) => Schema.FilterOutput +): Constructor { + return check(SchemaAST.makeFilter(filter)) +} + +/** + * Creates a branded type `Constructor` from one or more schema checks. + * + * **When to use** + * + * Use when you need a branded type constructor that performs runtime validation + * via schema checks. + * + * **Details** + * + * Calling the returned constructor validates the unbranded value and throws on + * failure. Use the returned `option`, `result`, or `is` methods for + * non-throwing validation. + * + * @see {@link nominal} for a brand constructor without runtime validation + * @see {@link all} for combining multiple brand constructors + * @category constructors + * @since 4.0.0 + */ +export function check>( + ...checks: readonly [ + SchemaAST.Check>, + ...Array>> + ] +): Constructor { + const result = (input: Brand.Unbranded): Result.Result => { + return Result.mapError(SchemaAST.runChecks(checks, input), (issue) => new BrandError(issue)) as any + } + return Object.assign((input: Brand.Unbranded) => Result.getOrThrow(result(input)), { + option: (input: Brand.Unbranded) => Option.getSuccess(result(input)), + result, + is: (input: Brand.Unbranded): input is Brand.Unbranded & A => Result.isSuccess(result(input)), + checks + }) +} + +/** + * Combines one or more brand constructors to form a single branded type. + * + * **When to use** + * + * Use to require an input to satisfy every runtime check collected by the + * provided brand constructors. + * + * **Details** + * + * If the provided constructors contain runtime checks, the combined + * constructor succeeds only when all checks pass. If no runtime checks are + * present, it behaves as a nominal constructor. + * + * @category combining + * @since 2.0.0 + */ +export function all, ...Array>]>( + ...brands: Brand.EnsureCommonBase +): Constructor< + Types.UnionToIntersection<{ [B in keyof Brands]: Brand.FromConstructor }[number]> extends + infer X extends Brand ? X : Brand +> { + const checks = brands.flatMap((brand) => brand.checks ?? []) + return Arr.isArrayNonEmpty(checks) ? + check(...checks) : + nominal() +} diff --git a/.repos/effect/packages/effect/src/Cache.ts b/.repos/effect/packages/effect/src/Cache.ts new file mode 100644 index 000000000..d1e3091b2 --- /dev/null +++ b/.repos/effect/packages/effect/src/Cache.ts @@ -0,0 +1,1391 @@ +/** + * Caches values loaded by an Effect lookup function. + * + * A cache stores successful and failed lookup results, shares an in-progress + * lookup when multiple callers request the same missing key, and limits entries + * by capacity and optional time-to-live rules. This module includes helpers for + * reading, setting, refreshing, invalidating, and inspecting cache contents. + * + * @since 4.0.0 + */ +import * as Context from "./Context.ts" +import * as Duration from "./Duration.ts" +import type * as Effect from "./Effect.ts" +import type * as Exit from "./Exit.ts" +import type * as Fiber from "./Fiber.ts" +import { dual } from "./Function.ts" +import * as core from "./internal/core.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as effect from "./internal/effect.ts" +import * as Iterable from "./Iterable.ts" +import * as MutableHashMap from "./MutableHashMap.ts" +import * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { Predicate } from "./Predicate.ts" +import * as Result from "./Result.ts" + +const TypeId = "~effect/Cache" + +/** + * A cache interface that provides a mutable key-value store with automatic TTL management, + * capacity limits, and lookup functions for cache misses. + * + * **Example** (Creating a basic cache) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Basic cache with string keys and number values + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Cache operations + * const value1 = yield* Cache.get(cache, "hello") // 5 + * const value2 = yield* Cache.get(cache, "world") // 5 + * const value3 = yield* Cache.get(cache, "hello") // 5 (cached) + * + * return [value1, value2, value3] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [5, 5, 5] + * ``` + * + * **Example** (Handling lookup failures) + * + * ```ts import.meta.vitest + * import { Cache, Effect, Exit } from "effect" + * + * // Cache with error handling + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => + * key === "error" + * ? Effect.fail("Lookup failed") + * : Effect.succeed(key.length) + * }) + * + * // Handle successful and failed lookups + * const success = yield* Cache.get(cache, "test") + * const failure = yield* Effect.exit(Cache.get(cache, "error")) + * + * return [success, failure] as const + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [4, Exit.fail("Lookup failed")] + * ``` + * + * **Example** (Using complex keys with TTL) + * + * ```ts import.meta.vitest + * import { Cache, Data, Duration, Effect } from "effect" + * + * // Cache with complex key types and TTL + * class UserId extends Data.Class<{ id: number }> {} + * + * const program = Effect.gen(function*() { + * const userCache = yield* Cache.make({ + * capacity: 1000, + * lookup: (userId: UserId) => Effect.succeed(`User-${userId.id}`), + * timeToLive: Duration.minutes(5) + * }) + * + * const userId = new UserId({ id: 123 }) + * const userName = yield* Cache.get(userCache, userId) + * + * return userName + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => "User-123" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Cache extends Pipeable { + readonly [TypeId]: typeof TypeId + readonly map: MutableHashMap.MutableHashMap> + readonly capacity: number + readonly lookup: (key: Key) => Effect.Effect + readonly timeToLive: (exit: Exit.Exit, key: Key) => Duration.Duration +} + +/** + * Represents a low-level cache entry containing a deferred lookup result and + * an optional expiration timestamp. + * + * **When to use** + * + * Use when inspecting a `Cache`'s low-level map and you need the stored + * deferred lookup result or expiration timestamp for a key. + * + * **Details** + * + * An `expiresAt` value of `undefined` means the entry does not expire. + * + * @see {@link Cache} for the public cache API that manages entries through + * combinators + * + * @category models + * @since 4.0.0 + */ +export interface Entry { + expiresAt: number | undefined + awaiters: number + readonly fiber: Fiber.Fiber + await(this: Entry): Effect.Effect +} + +/** + * Creates a cache with dynamic time-to-live based on the result and key. + * + * **When to use** + * + * Use when you need different cache entry lifetimes based on the lookup result + * or key characteristics. + * + * **Details** + * + * The timeToLive function receives both the exit result and the key, allowing + * for flexible TTL policies based on success/failure state and key characteristics. + * + * **Example** (Configuring dynamic time to live) + * + * ```ts import.meta.vitest + * import { Cache, Effect, Exit } from "effect" + * + * // Cache with TTL based on computed value + * const program = Effect.gen(function*() { + * const cache = yield* Cache.makeWith( + * (id: number) => Effect.succeed({ id, active: id % 2 === 0 }), + * { + * capacity: 1000, + * timeToLive(exit) { + * if (Exit.isSuccess(exit)) { + * const user = exit.value + * return user.active ? "1 hour" : "5 minutes" + * } + * return "30 seconds" + * } + * } + * ) + * + * 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 + * @category constructors + * @since 2.0.0 + */ +export const makeWith = < + Key, + A, + E = never, + R = never, + ServiceMode extends "lookup" | "construction" = never +>(lookup: (key: Key) => Effect.Effect, options: { + readonly capacity: number + readonly timeToLive?: ((exit: Exit.Exit, key: Key) => Duration.Input) | undefined + readonly requireServicesAt?: ServiceMode | undefined +}): Effect.Effect< + Cache, + never, + "lookup" extends ServiceMode ? never : R +> => + effect.contextWith((context: Context.Context) => { + const self = Object.create(Proto) + self.lookup = (key: Key): Effect.Effect => + effect.updateContext( + lookup(key), + (input) => Context.merge(context, input) + ) + self.map = MutableHashMap.make() + self.capacity = options.capacity + self.timeToLive = options.timeToLive + ? (exit: Exit.Exit, key: Key) => Duration.fromInputUnsafe(options.timeToLive!(exit, key)) + : defaultTimeToLive + return effect.succeed(self as Cache) + }) + +/** + * Creates a cache with a fixed time-to-live for all entries. + * + * **Details** + * + * This is the basic cache constructor where all entries share the same TTL. + * The lookup function will be called when a key is not found or has expired. + * + * **Example** (Creating a basic cache) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Basic cache with string keys + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key) => Effect.succeed(key.length) + * }) + * + * const result1 = yield* Cache.get(cache, "hello") + * const result2 = yield* Cache.get(cache, "world") + * return { result1, result2 } + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { result1: 5, result2: 5 } + * ``` + * + * **Example** (Creating a cache with TTL) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const users = new Map([ + * [123, { name: "Ada", email: "ada@example.com" }], + * [456, { name: "Grace", email: "grace@example.com" }] + * ]) + * + * const cache = yield* Cache.make< + * number, + * { name: string; email: string }, + * string + * >({ + * capacity: 500, + * lookup: (userId) => + * Effect.suspend(() => { + * const user = users.get(userId) + * return user === undefined + * ? Effect.fail(`User ${userId} not found`) + * : Effect.succeed(user) + * }), + * timeToLive: "15 minutes" + * }) + * + * const user1 = yield* Cache.get(cache, 123) + * const user2 = yield* Cache.get(cache, 123) + * 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 + * @since 2.0.0 + */ +export const make = < + Key, + A, + E = never, + R = never, + ServiceMode extends "lookup" | "construction" = never +>( + options: { + readonly lookup: (key: Key) => Effect.Effect + readonly capacity: number + readonly timeToLive?: Duration.Input | undefined + readonly requireServicesAt?: ServiceMode | undefined + } +): Effect.Effect< + Cache, + never, + "lookup" extends ServiceMode ? never : R +> => + makeWith(options.lookup, { + ...options, + timeToLive: options.timeToLive !== undefined ? () => options.timeToLive! : defaultTimeToLive + }) + +const Proto = { + ...PipeInspectableProto, + [TypeId]: TypeId, + toJSON(this: Cache) { + return { + _id: "Cache", + capacity: this.capacity, + map: this.map + } + } +} + +const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Duration => Duration.infinity + +/** + * Retrieves the value for a key, invoking the lookup function on a cache miss + * or expired entry. + * + * **Details** + * + * Concurrent `get` calls for the same missing key share the same pending + * lookup. The cache stores the lookup `Exit`, so failed lookups are cached and + * will fail again until the entry expires, is invalidated, or is refreshed. + * + * **Example** (Getting cached values) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Cache miss - triggers lookup function + * const result1 = yield* Cache.get(cache, "hello") + * + * // Cache hit - returns cached value without lookup + * const result2 = yield* Cache.get(cache, "hello") + * + * return { result1, result2 } + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { result1: 5, result2: 5 } + * ``` + * + * **Example** (Handling lookup failures) + * + * ```ts import.meta.vitest + * import { Cache, Effect, Exit } from "effect" + * + * // Error handling when lookup fails + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => + * key === "error" + * ? Effect.fail("Lookup failed") + * : Effect.succeed(key.length) + * }) + * + * // Successful lookup + * const success = yield* Cache.get(cache, "hello") + * + * // Failed lookup - returns error + * const failure = yield* Effect.exit(Cache.get(cache, "error")) + * return [success, failure] as const + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [5, Exit.fail("Lookup failed")] + * ``` + * + * **Example** (Sharing concurrent lookups) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Concurrent access - multiple gets of same key only invoke lookup once + * const program = Effect.gen(function*() { + * let lookupCount = 0 + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => + * Effect.sync(() => { + * lookupCount++ + * return key.length + * }) + * }) + * + * // Multiple concurrent gets + * const results = yield* Effect.all([ + * Cache.get(cache, "hello"), + * Cache.get(cache, "hello"), + * Cache.get(cache, "hello") + * ], { concurrency: "unbounded" }) + * + * return { results, lookupCount } + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { results: [5, 5, 5], lookupCount: 1 } + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const get: { + (key: Key): (self: Cache) => Effect.Effect + (self: Cache, key: Key): Effect.Effect +} = dual( + 2, + (self: Cache, key: Key): Effect.Effect => + core.withFiber((fiber) => { + const oentry = MutableHashMap.get(self.map, key) + if (Option.isSome(oentry) && !hasExpired(oentry.value, fiber)) { + // 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 oentry.value.await() + } + 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) + } + }) + 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 + } + return fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() >= entry.expiresAt +} + +const checkCapacity = (self: Cache) => { + let diff = MutableHashMap.size(self.map) - self.capacity + if (diff <= 0) return + // MutableHashMap has insertion order, so we can remove the oldest entries + for (const [key] of self.map) { + MutableHashMap.remove(self.map, key) + diff-- + if (diff === 0) return + } +} + +/** + * Reads an existing cache entry without invoking the lookup function. + * + * **Details** + * + * Returns `Option.none()` when the key is missing or expired, and `Option.some` + * when a cached lookup has succeeded. If the entry is still pending, waits for + * it to complete. If the cached or pending lookup fails, this effect fails with + * the same error. + * + * **Example** (Reading cached values without lookup) + * + * ```ts import.meta.vitest + * import { Cache, Effect, Option } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // No value in cache yet - returns None without lookup + * const empty = yield* Cache.getOption(cache, "hello") + * + * // Populate cache using get + * yield* Cache.get(cache, "hello") + * + * // Now getOption returns the cached value + * const cached = yield* Cache.getOption(cache, "hello") + * return [empty, cached] as const + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Option.none(), Option.some(5)] + * ``` + * + * **Example** (Skipping expired entries) + * + * ```ts import.meta.vitest + * import { Cache, Effect, Option } from "effect" + * import { TestClock } from "effect/testing" + * + * // Expired entries return None + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length), + * timeToLive: "1 hour" + * }) + * + * // Add value to cache + * yield* Cache.get(cache, "hello") + * + * // Value exists before expiration + * const beforeExpiry = yield* Cache.getOption(cache, "hello") + * + * // Simulate time passing + * yield* TestClock.adjust("2 hours") + * + * // Value expired - returns None + * const afterExpiry = yield* Cache.getOption(cache, "hello") + * 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.meta.vitest + * import { Cache, Deferred, Effect, Fiber, Option } from "effect" + * + * // Waits for ongoing computation to complete + * const program = Effect.gen(function*() { + * const deferred = yield* Deferred.make() + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (_key: string) => Deferred.await(deferred).pipe(Effect.as(42)) + * }) + * + * // Start lookup in background + * const getFiber = yield* Effect.forkChild(Cache.get(cache, "key")) + * + * // getOption waits for ongoing computation + * const optionFiber = yield* Effect.forkChild(Cache.getOption(cache, "key")) + * + * // Complete the computation + * yield* Deferred.succeed(deferred, void 0) + * + * const result = yield* Fiber.join(optionFiber) + * const value = yield* Fiber.join(getFiber) + * return [result, value] as const + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Option.some(42), 42] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const getOption: { + (key: Key): (self: Cache) => Effect.Effect, E> + (self: Cache, key: Key): Effect.Effect, E> +} = dual( + 2, + (self: Cache, key: Key): Effect.Effect, E> => + core.withFiber((fiber) => { + const entry = getImpl(self, key, fiber) + return entry ? effect.asSome(entry.await()) : effect.succeedNone + }) +) + +const getImpl = ( + self: Cache, + key: Key, + fiber: Fiber.Fiber, + isRead = true +): Entry | undefined => { + const oentry = MutableHashMap.get(self.map, key) + if (Option.isNone(oentry)) { + return undefined + } else if (hasExpired(oentry.value, fiber)) { + MutableHashMap.remove(self.map, key) + return undefined + } else if (isRead) { + MutableHashMap.remove(self.map, key) + MutableHashMap.set(self.map, key, oentry.value) + } + return oentry.value +} + +/** + * Retrieves the value associated with the specified key from the cache, only if + * it contains a resolved successful value. + * + * **Details** + * + * This checks only an existing non-expired entry. It returns `Option.some` when + * the entry has already resolved successfully, and `Option.none` for missing, + * expired, failed, or still-pending entries. + * + * @see {@link get} for triggering or awaiting the cache lookup + * @see {@link getOption} for reading an existing entry as an optional effect + * + * @category combinators + * @since 4.0.0 + */ +export const getSuccess: { + (key: Key): (self: Cache) => Effect.Effect> + (self: Cache, key: Key): Effect.Effect> +} = dual( + 2, + (self: Cache, key: Key): Effect.Effect> => + core.withFiber((fiber) => { + const exit = getImpl(self, key, fiber)?.fiber.pollUnsafe() + if (exit && effect.exitIsSuccess(exit)) { + return effect.succeedSome(exit.value) + } + return effect.succeedNone + }) +) + +/** + * Sets the value associated with the specified key in the cache. This will + * overwrite any existing value for that key, skipping the lookup function. + * + * **Example** (Setting values directly) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Set a value directly without invoking lookup + * yield* Cache.set(cache, "hello", 42) + * return yield* Cache.get(cache, "hello") + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 + * ``` + * + * **Example** (Overwriting cached values) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Overwriting existing cached values + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // First get populates via lookup + * const original = yield* Cache.get(cache, "test") // 4 + * + * // Set overwrites the cached value + * yield* Cache.set(cache, "test", 999) + * const updated = yield* Cache.get(cache, "test") // 999 + * + * return { original, updated } + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { original: 4, updated: 999 } + * ``` + * + * **Example** (Applying TTL to set values) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * import { TestClock } from "effect/testing" + * + * // TTL behavior with set operations + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key: string) => Effect.succeed(key.length), + * timeToLive: "1 hour" + * }) + * + * // Set value with TTL applied + * yield* Cache.set(cache, "temporary", 123) + * const beforeExpiry = yield* Cache.has(cache, "temporary") + * + * // Advance time past TTL + * yield* TestClock.adjust("2 hours") + * 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 import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Capacity enforcement with set operations + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 2, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Fill cache to capacity + * yield* Cache.set(cache, "a", 1) + * yield* Cache.set(cache, "b", 2) + * const sizeBeforeEviction = yield* Cache.size(cache) + * + * // Adding another entry evicts oldest + * yield* Cache.set(cache, "c", 3) + * 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 + * @since 4.0.0 + */ +export const set: { + (key: Key, value: A): (self: Cache) => Effect.Effect + (self: Cache, key: Key, value: A): Effect.Effect +} = dual( + 3, + (self: Cache, key: Key, value: A): Effect.Effect => + core.withFiber((fiber) => { + const exit = core.exitSucceed(value) + const entry = new EntryImpl(fiber, exit) + const ttl = self.timeToLive(exit, key) + if (Duration.isZero(ttl)) { + MutableHashMap.remove(self.map, key) + return effect.void + } + 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 + }) +) + +/** + * Checks whether the cache contains an entry for the specified key. + * + * **Details** + * + * This checks for an existing non-expired entry without invoking the cache + * lookup function. Expired entries are treated as absent. + * + * **Example** (Checking for cached keys) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Check non-existent key + * const missing = yield* Cache.has(cache, "missing") + * + * // Add entry and check existence + * yield* Cache.get(cache, "hello") + * const present = yield* Cache.has(cache, "hello") + * return [missing, present] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [false, true] + * ``` + * + * **Example** (Checking TTL expiration) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * import { TestClock } from "effect/testing" + * + * // TTL expiration behavior + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key: string) => Effect.succeed(key.length), + * timeToLive: "1 hour" + * }) + * + * // Add entry with TTL + * yield* Cache.get(cache, "expires") + * const initial = yield* Cache.has(cache, "expires") + * + * // Still valid before expiration + * yield* TestClock.adjust("30 minutes") + * const beforeExpiry = yield* Cache.has(cache, "expires") + * + * // Expired after TTL + * yield* TestClock.adjust("31 minutes") + * 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 import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Checking multiple keys efficiently + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 100, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Populate some entries + * yield* Cache.set(cache, "apple", 5) + * yield* Cache.set(cache, "banana", 6) + * + * // Check multiple keys + * const keys = ["apple", "banana", "cherry", "date"] + * const results: Array = [] + * for (const key of keys) { + * const exists = yield* Cache.has(cache, key) + * results.push(`${key}: ${exists}`) + * } + * return results + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["apple: true", "banana: true", "cherry: false", "date: false"] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const has: { + (key: Key): (self: Cache) => Effect.Effect + (self: Cache, key: Key): Effect.Effect +} = dual( + 2, + (self: Cache, key: Key): Effect.Effect => + core.withFiber((fiber) => { + const oentry = getImpl(self, key, fiber, false) + return effect.succeed(oentry !== undefined) + }) +) + +/** + * Invalidates the entry associated with the specified key in the cache. + * + * **Example** (Invalidating cached entries) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Add a value to the cache + * yield* Cache.get(cache, "hello") + * const beforeInvalidation = yield* Cache.has(cache, "hello") + * + * // Invalidate the entry + * yield* Cache.invalidate(cache, "hello") + * const afterInvalidation = yield* Cache.has(cache, "hello") + * + * // Invalidating non-existent keys doesn't error + * yield* Cache.invalidate(cache, "nonexistent") + * + * // Get after invalidation will invoke lookup again + * let lookupCount = 0 + * const cache2 = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => + * Effect.sync(() => { + * lookupCount++ + * return key.length + * }) + * }) + * + * 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 + * @since 4.0.0 + */ +export const invalidate: { + (key: Key): (self: Cache) => Effect.Effect + (self: Cache, key: Key): Effect.Effect +} = dual(2, (self: Cache, key: Key): Effect.Effect => + effect.sync(() => { + MutableHashMap.remove(self.map, key) + })) + +/** + * Invalidates the entry associated with the specified key in the cache when the + * predicate returns true for the cached value. + * + * **Example** (Invalidating entries conditionally) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Add values to the cache + * yield* Cache.get(cache, "hello") // value = 5 + * yield* Cache.get(cache, "hi") // value = 2 + * + * // Invalidate when value equals 5 + * const invalidated1 = yield* Cache.invalidateWhen( + * cache, + * "hello", + * (value) => value === 5 + * ) + * const hasHello = yield* Cache.has(cache, "hello") + * + * // Don't invalidate when predicate doesn't match + * const invalidated2 = yield* Cache.invalidateWhen( + * cache, + * "hi", + * (value) => value === 5 + * ) + * const hasHi = yield* Cache.has(cache, "hi") + * + * // Returns false for non-existent keys + * const invalidated3 = yield* Cache.invalidateWhen( + * cache, + * "nonexistent", + * () => true + * ) + * + * // Returns false for failed cached values + * const cacheWithErrors = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => + * key === "fail" ? Effect.fail("error") : Effect.succeed(key.length) + * }) + * + * yield* Effect.exit(Cache.get(cacheWithErrors, "fail")) + * const invalidated4 = yield* Cache.invalidateWhen( + * cacheWithErrors, + * "fail", + * () => true + * ) + * return [invalidated1, hasHello, invalidated2, hasHi, invalidated3, invalidated4] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [true, false, false, true, false, false] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const invalidateWhen: { + (key: Key, f: Predicate): (self: Cache) => Effect.Effect + (self: Cache, key: Key, f: Predicate): Effect.Effect +} = dual( + 3, + (self: Cache, key: Key, f: Predicate): Effect.Effect => + core.withFiber((fiber) => { + const oentry = getImpl(self, key, fiber, false) + if (oentry === undefined) { + return effect.succeed(false) + } + return oentry.await().pipe( + effect.map((value) => { + if (f(value)) { + MutableHashMap.remove(self.map, key) + return true + } + return false + }), + effect.catchCause(() => effect.succeed(false)) + ) + }) +) + +/** + * Forces a refresh of the value associated with the specified key in the cache. + * + * **Details** + * + * It will always invoke the lookup function to construct a new value, + * overwriting any existing value for that key. + * + * **Example** (Refreshing cached values) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Force refresh of existing cached values + * const program = Effect.gen(function*() { + * let counter = 0 + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.sync(() => `${key}-${++counter}`) + * }) + * + * // Initial cache population + * const value1 = yield* Cache.get(cache, "user") + * + * // Get from cache (no lookup) + * const value2 = yield* Cache.get(cache, "user") + * + * // Force refresh - always calls lookup + * const refreshed = yield* Cache.refresh(cache, "user") + * + * // Subsequent gets return refreshed value + * const value3 = yield* Cache.get(cache, "user") + * 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 import.meta.vitest + * import { Cache, Effect } from "effect" + * import { TestClock } from "effect/testing" + * + * // Refresh resets TTL (Time To Live) + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length), + * timeToLive: "1 hour" + * }) + * + * yield* Cache.get(cache, "test") + * yield* TestClock.adjust("45 minutes") + * + * // Entry would normally expire in 15 minutes + * 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 + * 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 import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Refresh non-existent keys + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(`value-for-${key}`) + * }) + * + * // Refresh non-existent key creates new entry + * const result = yield* Cache.refresh(cache, "newKey") + * + * // Verify it's now cached + * const cached = yield* Cache.has(cache, "newKey") + * return [result, cached] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["value-for-newKey", true] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const refresh: { + (key: Key): (self: Cache) => Effect.Effect + (self: Cache, key: Key): Effect.Effect +} = dual( + 2, + (self: Cache, key: Key): Effect.Effect => + core.withFiber((fiber) => { + 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) + } + entry.fiber.addObserver((exit) => { + if (effect.exitHasInterrupts(exit)) { + if (!existing) MutableHashMap.remove(self.map, key) + return + } + const ttl = self.timeToLive(exit, key) + if (Duration.isZero(ttl)) { + MutableHashMap.remove(self.map, key) + return effect.void + } + entry.expiresAt = Duration.isFinite(ttl) + ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) + : undefined + if (existing) { + MutableHashMap.set(self.map, key, entry) + } + }) + return entry.await() + }) +) + +/** + * Invalidates all entries in the cache. + * + * **Example** (Invalidating all entries) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Clear all cached entries at once + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Populate cache with multiple entries + * yield* Cache.get(cache, "apple") + * yield* Cache.get(cache, "banana") + * yield* Cache.get(cache, "cherry") + * + * const sizeBeforeInvalidation = yield* Cache.size(cache) + * const hasAppleBeforeInvalidation = yield* Cache.has(cache, "apple") + * + * // Clear all entries + * yield* Cache.invalidateAll(cache) + * + * // Verify cache is empty + * 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 + * @since 4.0.0 + */ +export const invalidateAll = (self: Cache): Effect.Effect => + effect.sync(() => { + MutableHashMap.clear(self.map) + }) + +/** + * Retrieves the approximate number of entries in the cache. + * + * **Details** + * + * Note that expired entries are counted until they are accessed and removed. + * The size reflects the current number of entries stored, not the number + * of valid entries. + * + * **Example** (Reading cache size) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Empty cache has size 0 + * const emptySize = yield* Cache.size(cache) + * + * // Add entries and check size + * yield* Cache.get(cache, "hello") + * yield* Cache.get(cache, "world") + * const sizeAfterAdding = yield* Cache.size(cache) + * + * // Size decreases after invalidation + * yield* Cache.invalidate(cache, "hello") + * const sizeAfterInvalidation = yield* Cache.size(cache) + * return [emptySize, sizeAfterAdding, sizeAfterInvalidation] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [0, 2, 1] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const size = (self: Cache): Effect.Effect => + effect.sync(() => MutableHashMap.size(self.map)) + +/** + * Retrieves all active keys from the cache, automatically filtering out expired entries. + * + * **Example** (Reading active keys) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * // Basic key enumeration + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Add some entries to the cache + * yield* Cache.get(cache, "hello") + * yield* Cache.get(cache, "world") + * yield* Cache.get(cache, "cache") + * + * // Retrieve all active keys + * const keys = yield* Cache.keys(cache) + * return Array.from(keys).sort() + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["cache", "hello", "world"] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const keys = (self: Cache): Effect.Effect> => + core.withFiber((fiber) => { + const now = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + return effect.succeed(Iterable.filterMap(self.map, ([key, entry]) => { + if (entry.expiresAt === undefined || entry.expiresAt > now) { + return Result.succeed(key) + } + MutableHashMap.remove(self.map, key) + return Result.failVoid + })) + }) + +/** + * Retrieves all successfully cached values from the cache, excluding failed + * lookups and expired entries. + * + * **Example** (Reading all cached values) + * + * ```ts import.meta.vitest + * import { Cache, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* Cache.make({ + * capacity: 10, + * lookup: (key: string) => Effect.succeed(key.length) + * }) + * + * // Add some values to the cache + * yield* Cache.get(cache, "a") + * yield* Cache.get(cache, "ab") + * yield* Cache.get(cache, "abc") + * + * // Retrieve all cached values + * const values = yield* Cache.values(cache) + * return Array.from(values).sort() + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [1, 2, 3] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const values = (self: Cache): Effect.Effect> => + effect.map(entries(self), Iterable.map(([, value]) => value)) + +/** + * Retrieves all key-value pairs from the cache as an iterable. This function + * only returns entries with successfully resolved values, filtering out any + * failed lookups or expired entries. + * + * **Gotchas** + * + * Expired entries are removed from the cache while `entries` filters them out. + * + * @see {@link keys} for retrieving only cached keys + * @see {@link values} for retrieving only cached values + * + * @category combinators + * @since 4.0.0 + */ +export const entries = (self: Cache): Effect.Effect> => + core.withFiber((fiber) => { + 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.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/.repos/effect/packages/effect/src/Cause.ts b/.repos/effect/packages/effect/src/Cause.ts new file mode 100644 index 000000000..2511f27bc --- /dev/null +++ b/.repos/effect/packages/effect/src/Cause.ts @@ -0,0 +1,1839 @@ +/** + * Records the full reason an `Effect` failed. + * + * A `Cause` can contain typed failures, unexpected defects, interruptions, + * and annotations. Keeping those details together lets code inspect or format + * failures without first collapsing them to a single error value. This module + * includes the `Cause` and `Reason` data types, helpers for building and + * checking causes, and small error types used by several Effect APIs. + * + * @since 2.0.0 + */ +import * as Context from "./Context.ts" +import type * as Effect from "./Effect.ts" +import type { Equal } from "./Equal.ts" +import type { Fiber } from "./Fiber.ts" +import type { Inspectable } from "./Inspectable.ts" +import * as core from "./internal/core.ts" +import * as effect from "./internal/effect.ts" +import type { Option } from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { StackFrame } from "./References.ts" +import type * as Result from "./Result.ts" +import type * as Types from "./Types.ts" + +/** + * Unique brand for `Cause` values, used for runtime type checks via {@link isCause}. + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId: "~effect/Cause" = core.CauseTypeId + +/** + * Unique brand for `Reason` values, used for runtime type checks via {@link isReason}. + * + * @category type IDs + * @since 4.0.0 + */ +export const ReasonTypeId: "~effect/Cause/Reason" = core.CauseReasonTypeId + +/** + * A structured representation of how an Effect failed. + * + * **When to use** + * + * Use to preserve the full structured failure information for an effect instead + * of collapsing it to a single error value. + * + * **Details** + * + * Access the individual failure entries through the `reasons` array, then + * narrow each entry with {@link isFailReason}, {@link isDieReason}, or + * {@link isInterruptReason}. + * + * - Use {@link hasFails} / {@link hasDies} / {@link hasInterrupts} to test + * for the presence of specific reason kinds without iterating. + * - Use {@link findError} / {@link findDefect} to extract the first value + * of a given kind. + * - Use {@link combine} to merge two causes. + * + * `Cause` implements `Equal` — two causes with the same reasons (by value) + * compare as equal. + * + * **Example** (Creating and inspecting a cause) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.fail("Something went wrong") // => Cause.fail("Something went wrong") + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Cause extends Pipeable, Inspectable, Equal { + readonly [TypeId]: typeof TypeId + readonly reasons: ReadonlyArray> +} + +/** + * Checks whether an arbitrary value is a `Cause`. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.isCause(Cause.fail("error")) // => true + * Cause.isCause("not a cause") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isCause: (self: unknown) => self is Cause = core.isCause + +/** + * Checks whether an arbitrary value is a `Reason` (`Fail`, `Die`, or `Interrupt`). + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const reason = Cause.fail("error").reasons[0] + * Cause.isReason(reason) // => true + * Cause.isReason("not a reason") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isReason: (self: unknown) => self is Reason = core.isCauseReason + +/** + * A single entry inside a `Cause`'s `reasons` array. + * + * **Details** + * + * Narrow to a concrete type with {@link isFailReason}, {@link isDieReason}, + * or {@link isInterruptReason}. + * + * - `Fail` — typed error, access via `.error` + * - `Die` — untyped defect, access via `.defect` + * - `Interrupt` — fiber interruption, access via `.fiberId` + * + * Every reason carries an `annotations` map and an `annotate` method for + * attaching tracing metadata. + * + * **Example** (Narrowing a reason) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const reason = Cause.fail("error").reasons[0] + * if (Cause.isFailReason(reason)) { + * reason.error // => "error" + * } + * ``` + * + * @category models + * @since 4.0.0 + */ +export type Reason = Fail | Die | Interrupt + +/** + * Narrows a `Reason` to `Fail`. + * + * **When to use** + * + * Use as a predicate for `Array.filter` to pick out typed `Fail` reasons when + * iterating over `cause.reasons`. + * + * **Example** (Filtering fail reasons) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const cause = Cause.fail("error") + * const fails = cause.reasons.filter(Cause.isFailReason) + * fails[0].error // => "error" + * ``` + * + * @see {@link isDieReason} — narrow to `Die` + * @see {@link isInterruptReason} — narrow to `Interrupt` + * + * @category guards + * @since 4.0.0 + */ +export const isFailReason: (self: Reason) => self is Fail = core.isFailReason + +/** + * Narrows a `Reason` to `Die`. + * + * **When to use** + * + * Use as a predicate for `Array.filter` to pick out `Die` (defect) reasons when + * iterating over `cause.reasons`. + * + * **Example** (Filtering die reasons) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const cause = Cause.die("defect") + * const dies = cause.reasons.filter(Cause.isDieReason) + * dies[0].defect // => "defect" + * ``` + * + * @see {@link isFailReason} — narrow to `Fail` + * @see {@link isInterruptReason} — narrow to `Interrupt` + * + * @category guards + * @since 4.0.0 + */ +export const isDieReason: (self: Reason) => self is Die = core.isDieReason + +/** + * Narrows a `Reason` to `Interrupt`. + * + * **When to use** + * + * Use as a predicate for `Array.filter` to pick out `Interrupt` reasons when + * iterating over `cause.reasons`. + * + * **Example** (Filtering interrupt reasons) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const cause = Cause.interrupt(123) + * const interrupts = cause.reasons.filter(Cause.isInterruptReason) + * interrupts[0].fiberId // => 123 + * ``` + * + * @see {@link isFailReason} — narrow to `Fail` + * @see {@link isDieReason} — narrow to `Die` + * + * @category guards + * @since 4.0.0 + */ +export const isInterruptReason: (self: Reason) => self is Interrupt = core.isInterruptReason + +/** + * Companion namespace for the `Cause` interface. + * + * @since 2.0.0 + */ +export declare namespace Cause { + /** + * Extracts the error type `E` from a `Cause`. + * + * **Example** (Extracting the error type) + * + * ```ts import.meta.vitest + * import type { Cause } from "effect" + * + * // string + * type E = Cause.Cause.Error> + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Error = T extends Cause ? E : never + + /** + * Base interface shared by all reason types (`Fail`, `Die`, `Interrupt`). + * + * **Details** + * + * Every reason carries: + * - `_tag` — discriminant string (`"Fail"`, `"Die"`, or `"Interrupt"`) + * - `annotations` — tracing metadata attached by the runtime + * - `annotate()` — returns a copy with additional annotations + * + * @category models + * @since 4.0.0 + */ + export interface ReasonProto extends Inspectable, Equal { + readonly [ReasonTypeId]: typeof ReasonTypeId + readonly _tag: Tag + readonly annotations: ReadonlyMap + annotate(annotations: Context.Context | ReadonlyMap, options?: { + readonly overwrite?: boolean | undefined + }): this + } +} + +/** + * Companion namespace for the `Reason` type. + * + * @since 4.0.0 + */ +export declare namespace Reason { + /** + * Extracts the error type `E` from a `Reason`. + * + * **Example** (Extracting the error type) + * + * ```ts import.meta.vitest + * import type { Cause } from "effect" + * + * // string + * type E = Cause.Reason.Error> + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Error = T extends Reason ? E : never +} + +/** + * An untyped defect — typically a programming error or an uncaught exception. + * + * **When to use** + * + * Use when inspecting `Cause` reasons that represent defects instead of typed + * failures or interruptions. + * + * **Details** + * + * The `defect` property is `unknown` because defects are not part of the + * typed error channel. Use {@link isDieReason} to narrow a `Reason` + * to this type. + * + * **Example** (Accessing the defect) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const cause = Cause.die("Unexpected") + * const reason = cause.reasons[0] + * if (Cause.isDieReason(reason)) { + * reason.defect // => "Unexpected" + * } + * ``` + * + * @see {@link die} for constructing a cause with a single `Die` reason + * @see {@link isDieReason} for narrowing a `Reason` to `Die` + * + * @category models + * @since 2.0.0 + */ +export interface Die extends Cause.ReasonProto<"Die"> { + readonly defect: unknown +} + +/** + * A typed, expected error produced by `Effect.fail`. + * + * **When to use** + * + * Use when inspecting `Cause` reasons that represent expected failures from the + * typed error channel. + * + * **Details** + * + * The `error` property carries the typed value `E`. Use {@link isFailReason} + * to narrow a `Reason` to this type. + * + * **Example** (Accessing the error) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const cause = Cause.fail("Something went wrong") + * const reason = cause.reasons[0] + * if (Cause.isFailReason(reason)) { + * reason.error // => "Something went wrong" + * } + * ``` + * + * @see {@link fail} for constructing a cause with a single `Fail` reason + * @see {@link isFailReason} for narrowing a `Reason` to `Fail` + * + * @category models + * @since 2.0.0 + */ +export interface Fail extends Cause.ReasonProto<"Fail"> { + readonly error: E +} + +/** + * A fiber interruption signal, optionally carrying the ID of the fiber that + * initiated the interruption. + * + * **Details** + * + * Use {@link isInterruptReason} to narrow a `Reason` to this type. + * + * **Example** (Accessing the fiber ID) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const cause = Cause.interrupt(123) + * const reason = cause.reasons[0] + * if (Cause.isInterruptReason(reason)) { + * reason.fiberId // => 123 + * } + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Interrupt extends Cause.ReasonProto<"Interrupt"> { + readonly fiberId: number | undefined +} + +/** + * Creates a `Cause` from an array of `Reason` values. + * + * **When to use** + * + * Use when you already have individual reasons (e.g. from filtering or + * transforming another cause's `reasons` array) and need to wrap them back + * into a `Cause`. + * + * **Details** + * + * - Returns a new `Cause`. + * - An empty array produces a cause equivalent to `empty`. + * + * **Gotchas** + * + * The `reasons` array is stored as provided. Treat the array as immutable + * after passing it to this function. + * + * **Example** (Building a cause from reasons) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const reasons = [ + * Cause.makeFailReason("err1"), + * Cause.makeFailReason("err2") + * ] + * Cause.fromReasons(reasons) // => Cause.combine(Cause.fail("err1"), Cause.fail("err2")) + * ``` + * + * @see {@link combine} — merge two existing causes + * + * @category constructors + * @since 4.0.0 + */ +export const fromReasons: ( + reasons: ReadonlyArray> +) => Cause = core.causeFromReasons + +/** + * Represents a `Cause` with an empty `reasons` array. + * + * **When to use** + * + * Use to represent the absence of failure when constructing or combining + * causes. + * + * **Details** + * + * Represents the absence of failure. Combining any cause with `empty` via + * {@link combine} returns the original cause unchanged. + * + * **Example** (Combining with the empty cause) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.combine(Cause.empty, Cause.fail("boom")) // => Cause.fail("boom") + * ``` + * + * @see {@link combine} for merging causes where `empty` acts as the identity + * + * @category constructors + * @since 2.0.0 + */ +export const empty: Cause = core.causeEmpty + +/** + * Creates a `Cause` containing a single `Fail` reason with the + * given typed error. + * + * **When to use** + * + * Use to construct a cause from an expected typed error. + * + * **Example** (Creating a fail cause) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.fail("Something went wrong") // => Cause.fromReasons([Cause.makeFailReason("Something went wrong")]) + * ``` + * + * @see {@link die} — for untyped defects + * @see {@link interrupt} — for fiber interruptions + * + * @category constructors + * @since 2.0.0 + */ +export const fail: (error: E) => Cause = core.causeFail + +/** + * Creates a `Cause` containing a single `Die` reason with the + * given defect. + * + * **When to use** + * + * Use to construct a cause from an untyped defect or unexpected thrown value. + * + * **Example** (Creating a die cause) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.die("Unexpected") // => Cause.fromReasons([Cause.makeDieReason("Unexpected")]) + * ``` + * + * @see {@link fail} — for typed errors + * @see {@link interrupt} — for fiber interruptions + * + * @category constructors + * @since 2.0.0 + */ +export const die: (defect: unknown) => Cause = core.causeDie + +/** + * Creates a `Cause` containing a single `Interrupt` reason, + * optionally carrying the interrupting fiber's ID. + * + * **Example** (Creating an interrupt cause) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.interrupt(123) // => Cause.fromReasons([Cause.makeInterruptReason(123)]) + * ``` + * + * @see {@link fail} — for typed errors + * @see {@link die} — for untyped defects + * + * @category constructors + * @since 2.0.0 + */ +export const interrupt: (fiberId?: number | undefined) => Cause = effect.causeInterrupt + +/** + * Creates a standalone `Fail` reason (not wrapped in a `Cause`). + * + * **When to use** + * + * Use when constructing a standalone typed failure reason for + * {@link fromReasons} or direct comparison. + * + * **Example** (Creating a Fail reason) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.makeFailReason("error") // => Cause.fail("error").reasons[0] + * ``` + * + * @see {@link makeDieReason} — create a `Die` reason + * @see {@link makeInterruptReason} — create an `Interrupt` reason + * + * @category constructors + * @since 4.0.0 + */ +export const makeFailReason = (error: E): Fail => new core.Fail(error) + +/** + * Creates a standalone `Die` reason (not wrapped in a `Cause`). + * + * **When to use** + * + * Use when constructing a standalone defect reason for {@link fromReasons} or + * direct comparison. + * + * **Example** (Creating a Die reason) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.makeDieReason("bug") // => Cause.die("bug").reasons[0] + * ``` + * + * @see {@link makeFailReason} — create a `Fail` reason + * @see {@link makeInterruptReason} — create an `Interrupt` reason + * + * @category constructors + * @since 4.0.0 + */ +export const makeDieReason = (defect: unknown): Die => new core.Die(defect) + +/** + * Creates a standalone `Interrupt` reason (not wrapped in a `Cause`), + * optionally carrying the interrupting fiber's ID. + * + * **When to use** + * + * Use when constructing a standalone interrupt reason for {@link fromReasons} + * or direct comparison. + * + * **Example** (Creating an Interrupt reason) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.makeInterruptReason(42) // => Cause.interrupt(42).reasons[0] + * ``` + * + * @see {@link makeFailReason} — create a `Fail` reason + * @see {@link makeDieReason} — create a `Die` reason + * + * @category constructors + * @since 4.0.0 + */ +export const makeInterruptReason: (fiberId?: number | undefined) => Interrupt = effect.makeInterruptReason + +/** + * Returns `true` if every reason in the cause is an `Interrupt` (and + * there is at least one reason). + * + * **When to use** + * + * Use when you need to detect failures caused only by interruption. + * + * **Example** (Checking interrupt-only causes) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * 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 + * + * @category predicates + * @since 4.0.0 + */ +export const hasInterruptsOnly: (self: Cause) => boolean = effect.hasInterruptsOnly + +/** + * Transforms the typed error values inside a `Cause` using the + * provided function. Only `Fail` reasons are affected; `Die` and `Interrupt` + * reasons pass through unchanged. + * + * **When to use** + * + * Use to transform expected typed failures while preserving defects and + * interruptions unchanged. + * + * **Details** + * + * If at least one `Fail` reason exists, this returns a new `Cause` + * containing the mapped failures. If the cause has no `Fail` reasons, the + * original cause is returned unchanged. + * + * **Example** (Mapping errors to uppercase) + * + * ```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)) { + * reason.error // => "ERROR" + * } + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (error: Types.NoInfer) => E2): (self: Cause) => Cause + (self: Cause, f: (error: Types.NoInfer) => E2): Cause +} = effect.causeMap + +/** + * Merges two causes into a single cause whose `reasons` array is the union + * of both inputs (de-duplicated by value equality). + * + * **When to use** + * + * Use to merge independent causes into one structured failure value. + * + * **Details** + * + * - Combining with `empty` returns the other cause unchanged. + * - If the result is structurally equal to `self`, `self` is returned + * (referential shortcut). + * + * **Example** (Combining two causes) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * 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 + * @see {@link empty} for the identity cause used when combining + * + * @category combining + * @since 4.0.0 + */ +export const combine: { + (that: Cause): (self: Cause) => Cause + (self: Cause, that: Cause): Cause +} = effect.causeCombine + +/** + * Collapses a `Cause` into a single `unknown` value, picking the "most + * important" failure in this order: + * + * **When to use** + * + * Use to collapse a structured cause to the single value that synchronous and + * promise runners would throw. + * + * **Details** + * + * 1. First `Fail` error (the `E` value) + * 2. First `Die` defect + * 3. A generic `Error("All fibers interrupted without error")` for interrupt-only causes + * 4. A generic `Error("Empty cause")` for `empty` + * + * This is the function used by `Effect.runPromise` and `Effect.runSync` to + * decide what to throw. + * + * **Gotchas** + * + * This function is lossy. Use {@link prettyErrors} or iterate `cause.reasons` + * when you need all failures. + * + * **Example** (Squashing a cause) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.squash(Cause.fail("error")) // => "error" + * Cause.squash(Cause.die("defect")) // => "defect" + * ``` + * + * @see {@link prettyErrors} — non-lossy conversion to `Array` + * @see {@link pretty} — human-readable string rendering + * + * @category destructors + * @since 2.0.0 + */ +export const squash: (self: Cause) => unknown = effect.causeSquash + +/** + * Returns `true` if the cause contains at least one `Fail` reason. + * + * **When to use** + * + * Use to check whether a cause includes typed failures before extracting, + * mapping, or rendering them. + * + * **Example** (Checking for typed errors) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.hasFails(Cause.fail("error")) // => true + * Cause.hasFails(Cause.die("defect")) // => false + * ``` + * + * @see {@link hasDies} — check for defects + * @see {@link hasInterrupts} — check for interruptions + * + * @category predicates + * @since 4.0.0 + */ +export const hasFails: (self: Cause) => boolean = effect.hasFails + +/** + * Returns a `Result` whose success value is the first `Fail` reason in + * the cause, including its annotations. If the cause has no `Fail` reason, the + * failure value is the original cause narrowed to `Cause`, because it + * contains no typed error reasons. + * + * **When to use** + * + * Use when you need the full `Fail` reason from a `Cause`, including + * annotations. + * + * **Example** (Extracting the first Fail reason) + * + * ```ts import.meta.vitest + * import { Cause, Result } from "effect" + * + * Cause.findFail(Cause.fail("error")) // => Result.succeed(Cause.makeFailReason("error")) + * ``` + * + * @see {@link findError} — extract the unwrapped `E` value + * @see {@link findDie} — extract the first `Die` reason + * + * @category filtering + * @since 4.0.0 + */ +export const findFail: (self: Cause) => Result.Result, Cause> = effect.findFail + +/** + * Returns a `Result` whose success value is the first typed error value `E` + * from a `Fail` reason in the cause. If the cause has no `Fail` reason, + * the failure value is the original cause narrowed to `Cause`, because + * it contains no typed error reasons. + * + * **When to use** + * + * Use when you need the first typed error value from a `Cause` as a `Result` + * that preserves the original cause when no match is found. + * + * **Example** (Extracting the first error value) + * + * ```ts import.meta.vitest + * import { Cause, Result } from "effect" + * + * Cause.findError(Cause.fail("error")) // => Result.succeed("error") + * ``` + * + * @see {@link findFail} — extract the full `Fail` reason + * @see {@link findErrorOption} — `Option`-based variant + * + * @category filtering + * @since 4.0.0 + */ +export const findError: (self: Cause) => Result.Result> = effect.findError + +/** + * Returns the first typed error value `E` from a cause wrapped in + * `Option.some`, or `Option.none` if no `Fail` reason exists. + * + * **When to use** + * + * Use when you need the first typed error value from a `Cause` as an `Option`, + * discarding the original cause. + * + * **Example** (Extracting an error as Option) + * + * ```ts import.meta.vitest + * import { Cause, Option } from "effect" + * + * Cause.findErrorOption(Cause.fail("error")) // => Option.some("error") + * Cause.findErrorOption(Cause.die("defect")) // => Option.none() + * ``` + * + * @see {@link findError} — `Result`-based variant + * + * @category filtering + * @since 4.0.0 + */ +export const findErrorOption: (input: Cause) => Option = effect.findErrorOption + +/** + * Returns `true` if the cause contains at least one `Die` reason. + * + * **When to use** + * + * Use to check whether a cause includes defects before extracting or rendering + * them. + * + * **Example** (Checking for defects) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.hasDies(Cause.die("defect")) // => true + * Cause.hasDies(Cause.fail("error")) // => false + * ``` + * + * @see {@link hasFails} — check for typed errors + * @see {@link hasInterrupts} — check for interruptions + * + * @category predicates + * @since 4.0.0 + */ +export const hasDies: (self: Cause) => boolean = effect.hasDies + +/** + * Returns a `Result` whose success value is the first `Die` reason in + * the cause, including its annotations. If the cause has no `Die` reason, the + * failure value is the original cause. + * + * **When to use** + * + * Use when you need the full `Die` reason from a `Cause`, including + * annotations. + * + * **Example** (Extracting the first Die reason) + * + * ```ts import.meta.vitest + * import { Cause, Result } from "effect" + * + * Cause.findDie(Cause.die("defect")) // => Result.succeed(Cause.makeDieReason("defect")) + * ``` + * + * @see {@link findDefect} — extract the unwrapped defect value + * @see {@link findFail} — extract the first `Fail` reason + * + * @category filtering + * @since 4.0.0 + */ +export const findDie: (self: Cause) => Result.Result> = effect.findDie + +/** + * Returns a `Result` whose success value is the first defect value from a + * `Die` reason in the cause. If the cause has no `Die` reason, the + * failure value is the original cause. + * + * **When to use** + * + * Use when you need the first defect value from a `Cause` as a `Result`, + * without the full `Die` reason. + * + * **Example** (Extracting the first defect) + * + * ```ts import.meta.vitest + * import { Cause, Result } from "effect" + * + * Cause.findDefect(Cause.die("defect")) // => Result.succeed("defect") + * ``` + * + * @see {@link findDie} — extract the full `Die` reason + * @see {@link findError} — extract the first typed error + * + * @category filtering + * @since 4.0.0 + */ +export const findDefect: (self: Cause) => Result.Result> = effect.findDefect + +/** + * Returns `true` if the cause contains at least one `Interrupt` reason. + * + * **Example** (Checking for interruptions) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.hasInterrupts(Cause.interrupt(123)) // => true + * Cause.hasInterrupts(Cause.fail("error")) // => false + * ``` + * + * @see {@link hasInterruptsOnly} — `true` only when *all* reasons are interrupts + * @see {@link hasFails} — check for typed errors + * @see {@link hasDies} — check for defects + * + * @category predicates + * @since 4.0.0 + */ +export const hasInterrupts: (self: Cause) => boolean = effect.hasInterrupts + +/** + * Returns a `Result` whose success value is the first `Interrupt` reason + * in the cause, including its annotations. If the cause has no `Interrupt` + * reason, the failure value is the original cause. + * + * **When to use** + * + * Use when you need the first `Interrupt` reason from a `Cause`, including the + * fiber ID and annotations. + * + * **Example** (Extracting the first interrupt) + * + * ```ts import.meta.vitest + * import { Cause, Result } from "effect" + * + * Cause.findInterrupt(Cause.interrupt(42)) // => Result.succeed(Cause.makeInterruptReason(42)) + * ``` + * + * @see {@link interruptors} — collect all interrupting fiber IDs as a `Set` + * + * @category filtering + * @since 4.0.0 + */ +export const findInterrupt: (self: Cause) => Result.Result> = effect.findInterrupt + +/** + * Collects the defined fiber IDs from all `Interrupt` reasons in the + * cause into a `ReadonlySet`. Interrupt reasons without a `fiberId` are + * ignored. Returns an empty set when the cause has no interrupting fiber IDs. + * + * **When to use** + * + * Use when you need interrupting fiber IDs as a set, with absence represented + * as an empty set. + * + * **Example** (Collecting interruptors) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * const cause = Cause.combine( + * Cause.interrupt(1), + * Cause.interrupt(2) + * ) + * + * Cause.interruptors(cause) // => new Set([1, 2]) + * ``` + * + * @see {@link filterInterruptors} — `Result`-based variant + * + * @category getters + * @since 2.0.0 + */ +export const interruptors: (self: Cause) => ReadonlySet = effect.causeInterruptors + +/** + * Returns a `Result` whose success value is the set of defined fiber IDs from + * the cause's `Interrupt` reasons. If the cause has no `Interrupt` + * reason, the failure value is the original cause. + * + * **When to use** + * + * Use when you need absence of interrupt reasons to fail with the original + * cause. + * + * **Gotchas** + * + * Interrupt reasons without a `fiberId` still count as interrupts, so the + * function succeeds with an empty `Set` when every interrupt reason has an + * undefined fiber ID. + * + * **Example** (Extracting interruptors with Result) + * + * ```ts import.meta.vitest + * import { Cause, Result } from "effect" + * + * Cause.filterInterruptors(Cause.interrupt(1)) // => Result.succeed(new Set([1])) + * ``` + * + * @see {@link interruptors} — always-succeeding variant + * + * @category filtering + * @since 4.0.0 + */ +export const filterInterruptors: (self: Cause) => Result.Result, Cause> = + effect.causeFilterInterruptors + +/** + * Converts a `Cause` into an `Array` suitable for logging or + * rethrowing. + * + * **When to use** + * + * Use to convert every renderable failure in a cause into individual `Error` + * values before logging or rethrowing. + * + * **Details** + * + * Each `Fail` and `Die` reason is converted into a standard + * `Error`: + * + * - **Objects / Error instances** — `message`, `name`, `stack`, and `cause` + * are preserved. Extra enumerable properties are copied. Stack traces are + * cleaned up and enriched with span annotations when available. + * - **Strings** — used directly as the `Error` message. + * - **Other primitives** (`null`, `undefined`, numbers, …) — wrapped in an + * `Error` with message `"Unknown error: "`. + * + * `Interrupt` reasons are collected separately. If the cause contains + * **only** interrupts (no `Fail` or `Die`), a single `InterruptError` is + * returned whose `cause` lists the interrupting fiber IDs. + * + * An empty cause returns an empty array. + * + * **Example** (Converting a cause to errors) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.prettyErrors(Cause.fail(new Error("boom")))[0].message // => "boom" + * ``` + * + * @see {@link pretty} — renders the cause as a single string + * @see {@link squash} — lossy collapse to a single thrown value + * + * @category formatting + * @since 3.2.0 + */ +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. + * + * **When to use** + * + * Use to render a whole cause as one human-readable string for logs or + * diagnostics. + * + * **Details** + * + * Delegates to {@link prettyErrors} to convert each reason to an `Error`, + * then joins their stack traces with newlines. Nested `Error.cause` chains + * are rendered inline with indentation: + * + * ```text + * ErrorName: message + * at ... + * at ... { + * [cause]: NestedError: message + * at ... + * } + * ``` + * + * Span annotations are appended to the relevant stack frames when available. + * + * **Gotchas** + * + * Rendering an empty cause produces an empty string because there are no + * errors to render. + * + * **Example** (Rendering a cause) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.pretty(Cause.fail("something went wrong")).includes("something went wrong") // => true + * ``` + * + * @see {@link prettyErrors} — get the individual `Error` instances + * + * @category formatting + * @since 2.0.0 + */ +export const pretty: (cause: Cause) => string = effect.causePretty + +/** + * Base interface for error classes that can be yielded directly inside + * `Effect.gen`. Yielding one of these errors fails the generator with that + * error as the typed failure value. + * + * **Details** + * + * All built-in error classes in this module (`NoSuchElementError`, + * `TimeoutError`, `IllegalArgumentError`, `ExceededCapacityError`, + * `AsyncFiberError`, and `UnknownError`) implement this interface. + * + * **Example** (Yielding an error in Effect.gen) + * + * ```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 + * @since 2.0.0 + */ +export interface YieldableError extends Error, Pipeable, Inspectable { + readonly [Effect.TypeId]: Effect.Variance + [Symbol.iterator](): Effect.EffectIterator> +} + +/** + * Checks whether an arbitrary value is a `NoSuchElementError`. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.isNoSuchElementError(new Cause.NoSuchElementError()) // => true + * Cause.isNoSuchElementError("nope") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isNoSuchElementError: (u: unknown) => u is NoSuchElementError = core.isNoSuchElementError + +/** + * Unique brand for `NoSuchElementError`. + * + * @category type IDs + * @since 4.0.0 + */ +export const NoSuchElementErrorTypeId: "~effect/Cause/NoSuchElementError" = core.NoSuchElementErrorTypeId + +/** + * An error indicating that an expected value was absent. + * + * **When to use** + * + * Use to model APIs that intentionally turn absence into an error. + * + * **Details** + * + * Used by APIs that convert absence into an exception or effect failure, such + * as `Option.getOrThrow`. Implements `YieldableError` so it can be + * yielded directly in `Effect.gen`. + * + * **Gotchas** + * + * Prefer APIs that return `Option` or a typed failure when absence is an + * expected case. This error is mainly for APIs that intentionally turn absence + * into a thrown value or failed effect. + * + * @category errors + * @since 4.0.0 + */ +export interface NoSuchElementError extends YieldableError { + readonly [NoSuchElementErrorTypeId]: typeof NoSuchElementErrorTypeId + readonly _tag: "NoSuchElementError" +} + +/** + * Constructs a `NoSuchElementError` with an optional message. + * + * **When to use** + * + * Use to create the error value for APIs that intentionally fail when an + * expected element is absent. + * + * **Example** (Creating a NoSuchElementError) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * new Cause.NoSuchElementError("Element not found").message // => "Element not found" + * ``` + * + * @see {@link isNoSuchElementError} for checking unknown values + * + * @category constructors + * @since 4.0.0 + */ +export const NoSuchElementError: new(message?: string) => NoSuchElementError = core.NoSuchElementError + +/** + * Checks whether an arbitrary value is a `Done` signal. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.isDone(Cause.Done()) // => true + * Cause.isDone("not done") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isDone: (u: unknown) => u is Done = core.isDone + +/** + * Unique brand for `Done` values. + * + * @category type IDs + * @since 4.0.0 + */ +export const DoneTypeId: "~effect/Cause/Done" = core.DoneTypeId + +/** + * A graceful completion signal for queues and streams. + * + * **When to use** + * + * Use to model normal producer completion through a stream or queue error + * channel. + * + * **Details** + * + * `Done` indicates that a producer has finished normally — no more elements + * will arrive. It is distinct from an error or interruption; it represents + * successful completion. The optional `value` field can carry a final + * leftover payload. + * + * **Example** (Signaling queue completion) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * yield* Queue.offer(queue, 1) + * yield* Queue.end(queue) + * + * yield* Queue.take(queue) + * const result = yield* Effect.flip(Queue.take(queue)) + * return Cause.isDone(result) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category errors + * @since 4.0.0 + */ +export interface Done { + readonly [DoneTypeId]: typeof DoneTypeId + readonly _tag: "Done" + readonly value: A +} + +/** + * Companion namespace for the `Done` interface. + * + * @since 4.0.0 + */ +export declare namespace Done { + /** + * Extracts the value type `A` from a `Done` that may be nested in an + * error union. + * + * @category utility types + * @since 4.0.0 + */ + export type Extract = E extends Done ? L : never + + /** + * Filters a type union to only keep `Done` members. + * + * @category filtering + * @since 4.0.0 + */ + export type Only = E extends Done ? Done : never +} + +/** + * Creates a `Done` signal with an optional value. + * + * **When to use** + * + * Use when you need to construct a low-level pull completion signal directly. + * + * @see {@link done} — create a failing `Effect` with `Done` + * + * @category constructors + * @since 4.0.0 + */ +export const Done: (value?: A) => Done = core.Done + +/** + * Creates an Effect that fails with a `Done` error. Shorthand for + * `Effect.fail(Cause.Done(value))`. + * + * **When to use** + * + * Use when you model stream or queue completion through the error channel. + * + * **Example** (Failing with Done) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * + * const program = Cause.done("finished") + * + * await Effect.runPromiseExit(program) // => Exit.fail(Cause.Done("finished")) + * ``` + * + * @see {@link Done} — create the signal value without an Effect + * + * @category constructors + * @since 4.0.0 + */ +export const done: (value?: A) => Effect.Effect> = core.done + +/** + * Unique brand for `TimeoutError`. + * + * @category type IDs + * @since 4.0.0 + */ +export const TimeoutErrorTypeId: "~effect/Cause/TimeoutError" = effect.TimeoutErrorTypeId + +/** + * Checks whether an arbitrary value is a `TimeoutError`. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.isTimeoutError(new Cause.TimeoutError()) // => true + * Cause.isTimeoutError("nope") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTimeoutError: (u: unknown) => u is TimeoutError = effect.isTimeoutError + +/** + * An error indicating that an operation exceeded its time limit. + * + * **Details** + * + * Produced by `Effect.timeout` and related APIs. Implements + * `YieldableError`. + * + * @category errors + * @since 4.0.0 + */ +export interface TimeoutError extends YieldableError { + readonly [TimeoutErrorTypeId]: typeof TimeoutErrorTypeId + readonly _tag: "TimeoutError" +} + +/** + * Constructs a `TimeoutError` with an optional message. + * + * **Example** (Creating a TimeoutError) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * new Cause.TimeoutError("Operation timed out").message // => "Operation timed out" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const TimeoutError: new(message?: string) => TimeoutError = effect.TimeoutError + +/** + * Unique brand for `IllegalArgumentError`. + * + * @category type IDs + * @since 4.0.0 + */ +export const IllegalArgumentErrorTypeId: "~effect/Cause/IllegalArgumentError" = effect.IllegalArgumentErrorTypeId + +/** + * Checks whether an arbitrary value is an `IllegalArgumentError`. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.isIllegalArgumentError(new Cause.IllegalArgumentError()) // => true + * Cause.isIllegalArgumentError("nope") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isIllegalArgumentError: (u: unknown) => u is IllegalArgumentError = effect.isIllegalArgumentError + +/** + * An error indicating that a function received an argument that violates + * its contract (e.g. negative where positive was expected). + * + * **Details** + * + * Implements `YieldableError`. + * + * @category errors + * @since 4.0.0 + */ +export interface IllegalArgumentError extends YieldableError { + readonly [IllegalArgumentErrorTypeId]: typeof IllegalArgumentErrorTypeId + readonly _tag: "IllegalArgumentError" +} + +/** + * Constructs an `IllegalArgumentError` with an optional message. + * + * **Example** (Creating an IllegalArgumentError) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * new Cause.IllegalArgumentError("Invalid argument").message // => "Invalid argument" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const IllegalArgumentError: new(message?: string) => IllegalArgumentError = effect.IllegalArgumentError + +/** + * Checks whether an arbitrary value is an `ExceededCapacityError`. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.isExceededCapacityError(new Cause.ExceededCapacityError()) // => true + * Cause.isExceededCapacityError("nope") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isExceededCapacityError: (u: unknown) => u is ExceededCapacityError = effect.isExceededCapacityError + +/** + * Unique brand for `ExceededCapacityError`. + * + * @category type IDs + * @since 4.0.0 + */ +export const ExceededCapacityErrorTypeId: "~effect/Cause/ExceededCapacityError" = effect.ExceededCapacityErrorTypeId + +/** + * An error indicating that a bounded resource (queue, pool, semaphore, etc.) + * has exceeded its capacity. + * + * **When to use** + * + * Use to model bounded-resource failures where an operation cannot proceed + * because capacity has been exhausted. + * + * **Details** + * + * Implements `YieldableError`. + * + * @category errors + * @since 4.0.0 + */ +export interface ExceededCapacityError extends YieldableError { + readonly [ExceededCapacityErrorTypeId]: typeof ExceededCapacityErrorTypeId + readonly _tag: "ExceededCapacityError" +} + +/** + * Constructs an `ExceededCapacityError` with an optional message. + * + * **When to use** + * + * Use to create the error value for bounded-resource capacity failures. + * + * **Example** (Creating an ExceededCapacityError) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * new Cause.ExceededCapacityError("Queue full").message // => "Queue full" + * ``` + * + * @see {@link isExceededCapacityError} for checking unknown values + * + * @category constructors + * @since 4.0.0 + */ +export const ExceededCapacityError: new(message?: string) => ExceededCapacityError = effect.ExceededCapacityError + +/** + * Unique brand present on `AsyncFiberError` values and used by + * `isAsyncFiberError` for runtime checks. + * + * @category type IDs + * @since 4.0.0 + */ +export const AsyncFiberErrorTypeId: "~effect/Cause/AsyncFiberError" = effect.AsyncFiberErrorTypeId + +/** + * Checks whether an arbitrary value is an `AsyncFiberError`. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * + * const fiber = Effect.runFork(Effect.void) + * + * const error = new Cause.AsyncFiberError(fiber) + * Cause.isAsyncFiberError(error) // => true + * Cause.isAsyncFiberError("nope") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isAsyncFiberError: (u: unknown) => u is AsyncFiberError = effect.isAsyncFiberError + +/** + * An error that occurs when trying to run an async fiber with Effect.runSync. + * + * **When to use** + * + * Use to inspect failures produced when synchronous runners encounter an effect + * that cannot complete synchronously. + * + * **Details** + * + * The `fiber` property stores the fiber that could not be synchronously + * resolved. This error implements `YieldableError`. + * + * **Example** (Accessing the fiber) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * + * const fiber = Effect.runFork(Effect.void) + * + * const value = new Cause.AsyncFiberError(fiber) + * const isSameFiber = value.fiber === fiber + * isSameFiber // => true + * ``` + * + * @category errors + * @since 4.0.0 + */ +export interface AsyncFiberError extends YieldableError { + readonly [AsyncFiberErrorTypeId]: typeof AsyncFiberErrorTypeId + readonly _tag: "AsyncFiberError" + readonly fiber: Fiber +} + +/** + * Constructs an `AsyncFiberError` for a fiber that could not be resolved + * synchronously. + * + * **When to use** + * + * Use to create the error value for a fiber that could not be completed by a + * synchronous runner. + * + * **Example** (Creating an AsyncFiberError) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * + * const fiber = Effect.runFork(Effect.void) + * + * new Cause.AsyncFiberError(fiber).message // => "An asynchronous Effect was executed with Effect.runSync" + * ``` + * + * @see {@link isAsyncFiberError} for checking unknown values + * + * @category constructors + * @since 4.0.0 + */ +export const AsyncFiberError: new(fiber: Fiber) => AsyncFiberError = effect.AsyncFiberError + +/** + * Unique brand for `UnknownError`. + * + * @category type IDs + * @since 4.0.0 + */ +export const UnknownErrorTypeId: "~effect/Cause/UnknownError" = effect.UnknownErrorTypeId + +/** + * Checks whether an arbitrary value is an `UnknownError`. + * + * **Example** (Checking the runtime type) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * Cause.isUnknownError(new Cause.UnknownError("x")) // => true + * Cause.isUnknownError("nope") // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isUnknownError: (u: unknown) => u is UnknownError = effect.isUnknownError + +/** + * A wrapper for errors whose type is not statically known. + * + * **Details** + * + * Used when a thrown or rejected value is not represented by a more specific + * typed error. The original value is stored in the `cause` property inherited + * from `Error`. Implements `YieldableError`. + * + * @category errors + * @since 4.0.0 + */ +export interface UnknownError extends YieldableError { + readonly [UnknownErrorTypeId]: typeof UnknownErrorTypeId + readonly _tag: "UnknownError" +} + +/** + * Constructs an `UnknownError`. The first argument is the original + * cause (stored in `Error.cause`); the second is an optional human-readable + * message. + * + * **Example** (Creating an UnknownError) + * + * ```ts import.meta.vitest + * import { Cause } from "effect" + * + * new Cause.UnknownError({ raw: true }, "Unexpected value").message // => "Unexpected value" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const UnknownError: new(cause: unknown, message?: string) => UnknownError = effect.UnknownError + +/** + * Attaches metadata to every reason in a `Cause`. + * + * **When to use** + * + * Use to attach diagnostic metadata to every reason in a cause. + * + * **Details** + * + * Annotations are stored as a `Context` on each reason and can be + * retrieved later via {@link reasonAnnotations} or {@link annotations}. + * The runtime uses this to attach stack traces and spans. + * + * - Returns a new `Cause`. + * - By default, existing keys are preserved. Pass `{ overwrite: true }` to + * replace them. + * + * **Example** (Annotating a cause) + * + * ```ts import.meta.vitest + * import { Cause, Context } from "effect" + * + * class RequestId extends Context.Service()("RequestId") {} + * + * 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 + * @see {@link reasonAnnotations} for reading annotations from a single reason + * + * @category annotations + * @since 4.0.0 + */ +export const annotate: { + ( + annotations: Context.Context, + options?: { readonly overwrite?: boolean | undefined } + ): (self: Cause) => Cause + ( + self: Cause, + annotations: Context.Context, + options?: { readonly overwrite?: boolean | undefined } + ): Cause +} = core.causeAnnotate + +/** + * Reads the annotations from a single `Reason` as a `Context`. + * + * **When to use** + * + * Use when you need tracing metadata (e.g. `StackTrace`) from + * a specific reason rather than the whole cause. + * + * **Example** (Reading reason annotations) + * + * ```ts import.meta.vitest + * import { Cause, Context } from "effect" + * + * class RequestId extends Context.Service()("RequestId") {} + * + * const reason = Cause.makeFailReason("error") + * const annotated = reason.annotate(Context.make(RequestId, "req-1")) + * + * Context.getOrUndefined(Cause.reasonAnnotations(annotated), RequestId) // => "req-1" + * ``` + * + * @see {@link annotations} — merged annotations from all reasons in a cause + * + * @category annotations + * @since 4.0.0 + */ +export const reasonAnnotations: (self: Reason) => Context.Context = effect.reasonAnnotations + +/** + * Reads the merged annotations from all reasons in a `Cause`. + * + * **When to use** + * + * Use to read diagnostic metadata merged from the whole cause. + * + * **Gotchas** + * + * When multiple reasons contain the same annotation key, the value from the + * later reason wins. + * + * **Example** (Reading merged annotations) + * + * ```ts import.meta.vitest + * import { Cause, Context } from "effect" + * + * class RequestId extends Context.Service()("RequestId") {} + * + * const cause = Cause.annotate( + * Cause.fail("error"), + * Context.make(RequestId, "req-1") + * ) + * + * Context.getOrUndefined(Cause.annotations(cause), RequestId) // => "req-1" + * ``` + * + * @see {@link reasonAnnotations} — annotations from a single reason + * + * @category annotations + * @since 4.0.0 + */ +export const annotations: (self: Cause) => Context.Context = effect.causeAnnotations + +/** + * Context annotation used to store the stack frame captured at the point of failure. + * + * **When to use** + * + * Use to read the failure stack-frame annotation from a `Reason` when building + * diagnostics, logging, or custom cause renderers. + * + * **Details** + * + * The runtime annotates every reason with this when a stack frame is + * available. Retrieve it via + * `Context.get(Cause.reasonAnnotations(reason), Cause.StackTrace)`. + * + * @see {@link reasonAnnotations} for reading annotations from a single reason + * @see {@link annotations} for reading merged annotations from a cause + * @see {@link InterruptorStackTrace} for the interrupt-specific stack-frame annotation + * + * @category services + * @since 4.0.0 + */ +export class StackTrace extends Context.Service()("effect/Cause/StackTrace") {} + +/** + * Context annotation used to store the stack frame captured at the point of + * interruption. + * + * **When to use** + * + * Use when you need the stack-frame annotation used by interrupt-only cause + * rendering. + * + * **Details** + * + * Similar to `StackTrace` but specific to `Interrupt` reasons. + * + * @see {@link StackTrace} for stack frames attached to failures + * @see {@link reasonAnnotations} for reading annotations from a single reason + * @see {@link annotate} for attaching annotations to a cause + * + * @category services + * @since 4.0.0 + */ +export class InterruptorStackTrace + extends Context.Service()("effect/Cause/InterruptorStackTrace") +{} diff --git a/.repos/effect/packages/effect/src/Channel.ts b/.repos/effect/packages/effect/src/Channel.ts new file mode 100644 index 000000000..cbd124762 --- /dev/null +++ b/.repos/effect/packages/effect/src/Channel.ts @@ -0,0 +1,8832 @@ +/** + * Provides low-level building blocks for streaming data through Effect. + * + * A `Channel` can read input elements, write output elements, fail with a typed + * error, and finish with a typed result while managing resources safely. + * Streams and sinks are built on channels, so most application code uses those + * higher-level modules instead. This module is useful when implementing stream + * operators or specialized streaming workflows. + * + * @since 2.0.0 + */ +// @effect-diagnostics returnEffectInGen:off +import * as Arr from "./Array.ts" +import * as Cause from "./Cause.ts" +import * as Chunk from "./Chunk.ts" +import * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +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, 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" +import type * as Predicate from "./Predicate.ts" +import { hasProperty, isTagged } from "./Predicate.ts" +import * as PubSub from "./PubSub.ts" +import * as Pull from "./Pull.ts" +import * as Queue from "./Queue.ts" +import { TracerTimingEnabled } from "./References.ts" +import * as Result from "./Result.ts" +import * as Schedule from "./Schedule.ts" +import * as Scope from "./Scope.ts" +import * as Semaphore from "./Semaphore.ts" +import * as String from "./String.ts" +import * as Take from "./Take.ts" +import { ParentSpan, type SpanOptions } from "./Tracer.ts" +import type * as Types from "./Types.ts" +import type * as Unify from "./Unify.ts" + +/** + * String literal type used as the unique brand for `Channel` values. + * + * @category type IDs + * @since 4.0.0 + */ +export type TypeId = "~effect/Channel" + +/** + * Runtime identifier stored on `Channel` values and used by `isChannel` to + * recognize them. + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId: TypeId = "~effect/Channel" + +/** + * Checks whether a value is a `Channel`. + * + * **Example** (Checking for channels) + * + * ```ts import.meta.vitest + * import { Channel } from "effect" + * + * const channel = Channel.succeed(42) + * Channel.isChannel(channel) // => true + * Channel.isChannel("not a channel") // => false + * ``` + * + * @category guards + * @since 3.5.4 + */ +export const isChannel = ( + u: unknown +): u is Channel => hasProperty(u, TypeId) + +/** + * A `Channel` is a nexus of I/O operations, which supports both reading and + * writing. A channel may read values of type `InElem` and write values of type + * `OutElem`. When the channel finishes, it yields a value of type `OutDone`. A + * channel may fail with a value of type `OutErr`. + * + * **Details** + * + * Channels are the foundation of Streams: both streams and sinks are built on + * channels. Most users shouldn't have to use channels directly, as streams and + * sinks are much more convenient and cover all common use cases. However, when + * adding new stream and sink operators, or doing something highly specialized, + * it may be useful to use channels directly. + * + * Channels compose in a variety of ways: + * + * - **Piping**: One channel can be piped to another channel, assuming the + * input type of the second is the same as the output type of the first. + * - **Sequencing**: The terminal value of one channel can be used to create + * another channel, and both the first channel and the function that makes + * the second channel can be composed into a channel. + * - **Concatenating**: The output of one channel can be used to create other + * channels, which are all concatenated together. The first channel and the + * function that makes the other channels can be composed into a channel. + * + * **Example** (Typing channels) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // A channel that outputs numbers and requires no environment + * type NumberChannel = Channel.Channel + * + * // A channel that outputs strings, can fail with Error, completes with boolean + * type StringChannel = Channel.Channel + * + * // A channel with all type parameters specified + * type FullChannel = Channel.Channel< + * string, // OutElem - output elements + * Error, // OutErr - output errors + * number, // OutDone - completion value + * number, // InElem - input elements + * string, // InErr - input errors + * boolean, // InDone - input completion + * { db: string } // Env - required environment + * > + * + * const channel: NumberChannel = Channel.succeed(1) + * Effect.runSync(Channel.runCollect(channel)) // => [1] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Channel< + out OutElem, + out OutErr = never, + out OutDone = void, + in InElem = unknown, + in InErr = unknown, + in InDone = unknown, + out Env = never +> extends Variance, Pipeable { + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: ChannelUnify + [Unify.ignoreSymbol]?: ChannelUnifyIgnore +} + +/** + * Type-level unification support for `Channel` values. + * + * **Details** + * + * This preserves all `Channel` type parameters when `Unify` normalizes unions + * or generic return types that include channels. Users normally do not need to + * reference this interface directly. + * + * @category models + * @since 2.0.0 + */ +export interface ChannelUnify extends Effect.EffectUnify { + Channel?: () => A[Unify.typeSymbol] extends + | Channel + | infer _ ? Channel + : never +} + +/** + * Marker used by `Unify` while resolving `Channel` values. + * + * **Details** + * + * It prevents the inherited `Effect` unifier from being selected when the + * channel-specific unifier should preserve `Channel` input, output, and + * environment type parameters. Users normally do not need to reference this + * interface directly. + * + * @category models + * @since 2.0.0 + */ +export interface ChannelUnifyIgnore { + Effect?: true +} + +type TagsWithReason = { + [T in Types.Tags]: Types.ReasonTags> extends never ? never : T +}[Types.Tags] + +/** + * Phantom variance marker for the type parameters of `Channel`. + * + * **Details** + * + * Output element, output error, output done, and environment types are + * covariant. Input element, input error, and input done types are + * contravariant. This is type-level machinery and is not used directly at + * runtime. + * + * @category models + * @since 2.0.0 + */ +export interface Variance< + out OutElem, + out OutErr, + out OutDone, + in InElem, + in InErr, + in InDone, + out Env +> { + readonly [TypeId]: VarianceStruct +} +/** + * Structural encoding used by `Variance` to record each `Channel` type + * parameter's variance. + * + * **Details** + * + * The `_OutElem`, `_OutErr`, `_OutDone`, and `_Env` fields are covariant; the + * `_InElem`, `_InErr`, and `_InDone` fields are contravariant. Users normally + * do not need to reference this interface directly. + * + * @category models + * @since 2.0.0 + */ +export interface VarianceStruct< + out OutElem, + out OutErr, + out OutDone, + in InElem, + in InErr, + in InDone, + out Env +> { + _Env: Types.Covariant + _InErr: Types.Contravariant + _InElem: Types.Contravariant + _InDone: Types.Contravariant + _OutErr: Types.Covariant + _OutElem: Types.Covariant + _OutDone: Types.Covariant +} + +const ChannelProto = { + [TypeId]: { + _Env: identity_, + _InErr: identity_, + _InElem: identity_, + _OutErr: identity_, + _OutElem: identity_ + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +// ----------------------------------------------------------------------------- +// Constructors +// ----------------------------------------------------------------------------- + +/** + * Creates a `Channel` from a transformation function that operates on upstream pulls. + * + * **Example** (Creating channels from transforms) + * + * ```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 + * @since 4.0.0 + */ +export const fromTransform = ( + transform: ( + upstream: Pull.Pull, + scope: Scope.Scope + ) => Effect.Effect, EX, Env> +): Channel< + OutElem, + Pull.ExcludeDone | EX, + OutDone, + InElem, + InErr, + InDone, + Env | EnvX +> => { + const self = Object.create(ChannelProto) + self.transform = (upstream: any, scope: Scope.Scope) => + Effect.catchCause(transform(upstream, scope), (cause) => Effect.succeed(Effect.failCause(cause))) + return self +} + +/** + * Transforms a Channel by applying a function to its Pull implementation. + * + * **Example** (Transforming pull behavior) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // Transform a channel by modifying its pull behavior + * const originalChannel = Channel.fromIterable([1, 2, 3]) + * + * const transformedChannel = Channel.transformPull( + * originalChannel, + * (pull, scope) => + * Effect.succeed( + * Effect.map(pull, (value) => value * 2) + * ) + * ) + * await Effect.runPromise(Channel.runCollect(transformedChannel)) // => [2, 4, 6] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const transformPull = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem2, + OutErr2, + OutDone2, + Env2, + OutErrX, + EnvX +>( + self: Channel, + f: ( + pull: Pull.Pull, + scope: Scope.Scope + ) => Effect.Effect, OutErrX, EnvX> +): Channel< + OutElem2, + Pull.ExcludeDone | OutErrX, + OutDone2, + InElem, + InErr, + InDone, + Env | Env2 | EnvX +> => fromTransform((upstream, scope) => Effect.flatMap(toTransform(self)(upstream, scope), (pull) => f(pull, scope))) + +/** + * Creates a `Channel` from an `Effect` that produces a `Pull`. + * + * **Example** (Creating channels from pulls) + * + * ```ts import.meta.vitest + * import { Cause, Channel, Effect } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const fromPull = ( + effect: Effect.Effect, EX, Env> +): Channel | EX, OutDone, unknown, unknown, unknown, Env | EnvX> => + fromTransform((_, __) => effect) as any + +/** + * Creates a `Channel` from a transformation function that operates on upstream + * pulls, but also provides a forked scope that closes when the resulting + * Channel completes. + * + * **When to use** + * + * Use when building channels that require scoped resource lifecycle management, + * providing both the channel scope and a forked scope that automatically closes + * when the channel completes. + * + * @see {@link fromTransform} for a simpler transformation without a forked scope + * @category constructors + * @since 4.0.0 + */ +export const fromTransformBracket = ( + f: ( + upstream: Pull.Pull, + scope: Scope.Scope, + forkedScope: Scope.Scope + ) => Effect.Effect, EX, Env> +): Channel | EX, OutDone, InElem, InErr, InDone, Env | EnvX> => + fromTransform( + Effect.fnUntraced(function*(upstream, scope) { + const closableScope = Scope.forkUnsafe(scope) + const onCause = (cause: Cause.Cause>) => + Scope.close(closableScope, Pull.doneExitFromCause(cause)) + const pull = yield* Effect.onError( + f(upstream, scope, closableScope), + onCause + ) + return Effect.onError(pull, onCause) + }) + ) + +/** + * Converts a `Channel` back to its underlying transformation function. + * + * **Example** (Extracting channel transforms) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.succeed(42) + * const transform = Channel.toTransform(channel) + * typeof transform // => "function" + * Effect.runSync(Channel.runCollect(channel)) // => [42] + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const toTransform = ( + channel: Channel +): ( + upstream: Pull.Pull, + scope: Scope.Scope +) => Effect.Effect, never, Env> => (channel as any).transform + +/** + * The default chunk size used by channels for batching operations. + * + * **Example** (Reading the default chunk size) + * + * ```ts import.meta.vitest + * import { Channel } from "effect" + * + * Channel.DefaultChunkSize // => 4096 + * ``` + * + * @category constants + * @since 4.0.0 + */ +export const DefaultChunkSize: number = 4096 + +const asyncQueue = ( + scope: Scope.Scope, + f: (queue: Queue.Queue) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + } +) => + Queue.make({ + capacity: options?.bufferSize, + strategy: options?.strategy + }).pipe( + Effect.tap((queue) => Scope.addFinalizer(scope, Queue.shutdown(queue))), + Effect.tap((queue) => Effect.forkIn(Scope.provide(f(queue), scope), scope)) + ) + +/** + * Creates a `Channel` that interacts with a callback function using a queue. + * + * **Example** (Creating channels from callbacks) + * + * ```ts import.meta.vitest + * import { Channel, Effect, Queue } from "effect" + * + * const channel = Channel.callback((queue) => + * Effect.gen(function*() { + * 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 + * @since 4.0.0 + */ +export const callback = ( + f: (queue: Queue.Queue) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + } +): Channel> => + fromTransform((_, scope) => Effect.map(asyncQueue(scope, f, options), Queue.take)) + +/** + * Creates a `Channel` that interacts with a callback function using a queue, emitting arrays. + * + * **Example** (Creating array channels from callbacks) + * + * ```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) + * })) + * await Effect.runPromise(Channel.runCollect(channel)) // => [[1, 2]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const callbackArray = ( + f: (queue: Queue.Queue) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + } +): Channel, E, void, unknown, unknown, unknown, Exclude> => + fromTransform((_, scope) => Effect.map(asyncQueue(scope, f, options), Queue.takeAll)) + +/** + * Creates a `Channel` that lazily evaluates to another channel. + * + * **Example** (Suspending channel creation) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.suspend(() => Channel.succeed(42)) + * Effect.runSync(Channel.runCollect(channel)) // => [42] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const suspend = ( + evaluate: LazyArg> +): Channel => + fromTransform((upstream, scope) => Effect.suspend(() => toTransform(evaluate())(upstream, scope))) + +/** + * Acquires a resource, uses it to build a `Channel`, and guarantees that + * `release` runs with the channel's `Exit` when the channel completes, fails, + * or is interrupted. + * + * **Details** + * + * Acquisition is uninterruptible. If acquisition fails, `use` is not run and + * `release` is not registered. + * + * **Example** (Managing resources with acquire-use-release) + * + * ```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.sync(() => released.push(resource)) + * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channel)), released] // => [["RESOURCE"], ["resource"]] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const acquireUseRelease = ( + acquire: Effect.Effect, + use: (a: A) => Channel, + release: (a: A, exit: Exit.Exit) => Effect.Effect +): Channel => + fromTransformBracket( + Effect.fnUntraced(function*(upstream, scope, forkedScope) { + let option = Option.none() + yield* Scope.addFinalizerExit(forkedScope, (exit) => + Option.isSome(option) + ? release(option.value, exit as any) + : Effect.void) + const value = yield* Effect.uninterruptible(acquire) + option = Option.some(value) + return yield* toTransform(use(value))(upstream, scope) + }) + ) + +/** + * Acquires a resource, emits the acquired value as a single channel element, + * and registers `release` in the channel scope. + * + * **Details** + * + * The release action runs when the channel scope closes and receives the scope + * exit. If acquisition fails, no element is emitted and `release` is not + * registered. + * + * **Example** (Managing resources with acquire-release) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const released: Array = [] + * const channel = Channel.acquireRelease( + * Effect.succeed("resource"), + * (resource, exit) => Effect.sync(() => released.push(resource)) + * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channel)), released] // => [["resource"], ["resource"]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const acquireRelease: { + ( + release: (z: Z, e: Exit.Exit) => Effect.Effect + ): (self: Effect.Effect) => Channel + ( + self: Effect.Effect, + release: (z: Z, e: Exit.Exit) => Effect.Effect + ): Channel +} = dual(2, ( + self: Effect.Effect, + release: (z: Z, e: Exit.Exit) => Effect.Effect +): Channel => + unwrap(Effect.map( + Effect.acquireRelease(self, release), + succeed + ))) + +/** + * Creates a `Channel` from an iterator. + * + * **Example** (Creating channels from iterators) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const channel = Channel.fromIterator(() => numbers[Symbol.iterator]()) + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3, 4, 5] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromIterator = (iterator: LazyArg>): Channel => + fromPull( + Effect.sync(() => { + const iter = iterator() + return Effect.suspend(() => { + const state = iter.next() + return state.done ? Cause.done(state.value) : Effect.succeed(state.value) + }) + }) + ) + +/** + * Creates a `Channel` that emits all elements from an array. + * + * **Example** (Creating channels from arrays) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.fromArray([1, 2, 3, 4, 5]) + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3, 4, 5] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromArray = (array: ReadonlyArray): Channel => + fromPull(Effect.sync(() => { + let index = 0 + return Effect.suspend(() => index >= array.length ? Cause.done() : Effect.succeed(array[index++])) + })) + +/** + * Creates a `Channel` that emits all elements from a chunk. + * + * **Example** (Creating channels from chunks) + * + * ```ts import.meta.vitest + * import { Channel, Chunk, Effect } from "effect" + * + * const chunk = Chunk.make(1, 2, 3) + * const channel = Channel.fromChunk(chunk) + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chunk.toReadonlyArray(chunk)) + +/** + * Creates a `Channel` from an iterator that emits arrays of elements. + * + * **Example** (Batching iterator output) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // Create a channel from a simple iterator + * const numberIterator = (): Iterator => { + * let count = 0 + * return { + * next: () => { + * if (count < 3) { + * return { value: count++, done: false } + * } + * return { value: "finished", done: true } + * } + * } + * } + * + * const channel = Channel.fromIteratorArray(() => numberIterator(), 2) + * Effect.runSync(Channel.runCollect(channel)) // => [[0, 1], [2]] + * ``` + * + * **Example** (Batching generator output) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // Create channel from a generator function + * function* fibonacci(): Generator { + * let a = 0, b = 1 + * for (let i = 0; i < 5; i++) { + * yield a + * ;[a, b] = [b, a + b] + * } + * } + * + * const fibChannel = Channel.fromIteratorArray(() => fibonacci(), 3) + * Effect.runSync(Channel.runCollect(fibChannel)) // => [[0, 1, 1], [2, 3]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromIteratorArray = ( + iterator: LazyArg>, + chunkSize = DefaultChunkSize +): Channel, never, L> => + 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) { + const state = iter.next() + if (state.done) { + if (buffer.length === 0) { + return Cause.done(state.value) + } + done = Option.some(state.value) + break + } + buffer.push(state.value) + } + return Effect.succeed(buffer as any as Arr.NonEmptyReadonlyArray) + }) + }) + ) + +/** + * Creates a `Channel` that emits all elements from an iterable. + * + * **Example** (Creating channels from iterables) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const set = new Set([1, 2, 3]) + * const channel = Channel.fromIterable(set) + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromIterable = (iterable: Iterable): Channel => + fromIterator(() => iterable[Symbol.iterator]()) + +/** + * Creates a `Channel` that emits arrays of elements from an iterable. + * + * **Example** (Batching iterable output) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const channel = Channel.fromIterableArray(numbers, 4) + * Effect.runSync(Channel.runCollect(channel)) // => [[1, 2, 3, 4], [5]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromIterableArray = ( + iterable: Iterable, + chunkSize = DefaultChunkSize +): Channel, never, L> => fromIteratorArray(() => iterable[Symbol.iterator](), chunkSize) + +/** + * Creates a `Channel` that emits a single value and then ends. + * + * **Example** (Creating channels that succeed) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.succeed(42) + * Effect.runSync(Channel.runCollect(channel)) // => [42] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const succeed = (value: A): Channel => fromEffect(Effect.succeed(value)) + +/** + * Creates a `Channel` that immediately ends with the specified value. + * + * **Example** (Ending with a value) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.end("done") + * Effect.runSync(Channel.runCollect(channel)) // => [] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const end = (value: A): Channel => fromPull(Effect.succeed(Cause.done(value))) + +/** + * Creates a `Channel` that immediately ends with the lazily evaluated value. + * + * @category constructors + * @since 4.0.0 + */ +export const endSync = (evaluate: LazyArg): Channel => + fromPull(Effect.sync(() => Cause.done(evaluate()))) + +/** + * Creates a `Channel` that emits a single value computed by a lazy evaluation. + * + * **Example** (Computing values lazily) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * let requests = 0 + * + * const channel = Channel.sync(() => { + * requests += 1 + * return `request-${requests}` + * }) + * Effect.runSync(Channel.runCollect(channel)) // => ["request-1"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const sync = (evaluate: LazyArg): Channel => fromEffect(Effect.sync(evaluate)) + +/** + * Represents a `Channel` that emits no elements. + * + * **Example** (Creating empty channels) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // Create an empty channel + * const emptyChannel = Channel.empty + * + * // Use empty channel in composition + * const combined = Channel.concatWith(emptyChannel, () => Channel.succeed(42)) + * // Will immediately provide the second channel's output + * + * // 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 + * @since 4.0.0 + */ +export const empty: Channel = fromPull(Effect.succeed(Cause.done())) + +/** + * Represents a `Channel` that never completes. + * + * **Example** (Creating non-terminating channels) + * + * ```ts import.meta.vitest + * import { Channel } from "effect" + * + * // Create a channel that never completes + * const neverChannel = Channel.never + * + * // Use in conditional logic + * const withFallback = Channel.concatWith( + * neverChannel, + * () => Channel.succeed("fallback") + * ) + * + * // 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 + * @since 2.0.0 + */ +export const never: Channel = fromPull(Effect.succeed(Effect.never)) + +/** + * Constructs a channel that fails immediately with the specified error. + * + * **Example** (Failing with an error) + * + * ```ts import.meta.vitest + * import { Channel, Effect, Exit } from "effect" + * + * const failedChannel = Channel.fail("Something went wrong") + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))) // => Exit.fail("Something went wrong") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fail = (error: E): Channel => fromPull(Effect.succeed(Effect.fail(error))) + +/** + * Constructs a channel that fails immediately with the specified lazily + * evaluated error. + * + * **When to use** + * + * Use when the error value should be computed each time the channel runs instead + * of when the channel is constructed. + * + * **Example** (Failing with a lazy error) + * + * ```ts import.meta.vitest + * import { Channel, Effect, Exit } from "effect" + * + * let attempts = 0 + * const conditionalError = Channel.failSync(() => { + * attempts += 1 + * return `Error after attempt ${attempts}` + * }) + * const observed = [ + * Effect.runSync(Effect.exit(Channel.runCollect(conditionalError))), + * attempts + * ] // => [Exit.fail("Error after attempt 1"), 1] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failSync = (evaluate: LazyArg): Channel => fromPull(Effect.failSync(evaluate)) + +/** + * Constructs a channel that fails immediately with the specified `Cause`. + * + * **When to use** + * + * Use when the channel failure must preserve a full `Cause`, such as defects, + * interruptions, or combined failures. + * + * **Example** (Failing with causes) + * + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" + * + * const simpleCause = Cause.fail("Simple error") + * const failedChannel = Channel.failCause(simpleCause) + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))) // => Exit.failCause(simpleCause) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCause = (cause: Cause.Cause): Channel => fromPull(Effect.failCause(cause)) + +/** + * Constructs a channel that fails immediately with the specified lazily + * evaluated `Cause`. + * + * **Example** (Failing with lazy causes) + * + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" + * + * // Create a channel that fails with a lazily computed cause + * let attempts = 0 + * const failedChannel = Channel.failCauseSync(() => { + * attempts += 1 + * return Cause.fail(`Runtime error after attempt ${attempts}`) + * }) + * + * const observed = [ + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))), + * attempts + * ] // => [Exit.fail("Runtime error after attempt 1"), 1] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCauseSync = ( + evaluate: LazyArg> +): Channel => fromPull(Effect.failCauseSync(evaluate)) + +/** + * Constructs a channel that fails immediately with the specified defect. + * + * **Example** (Dying with defects) + * + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" + * + * const defect = "Unrecoverable error" + * const diedChannel = Channel.die(defect) + * Effect.runSync(Effect.exit(Channel.runCollect(diedChannel))) // => Exit.failCause(Cause.die(defect)) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const die = (defect: unknown): Channel => failCause(Cause.die(defect)) + +/** + * Uses an effect to write a single value to the channel. + * + * **Example** (Creating channels from effects) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const successChannel = Channel.fromEffect( + * Effect.succeed("Hello from effect!") + * ) + * Effect.runSync(Channel.runCollect(successChannel)) // => ["Hello from effect!"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromEffect = ( + effect: Effect.Effect +): Channel, void, unknown, unknown, unknown, R> => + fromPull( + Effect.sync(() => { + let done = false + return Effect.suspend((): Pull.Pull => { + if (done) return Cause.done() + done = true + return effect + }) + }) + ) + +/** + * Creates a channel that evaluates an effect and uses its successful value as + * the channel's done value without emitting any output elements. + * + * **Details** + * + * If the effect fails, the channel fails with the effect's error. + * + * @category constructors + * @since 4.0.0 + */ +export const fromEffectDone = ( + effect: Effect.Effect +): Channel, A, unknown, unknown, unknown, R> => + fromPull(Effect.succeed(Effect.flatMap(effect, Cause.done))) + +/** + * Uses an effect and discards its result. + * + * @category constructors + * @since 4.0.0 + */ +export const fromEffectDrain = ( + effect: Effect.Effect +): Channel => fromPull(Effect.flatMap(effect, () => Cause.done())) as any + +/** + * Creates a channel from an effect that produces a `Take`. + * + * **Details** + * + * A successful `Take` emits a non-empty array of output elements. A failed + * `Take` fails the channel. A done `Take` completes the channel with its done + * value. + * + * @category constructors + * @since 4.0.0 + */ +export const fromEffectTake = ( + effect: Effect.Effect, E2, R> +): Channel, E | E2, Done, unknown, unknown, unknown, R> => + fromPull(Effect.succeed(Effect.flatMap(effect, Take.toPull))) + +/** + * Creates a channel from a queue. + * + * **Example** (Creating channels from queues) + * + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(3) + * yield* Queue.offerAll(queue, ["item1", "item2", "item3"]) + * yield* Queue.end(queue) + * const channel = Channel.fromQueue(queue) + * return yield* Channel.runCollect(channel) + * }) + * await Effect.runPromise(program) // => ["item1", "item2", "item3"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromQueue = ( + queue: Queue.Dequeue +): Channel> => fromPull(Effect.succeed(Queue.take(queue))) + +/** + * Creates a channel from a queue that emits arrays of elements. + * + * **Example** (Creating batched channels from queues) + * + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(4) + * yield* Queue.offerAll(queue, [1, 2, 3, 4]) + * yield* Queue.end(queue) + * const arrayChannel = Channel.fromQueueArray(queue) + * return yield* Channel.runCollect(arrayChannel) + * }) + * await Effect.runPromise(program) // => [[1, 2, 3, 4]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromQueueArray = ( + queue: Queue.Dequeue +): Channel, Exclude> => fromPull(Effect.succeed(Queue.takeAll(queue))) + +/** + * Creates a channel that forwards upstream input elements, input errors, and + * the upstream done value unchanged. + * + * @category constructors + * @since 2.0.0 + */ +export const identity = (): Channel => + fromTransform((upstream, _scope) => Effect.succeed(upstream)) + +/** + * Creates a channel from a PubSub subscription. + * + * **Example** (Creating channels from subscriptions) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" + * + * class SubscriptionError extends Data.TaggedError("SubscriptionError")<{ + * readonly reason: string + * }> {} + * + * const program = Effect.gen(function*() { + * // Create a PubSub + * const pubsub = yield* PubSub.bounded(32) + * + * // Create a subscription + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish some messages + * yield* PubSub.publish(pubsub, "Hello") + * yield* PubSub.publish(pubsub, "World") + * yield* PubSub.publish(pubsub, "from") + * yield* PubSub.publish(pubsub, "PubSub") + * + * // Create a channel from the subscription + * const channel = Channel.fromSubscription(subscription) + * + * // The channel will receive all published messages + * return channel + * }) + * const result = Effect.scoped(Effect.flatMap(program, Channel.runHead)) + * await Effect.runPromise(result) // => Option.some("Hello") + * + * // Real-time notifications example + * const notificationChannel = Effect.gen(function*() { + * const eventBus = yield* PubSub.unbounded<{ type: string; payload: any }>() + * const userSubscription = yield* PubSub.subscribe(eventBus) + * + * return Channel.fromSubscription(userSubscription) + * }) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromSubscription = ( + subscription: PubSub.Subscription +): Channel => fromPull(Effect.succeed(Effect.onInterrupt(PubSub.take(subscription), () => Cause.done()))) + +/** + * Creates a channel from a PubSub subscription that outputs arrays of values. + * + * **Details** + * + * This constructor creates a channel that reads from a PubSub subscription and outputs + * arrays of values in chunks. It's useful when you want to process multiple values at once + * for better performance. + * + * **Example** (Batching subscription values) + * + * ```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 subscription = yield* PubSub.subscribe(pubsub) + * + * // Create a channel that reads arrays of values + * const channel = Channel.fromSubscriptionArray(subscription) + * + * // Publish some values + * yield* PubSub.publish(pubsub, 1) + * yield* PubSub.publish(pubsub, 2) + * yield* PubSub.publish(pubsub, 3) + * yield* PubSub.publish(pubsub, 4) + * + * // 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.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" + * + * class BatchProcessingError extends Data.TaggedError("BatchProcessingError")<{ + * readonly reason: string + * }> {} + * + * const batchProcessor = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(32) + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Create a channel that processes items in batches + * const batchChannel = Channel.fromSubscriptionArray(subscription) + * + * // Transform to process each batch + * 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.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" + * + * const metricsAggregator = Effect.gen(function*() { + * const metricsPubSub = yield* PubSub.bounded< + * { timestamp: number; value: number } + * >(100) + * const subscription = yield* PubSub.subscribe(metricsPubSub) + * + * // Create a channel that collects metrics in chunks + * const metricsChannel = Channel.fromSubscriptionArray(subscription) + * + * // Transform to calculate aggregate statistics + * const aggregatedChannel = Channel.map(metricsChannel, (metrics) => { + * const values = metrics.map((m) => m.value) + * const sum = values.reduce((a, b) => a + b, 0) + * const avg = sum / values.length + * const min = Math.min(...values) + * const max = Math.max(...values) + * + * return { + * count: values.length, + * sum, + * average: avg, + * min, + * max, + * firstTimestamp: Math.min(...metrics.map((m) => m.timestamp)), + * lastTimestamp: Math.max(...metrics.map((m) => m.timestamp)) + * } + * }) + * + * 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 + * @since 4.0.0 + */ +export const fromSubscriptionArray = ( + subscription: PubSub.Subscription +): Channel> => + fromPull(Effect.succeed(Effect.onInterrupt(PubSub.takeAll(subscription), () => Cause.done()))) + +/** + * Creates a channel from a PubSub that outputs individual values. + * + * **Details** + * + * This constructor creates a channel that reads from a PubSub by automatically + * subscribing to it. The channel outputs individual values as they are published + * to the PubSub, making it ideal for real-time streaming scenarios. + * + * **Example** (Creating channels from PubSubs) + * + * ```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.unbounded({ replay: 3 }) + * + * // Create a channel that reads individual values + * const channel = Channel.fromPubSub(pubsub) + * + * // Publish some values + * yield* PubSub.publish(pubsub, 1) + * yield* PubSub.publish(pubsub, 2) + * yield* PubSub.publish(pubsub, 3) + * + * // 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.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" + * + * const notificationService = Effect.gen(function*() { + * const notificationPubSub = yield* PubSub.unbounded({ replay: 1 }) + * + * // Create a channel for real-time notifications + * const notificationChannel = Channel.fromPubSub(notificationPubSub) + * + * // Transform notifications to add timestamps + * const receivedAt = "2024-01-01T00:00:00.000Z" + * const timestampedChannel = Channel.map(notificationChannel, (message) => ({ + * message, + * receivedAt, + * 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.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" + * + * interface DomainEvent { + * readonly type: string + * readonly payload: unknown + * readonly timestamp: number + * } + * + * const eventProcessor = Effect.gen(function*() { + * const eventPubSub = yield* PubSub.unbounded({ replay: 1 }) + * + * // Create a channel for processing domain events + * const eventChannel = Channel.fromPubSub(eventPubSub) + * + * // Filter and transform events + * const processedChannel = Channel.map(eventChannel, (event) => { + * if (event.type === "user.created") { + * return { + * ...event, + * processed: true, + * processedAt: event.timestamp + 1 + * } + * } + * 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 + * @since 2.0.0 + */ +export const fromPubSub = ( + pubsub: PubSub.PubSub +): Channel => unwrap(Effect.map(PubSub.subscribe(pubsub), fromSubscription)) + +/** + * Creates a channel from a PubSub that outputs arrays of values. + * + * **Details** + * + * This constructor creates a channel that reads from a PubSub by automatically + * subscribing to it and collecting values into arrays. The channel outputs + * arrays of values in chunks, making it ideal for batch processing scenarios. + * + * **Example** (Batching PubSub values) + * + * ```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.unbounded({ replay: 4 }) + * + * // Create a channel that reads arrays of values + * const channel = Channel.fromPubSubArray(pubsub) + * + * // Publish some values + * yield* PubSub.publish(pubsub, 1) + * yield* PubSub.publish(pubsub, 2) + * yield* PubSub.publish(pubsub, 3) + * yield* PubSub.publish(pubsub, 4) + * + * // 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.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" + * + * interface Order { + * readonly id: string + * readonly customerId: string + * readonly items: ReadonlyArray + * readonly total: number + * readonly submittedAt: number + * } + * + * const orderBatchProcessor = Effect.gen(function*() { + * const orderPubSub = yield* PubSub.unbounded({ replay: 1 }) + * + * // Create a channel that processes orders in batches + * const orderChannel = Channel.fromPubSubArray(orderPubSub) + * + * // Transform to process each batch of orders + * const processedChannel = Channel.map(orderChannel, (orderBatch) => { + * const totalRevenue = orderBatch.reduce((sum, order) => sum + order.total, 0) + * const customerCount = new Set(orderBatch.map((order) => + * order.customerId + * )).size + * + * return { + * batchSize: orderBatch.length, + * totalRevenue, + * uniqueCustomers: customerCount, + * firstSubmittedAt: Math.min(...orderBatch.map((order) => order.submittedAt)), + * orders: orderBatch + * } + * }) + * + * 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.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" + * + * interface LogEntry { + * readonly timestamp: number + * readonly level: "info" | "warn" | "error" + * readonly message: string + * readonly source: string + * } + * + * const logAggregator = Effect.gen(function*() { + * const logPubSub = yield* PubSub.unbounded({ replay: 1 }) + * + * // Create a channel that collects logs in batches + * const logChannel = Channel.fromPubSubArray(logPubSub) + * + * // Transform to analyze log batches + * const analysisChannel = Channel.map(logChannel, (logBatch) => { + * const errorCount = logBatch.filter((log) => log.level === "error").length + * const warnCount = logBatch.filter((log) => log.level === "warn").length + * const infoCount = logBatch.filter((log) => log.level === "info").length + * + * const timeRange = { + * start: Math.min(...logBatch.map((log) => log.timestamp)), + * end: Math.max(...logBatch.map((log) => log.timestamp)) + * } + * + * return { + * batchId: `${timeRange.start}-${timeRange.end}`, + * totalEntries: logBatch.length, + * errorCount, + * warnCount, + * infoCount, + * timeRange, + * sources: [...new Set(logBatch.map((log) => log.source))] + * } + * }) + * + * 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 + * @since 4.0.0 + */ +export const fromPubSubArray = (pubsub: PubSub.PubSub): Channel> => + unwrap(Effect.map(PubSub.subscribe(pubsub), fromSubscriptionArray)) + +/** + * Subscribes to a `PubSub` of `Take` values and exposes them as a channel. + * + * **Details** + * + * Output `Take` values are emitted as non-empty arrays. Failed `Take` values + * fail the channel. Done `Take` values complete the channel. + * + * @category constructors + * @since 4.0.0 + */ +export const fromPubSubTake = ( + pubsub: PubSub.PubSub> +): Channel, E, Done> => + unwrap(Effect.map(PubSub.subscribe(pubsub), (sub) => fromEffectTake(PubSub.take(sub)))) + +/** + * Creates a Channel from a Schedule. + * + * @category constructors + * @since 4.0.0 + */ +export const fromSchedule = ( + schedule: Schedule.Schedule +): 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`. + * + * **Details** + * + * Each yielded value is emitted as an output element. The iterator's return + * value becomes the channel's done value. Thrown or rejected iterator errors + * are converted with `onError`. If the channel scope closes early and the + * iterator has a `return` method, that method is called. + * + * @category constructors + * @since 4.0.0 + */ +export const fromAsyncIterable = ( + iterable: AsyncIterable, + onError: (error: unknown) => E +): Channel => + fromTransform(Effect.fnUntraced(function*(_, scope) { + const iter = iterable[Symbol.asyncIterator]() + if (iter.return) { + yield* Scope.addFinalizer(scope, Effect.promise(() => iter.return!())) + } + return Effect.flatMap( + Effect.tryPromise({ + try: () => iter.next(), + catch: onError + }), + (result) => result.done ? Cause.done(result.value) : Effect.succeed(result.value) + ) + })) + +/** + * Creates a channel from an `AsyncIterable`, emitting each yielded value as a + * single-element non-empty array. + * + * **Details** + * + * The iterator's return value becomes the channel's done value. Thrown or + * rejected iterator errors are converted with `onError`. If the channel scope + * closes early and the iterator has a `return` method, that method is called. + * + * @category constructors + * @since 4.0.0 + */ +export const fromAsyncIterableArray = ( + iterable: AsyncIterable, + onError: (error: unknown) => E +): Channel, E, D> => map(fromAsyncIterable(iterable, onError), Arr.of) + +/** + * Maps the output of this channel using the specified function. + * + * **Example** (Mapping channel output) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class TransformError extends Data.TaggedError("TransformError")<{ + * readonly reason: string + * }> {} + * + * // Basic mapping of channel values + * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) + * const doubledChannel = Channel.map(numbersChannel, (n) => n * 2) + * 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()) + * Effect.runSync(Channel.runCollect(upperCaseChannel)) // => ["HELLO", "WORLD", "EFFECT"] + * + * // Complex object transformation + * type User = { id: number; name: string } + * type UserDisplay = { displayName: string; isActive: boolean } + * + * const usersChannel = Channel.fromIterable([ + * { id: 1, name: "Alice" }, + * { id: 2, name: "Bob" } + * ]) + * const displayChannel = Channel.map(usersChannel, (user): UserDisplay => ({ + * displayName: `User: ${user.name}`, + * isActive: true + * })) + * Effect.runSync(Channel.runCollect(displayChannel)) // => [{ displayName: "User: Alice", isActive: true }, { displayName: "User: Bob", isActive: true }] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const map: { + ( + f: (o: OutElem, i: number) => OutElem2 + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (o: OutElem, i: number) => OutElem2 + ): Channel +} = dual( + 2, + ( + self: Channel, + f: (o: OutElem, i: number) => OutElem2 + ): Channel => + transformPull(self, (pull) => + Effect.sync(() => { + let i = 0 + return Effect.map(pull, (o) => f(o, i++)) + })) +) + +/** + * Maps the done value of this channel using the specified function. + * + * @category sequencing + * @since 4.0.0 + */ +export const mapDone: { + ( + f: (o: OutDone) => OutDone2 + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (o: OutDone) => OutDone2 + ): Channel +} = dual( + 2, + ( + self: Channel, + f: (o: OutDone) => OutDone2 + ): Channel => mapDoneEffect(self, (o) => Effect.succeed(f(o))) +) + +/** + * Maps the done value of this channel using the specified effectful function. + * + * **When to use** + * + * Use when the terminal done value transformation needs services or can fail, + * while emitted elements should pass through unchanged. + * + * @category sequencing + * @since 4.0.0 + */ +export const mapDoneEffect: { + ( + f: (o: OutDone) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (o: OutDone) => Effect.Effect + ): Channel +} = dual( + 2, + ( + self: Channel, + f: (o: OutDone) => Effect.Effect + ): Channel => + transformPull(self, (pull) => + Effect.succeed(Pull.catchDone( + pull, + (done) => Effect.flatMap(f(done as OutDone), Cause.done) + ))) +) + +const concurrencyIsSequential = ( + concurrency: number | "unbounded" | undefined +) => concurrency === undefined || (concurrency !== "unbounded" && concurrency <= 1) + +/** + * Maps each output element with an effectful function, preserving the source + * channel's done value. + * + * **When to use** + * + * Use when transforming each channel output needs an Effect, service + * dependency, failure channel, or configured concurrency. + * + * **Details** + * + * The mapping function receives the output element and its zero-based index. + * By default elements are mapped sequentially. Use `options.concurrency` to + * map multiple elements concurrently, and `options.unordered` to allow + * concurrently mapped outputs to be emitted as soon as they complete. + * + * **Example** (Mapping channel output with effects) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) + * const processedChannel = Channel.mapEffect( + * numbersChannel, + * (n) => Effect.succeed(n * n) + * ) + * await Effect.runPromise(Channel.runCollect(processedChannel)) // => [1, 4, 9, 16, 25] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const mapEffect: { + ( + f: (d: OutElem, i: number) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (d: OutElem, i: number) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } + ): Channel +} = dual( + (args) => isChannel(args[0]), + ( + self: Channel, + f: (d: OutElem, i: number) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } + ): Channel => + concurrencyIsSequential(options?.concurrency) + ? mapEffectSequential(self, f) + : mapEffectConcurrent(self, f, options as any) +) + +const mapEffectSequential = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem2, + EX, + RX +>( + self: Channel, + f: (o: OutElem, i: number) => Effect.Effect +): Channel => + fromTransform((upstream, scope) => { + let i = 0 + return Effect.map(toTransform(self)(upstream, scope), Effect.flatMap((o) => f(o, i++))) + }) + +const mapEffectConcurrent = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem2, + EX, + RX +>( + self: Channel, + f: (o: OutElem, i: number) => Effect.Effect, + options: { + readonly concurrency: number | "unbounded" + readonly unordered?: boolean | undefined + } +): Channel => + fromTransformBracket( + Effect.fnUntraced(function*(upstream, scope, forkedScope) { + let i = 0 + const pull = yield* toTransform(self)(upstream, scope) + const concurrencyN = options.concurrency === "unbounded" + ? Number.MAX_SAFE_INTEGER + : options.concurrency + const queue = yield* Queue.bounded>(0) + yield* Scope.addFinalizer(forkedScope, Queue.shutdown(queue)) + + const runFork = Effect.runForkWith(yield* Effect.context()) + const trackFiber = Fiber.runIn(forkedScope) + + if (options.unordered) { + const semaphore = Semaphore.makeUnsafe(concurrencyN) + const release = constant(semaphore.release(1)) + const handle = Effect.matchCauseEffect({ + onFailure: (cause: Cause.Cause) => Effect.flatMap(Queue.failCause(queue, cause), release), + onSuccess: (value: OutElem2) => Effect.flatMap(Queue.offer(queue, value), release) + }) + yield* semaphore.take(1).pipe( + Effect.flatMap(() => pull), + Effect.flatMap((value) => { + trackFiber(runFork(handle(f(value, i++)))) + return Effect.void + }), + Effect.forever({ disableYield: true }), + Effect.catchCause((cause) => + semaphore.withPermits(concurrencyN - 1)( + Queue.failCause(queue, cause) + ) + ), + Effect.forkIn(forkedScope) + ) + } else { + // capacity is n - 2 because + // - 1 for the offer *after* starting a fiber + // - 1 for the current processing fiber + const effects = yield* Queue.bounded< + Effect.Effect>, + OutErr | EX | Cause.Done + >(concurrencyN - 2) + yield* Scope.addFinalizer(forkedScope, Queue.shutdown(effects)) + + yield* Queue.take(effects).pipe( + Effect.flatten, + Effect.flatMap((value) => Queue.offer(queue, value)), + Effect.forever({ disableYield: true }), + Effect.catchCause((cause) => Queue.failCause(queue, cause)), + Effect.forkIn(forkedScope) + ) + + let errorCause: Cause.Cause | undefined + const onExit = (exit: Exit.Exit) => { + if (exit._tag === "Success") return + errorCause = exit.cause + Queue.failCauseUnsafe(queue, exit.cause) + } + yield* pull.pipe( + Effect.flatMap((value) => { + if (errorCause) return Effect.failCause(errorCause) + const fiber = runFork(f(value, i++)) + trackFiber(fiber) + fiber.addObserver(onExit) + return Queue.offer(effects, Fiber.join(fiber)) + }), + Effect.forever({ disableYield: true }), + Effect.catchCause((cause) => + Queue.offer(effects, Exit.failCause(cause)).pipe( + Effect.andThen(Queue.failCause(effects, cause)) + ) + ), + Effect.forkIn(forkedScope) + ) + } + + return Queue.take(queue) + }) + ) + +/** + * Returns a new channel which is the same as this one but applies the given + * function to the input channel’s input elements. + * + * @category sequencing + * @since 2.0.0 + */ +export const mapInput: { + ( + f: (i: InElem2) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (i: InElem2) => Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + f: (i: InElem2) => Effect.Effect +): Channel => + fromTransform((upstream, scope) => + toTransform(self)( + Effect.flatMap(upstream, (el) => f(el)) as Pull.Pull, + scope + ) + )) + +/** + * Returns a new channel which is the same as this one but applies the given + * function to the input errors. + * + * @category sequencing + * @since 2.0.0 + */ +export const mapInputError: { + ( + f: (i: InErr2) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (i: InErr2) => Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + f: (i: InErr2) => Effect.Effect +): Channel => + fromTransform((upstream, scope) => + toTransform(self)( + Effect.catch(upstream, (err): Pull.Pull => { + if (Cause.isDone(err)) return Effect.fail(err) + return Effect.flatMap(f(err), Effect.fail) as Pull.Pull + }), + scope + ) + )) + +/** + * Applies a side effect function to each output element of the channel, + * returning a new channel that emits the same elements. + * + * **Details** + * + * The `tap` function allows you to perform side effects (like logging or + * debugging) on each element emitted by a channel without modifying the + * elements themselves. + * + * **Example** (Tapping channel output) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class LogError extends Data.TaggedError("LogError")<{ + * readonly message: string + * }> {} + * + * // Create a channel that outputs numbers + * 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) => Effect.sync(() => processed.push(n)) + * ) + * + * const observed = [await Effect.runPromise(Channel.runCollect(tappedChannel)), processed] // => [[1, 2, 3], [1, 2, 3]] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const tap: { + ( + f: (d: Types.NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + } + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (d: Types.NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + } + ): Channel +} = dual( + (args) => isChannel(args[0]), + ( + self: Channel, + f: (d: Types.NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + } + ): Channel => + mapEffect(self, (a) => Effect.as(f(a), a), options) +) + +/** + * Maps each output element to a channel and flattens the child channel + * outputs. + * + * **Details** + * + * The source channel's done value is preserved. Child channel done values are + * used only for child-channel completion. By default child channels are run + * sequentially. Use `options.concurrency` and `options.bufferSize` to run child + * channels concurrently. + * + * **Example** (Flat mapping channel output) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class ProcessError extends Data.TaggedError("ProcessError")<{ + * readonly cause: string + * }> {} + * + * // Create a channel that outputs numbers + * const numberChannel = Channel.fromIterable([1, 2, 3]) + * + * // FlatMap each number to create new channels + * const flatMappedChannel = Channel.flatMap( + * numberChannel, + * (n) => + * Channel.fromIterable(Array.from({ length: n }, (_, i) => `item-${n}-${i}`)) + * ) + * + * 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 + * @since 2.0.0 + */ +export const flatMap: { + ( + f: (d: OutElem) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): ( + self: Channel + ) => Channel< + OutElem1, + OutErr1 | OutErr, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + f: (d: OutElem) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): Channel< + OutElem1, + OutErr | OutErr1, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > +} = dual( + (args) => isChannel(args[0]), + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + f: (d: OutElem) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): Channel< + OutElem1, + OutErr | OutErr1, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > => + concurrencyIsSequential(options?.concurrency) + ? flatMapSequential(self, f) + : flatMapConcurrent(self, f, options as any) +) + +const flatMapSequential = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + f: (d: OutElem) => Channel +): Channel< + OutElem1, + OutErr | OutErr1, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => + fromTransform((upstream, scope) => + Effect.map(toTransform(self)(upstream, scope), (pull) => { + let childPull: Effect.Effect | undefined + let childScope: Scope.Closeable | undefined + const makePull: Pull.Pull< + OutElem1, + OutErr | OutErr1, + OutDone, + Env1 + > = Effect.flatMap(pull, (value) => { + childScope ??= Scope.forkUnsafe(scope) + return Effect.flatMapEager(toTransform(f(value))(upstream, childScope), (pull) => { + childPull = catchHalt(pull) as any + return childPull! + }) + }) + const catchHalt = Pull.catchDone((_) => { + childPull = undefined + // we can reuse the scope if the only finalizer is the "fork" one + if (childScope!.state._tag === "Open" && scopeFinalizerCountUnsafe(childScope!) === 1) { + return makePull + } + const close = Scope.close(childScope!, Exit.void) + childScope = undefined + return Effect.flatMap(close, () => makePull) + }) + return Effect.suspend(() => childPull ?? makePull) + }) + ) + +const flatMapConcurrent = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + f: (d: OutElem) => Channel, + options: { + readonly concurrency: number | "unbounded" + readonly bufferSize?: number | undefined + } +): Channel< + OutElem1, + OutErr | OutErr1, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => self.pipe(map(f), mergeAll(options)) + +/** + * Concatenates this channel with another channel created from the terminal value + * of this channel. The new channel is created using the provided function. + * + * **Example** (Concatenating with completion values) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class ConcatError extends Data.TaggedError("ConcatError")<{ + * readonly reason: string + * }> {} + * + * // Create a channel that outputs numbers and terminates with sum + * const numberChannel = Channel.fromIterable([1, 2, 3]).pipe( + * Channel.concatWith((sum: void) => Channel.succeed(`Completed processing`)) + * ) + * + * Effect.runSync(Channel.runCollect(numberChannel)) // => [1, 2, 3, "Completed processing"] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const concatWith: { + ( + f: (leftover: Types.NoInfer) => Channel + ): ( + self: Channel + ) => Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + f: (leftover: Types.NoInfer) => Channel + ): Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > +} = dual(2, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + f: (leftover: Types.NoInfer) => Channel +): Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env +> => + fromTransform((upstream, scope) => + Effect.sync(() => { + let currentPull: Pull.Pull | undefined + const forkedScope = Scope.forkUnsafe(scope) + const makePull = Effect.flatMap(toTransform(self)(upstream, forkedScope), (pull) => { + currentPull = Pull.catchDone(pull, (leftover) => { + return Scope.close(forkedScope, Exit.void).pipe( + Effect.flatMap(() => toTransform(f(leftover as OutDone))(upstream, scope)), + Effect.flatMap((pull) => { + currentPull = pull + return pull + }) + ) + }) + return currentPull + }) + return Effect.suspend(() => currentPull ?? makePull) + }) + )) + +/** + * Concatenates this channel with another channel, so that the second channel + * starts emitting values after the first channel has completed. + * + * **Example** (Concatenating channels) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class ConcatError extends Data.TaggedError("ConcatError")<{ + * readonly reason: string + * }> {} + * + * // Create two channels + * const firstChannel = Channel.fromIterable([1, 2, 3]) + * const secondChannel = Channel.fromIterable(["a", "b", "c"]) + * + * // Concatenate them + * const concatenatedChannel = Channel.concat(firstChannel, secondChannel) + * + * Effect.runSync(Channel.runCollect(concatenatedChannel)) // => [1, 2, 3, "a", "b", "c"] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const concat: { + ( + that: Channel + ): ( + self: Channel + ) => Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + that: Channel + ): Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > +} = dual(2, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + that: Channel +): Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env +> => concatWith(self, (_) => that)) + +/** + * Combines two channels with a stateful pull function. + * + * **When to use** + * + * Use to coordinate pulling from two channels when each output element depends + * on both sides and local state. + * + * **Details** + * + * The combining function receives the current state and pull functions for the + * left and right channels. It returns the next output element together with the + * next state. + * + * @category sequencing + * @since 4.0.0 + */ +export const combine: { + ( + that: Channel, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, + pullRight: Pull.Pull + ) => Effect.Effect + ): (self: Channel) => Channel< + A, + Pull.ExcludeDone, + Cause.Done.Extract, + InElem & InElem2, + InErr & InErr2, + InDone & InDone2, + Env | Env2 | R + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem2, + OutErr2, + OutDone2, + InElem2, + InErr2, + InDone2, + Env2, + S, + A, + E, + R + >( + self: Channel, + that: Channel, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, + pullRight: Pull.Pull + ) => Effect.Effect + ): Channel< + A, + Pull.ExcludeDone, + Cause.Done.Extract, + InElem & InElem2, + InErr & InErr2, + InDone & InDone2, + Env | Env2 | R + > +} = dual(4, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem2, + OutErr2, + OutDone2, + InElem2, + InErr2, + InDone2, + Env2, + S, + A, + E, + R +>( + self: Channel, + that: Channel, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, + pullRight: Pull.Pull + ) => Effect.Effect +): Channel< + A, + Pull.ExcludeDone, + Cause.Done.Extract, + InElem & InElem2, + InErr & InErr2, + InDone & InDone2, + Env | Env2 | R +> => + fromTransform(Effect.fnUntraced(function*(upstream, scope) { + const leftPull = yield* toTransform(self)(upstream, scope) + const rightPull = yield* toTransform(that)(upstream, scope) + let state = s() + return Effect.suspend(() => { + const combinedPull = f(state, leftPull, rightPull) + return Effect.map(combinedPull, ([a, s1]) => { + state = s1 + return a + }) + }) + }))) + +/** + * Runs a fallback channel if this channel completes without emitting any + * output elements. + * + * **Details** + * + * If the source emits at least one element, the source is used unchanged. If + * the source completes before emitting an element, the fallback function + * receives the source done value and returns the replacement channel. + * + * @category sequencing + * @since 4.0.0 + */ +export const orElseIfEmpty: { + ( + f: (leftover: Types.NoInfer) => Channel + ): ( + self: Channel + ) => Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + f: (leftover: Types.NoInfer) => Channel + ): Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > +} = dual(2, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + f: (leftover: Types.NoInfer) => Channel +): Channel< + OutElem | OutElem1, + OutErr1 | OutErr, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env +> => + fromTransform((upstream, scope) => + Effect.sync(() => { + let currentPull: Pull.Pull | undefined + const forkedScope = Scope.forkUnsafe(scope) + const makePull = Effect.flatMap(toTransform(self)(upstream, forkedScope), (pull) => { + const next = pull.pipe( + Effect.tap(() => { + currentPull = pull + return Effect.void + }), + Pull.catchDone((leftover) => + Scope.close(forkedScope, Exit.succeed(leftover)).pipe( + Effect.andThen(toTransform(f(leftover as OutDone))(upstream, scope)), + Effect.flatMap((pull) => { + currentPull = pull + return pull + }) + ) + ) + ) + currentPull = next + return next + }) + return Effect.suspend(() => currentPull ?? makePull) + }) + )) + +/** + * Flattens a channel of channels. + * + * **Example** (Flattening nested channels) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class FlattenError extends Data.TaggedError("FlattenError")<{ + * readonly cause: string + * }> {} + * + * // Create a channel that outputs channels + * const nestedChannels = Channel.fromIterable([ + * Channel.fromIterable([1, 2]), + * Channel.fromIterable([3, 4]), + * Channel.fromIterable([5, 6]) + * ]) + * + * // Flatten the nested channels + * const flattenedChannel = Channel.flatten(nestedChannels) + * + * Effect.runSync(Channel.runCollect(flattenedChannel)) // => [1, 2, 3, 4, 5, 6] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const flatten = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + channels: Channel< + Channel, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + > +): Channel => + flatMap(channels, identity_) + +/** + * Flattens a channel that outputs arrays into a channel that outputs individual elements. + * + * **Example** (Flattening arrays of channel output) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class FlattenError extends Data.TaggedError("FlattenError")<{ + * readonly message: string + * }> {} + * + * // Create a channel that outputs arrays + * const arrayChannel = Channel.fromIterable([ + * [1, 2, 3], + * [4, 5], + * [6, 7, 8, 9] + * ]) + * + * // Flatten the arrays into individual elements + * const flattenedChannel = Channel.flattenArray(arrayChannel) + * + * Effect.runSync(Channel.runCollect(flattenedChannel)) // => [1, 2, 3, 4, 5, 6, 7, 8, 9] + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export const flattenArray = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env +>( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env> +): Channel => + transformPull(self, (pull) => { + let array: ReadonlyArray | undefined + let index = 0 + const pump = Effect.suspend(function loop(): Pull.Pull { + if (array === undefined) { + return Effect.flatMap(pull, (array_) => { + switch (array_.length) { + case 0: + return loop() + case 1: + return Effect.succeed(array_[0]) + default: { + array = array_ + return Effect.succeed(array_[index++]) + } + } + }) + } + const next = array[index++] + if (index >= array.length) { + array = undefined + index = 0 + } + return Effect.succeed(next) + }) + return Effect.succeed(pump) + }) + +/** + * Flattens a channel that emits `Take` values into a channel that emits the + * `Take` outputs directly. + * + * **Details** + * + * Output `Take` values are emitted as non-empty arrays. Failed `Take` values + * fail the returned channel. Done `Take` values complete the returned channel. + * + * @category transforming + * @since 4.0.0 + */ +export const flattenTake = < + OutElem, + OutErr, + OutDone, + OutErr2, + OutDone2, + InElem, + InErr, + InDone, + Env +>( + self: Channel, OutErr2, OutDone2, InElem, InErr, InDone, Env> +): Channel, OutErr | OutErr2, OutDone, InElem, InErr, InDone, Env> => + mapEffectSequential(self, Take.toPull) as any + +/** + * Creates a new channel that consumes all output from the source channel + * but emits nothing, preserving only the completion value. + * + * **Example** (Draining channel output) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // Create a channel that outputs values + * const sourceChannel = Channel.fromIterable([1, 2, 3, 4, 5]) + * + * // Drain all output, keeping only the completion + * const drainedChannel = Channel.drain(sourceChannel) + * + * Effect.runSync(Channel.runCollect(drainedChannel)) // => [] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const drain = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env +>( + self: Channel< + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + > +): Channel => + transformPull(self, (pull) => Effect.succeed(Effect.forever(pull, { disableYield: true }))) + +/** + * Repeats this channel according to the provided schedule. + * + * @category repetition + * @since 4.0.0 + */ +export const repeat: { + ( + schedule: + | Schedule.Schedule, SE, SR> + | (( + $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule + ) => Schedule.Schedule, SE, SR>) + ): ( + self: Channel + ) => Channel + ( + self: Channel, + schedule: + | Schedule.Schedule + | (( + $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule + ) => Schedule.Schedule, SE, SR>) + ): Channel +} = dual(2, ( + self: Channel, + schedule: + | Schedule.Schedule + | (( + $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule + ) => Schedule.Schedule, SE, SR>) +): Channel => + Schedule.toStepWithMetadata(typeof schedule === "function" ? schedule(identity_) : schedule).pipe( + Effect.map((step) => { + let meta = Schedule.CurrentMetadata.defaultValue() + const loop: Channel< + OutElem, + OutErr | SE, + OutDone, + InElem, + InErr, + InDone, + Env | SR + > = concatWith( + provideServiceEffect(self, Schedule.CurrentMetadata, Effect.sync(() => meta)), + (done) => + step(done).pipe( + Effect.map((meta_) => { + meta = meta_ + return loop + }), + Pull.catchDone(() => Effect.succeed(end(done))), + unwrap + ) + ) + return loop + }), + unwrap + )) + +/** + * Repeats this channel forever. + * + * @category repetition + * @since 4.0.0 + */ +export const forever = ( + self: Channel +): Channel => concatWith(self, () => forever(self)) + +/** + * Runs a schedule step for each output element while preserving the emitted + * elements. + * + * **Details** + * + * The schedule receives each output element as input. Schedule delays are + * applied between emitted elements. If the schedule fails, the returned channel + * fails. If the schedule finishes, the returned channel completes with the + * schedule output. + * + * @category sequencing + * @since 4.0.0 + */ +export const schedule: { + ( + schedule: Schedule.Schedule, SE, SR> + ): ( + self: Channel + ) => Channel + ( + self: Channel, + schedule: Schedule.Schedule + ): Channel +} = dual(2, ( + self: Channel, + schedule: Schedule.Schedule +): Channel => + transformPull( + self, + (pull, _scope) => + Effect.map( + Schedule.toStepWithSleep(schedule), + (step) => { + const pullWithStep: Pull.Pull< + OutElem, + OutErr | SE, + OutDone | SO, + SR + > = Effect.tap(pull, step) + return pullWithStep + } + ) + )) + +/** + * Filters the output elements of a channel using a predicate function. + * Elements that don't match the predicate are discarded. + * + * **Example** (Filtering channel output) + * + * ```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) + * Effect.runSync(Channel.runCollect(evenChannel)) // => [2, 4, 6, 8] + * + * // Filter with type refinement + * const mixedChannel = Channel.fromIterable([1, "hello", 2, "world", 3]) + * const numbersOnlyChannel = Channel.filter( + * mixedChannel, + * (value): value is number => typeof value === "number" + * ) + * Effect.runSync(Channel.runCollect(numbersOnlyChannel)) // => [1, 2, 3] + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const filter: { + ( + refinement: Predicate.Refinement + ): ( + self: Channel + ) => Channel + ( + predicate: Predicate.Predicate + ): ( + self: Channel + ) => Channel + ( + self: Channel, + refinement: Predicate.Refinement + ): Channel + ( + self: Channel, + predicate: Predicate.Predicate + ): Channel +} = dual(2, ( + self: Channel, + predicate: Predicate.Predicate +): Channel => + fromTransform((upstream, scope) => + Effect.map( + toTransform(self)(upstream, scope), + (pull) => + Effect.flatMap(pull, function loop(elem): Pull.Pull { + return predicate(elem) + ? Effect.succeed(elem) + : Effect.flatMap(pull, loop) + }) + ) + )) + +/** + * Filters and maps output elements using a `Filter`. + * + * **When to use** + * + * Use to keep only channel output elements accepted by a `Filter` and emit + * each filter success value. + * + * **Details** + * + * Successful filter results are emitted as mapped values. Failed filter + * results are discarded. The source channel's errors and done value are + * preserved. + * + * @see {@link filter} for keeping original output elements with a predicate + * @see {@link filterMapEffect} for using an effectful `Filter` + * @see {@link filterMapArray} for filtering arrays of output elements + * + * @category filtering + * @since 4.0.0 + */ +export const filterMap: { + ( + filter: Filter.Filter + ): ( + self: Channel + ) => Channel + ( + self: Channel, + filter: Filter.Filter + ): Channel +} = dual(2, ( + self: Channel, + filter: Filter.Filter +): Channel => + fromTransform((upstream, scope) => + Effect.map( + toTransform(self)(upstream, scope), + (pull) => + Effect.flatMap(pull, function loop(elem): Pull.Pull { + const result = filter(elem) + return Result.isFailure(result) + ? Effect.flatMap(pull, loop) + : Effect.succeed(result.success) + }) + ) + )) + +/** + * Filters output elements with an effectful predicate. + * + * **When to use** + * + * Use when the keep/discard decision depends on an Effect or service and + * predicate failures should fail the returned channel. + * + * **Details** + * + * Elements for which the predicate succeeds with `true` are emitted. Elements + * for which the predicate succeeds with `false` are discarded. Predicate + * failures fail the returned channel. + * + * @category filtering + * @since 4.0.0 + */ +export const filterEffect: { + ( + predicate: (a: OutElem) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + predicate: (a: OutElem) => Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + predicate: (a: OutElem) => Effect.Effect +): Channel => + fromTransform((upstream, scope) => + Effect.map( + toTransform(self)(upstream, scope), + (pull) => + Effect.flatMap(pull, function loop(elem): Pull.Pull { + return Effect.flatMap( + predicate(elem), + (passes) => + passes + ? Effect.succeed(elem) + : Effect.flatMap(pull, loop) + ) + }) + ) + )) + +/** + * Filters and maps output elements using an effectful `Filter`. + * + * **When to use** + * + * Use to apply effectful logic that can discard channel output elements and + * emit transformed values for the elements that pass. + * + * **Details** + * + * Successful filter results are emitted as mapped values. Failed filter + * results are discarded. Failures from the effectful filter fail the returned + * channel. + * + * @see {@link filterMap} for using a synchronous `Filter` + * @see {@link filterEffect} for effectfully keeping original output elements + * @see {@link mapEffect} for effectfully transforming every output element + * @see {@link filterMapArrayEffect} for effectful filtering of array outputs + * + * @category filtering + * @since 4.0.0 + */ +export const filterMapEffect: { + ( + filter: Filter.FilterEffect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + filter: Filter.FilterEffect + ): Channel +} = dual(2, ( + self: Channel, + filter: Filter.FilterEffect +): Channel => + fromTransform((upstream, scope) => + Effect.map( + toTransform(self)(upstream, scope), + (pull) => + Effect.flatMap(pull, function loop(elem): Pull.Pull { + return Effect.flatMap( + filter(elem), + (result) => + Result.isFailure(result) + ? Effect.flatMap(pull, loop) + : Effect.succeed(result.success) + ) + }) + ) + )) + +/** + * Filters arrays of elements emitted by a channel, applying the filter + * to each element within the arrays and only emitting non-empty filtered arrays. + * + * **Example** (Filtering array output) + * + * ```ts import.meta.vitest + * import { Array, Channel, Effect } from "effect" + * + * const nonEmptyArrayPredicate = Array.isReadonlyArrayNonEmpty + * + * // Create a channel that outputs arrays of mixed data + * const arrayChannel = Channel.fromIterable([ + * Array.make(1, 2, 3, 4, 5), + * Array.make(6, 7, 8, 9, 10), + * Array.make(11, 12, 13, 14, 15) + * ]).pipe(Channel.filter(nonEmptyArrayPredicate)) + * + * // Filter arrays to keep only even numbers + * const evenArraysChannel = Channel.filterArray(arrayChannel, (n) => n % 2 === 0) + * 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 + * const oddChannel = Channel.fromIterable([ + * Array.make(1, 3, 5), + * Array.make(2, 4), + * Array.make(7, 9) + * ]).pipe(Channel.filter(nonEmptyArrayPredicate)) + * const filteredOddChannel = Channel.filterArray(oddChannel, (n) => n % 2 === 0) + * Effect.runSync(Channel.runCollect(filteredOddChannel)) // => [[2, 4]] + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const filterArray: { + ( + refinement: Predicate.Refinement + ): ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ) => Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ( + predicate: Predicate.Predicate> + ): ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ) => Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + refinement: Predicate.Refinement + ): Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + predicate: Predicate.Predicate> + ): Channel, OutErr, OutDone, InElem, InErr, InDone, Env> +} = dual(2, ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + predicate: Predicate.Predicate> +): Channel, OutErr, OutDone, InElem, InErr, InDone, Env> => + transformPull(self, (pull) => + Effect.succeed(Effect.flatMap( + pull, + function loop(arr): Pull.Pull, OutErr, OutDone> { + const passes: Array = [] + for (let i = 0; i < arr.length; i++) { + if (predicate(arr[i] as Types.NoInfer)) { + passes.push(arr[i]) + } + } + return Arr.isReadonlyArrayNonEmpty(passes) + ? Effect.succeed(passes) + : Effect.flatMap(pull, loop) + } + )))) + +/** + * Filters and maps each element inside emitted non-empty arrays using a + * `Filter`. + * + * **Details** + * + * Successful filter results are kept as mapped values. Failed filter results + * are removed from the array. Arrays that become empty are discarded. + * + * @category filtering + * @since 4.0.0 + */ +export const filterMapArray: { + ( + filter: Filter.Filter, B, X> + ): ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ) => Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + filter: Filter.Filter + ): Channel, OutErr, OutDone, InElem, InErr, InDone, Env> +} = dual(2, ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + filter: Filter.Filter +): Channel, OutErr, OutDone, InElem, InErr, InDone, Env> => + transformPull(self, (pull) => + Effect.succeed(Effect.flatMap( + pull, + function loop(arr): Pull.Pull, OutErr, OutDone> { + const passes: Array = [] + for (let i = 0; i < arr.length; i++) { + const result = filter(arr[i]) + if (Result.isSuccess(result)) { + passes.push(result.success) + } + } + return Arr.isReadonlyArrayNonEmpty(passes) + ? Effect.succeed(passes) + : Effect.flatMap(pull, loop) + } + )))) + +/** + * Filters each element inside emitted non-empty arrays with an effectful + * predicate. + * + * **When to use** + * + * Use when filtering array-valued channel outputs requires Effects or services, + * and arrays that become empty should be skipped. + * + * **Details** + * + * The predicate receives the element and its index within the array. Elements + * for which the predicate succeeds with `true` are kept. Arrays that become + * empty are discarded. Predicate failures fail the returned channel. + * + * @category filtering + * @since 4.0.0 + */ +export const filterArrayEffect: { + ( + predicate: (a: Types.NoInfer, index: number) => Effect.Effect + ): ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ) => Channel, OutErr | E, OutDone, InElem, InErr, InDone, Env | R> + ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + predicate: (a: Types.NoInfer, index: number) => Effect.Effect + ): Channel, OutErr | E, OutDone, InElem, InErr, InDone, Env | R> +} = dual(2, ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + predicate: (a: Types.NoInfer, index: number) => Effect.Effect +): Channel, OutErr | E, OutDone, InElem, InErr, InDone, Env | R> => + transformPull(self, (pull) => { + const f = Effect.flatMap(pull, (arr) => Effect.filter(arr, predicate)) + return Effect.succeed(Effect.flatMap( + f, + function loop(arr): Pull.Pull, OutErr | E, OutDone, R> { + return Arr.isReadonlyArrayNonEmpty(arr) ? Effect.succeed(arr) : Effect.flatMap(f, loop) + } + )) + })) + +/** + * Filters and maps each element inside emitted non-empty arrays using an + * effectful `Filter`. + * + * **When to use** + * + * Use when array-valued channel outputs need an effectful filter-map that can + * fail and can discard arrays that become empty. + * + * **Details** + * + * Successful filter results are kept as mapped values. Failed filter results + * are removed from the array. Arrays that become empty are discarded. Failures + * from the effectful filter fail the returned channel. + * + * @category filtering + * @since 4.0.0 + */ +export const filterMapArrayEffect: { + ( + filter: Filter.FilterEffect, B, X, EX, RX> + ): ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ) => Channel, OutErr | EX, OutDone, InElem, InErr, InDone, Env | RX> + ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + filter: Filter.FilterEffect + ): Channel, OutErr | EX, OutDone, InElem, InErr, InDone, Env | RX> +} = dual(2, ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + filter: Filter.FilterEffect +): Channel, OutErr | EX, OutDone, InElem, InErr, InDone, Env | RX> => + transformPull(self, (pull) => + Effect.succeed(Effect.flatMap( + pull, + function loop(arr): Pull.Pull, OutErr | EX, OutDone, RX> { + return Effect.flatMap( + Effect.filterMapEffect(arr, filter as any), + (passes) => + Arr.isReadonlyArrayNonEmpty(passes) + ? Effect.succeed(passes as Arr.NonEmptyReadonlyArray) + : Effect.flatMap(pull, loop) + ) + } + )))) + +/** + * Maps over a channel statefully with an accumulator, where each element can produce multiple output values. + * + * **Example** (Mapping with accumulated state) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // Create a channel with numbers + * const numbersChannel = Channel.fromIterable([1, 2, 3, 4]) + * + * // Use mapAccum to create running sums and emit both current and sum + * const runningSum = Channel.mapAccum( + * numbersChannel, + * () => 0, // initial accumulator state + * (sum, current) => { + * const newSum = sum + current + * // Return [newState, outputValues] + * return [newSum, [current, newSum]] as const + * } + * ) + * // Using with Effect for async processing + * const asyncMapAccum = Channel.mapAccum( + * numbersChannel, + * () => "", + * (acc, value) => + * Effect.gen(function*() { + * const newAcc = acc + value.toString() + * 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 + * @since 4.0.0 + */ +export const mapAccum: { + ( + initial: LazyArg, + f: ( + s: S, + a: Types.NoInfer + ) => + | Effect.Effect], E, R> + | readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => Array) | undefined + } + ): < + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + B, + OutErr | E, + OutDone, + InElem, + InErr, + InDone, + Env | R + > + ( + self: Channel, + initial: LazyArg, + f: ( + s: S, + a: Types.NoInfer + ) => + | Effect.Effect], E, R> + | readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => Array) | undefined + } + ): Channel +} = dual( + (args) => isChannel(args[0]), + ( + self: Channel, + initial: LazyArg, + f: ( + s: S, + a: Types.NoInfer + ) => + | Effect.Effect], E, R> + | readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): Channel => + fromTransform((upstream, scope) => + Effect.map(toTransform(self)(upstream, scope), (pull) => { + let state = initial() + let current: ReadonlyArray | undefined + let index = 0 + let cause: Cause.Cause> | undefined + const pullNext = Effect.matchCauseEffect(pull, { + onFailure(cause_) { + cause = cause_ + const b = options?.onHalt && options.onHalt(state) + return b && b.length > 0 + ? Effect.succeed([state, b] as const) + : Effect.failCause(cause_) + }, + onSuccess(a): Effect.Effect], E, R> { + const b = f(state, a) + return Arr.isArray(b) + ? Effect.succeed(b as any) + : b as any + } + }) + const pump = Effect.suspend(function loop(): Pull.Pull { + if (current === undefined) { + if (cause) return Effect.failCause(cause) + return Effect.flatMap(pullNext, ([newState, values]) => { + state = newState + if (values.length === 0) { + return loop() + } else if (values.length === 1) { + return Effect.succeed(values[0]) + } + current = values + return loop() + }) + } + const next = current[index++] + if (index >= current.length) { + current = undefined + index = 0 + } + return Effect.succeed(next) + }) + return pump + }) + ) +) + +/** + * Transforms a channel statefully by scanning over its output with an accumulator function. + * Emits the intermediate results of the scan operation. + * + * **Example** (Scanning channel output) + * + * ```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) + * Effect.runSync(Channel.runCollect(runningSumChannel)) // => [0, 1, 3, 6, 10, 15] + * // Note: emits the initial value and each intermediate result + * + * // Scan with string concatenation + * const wordsChannel = Channel.fromIterable(["hello", "world", "from", "effect"]) + * const sentenceChannel = Channel.scan( + * wordsChannel, + * "", + * (sentence, word) => sentence === "" ? word : `${sentence} ${word}` + * ) + * Effect.runSync(Channel.runCollect(sentenceChannel)) // => ["", "hello", "hello world", "hello world from", "hello world from effect"] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const scan: { + (initial: S, f: (s: S, a: Types.NoInfer) => S): < + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + S, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + > + ( + self: Channel, + initial: S, + f: (s: S, a: Types.NoInfer) => S + ): Channel +} = dual(3, ( + self: Channel, + initial: S, + f: (s: S, a: Types.NoInfer) => S +): Channel => + scanEffect(self, initial, (s, a) => Effect.succeed(f(s, a)))) + +/** + * Transforms a channel statefully by scanning over its output with an effectful accumulator function. + * Emits the intermediate results of the scan operation. + * + * **When to use** + * + * Use when maintaining accumulated state over channel output requires Effects + * or can fail, while still emitting each intermediate state. + * + * **Example** (Scanning channel output with effects) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class ScanError extends Data.TaggedError("ScanError")<{ + * readonly reason: string + * }> {} + * + * // Create a channel with numbers + * const numbersChannel = Channel.fromIterable([1, 2, 3, 4]) + * + * // Effectful scan with async operations + * const asyncScanChannel = Channel.scanEffect( + * numbersChannel, + * "", + * (acc, value) => + * Effect.gen(function*() { + * return acc + value.toString() + * }) + * ) + * await Effect.runPromise(Channel.runCollect(asyncScanChannel)) // => ["", "1", "12", "123", "1234"] + * + * // Scan with error handling + * const errorHandlingScan = Channel.scanEffect( + * numbersChannel, + * 0, + * (sum, n) => { + * if (n < 0) { + * return Effect.fail(new ScanError({ reason: "negative number" })) + * } + * return Effect.succeed(sum + n) + * } + * ) + * await Effect.runPromise(Channel.runCollect(errorHandlingScan)) // => [0, 1, 3, 6, 10] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const scanEffect: { + (initial: S, f: (s: S, a: Types.NoInfer) => Effect.Effect): < + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + S, + OutErr | E, + OutDone, + InElem, + InErr, + InDone, + Env | R + > + ( + self: Channel, + initial: S, + f: (s: S, a: Types.NoInfer) => Effect.Effect + ): Channel +} = dual(3, ( + self: Channel, + initial: S, + f: (s: S, a: Types.NoInfer) => Effect.Effect +): Channel => + fromTransform((upstream, scope) => + Effect.map(toTransform(self)(upstream, scope), (pull) => { + let state = initial + let isFirst = true + return Effect.suspend(() => { + if (isFirst) { + isFirst = false + return Effect.succeed(state) + } + return Effect.map( + Effect.flatMap(pull, (a) => f(state, a)), + (newState) => { + state = newState + return state + } + ) + }) + }) + )) + +/** + * Catches any cause of failure from the channel and allows recovery by + * creating a new channel based on the caught cause. + * + * **Example** (Recovering from failure causes) + * + * ```ts import.meta.vitest + * import { Cause, Channel, Data, Effect } from "effect" + * + * class ProcessError extends Data.TaggedError("ProcessError")<{ + * readonly reason: string + * }> {} + * + * class RecoveryError extends Data.TaggedError("RecoveryError")<{ + * readonly message: string + * }> {} + * + * // Create a failing channel + * const failingChannel = Channel.fail( + * new ProcessError({ reason: "network error" }) + * ) + * + * // Catch the cause and provide recovery + * const recoveredChannel = Channel.catchCause(failingChannel, (cause) => { + * if (Cause.hasFails(cause)) { + * return Channel.succeed("Recovered from failure") + * } + * return Channel.succeed("Recovered from interruption") + * }) + * + * Effect.runSync(Channel.runCollect(recoveredChannel)) // => ["Recovered from failure"] + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const catchCause: { + ( + f: (d: Cause.Cause) => Channel + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem | OutElem1, + 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: (d: Cause.Cause) => Channel + ): Channel< + OutElem | OutElem1, + 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: (d: Cause.Cause) => Channel +): Channel< + OutElem | OutElem1, + OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => + fromTransform((upstream, scope) => { + let forkedScope = Scope.forkUnsafe(scope) + return Effect.map(toTransform(self)(upstream, forkedScope), (pull) => { + let currentPull: Pull.Pull = pull.pipe( + Effect.catchCause((cause): Pull.Pull => { + if (Pull.isDoneCause(cause)) { + return Effect.failCause(cause as Cause.Cause>) + } + const toClose = forkedScope + forkedScope = Scope.forkUnsafe(scope) + return Scope.close(toClose, Exit.failCause(cause)).pipe( + Effect.andThen(toTransform(f(cause as Cause.Cause))(upstream, forkedScope)), + Effect.flatMap((childPull) => { + currentPull = childPull + return childPull + }) + ) + }) + ) + return Effect.suspend(() => currentPull) + }) + })) + +/** + * Runs an effect with the full failure `Cause` when the channel fails, then + * fails the returned channel with the original cause. + * + * **When to use** + * + * Use when observing the full channel failure `Cause` is needed without + * changing successful output or replacing the original cause. + * + * **Details** + * + * Use this for observing failures, such as logging or metrics. If the observer + * effect fails, that failure can fail the returned channel. + * + * @category error handling + * @since 4.0.0 + */ +export const tapCause: { + ( + f: (d: Cause.Cause) => Effect.Effect + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem, + OutErr | E, + OutDone | void, + InElem, + InErr, + InDone, + Env | R + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + A, + E, + R + >( + self: Channel, + f: (d: Cause.Cause) => Effect.Effect + ): Channel< + OutElem, + OutErr | E, + OutDone | void, + InElem, + InErr, + InDone, + Env | R + > +} = dual(2, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + A, + E, + R +>( + self: Channel, + f: (d: Cause.Cause) => Effect.Effect +): Channel< + OutElem, + OutErr | E, + OutDone | void, + InElem, + InErr, + InDone, + Env | R +> => catchCause(self, (cause) => fromEffectDrain(Effect.flatMap(f(cause), (_) => Effect.failCause(cause))))) + +/** + * Catches causes of failure that match a specific filter, allowing + * conditional error recovery based on the type of failure. + * + * **When to use** + * + * Use to recover a channel only when its full `Cause` satisfies a boolean + * predicate. + * + * **Details** + * + * When the predicate matches, the recovery function receives the original + * cause. When it does not match, the returned channel fails with the original + * cause. + * + * @see {@link catchCauseFilter} for selecting causes with a `Filter` + * @see {@link catchCause} for recovering from every cause + * @see {@link catchIf} for recovering from typed channel errors + * + * @category error handling + * @since 4.0.0 + */ +export const catchCauseIf: { + < + OutErr, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Channel + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(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, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Channel + ): Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > +} = dual(3, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Channel +): Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => + catchCause( + self, + ( + cause + ): Channel< + OutElem1, + OutErr | OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + > => { + return predicate(cause) + ? f(cause) + : failCause(cause as any) + } + )) + +/** + * Recovers from channel failures whose full `Cause` is selected by a `Filter`. + * + * **When to use** + * + * Use when you need to recover a channel only from causes selected by a + * `Filter`, while giving the recovery both the selected value and the original + * `Cause`. + * + * **Details** + * + * When the filter succeeds, the recovery function receives the selected value + * and the original cause. When the filter fails, the returned channel fails + * with the residual cause produced by the filter. + * + * @see {@link catchCauseIf} for selecting causes with a predicate + * @see {@link catchFilter} for selecting typed errors with a `Filter` + * @see {@link catchCause} for recovering from every cause + * + * @category error handling + * @since 4.0.0 + */ +export const catchCauseFilter: { + < + OutErr, + EB, + X extends Cause.Cause, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + filter: Filter.Filter, EB, X>, + f: ( + failure: EB, + cause: Cause.Cause + ) => Channel + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem | OutElem1, + Cause.Cause.Error | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + EB, + X extends Cause.Cause, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + filter: Filter.Filter, EB, X>, + f: ( + failure: EB, + cause: Cause.Cause + ) => Channel + ): Channel< + OutElem | OutElem1, + Cause.Cause.Error | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > +} = dual(3, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + EB, + X extends Cause.Cause, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + filter: Filter.Filter, EB, X>, + f: ( + failure: EB, + cause: Cause.Cause + ) => Channel +): Channel< + OutElem | OutElem1, + Cause.Cause.Error | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => + catchCause( + self, + ( + cause + ): Channel< + OutElem1, + Cause.Cause.Error | OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + > => { + const result = filter(cause) + return Result.isFailure(result) + ? failCause(result.failure) + : f(result.success, cause) + } + )) + +const catch_: { + ( + f: (d: OutErr) => Channel + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem | OutElem1, + 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: (d: OutErr) => Channel + ): Channel< + OutElem | OutElem1, + 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: (d: OutErr) => Channel +): Channel< + OutElem | OutElem1, + OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => catchCauseFilter(self, Cause.findError, (e) => f(e))) + +export { + /** + * Recovers from typed channel errors by running a fallback channel. + * + * @category error handling + * @since 4.0.0 + */ + catch_ as catch +} + +/** + * Runs an effect when the channel fails with a typed error, then preserves the + * original channel failure. + * + * **Details** + * + * The effect is not run for normal channel completion. If the observer effect + * fails, that failure can fail the returned channel. + * + * @category error handling + * @since 4.0.0 + */ +export const tapError: { + ( + f: (d: OutErr) => Effect.Effect + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem, + OutErr | E, + OutDone | void, + InElem, + InErr, + InDone, + Env | R + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + A, + E, + R + >( + self: Channel, + f: (d: OutErr) => Effect.Effect + ): Channel< + OutElem, + OutErr | E, + OutDone | void, + InElem, + InErr, + InDone, + Env | R + > +} = dual(2, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + A, + E, + R +>( + self: Channel, + f: (d: OutErr) => Effect.Effect +): Channel< + OutElem, + OutErr | E, + OutDone | void, + InElem, + InErr, + InDone, + Env | R +> => + transformPull( + self, + (pull) => + Effect.succeed(Effect.tapError( + pull, + (err) => Cause.isDone(err) ? Effect.void : Effect.asVoid(f(err)) + )) + )) + +/** + * Recovers from typed channel errors that match a predicate or refinement. + * + * **When to use** + * + * Use to recover from typed channel errors when a predicate or refinement + * selects the failures that should switch to a recovery channel. + * + * **Details** + * + * Matching errors are handled by the recovery function. Non-matching errors + * are handled by `orElse` when provided. Without `orElse`, non-matching errors + * are re-failed. + * + * @see {@link catch_ catch} for recovering from every typed channel error + * @see {@link catchFilter} for selecting typed errors with a `Filter` + * @see {@link catchTag} for selecting tagged typed errors + * @see {@link catchCauseFilter} for selecting full causes with a `Filter` + * + * @category error handling + * @since 4.0.0 + */ +export const catchIf: { + < + OutErr, + EB extends OutErr, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + refinement: Predicate.Refinement, + f: (failure: EB) => Channel, + orElse?: + | (( + failure: Exclude + ) => Channel) + | undefined + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem | OutElem1 | Exclude, + OutErr1 | OutErr2 | (OutElem2 extends Types.unassigned ? Exclude : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > + < + OutErr, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + predicate: Predicate.Predicate, + f: (failure: OutErr) => Channel, + orElse?: + | (( + failure: OutErr + ) => Channel) + | undefined + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem | OutElem1 | Exclude, + OutErr1 | OutErr2 | (OutElem2 extends Types.unassigned ? OutErr : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + EB extends OutErr, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + self: Channel, + refinement: Predicate.Refinement, + f: (failure: EB) => Channel, + orElse?: + | (( + failure: Exclude + ) => Channel) + | undefined + ): Channel< + OutElem | OutElem1 | Exclude, + OutErr1 | OutErr2 | (OutElem2 extends Types.unassigned ? Exclude : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + self: Channel, + predicate: Predicate.Predicate, + f: (failure: OutErr) => Channel, + orElse?: + | (( + failure: OutErr + ) => Channel) + | undefined + ): Channel< + OutElem | OutElem1 | Exclude, + OutErr1 | OutErr2 | (OutElem2 extends Types.unassigned ? OutErr : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > +} = dual((args) => isChannel(args[0]), < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = never, + OutErr2 = OutErr, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never +>( + self: Channel, + predicate: Predicate.Predicate, + f: (failure: OutErr) => Channel, + orElse?: + | (( + failure: OutErr + ) => Channel) + | undefined +): Channel< + OutElem | OutElem1 | OutElem2, + OutErr1 | OutErr2, + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 +> => + catch_( + self, + (err): Channel< + OutElem1 | OutElem2, + OutErr1 | OutErr2, + OutDone1 | OutDone2, + InElem1 & InElem2, + InErr1 & InErr2, + InDone1 & InDone2, + Env1 | Env2 + > => { + return predicate(err) + ? f(err) + : orElse + ? orElse(err) + : fail(err as any) as any + } + )) + +/** + * Recovers from typed channel errors selected by a `Filter`. + * + * **When to use** + * + * Use to recover from channel errors with a reusable `Filter` when matching + * can also narrow or transform the error before choosing the recovery channel. + * + * **Details** + * + * Successful filter results are handled by the recovery function. Failed + * filter results are handled by `orElse` when provided. Without `orElse`, + * failed filter results are re-failed. + * + * @see {@link catchIf} for selecting typed errors with a predicate + * @see {@link catchTag} for selecting tagged typed errors + * @see {@link catchCauseFilter} for selecting full causes with a `Filter` + * + * @category error handling + * @since 4.0.0 + */ +export const catchFilter: { + < + OutErr, + EB, + X, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + filter: Filter.Filter, + f: (failure: EB) => Channel, + orElse?: + | (( + failure: X + ) => Channel) + | undefined + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem | OutElem1 | Exclude, + OutErr1 | OutErr2 | (OutElem2 extends Types.unassigned ? X : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + EB, + X, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + self: Channel, + filter: Filter.Filter, + f: (failure: EB) => Channel, + orElse?: + | (( + failure: X + ) => Channel) + | undefined + ): Channel< + OutElem | OutElem1 | Exclude, + OutErr1 | OutErr2 | (OutElem2 extends Types.unassigned ? X : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > +} = dual((args) => isChannel(args[0]), < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + EB, + X, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = never, + OutErr2 = X, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never +>( + self: Channel, + filter: Filter.Filter, + f: (failure: EB) => Channel, + orElse?: + | (( + failure: X + ) => Channel) + | undefined +): Channel< + OutElem | OutElem1 | OutElem2, + OutErr1 | OutErr2, + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 +> => + catch_( + self, + (err): Channel< + OutElem1 | OutElem2, + OutErr1 | OutErr2, + OutDone1 | OutDone2, + InElem1 & InElem2, + InErr1 & InErr2, + InDone1 & InDone2, + Env1 | Env2 + > => { + const result = filter(err) + return Result.isFailure(result) + ? orElse + ? orElse(result.failure) + : fail(result.failure as any) as any + : f(result.success) + } + )) + +/** + * Recovers from tagged channel errors whose `_tag` matches one or more tags. + * + * **Details** + * + * Matching tagged errors are handled by the recovery function. Non-matching + * errors are handled by `orElse` when provided. Without `orElse`, + * non-matching errors are re-failed. + * + * @category error handling + * @since 4.0.0 + */ +export const catchTag: { + < + OutErr, + const K extends Types.Tags | Arr.NonEmptyReadonlyArray>, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + k: K, + f: ( + e: Types.ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Channel, + orElse?: + | (( + e: Types.ExcludeTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Channel) + | undefined + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >(self: Channel) => Channel< + OutElem | OutElem1 | Exclude, + | OutErr1 + | OutErr2 + | (OutElem2 extends Types.unassigned + ? Types.ExcludeTag ? K[number] : K> + : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + const K extends Types.Tags | Arr.NonEmptyReadonlyArray>, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + self: Channel, + k: K, + f: ( + e: Types.ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Channel, + orElse?: + | (( + e: Types.ExcludeTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Channel) + | undefined + ): Channel< + OutElem | OutElem1 | Exclude, + | OutErr1 + | OutErr2 + | (OutElem2 extends Types.unassigned + ? Types.ExcludeTag ? K[number] : K> + : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > +} = dual((args) => isChannel(args[0]), < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + const K extends Types.Tags | Arr.NonEmptyReadonlyArray>, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = never, + OutErr2 = Types.ExcludeTag ? K[number] : K>, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never +>( + self: Channel, + k: K, + f: ( + e: Types.ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Channel, + orElse?: + | (( + e: Types.ExcludeTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Channel) + | undefined +): Channel< + OutElem | OutElem1 | OutElem2, + OutErr1 | OutErr2, + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 +> => { + const pred = Array.isArray(k) + ? ((e: OutErr): e is any => hasProperty(e, "_tag") && k.includes(e._tag)) + : isTagged(k as string) + return catchIf(self, pred, f, orElse as any) as any +}) + +/** + * Catches a specific reason within a tagged error. + * + * **Example** (Recovering from nested reasons) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class RateLimitError extends Data.TaggedError("RateLimitError")<{ + * retryAfter: number + * }> {} + * + * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ + * limit: number + * }> {} + * + * class AiError extends Data.TaggedError("AiError")<{ + * reason: RateLimitError | QuotaExceededError + * }> {} + * + * 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 + * @since 4.0.0 + */ +export const catchReason: { + < + OutErr, + K extends Types.Tags, + RK extends Types.ReasonTags, K>>, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + errorTag: K, + reasonTag: RK, + f: ( + reason: Types.ExtractReason, K>, RK>, + error: Types.NarrowReason, K>, RK> + ) => Channel, + orElse?: + | (( + reason: Types.ExcludeReason, K>, RK>, + error: Types.OmitReason, K>, RK> + ) => Channel) + | undefined + ): < + OutElem, + OutDone, + InElem, + InErr, + InDone, + Env + >( + self: Channel + ) => Channel< + OutElem | OutElem1 | Exclude, + | Types.ExcludeTag + | OutErr1 + | OutErr2 + | (OutElem2 extends Types.unassigned ? Types.ExtractTag : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + K extends Types.Tags, + RK extends Types.ReasonTags, K>>, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + self: Channel, + errorTag: K, + reasonTag: RK, + f: ( + reason: Types.ExtractReason, K>, RK>, + error: Types.NarrowReason, K>, RK> + ) => Channel, + orElse?: + | (( + reason: Types.ExcludeReason, K>, RK>, + error: Types.OmitReason, K>, RK> + ) => Channel) + | undefined + ): Channel< + OutElem | OutElem1 | Exclude, + | Types.ExcludeTag + | OutErr1 + | OutErr2 + | (OutElem2 extends Types.unassigned ? Types.ExtractTag : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 + > +} = dual((args) => isChannel(args[0]), < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + K extends Types.Tags, + RK extends Types.ReasonTags, K>>, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never +>( + self: Channel, + errorTag: K, + reasonTag: RK, + f: ( + reason: Types.ExtractReason, K>, RK>, + error: Types.NarrowReason, K>, RK> + ) => Channel, + orElse?: + | (( + reason: Types.ExcludeReason, K>, RK>, + error: Types.OmitReason, K>, RK> + ) => Channel) + | undefined +): Channel< + OutElem | OutElem1 | Exclude, + | Types.ExcludeTag + | OutErr1 + | OutErr2 + | (OutElem2 extends Types.unassigned ? Types.ExtractTag : never), + OutDone | OutDone1 | OutDone2, + InElem & InElem1 & InElem2, + InErr & InErr1 & InErr2, + InDone & InDone1 & InDone2, + Env | Env1 | Env2 +> => + catch_( + self, + (error): Channel< + OutElem1 | Exclude, + OutErr1 | OutErr2, + OutDone1 | OutDone2, + InElem1 & InElem2, + InErr1 & InErr2, + InDone1 & InDone2, + Env1 | Env2 + > => { + if (isTagged(error, errorTag) && hasProperty(error, "reason")) { + const reason = error.reason as Types.ExcludeReason, K>, RK> + if (isTagged(reason, reasonTag)) { + return f(reason as any, error as any) + } + return orElse ? orElse(reason, error as any) as any : fail(error) as any + } + return fail(error) as any + } + )) + +/** + * Catches multiple reasons within a tagged error using an object of handlers. + * + * @category error handling + * @since 4.0.0 + */ +export const catchReasons: { + < + K extends Types.Tags, + OutErr, + Cases extends { + [RK in Types.ReasonTags, K>>]+?: ( + reason: Types.ExtractReason, K>, RK>, + error: Types.NarrowReason, K>, RK> + ) => Channel + }, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: Types.ExcludeReason, K>, Extract>, + error: Types.OmitReason, K>, Extract> + ) => Channel) + | undefined + ): ( + self: Channel + ) => Channel< + | OutElem + | Exclude + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? OutElem1 : never + }[keyof Cases], + | Types.ExcludeTag + | OutErr2 + | (OutElem2 extends Types.unassigned ? Types.ExtractTag : never) + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? OutErr1 : never + }[keyof Cases], + | OutDone + | OutDone2 + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? OutDone1 : never + }[keyof Cases], + & InElem + & InElem2 + & { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? InElem1 : never + }[keyof Cases], + & InErr + & InErr2 + & { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? InErr1 : never + }[keyof Cases], + & InDone + & InDone2 + & { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? InDone1 : never + }[keyof Cases], + | Env + | Env2 + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? Env1 : never + }[keyof Cases] + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + K extends Types.Tags, + Cases extends { + [RK in Types.ReasonTags>]+?: ( + reason: Types.ExtractReason, RK>, + error: Types.NarrowReason, RK> + ) => Channel + }, + OutElem2 = Types.unassigned, + OutErr2 = never, + OutDone2 = never, + InElem2 = unknown, + InErr2 = unknown, + InDone2 = unknown, + Env2 = never + >( + self: Channel, + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: Types.ExcludeReason, K>, Extract>, + error: Types.OmitReason, K>, Extract> + ) => Channel) + | undefined + ): Channel< + | OutElem + | Exclude + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? OutElem1 : never + }[keyof Cases], + | Types.ExcludeTag + | OutErr2 + | (OutElem2 extends Types.unassigned ? Types.ExtractTag : never) + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? OutErr1 : never + }[keyof Cases], + | OutDone + | OutDone2 + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? OutDone1 : never + }[keyof Cases], + & InElem + & InElem2 + & { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? InElem1 : never + }[keyof Cases], + & InErr + & InErr2 + & { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? InErr1 : never + }[keyof Cases], + & InDone + & InDone2 + & { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? InDone1 : never + }[keyof Cases], + | Env + | Env2 + | { + [RK in keyof Cases]: Cases[RK] extends + (...args: Array) => Channel ? Env1 : never + }[keyof Cases] + > +} = dual((args) => isChannel(args[0]), (self, errorTag, cases, orElse) => { + let keys: Set + return catch_(self, (error) => { + if ( + isTagged(error, errorTag) && + hasProperty(error, "reason") && + hasProperty(error.reason, "_tag") && + String.isString(error.reason._tag) + ) { + const reason = error.reason as { readonly _tag: string } + keys ??= new Set(Object.keys(cases)) + if (keys.has(reason._tag)) { + return (cases as any)[reason._tag](reason as any, error) + } + return orElse ? orElse(reason, error) as any : fail(error) as any + } + return fail(error) as any + }) +}) + +/** + * Promotes nested reason errors into the channel error, replacing the parent error. + * + * **Example** (Promoting nested reasons) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" + * + * class RateLimitError extends Data.TaggedError("RateLimitError")<{ + * retryAfter: number + * }> {} + * + * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ + * limit: number + * }> {} + * + * class AiError extends Data.TaggedError("AiError")<{ + * reason: RateLimitError | QuotaExceededError + * }> {} + * + * 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 + * @since 4.0.0 + */ +export const unwrapReason: { + < + K extends TagsWithReason, + OutErr + >( + errorTag: K + ): ( + self: Channel + ) => Channel< + OutElem, + Types.ExcludeTag | Types.ReasonOf>, + OutDone, + InElem, + InErr, + InDone, + Env + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + K extends TagsWithReason + >( + self: Channel, + errorTag: K + ): Channel< + OutElem, + Types.ExcludeTag | Types.ReasonOf>, + OutDone, + InElem, + InErr, + InDone, + Env + > +} = dual(2, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + K extends TagsWithReason +>( + self: Channel, + errorTag: K +): Channel< + OutElem, + Types.ExcludeTag | Types.ReasonOf>, + OutDone, + InElem, + InErr, + InDone, + Env +> => + catchFilter( + self, + (error) => + isTagged(error, errorTag) && hasProperty(error, "reason") ? Result.succeed(error.reason) : Result.fail(error), + fail + ) as any) + +/** + * Returns a new channel, which is the same as this one, except the failure + * value of the returned channel is created by applying the specified function + * to the failure value of this channel. + * + * @category error handling + * @since 2.0.0 + */ +export const mapError: { + ( + f: (err: OutErr) => OutErr2 + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (err: OutErr) => OutErr2 + ): Channel +} = dual(2, ( + self: Channel, + f: (err: OutErr) => OutErr2 +): Channel => catch_(self, (err) => fail(f(err)))) + +/** + * Converts all errors in the channel to defects (unrecoverable failures). + * This is useful when you want to treat errors as programming errors. + * + * **Example** (Converting failures to defects) + * + * ```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 error = new ValidationError({ field: "email" }) + * const failingChannel = Channel.fail(error) + * + * // Convert failures to defects + * const fatalChannel = Channel.orDie(failingChannel) + * + * Effect.runSync(Effect.exit(Channel.runCollect(fatalChannel))) // => Exit.failCause(Cause.die(error)) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const orDie = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env +>( + self: Channel +): Channel => catch_(self, die) + +/** + * Ignores all errors in the channel, converting them to an empty channel. + * + * **Details** + * + * Use the `log` option to emit the full {@link Cause} when the channel fails. + * + * @category error handling + * @since 4.0.0 + */ +export const ignore: < + Arg extends Channel | { + readonly log?: boolean | Severity | undefined + } | undefined = { + readonly log?: boolean | Severity | undefined + } +>( + selfOrOptions: Arg, + options?: { + readonly log?: boolean | Severity | undefined + } | undefined +) => [Arg] extends + [Channel] + ? Channel + : ( + self: Channel + ) => Channel = dual( + (args) => isChannel(args[0]), + ( + self: Channel, + options?: { + readonly log?: boolean | Severity | undefined + } | undefined + ): Channel => { + if (!options?.log) { + return catch_(self, () => empty) + } + const logEffect = Effect.logWithLevel(options.log === true ? undefined : options.log) + return catch_( + tapCause(self, (cause) => Cause.hasFails(cause) ? logEffect(cause) : Effect.void), + () => empty + ) + } + ) + +const ignoreCause_ = < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env +>( + self: Channel +): Channel => catchCause(self, () => empty) + +/** + * Ignores all errors in the channel including defects, converting them to an empty channel. + * + * **When to use** + * + * Use when a channel should become best-effort and all failure causes, including + * defects and interruptions, can be converted to empty output. + * + * **Details** + * + * Use the `log` option to emit the full {@link Cause} when the channel fails. + * + * @category error handling + * @since 4.0.0 + */ +export const ignoreCause: < + Arg extends Channel | { + readonly log?: boolean | Severity | undefined + } | undefined = { + readonly log?: boolean | Severity | undefined + } +>( + selfOrOptions: Arg, + options?: { + readonly log?: boolean | Severity | undefined + } | undefined +) => [Arg] extends + [Channel] + ? Channel + : ( + self: Channel + ) => Channel = dual( + (args) => isChannel(args[0]), + ( + self: Channel, + options?: { readonly log?: boolean | Severity | undefined } | undefined + ): Channel => { + if (!options?.log) return ignoreCause_(self) + const logEffect = Effect.logWithLevel(options.log === true ? undefined : options.log) + return ignoreCause_(tapCause(self, (cause) => logEffect(cause))) + } + ) + +/** + * Returns a new channel that retries this channel according to the specified + * schedule whenever it fails. + * + * @category error handling + * @since 4.0.0 + */ +export const retry: { + ( + schedule: + | Schedule.Schedule, SE, SR> + | (( + $: ( + _: Schedule.Schedule, SE, SR> + ) => Schedule.Schedule + ) => Schedule.Schedule, SE, SR>) + ): ( + self: Channel + ) => Channel + ( + self: Channel, + schedule: + | Schedule.Schedule + | (( + $: ( + _: Schedule.Schedule, SE, SR> + ) => Schedule.Schedule + ) => Schedule.Schedule, SE, SR>) + ): Channel +} = dual(2, ( + self: Channel, + schedule: + | Schedule.Schedule + | (( + $: (_: Schedule.Schedule, SE, R>) => Schedule.Schedule + ) => Schedule.Schedule, SE, SR>) +): Channel => + suspend(() => { + let step: ((input: OutErr) => Pull.Pull, SE, SO, SR>) | undefined = undefined + let meta = Schedule.CurrentMetadata.defaultValue() + const selfWithMeta = provideServiceEffect(self, Schedule.CurrentMetadata, Effect.sync(() => meta)) + const withReset = onFirst(selfWithMeta, () => { + step = undefined + return Effect.void + }) + const resolvedSchedule = typeof schedule === "function" ? schedule(identity_) : schedule + const loop: Channel< + OutElem, + OutErr | SE, + OutDone, + InElem, + InErr, + InDone, + Env | SR + > = catch_( + withReset, + Effect.fnUntraced( + function*(error) { + if (!step) { + step = yield* Schedule.toStepWithMetadata(resolvedSchedule) + } + meta = yield* step(error) + return loop + }, + (effect, error) => Pull.catchDone(effect, () => Effect.succeed(fail(error))), + unwrap + ) + ) + return loop + })) + +/** + * Maps each output element to a channel and emits values from the most recent + * active child channels. + * + * **Details** + * + * With the default concurrency of `1`, starting a new child channel interrupts + * the previous child channel. Use `options.concurrency` to allow more active + * child channels. The source channel's done value is preserved. + * + * **Example** (Switching mapped channels) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class SwitchError extends Data.TaggedError("SwitchError")<{ + * readonly reason: string + * }> {} + * + * // Create a channel that outputs numbers + * const numberChannel = Channel.fromIterable([1, 2, 3]) + * + * // Switch to new channels based on each value + * const switchedChannel = Channel.switchMap( + * numberChannel, + * (n) => Channel.fromIterable([`value-${n}`]) + * ) + * + * await Effect.runPromise(Channel.runCollect(switchedChannel)) // => ["value-3"] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const switchMap: { + ( + f: (d: OutElem) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): ( + self: Channel + ) => Channel< + OutElem1, + OutErr1 | OutErr, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + f: (d: OutElem) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): Channel< + OutElem1, + OutErr | OutErr1, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > +} = dual( + (args) => isChannel(args[0]), + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + f: (d: OutElem) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): Channel< + OutElem1, + OutErr | OutErr1, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > => + self.pipe( + map(f), + mergeAll({ + ...options, + concurrency: options?.concurrency ?? 1, + switch: true + }) + ) +) + +/** + * Merges multiple channels with specified concurrency and buffering options. + * + * **When to use** + * + * Use when channel outputs are themselves channels and multiple inner channels + * should run with configured concurrency and buffering. + * + * **Example** (Merging nested channels) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class MergeAllError extends Data.TaggedError("MergeAllError")<{ + * readonly reason: string + * }> {} + * + * // Create channels that output other channels + * const nestedChannels = Channel.fromIterable([ + * Channel.fromIterable([1, 2]), + * Channel.fromIterable([3, 4]), + * Channel.fromIterable([5, 6]) + * ]) + * + * // Merge all channels with bounded concurrency + * const mergedChannel = Channel.mergeAll({ + * concurrency: 1, + * bufferSize: 16 + * })(nestedChannels) + * + * await Effect.runPromise(Channel.runCollect(mergedChannel)) // => [1, 2, 3, 4, 5, 6] + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const mergeAll: { + (options: { + readonly concurrency: number | "unbounded" + readonly bufferSize?: number | undefined + readonly switch?: boolean | undefined + }): ( + channels: Channel< + Channel, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + > + ) => Channel< + OutElem, + OutErr1 | OutErr, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > + ( + channels: Channel< + Channel, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + >, + options: { + readonly concurrency: number | "unbounded" + readonly bufferSize?: number | undefined + readonly switch?: boolean | undefined + } + ): Channel< + OutElem, + OutErr1 | OutErr, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > +} = dual( + 2, + ( + channels: Channel< + Channel, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + >, + { bufferSize = 16, concurrency, switch: switch_ = false }: { + readonly concurrency: number | "unbounded" + readonly bufferSize?: number | undefined + readonly switch?: boolean | undefined + } + ): Channel< + OutElem, + OutErr1 | OutErr, + OutDone, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > => + fromTransformBracket( + Effect.fnUntraced(function*(upstream, scope, forkedScope) { + const concurrencyN = concurrency === "unbounded" + ? Number.MAX_SAFE_INTEGER + : Math.max(1, concurrency) + const semaphore = switch_ ? undefined : Semaphore.makeUnsafe(concurrencyN) + const doneLatch = yield* Latch.make(true) + const fibers = new Set>() + + const queue = yield* Queue.bounded>( + bufferSize + ) + yield* Scope.addFinalizer(forkedScope, Queue.shutdown(queue)) + + const pull = yield* toTransform(channels)(upstream, scope) + + yield* Effect.gen(function*() { + while (true) { + 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) + + while (fibers.size >= concurrencyN) { + const fiber = Iterable.headUnsafe(fibers) + fibers.delete(fiber) + if (fibers.size === 0) yield* doneLatch.open + yield* Fiber.interrupt(fiber) + } + + const fiber = yield* childPull.pipe( + Effect.tap(() => Effect.yieldNow), + Effect.flatMap((value) => Queue.offer(queue, value)), + Effect.forever({ disableYield: true }), + Effect.onError(Effect.fnUntraced(function*(cause) { + const halt = Pull.filterDone(cause) + yield* Effect.exit(Scope.close( + childScope, + !Result.isFailure(halt) ? Exit.succeed(halt.success.value) : Exit.failCause(halt.failure) + )) + if (!fibers.has(fiber)) return + fibers.delete(fiber) + if (semaphore) yield* semaphore.release(1) + if (fibers.size === 0) yield* doneLatch.open + if (Result.isSuccess(halt)) return + return yield* Queue.failCause(queue, cause as any) + })), + Effect.forkChild + ) + + doneLatch.closeUnsafe() + fibers.add(fiber) + } + }).pipe( + 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) + ) + + return Queue.take(queue) + }) + ) +) + +/** + * Represents strategies for halting merged channels when one completes or fails. + * + * **Example** (Choosing merge halt strategies) + * + * ```ts import.meta.vitest + * import { Channel } from "effect" + * + * // Different halt strategies for channel merging + * const strategies: Array = ["left", "right", "both", "either"] // => ["left", "right", "both", "either"] + * ``` + * + * @category models + * @since 4.0.0 + */ +export type HaltStrategy = "left" | "right" | "both" | "either" + +/** + * Returns a new channel, which is the merge of this channel and the specified + * channel. + * + * **Example** (Merging channels) + * + * ```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"]) + * + * // The default "both" strategy waits for both channels to complete + * const mergedChannel = Channel.merge(leftChannel, rightChannel) + * + * const values = await Effect.runPromise(Channel.runCollect(mergedChannel)) + * values.map(String).sort() // => ["1", "2", "3", "a", "b", "c"] + * ``` + * + * @category combining + * @since 4.0.0 + */ +export const merge: { + ( + right: Channel, + options?: { + readonly haltStrategy?: HaltStrategy | undefined + } | undefined + ): ( + left: Channel + ) => Channel< + OutElem1 | OutElem, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env1 | Env + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + left: Channel, + right: Channel, + options?: { + readonly haltStrategy?: HaltStrategy | undefined + } | undefined + ): Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > +} = dual((args) => isChannel(args[0]) && isChannel(args[1]), < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + left: Channel, + right: Channel, + options?: { + readonly haltStrategy?: HaltStrategy | undefined + } | undefined +): Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => + fromTransformBracket(Effect.fnUntraced(function*(upstream, _scope, forkedScope) { + const strategy = options?.haltStrategy ?? "both" + const queue = yield* Queue.bounded>(0) + yield* Scope.addFinalizer(forkedScope, Queue.shutdown(queue)) + let done = 0 + function onExit( + side: "left" | "right", + cause: Cause.Cause> + ): Effect.Effect { + done++ + if (!Pull.isDoneCause(cause)) { + return Queue.failCause(queue, cause) + } + switch (strategy) { + case "both": { + return done === 2 ? Queue.failCause(queue, cause) : Effect.void + } + case "left": + case "right": { + return side === strategy ? Queue.failCause(queue, cause) : Effect.void + } + case "either": { + return Queue.failCause(queue, cause) + } + } + } + const runSide = ( + side: "left" | "right", + channel: Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + >, + scope: Scope.Closeable + ) => + toTransform(channel)(upstream, scope).pipe( + Effect.flatMap((pull) => + pull.pipe( + Effect.flatMap((value) => Queue.offer(queue, value)), + Effect.forever + ) + ), + Effect.onError((cause) => + Effect.andThen( + Scope.close(scope, Pull.doneExitFromCause(cause)), + onExit(side, cause) + ) + ), + Effect.forkIn(forkedScope) + ) + yield* runSide("left", left, Scope.forkUnsafe(forkedScope)) + yield* runSide("right", right, Scope.forkUnsafe(forkedScope)) + return Queue.take(queue) + }))) + +/** + * Runs an effect concurrently with a channel while emitting only the channel's + * output elements. + * + * **When to use** + * + * Use when a side effect should run for the lifetime of a channel and only the + * channel's output elements should be emitted. + * + * **Details** + * + * The effect's successful value is ignored. If the effect fails while the + * channel is running, the returned channel fails with that error. + * + * @category combining + * @since 4.0.0 + */ +export const mergeEffect: { + ( + effect: Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + effect: Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + effect: Effect.Effect +): Channel => + merge( + self, + fromEffectDrain(effect), + { haltStrategy: "left" } + ) as any) + +/** + * Splits upstream string chunks into lines, recognizing `\n`, `\r\n`, and + * standalone `\r` as line terminators. The behavior matches + * `String.linesIterator` regardless of how the input is chunked. + * + * **Details** + * + * A line terminator at the very end of the stream does **not** produce a + * trailing empty line (consistent with `String.linesIterator`). Conversely, + * if the stream ends without a terminator the final partial line is still + * emitted. + * + * **Example** (Splitting string chunks into lines) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const result = await Effect.runPromise(Stream.runCollect( + * Stream.splitLines(Stream.make("hel", "lo\r\nwor", "ld\n")) + * )) + * result // => ["hello", "world"] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +export const splitLines = (): Channel< + Arr.NonEmptyReadonlyArray, + Err, + Done, + Arr.NonEmptyReadonlyArray, + Err, + Done +> => + fromTransform((upstream, _scope) => + Effect.sync(() => { + // 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). + let midCRLF = false + // Remembers the upstream Done value after the first time the upstream + // signals completion, so subsequent pulls return Done immediately + // without pulling upstream again. + let done = Option.none() + + function splitLinesArray(chunk: Arr.NonEmptyReadonlyArray): Arr.NonEmptyReadonlyArray | null { + const chunkBuilder: Array = [] + + function pushLine(segment: string): void { + if (stringBuilder.length === 0) { + chunkBuilder.push(segment) + } else { + chunkBuilder.push(stringBuilder + segment) + stringBuilder = "" + } + } + + for (let i = 0; i < chunk.length; i++) { + const str = chunk[i] + if (str.length !== 0) { + let from = 0 + let indexOfCR = str.indexOf("\r") + let indexOfLF = str.indexOf("\n") + if (midCRLF) { + if (indexOfLF === 0) { + pushLine("") + from = 1 + indexOfLF = str.indexOf("\n", from) + } else { + pushLine("") + } + midCRLF = false + } + while (indexOfCR !== -1 || indexOfLF !== -1) { + if (indexOfCR === -1 || (indexOfLF !== -1 && indexOfLF < indexOfCR)) { + pushLine(str.substring(from, indexOfLF)) + from = indexOfLF + 1 + indexOfLF = str.indexOf("\n", from) + } else { + if (str.length === indexOfCR + 1) { + midCRLF = true + 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)) + } + } + return Arr.isReadonlyArrayNonEmpty(chunkBuilder) ? chunkBuilder : null + } + + const pullOrFlush: Pull.Pull, Err, Done> = Effect.suspend(() => { + if (done._tag === "Some") { + return Cause.done(done.value) + } + return Pull.matchEffect(upstream, { + onSuccess: loop, + onFailure: Effect.failCause, + onDone: (leftover) => { + done = Option.some(leftover) + if (stringBuilder.length > 0 || midCRLF) { + const last = stringBuilder + stringBuilder = "" + midCRLF = false + return Effect.succeed([last] as Arr.NonEmptyReadonlyArray) + } + return Cause.done(leftover) + } + }) + }) + + function loop(chunk: Arr.NonEmptyReadonlyArray): Pull.Pull, Err, Done> { + const lines = splitLinesArray(chunk) + return lines !== null ? Effect.succeed(lines) : pullOrFlush + } + + return pullOrFlush + }) + ) + +/** + * Decodes incoming `Uint8Array` chunks into strings using `TextDecoder`. + * + * **Details** + * + * Input chunks are decoded with streaming enabled so multi-byte characters may + * span `Uint8Array` boundaries. The optional `encoding` and `options` are + * passed to `TextDecoder`. + * + * @category decoding + * @since 4.0.0 + */ +export const decodeText = (encoding?: string, options?: TextDecoderOptions): Channel< + Arr.NonEmptyReadonlyArray, + Err, + Done, + Arr.NonEmptyReadonlyArray, + Err, + Done +> => + fromTransform((upstream, _scope) => + Effect.sync(() => { + const decoder = new TextDecoder(encoding, options) + const streamOptions = { stream: true } + return Effect.map(upstream, Arr.map((line) => decoder.decode(line, streamOptions))) + }) + ) + +/** + * Encodes incoming string chunks into `Uint8Array` values using `TextEncoder`. + * + * **Details** + * + * Each string inside an emitted array is encoded independently. + * + * @category encoding + * @since 4.0.0 + */ +export const encodeText = (): Channel< + Arr.NonEmptyReadonlyArray, + Err, + Done, + Arr.NonEmptyReadonlyArray, + Err, + Done +> => + fromTransform((upstream, _scope) => + Effect.sync(() => { + const encoder = new TextEncoder() + return Effect.map(upstream, Arr.map((line) => encoder.encode(line))) + }) + ) + +/** + * Returns a new channel that pipes the output of this channel into the + * specified channel. The returned channel has the input type of this channel, + * and the output type of the specified channel, terminating with the value of + * the specified channel. + * + * **Example** (Piping one channel into another) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class PipeError extends Data.TaggedError("PipeError")<{ + * readonly stage: string + * }> {} + * + * // Create source and transform channels + * const sourceChannel = Channel.fromIterable([1, 2, 3]) + * const transformChannel = Channel.map(sourceChannel, (n: number) => n * 2) + * + * // Pipe the source into the transform + * const pipedChannel = Channel.pipeTo(sourceChannel, transformChannel) + * + * Effect.runSync(Channel.runCollect(pipedChannel)) // => [2, 4, 6] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const pipeTo: { + ( + that: Channel + ): ( + self: Channel + ) => Channel + ( + self: Channel, + that: Channel + ): Channel +} = dual( + 2, + ( + self: Channel, + that: Channel + ): Channel => + fromTransform((upstream, scope) => + Effect.flatMap(toTransform(self)(upstream, scope), (upstream) => toTransform(that)(upstream, scope)) + ) +) + +/** + * Returns a new channel that pipes the output of this channel into the + * specified channel and preserves this channel's failures without providing + * them to the other channel for observation. + * + * **Example** (Piping while preserving failures) + * + * ```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 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) + * + * Effect.runSync(Effect.exit(Channel.runCollect(safePipedChannel))) // => Exit.fail(error) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const pipeToOrFail: { + ( + that: Channel + ): ( + self: Channel + ) => Channel + ( + self: Channel, + that: Channel + ): Channel +} = dual( + 2, + ( + self: Channel, + that: Channel + ): Channel => + fromTransform((upstream, scope) => + Effect.flatMap(toTransform(self)(upstream, scope), (upstream) => { + const upstreamPull = Effect.catchCause( + upstream, + (cause) => Pull.isDoneCause(cause) ? Effect.failCause(cause) : Effect.die(Cause.Done(cause)) + ) as Pull.Pull + + return Effect.map( + toTransform(that)(upstreamPull, scope), + (pull) => + Effect.catchDefect( + pull, + (defect) => + Cause.isDone(defect) ? Effect.failCause(defect.value as Cause.Cause) : Effect.die(defect) + ) + ) + }) + ) +) + +/** + * Constructs a `Channel` from a scoped effect that will result in a + * `Channel` if successful. + * + * **Example** (Unwrapping channel effects) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class UnwrapError extends Data.TaggedError("UnwrapError")<{ + * readonly reason: string + * }> {} + * + * // Create an effect that produces a channel + * const channelEffect = Effect.succeed( + * Channel.fromIterable([1, 2, 3]) + * ) + * + * // Unwrap the effect to get the channel + * const unwrappedChannel = Channel.unwrap(channelEffect) + * + * Effect.runSync(Channel.runCollect(unwrappedChannel)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unwrap = ( + channel: Effect.Effect, E, R> +): Channel | R2> => + fromTransform((upstream, scope) => { + let pull: Pull.Pull | undefined + return Effect.succeed(Effect.suspend(() => { + if (pull) return pull + return channel.pipe( + Scope.provide(scope), + Effect.flatMap((channel) => toTransform(channel)(upstream, scope)), + Effect.flatMap((pull_) => pull = pull_) + ) + })) + }) + +/** + * Runs a channel with a scope provided for the duration of the channel + * execution, removing the channel's `Scope` requirement. + * + * @category resource management + * @since 2.0.0 + */ +export const scoped = ( + self: Channel +): Channel> => + fromTransformBracket((upstream, scope, forkedScope) => + Effect.map( + Scope.provide(toTransform(self)(upstream, scope), forkedScope), + Scope.provide(forkedScope) + ) + ) + +/** + * Runs an input handler against the upstream pull while the wrapped channel + * runs without receiving upstream input directly. + * + * **Details** + * + * The input handler is forked in the channel scope. The wrapped channel is run + * with an already-completed input. + * + * **Example** (Embedding custom input handling) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * // Create a base channel + * const baseChannel = Channel.fromIterable([1, 2, 3]) + * + * // Drain the embedded input while the base channel runs + * const embeddedChannel = Channel.embedInput( + * baseChannel, + * (upstream) => + * upstream.pipe( + * Effect.forever, + * Effect.ignore + * ) + * ) + * await Effect.runPromise(Channel.runCollect(embeddedChannel)) // => [1, 2, 3] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const embedInput: { + ( + input: ( + upstream: Pull.Pull + ) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + input: ( + upstream: Pull.Pull + ) => Effect.Effect + ): Channel +} = dual( + 2, + ( + self: Channel, + input: ( + upstream: Pull.Pull + ) => Effect.Effect + ): Channel => + fromTransformBracket((upstream, scope, forkedScope) => + Effect.andThen( + Effect.forkIn(input(upstream), forkedScope), + toTransform(self)(Cause.done(), scope) + ) + ) +) + +/** + * Buffers individual output elements in a queue with the configured `capacity` + * so a faster producer can progress independently of a slower consumer. + * + * **When to use** + * + * Use when output elements can be decoupled from downstream demand and the + * configured backpressure or loss strategy is acceptable. + * + * **Details** + * + * Finite queues use the `strategy` option. The default `"suspend"` strategy + * applies backpressure, while `"dropping"` and `"sliding"` can discard output + * elements when the queue is full. `"unbounded"` capacity does not use a finite + * capacity strategy. + * + * **Gotchas** + * + * Dropping and sliding strategies can lose output elements under backpressure. + * + * @see {@link bufferArray} for buffering elements from array outputs + * + * @category buffering + * @since 2.0.0 + */ +export const buffer: { + ( + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): ( + self: Channel + ) => Channel + ( + self: Channel, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Channel +} = dual(2, ( + self: Channel, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } +): Channel => + fromTransform(Effect.fnUntraced(function*(upstream, scope) { + const pull = yield* toTransform(self)(upstream, scope) + const queue = yield* Queue.make>({ + capacity: options.capacity === "unbounded" ? undefined : options.capacity, + strategy: options.capacity === "unbounded" ? undefined : options.strategy + }) + yield* Scope.addFinalizer(scope, Queue.shutdown(queue)) + yield* pull.pipe( + Effect.flatMap((value) => Queue.offer(queue, value)), + Effect.forever({ disableYield: true }), + Effect.onError((cause) => Queue.failCause(queue, cause)), + Effect.forkIn(scope) + ) + return Queue.take(queue) + }))) + +/** + * Buffers array output elements in a queue with the configured `capacity` so a + * faster producer can progress independently of a slower consumer. + * + * **When to use** + * + * Use when emitted arrays are batches of elements and it is acceptable for + * buffering to flatten and rebuild those batches. + * + * **Details** + * + * Finite queues use the `strategy` option. The default `"suspend"` strategy + * applies backpressure, while `"dropping"` and `"sliding"` can discard output + * elements when the queue is full. `"unbounded"` capacity does not use a finite + * capacity strategy. + * + * **Gotchas** + * + * Input arrays are offered to the queue element-by-element and outputs are + * rebuilt from the currently available queued elements, so upstream array + * boundaries are not preserved. + * + * @see {@link buffer} for buffering output elements without flattening arrays + * + * @category buffering + * @since 4.0.0 + */ +export const bufferArray: { + ( + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ) => Channel, OutErr, OutDone, InElem, InErr, InDone, Env> + ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Channel, OutErr, OutDone, InElem, InErr, InDone, Env> +} = dual(2, ( + self: Channel, OutErr, OutDone, InElem, InErr, InDone, Env>, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } +): Channel, OutErr, OutDone, InElem, InErr, InDone, Env> => + fromTransform(Effect.fnUntraced(function*(upstream, scope) { + const pull = yield* toTransform(self)(upstream, scope) + const queue = yield* Queue.make>({ + capacity: options.capacity === "unbounded" ? undefined : options.capacity, + strategy: options.capacity === "unbounded" ? undefined : options.strategy + }) + yield* Scope.addFinalizer(scope, Queue.shutdown(queue)) + yield* pull.pipe( + Effect.flatMap((value) => Queue.offerAll(queue, value)), + Effect.forever({ disableYield: true }), + Effect.onError((cause) => Queue.failCause(queue, cause)), + Effect.forkIn(scope) + ) + return Queue.takeAll(queue) + }))) + +/** + * Interrupts a channel when another effect completes. + * + * **When to use** + * + * Use to race channel execution against an external effect whose success can + * become the channel's done value. + * + * **Details** + * + * If the effect completes first, its success value becomes the returned + * channel's done value. If the channel completes first, the original channel's + * done value is preserved. + * + * @category interruption + * @since 2.0.0 + */ +export const interruptWhen: { + ( + effect: Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + effect: Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + effect: Effect.Effect +): Channel => + merge( + self, + fromPull(Effect.succeed(Effect.flatMap(effect, Cause.done))), + { haltStrategy: "either" } + )) + +/** + * Stops a channel when the specified effect completes or fails. + * + * **Details** + * + * If the effect completes before the channel is done, its success value becomes + * the returned channel's done value. If the effect fails, the returned channel + * fails with that error. If the channel completes first, the channel's done + * value is preserved. + * + * @category interruption + * @since 4.0.0 + */ +export const haltWhen: { + ( + effect: Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + effect: Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + effect: Effect.Effect +): Channel => + fromTransformBracket(Effect.fnUntraced(function*(upstream, scope, forkedScope) { + const pull = yield* toTransform(self)(upstream, scope) + 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 + }) + }) + }))) + +/** + * Attaches a finalizer that runs only when the channel exits with failure. + * + * **Details** + * + * The finalizer receives the failure `Cause`. The original channel failure is + * preserved. The finalizer itself must not fail. + * + * @category error handling + * @since 4.0.0 + */ +export const onError: { + ( + finalizer: (cause: Cause.Cause) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + finalizer: (cause: Cause.Cause) => Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + finalizer: (cause: Cause.Cause) => Effect.Effect +): Channel => + onExit(self, (exit) => Exit.isFailure(exit) ? finalizer(exit.cause) : Effect.void)) + +/** + * Returns a channel with an exit-aware finalizer that is guaranteed to run once + * the channel begins execution, whether it succeeds or fails. + * + * **Example** (Running exit finalizers) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" + * + * class ExitError extends Data.TaggedError("ExitError")<{ + * readonly stage: string + * }> {} + * + * // Create a channel + * const dataChannel = Channel.fromIterable([1, 2, 3]) + * + * // Attach exit handler + * const exits: Array> = [] + * const channelWithExit = Channel.onExit(dataChannel, (exit) => { + * exits.push(exit) + * return Effect.void + * }) + * const observed = [await Effect.runPromise(Channel.runCollect(channelWithExit)), exits] // => [[1, 2, 3], [Exit.void]] + * ``` + * + * @category resource management + * @since 4.0.0 + */ +export const onExit: { + ( + finalizer: (e: Exit.Exit) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + finalizer: (e: Exit.Exit) => Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + finalizer: (e: Exit.Exit) => Effect.Effect +): Channel => + fromTransformBracket((upstream, scope, forkedScope) => + Scope.addFinalizerExit(forkedScope, finalizer as any).pipe( + Effect.andThen(toTransform(self)(upstream, scope)) + ) + )) + +/** + * Runs an effect before the channel starts. + * + * **Details** + * + * The effect's successful value is ignored. If the effect fails, the returned + * channel fails before running the source channel. + * + * @category hooks + * @since 4.0.0 + */ +export const onStart: { + ( + onStart: Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + onStart: Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + onStart: Effect.Effect +): Channel => unwrap(Effect.as(onStart, self))) + +/** + * Runs an effect the first time the channel emits an output element. + * + * **When to use** + * + * Use when initialization depends on the first output element rather than only + * on channel startup. + * + * **Details** + * + * The effect receives the first emitted element. The first element is still + * emitted unchanged. The effect is not run if the channel completes without + * emitting an element. + * + * @category hooks + * @since 4.0.0 + */ +export const onFirst: { + ( + onFirst: (element: Types.NoInfer) => Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + onFirst: (element: Types.NoInfer) => Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + onFirst: (element: Types.NoInfer) => Effect.Effect +): Channel => + transformPull(self, (pull) => + Effect.sync(() => { + let isFirst = true + const pullFirst = Effect.tap(pull, (element) => { + isFirst = false + return onFirst(element) + }) + return Effect.suspend(() => isFirst ? pullFirst : pull) + }))) + +/** + * Runs an effect when the channel completes successfully. + * + * **Details** + * + * The effect runs before the original done value is propagated. The effect is + * not run when the channel fails. If the effect fails, the returned channel + * fails with that error. + * + * @category hooks + * @since 4.0.0 + */ +export const onEnd: { + ( + onEnd: Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + onEnd: Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + onEnd: Effect.Effect +): Channel => + transformPull(self, (pull) => + Effect.succeed(Pull.catchDone( + pull, + (leftover) => Effect.flatMap(onEnd, () => Cause.done(leftover as OutDone)) + )))) + +/** + * Returns a channel with a finalizer effect that is guaranteed to run once the + * channel begins execution, whether it succeeds or fails. + * + * **Example** (Ensuring cleanup runs) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class EnsureError extends Data.TaggedError("EnsureError")<{ + * readonly operation: string + * }> {} + * + * // Create a channel + * const dataChannel = Channel.fromIterable([1, 2, 3]) + * + * // Ensure cleanup always runs + * const events: Array = [] + * const channelWithCleanup = Channel.ensuring( + * dataChannel, + * Effect.sync(() => events.push("cleanup")) + * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channelWithCleanup)), events] // => [[1, 2, 3], ["cleanup"]] + * ``` + * + * @category resource management + * @since 2.0.0 + */ +export const ensuring: { + ( + finalizer: Effect.Effect + ): ( + self: Channel + ) => Channel + ( + self: Channel, + finalizer: Effect.Effect + ): Channel +} = dual(2, ( + self: Channel, + finalizer: Effect.Effect +): Channel => onExit(self, (_) => finalizer)) + +const runWith = < + OutElem, + OutErr, + OutDone, + Env, + EX, + RX, + AH = OutDone, + EH = never, + RH = never +>( + self: Channel, + f: (pull: Pull.Pull) => Effect.Effect, + onHalt?: (leftover: OutDone) => Effect.Effect +): Effect.Effect | EH, Env | RX | RH> => + Effect.suspend(() => { + const scope = Scope.makeUnsafe() + const makePull = toTransform(self)(Cause.done(), scope) + return Pull.catchDone(Effect.flatMap(makePull, f), onHalt ? onHalt : Effect.succeed as any).pipe( + Effect.onExit((exit) => Scope.close(scope, exit)) + ) as any + }) + +/** + * Creates a channel from the specified services. + * + * @category accessors + * @since 2.0.0 + */ +export const contextWith = ( + f: (context: Context.Context) => Channel +): Channel => + fromTransform((upstream, scope) => + Effect.contextWith((context: Context.Context) => toTransform(f(context))(upstream, scope)) + ) + +/** + * Provides a `Context` to the channel, removing the corresponding service + * requirements from the returned channel. + * + * @category providing services + * @since 2.0.0 + */ +export const provideContext: { + ( + context: Context.Context + ): ( + self: Channel + ) => Channel> + ( + self: Channel, + context: Context.Context + ): Channel> +} = dual(2, ( + self: Channel, + context: Context.Context +): Channel> => + fromTransform((upstream, scope) => + Effect.map( + Effect.provideContext(toTransform(self)(upstream, scope), context), + Effect.provideContext(context) + ) + )) + +/** + * Provides a concrete service for a context key, removing that service + * requirement from the returned channel. + * + * @category providing services + * @since 2.0.0 + */ +export const provideService: { + ( + key: Context.Key, + service: NoInfer + ): ( + self: Channel + ) => Channel> + ( + self: Channel, + key: Context.Key, + service: NoInfer + ): Channel> +} = dual(3, ( + self: Channel, + key: Context.Key, + service: NoInfer +): Channel> => + fromTransform((upstream, scope) => + Effect.map( + Effect.provideService(toTransform(self)(upstream, scope), key, service), + Effect.provideService(key, service) + ) + )) + +/** + * Provides a service to the channel after obtaining it from an effect. + * + * **When to use** + * + * Use to supply a channel dependency when constructing the service itself is + * effectful or can fail. + * + * **Details** + * + * If the service effect fails, the returned channel fails. The provided service + * removes the corresponding service requirement from the returned channel. + * + * @category providing services + * @since 4.0.0 + */ +export const provideServiceEffect: { + ( + key: Context.Key, + service: Effect.Effect, ES, RS> + ): ( + self: Channel + ) => Channel | RS> + ( + self: Channel, + key: Context.Key, + service: Effect.Effect, ES, RS> + ): Channel | RS> +} = dual(3, ( + self: Channel, + key: Context.Key, + service: Effect.Effect, ES, RS> +): Channel | RS> => + fromTransform((upstream, scope) => + Effect.flatMap( + service, + (s) => toTransform(provideService(self, key, s))(upstream, scope) + ) + )) + +/** + * Provides a `Layer` or `Context` to the channel, removing the corresponding + * service requirements. + * + * **Details** + * + * Providing a `Context` delegates to `provideContext`. Providing a `Layer` + * builds the layer in the channel scope. Use `options.local` to build a fresh + * layer instance for this provision. + * + * @category providing services + * @since 4.0.0 + */ +export const provide: { + ( + layer: Layer.Layer | Context.Context, + options?: { + readonly local?: boolean | undefined + } | undefined + ): ( + self: Channel + ) => Channel | R> + ( + self: Channel, + layer: Layer.Layer | Context.Context, + options?: { + readonly local?: boolean | undefined + } | undefined + ): Channel | R> +} = dual((args) => isChannel(args[0]), ( + self: Channel, + layer: Layer.Layer | Context.Context, + options?: { + readonly local?: boolean | undefined + } | undefined +): Channel | R> => + Context.isContext(layer) ? provideContext(self, layer) : fromTransform((upstream, scope) => + Effect.flatMap( + options?.local + ? Layer.buildWithMemoMap(layer, Layer.makeMemoMapUnsafe(), scope) + : Layer.buildWithScope(layer, scope), + (context) => + Effect.map( + Effect.provideContext(toTransform(self)(upstream, scope), context), + Effect.provideContext(context) + ) + ) + )) + +/** + * Transforms the current context before running the channel. + * + * **Details** + * + * The function receives the surrounding context and returns the context to + * provide to the channel. The returned channel requires the services needed to + * build that context. + * + * @category providing services + * @since 4.0.0 + */ +export const updateContext: { + ( + f: (context: Context.Context) => Context.Context + ): ( + self: Channel + ) => Channel + ( + self: Channel, + f: (context: Context.Context) => Context.Context + ): Channel +} = dual(2, ( + self: Channel, + f: (context: Context.Context) => Context.Context +): Channel => + fromTransform((upstream, scope) => + Effect.contextWith((context) => { + const toProvide = f(context) + return toTransform(provideContext(self, toProvide))(upstream, scope) + }) + )) + +/** + * Updates a service in the current context before running the channel. + * + * **Details** + * + * The existing service is read from the context. The updated service is + * provided to the channel under the same key. + * + * @category providing services + * @since 2.0.0 + */ +export const updateService: { + ( + key: Context.Key, + f: (service: NoInfer) => S + ): ( + self: Channel + ) => Channel + ( + self: Channel, + service: Context.Key, + f: (service: NoInfer) => S + ): Channel +} = dual(3, ( + self: Channel, + service: Context.Key, + f: (service: NoInfer) => S +): Channel => + updateContext(self, (context) => + Context.add( + context, + service, + f(Context.get(context, service)) + ))) + +/** + * Runs the channel inside a tracing span with the specified name and options. + * + * **Details** + * + * The created span is provided as the current parent span while the channel + * runs. The span is ended with the channel's exit value. + * + * @category tracing + * @since 2.0.0 + */ +export const withSpan: { + ( + name: string, + options?: SpanOptions + ): ( + self: Channel + ) => Channel> + ( + self: Channel, + name: string, + options?: SpanOptions + ): Channel> +} = function() { + const dataFirst = isChannel(arguments[0]) + const name = dataFirst ? arguments[1] : arguments[0] + const options = addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) + if (dataFirst) { + const self = arguments[0] + return withSpanImpl(self, name, options) + } + return (self: any) => withSpanImpl(self, name, options) +} as any + +const withSpanImpl = ( + self: Channel, + name: string, + options?: SpanOptions +): Channel> => + acquireUseRelease( + Effect.makeSpan(name, options), + (span) => provideService(self, ParentSpan, span), + (span, exit) => + Effect.withFiber((fiber) => { + const clock = fiber.getRef(ClockRef) + const timingEnabled = fiber.getRef(TracerTimingEnabled) + return endSpan(span, exit, clock, timingEnabled) + }) + ) + +/** + * The starting channel for Do notation, emitting an empty object. + * + * @category constructors + * @since 4.0.0 + */ +export const Do: Channel<{}> = succeed({}) + +const let_: { + ( + name: Exclude, + f: (a: NoInfer) => B + ): ( + self: Channel + ) => Channel< + { [K in N | keyof OutElem]: K extends keyof OutElem ? OutElem[K] : B }, + OutErr, + OutDone, + InElem, + InErr, + InDone, + R + > + ( + self: Channel, + name: Exclude, + f: (a: NoInfer) => B + ): Channel< + { [K in N | keyof OutElem]: K extends keyof OutElem ? OutElem[K] : B }, + OutErr, + OutDone, + InElem, + InErr, + InDone, + R + > +} = dual(3, ( + self: Channel, + name: Exclude, + f: (a: NoInfer) => B +): Channel< + { [K in N | keyof OutElem]: K extends keyof OutElem ? OutElem[K] : B }, + OutErr, + OutDone, + InElem, + InErr, + InDone, + R +> => + map(self, (elem) => (({ + ...elem, + [name]: f(elem) + }) as any))) +export { + /** + * Adds a computed field to each object emitted by a channel. + * + * @category mapping + * @since 4.0.0 + */ + let_ as let +} + +/** + * Adds a field to each object emitted by a channel by running another channel + * derived from that object. + * + * **Details** + * + * The field name must not already exist on the emitted object. The derived + * channel's output becomes the value of the new field. `options.concurrency` + * and `options.bufferSize` control how derived channels are flattened. + * + * @category sequencing + * @since 4.0.0 + */ +export const bind: { + ( + name: Exclude, + f: (a: NoInfer) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): ( + self: Channel + ) => Channel< + { [K in N | keyof OutElem]: K extends keyof OutElem ? OutElem[K] : B }, + OutErr2 | OutErr, + OutDone, + InElem & InElem2, + InErr & InErr2, + InDone & InDone2, + Env2 | Env + > + < + OutElem extends object, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + N extends string, + B, + OutErr2, + OutDone2, + InElem2, + InErr2, + InDone2, + Env2 + >( + self: Channel, + name: Exclude, + f: (a: NoInfer) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } + ): Channel< + { [K in N | keyof OutElem]: K extends keyof OutElem ? OutElem[K] : B }, + OutErr2 | OutErr, + OutDone, + InElem & InElem2, + InErr & InErr2, + InDone & InDone2, + Env2 | Env + > +} = dual((args) => isChannel(args[0]), < + OutElem extends object, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + N extends string, + B, + OutErr2, + OutDone2, + InElem2, + InErr2, + InDone2, + Env2 +>( + self: Channel, + name: Exclude, + f: (a: NoInfer) => Channel, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } +): Channel< + { [K in N | keyof OutElem]: K extends keyof OutElem ? OutElem[K] : B }, + OutErr2 | OutErr, + OutDone, + InElem & InElem2, + InErr & InErr2, + InDone & InDone2, + Env2 | Env +> => + flatMap( + self, + (elem) => map(f(elem), (b) => ({ ...elem, [name]: b } as any)), + options + )) + +/** + * Wraps each output element in an object under the specified field name. + * + * **When to use** + * + * Use when you need to start a Channel Do-notation chain from an existing + * output value by assigning that value to a field name. + * + * @see {@link Do} for starting Do notation from an empty object + * @see {@link bind} for adding a field produced by another channel + * @see {@link let_ let} for adding a computed field + * + * @category mapping + * @since 4.0.0 + */ +export const bindTo: { + (name: N): ( + self: Channel + ) => Channel< + { [K in N]: OutElem }, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + > + ( + self: Channel, + name: N + ): Channel< + { [K in N]: OutElem }, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env + > +} = dual(2, ( + self: Channel, + name: N +): Channel< + { [K in N]: OutElem }, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env +> => map(self, (elem) => ({ [name]: elem } as any))) + +/** + * Runs a channel and counts the number of elements it outputs. + * + * **Example** (Counting channel output) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class CountError extends Data.TaggedError("CountError")<{ + * readonly reason: string + * }> {} + * + * // Create a channel with multiple elements + * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) + * + * // Count the elements + * const countEffect = Channel.runCount(numbersChannel) + * + * Effect.runSync(countEffect) // => 5 + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runCount = ( + self: Channel +): 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.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class DrainError extends Data.TaggedError("DrainError")<{ + * readonly stage: string + * }> {} + * + * // Create a channel that outputs elements and completes with a result + * const resultChannel = Channel.fromIterable([1, 2, 3]) + * 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) // => "completed" + * ``` + * + * @category running + * @since 2.0.0 + */ +export const runDrain = ( + self: Channel +): Effect.Effect => runWith(self, (pull) => Effect.forever(pull, { disableYield: true })) + +/** + * Runs a channel and applies an effect to each output element. + * + * **Example** (Running effects for each output) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class ForEachError extends Data.TaggedError("ForEachError")<{ + * readonly element: unknown + * }> {} + * + * // Create a channel with numbers + * const numbersChannel = Channel.fromIterable([1, 2, 3]) + * + * // Run forEach to process each element + * const processed: Array = [] + * const forEachEffect = Channel.runForEach( + * numbersChannel, + * (n) => Effect.sync(() => processed.push(n)) + * ) + * + * await Effect.runPromise(forEachEffect) + * processed // => [1, 2, 3] + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runForEach: { + ( + f: (o: OutElem) => Effect.Effect + ): ( + self: Channel + ) => Effect.Effect + ( + self: Channel, + f: (o: OutElem) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Channel, + f: (o: OutElem) => Effect.Effect + ): Effect.Effect => + runWith(self, (pull) => Effect.forever(Effect.flatMap(pull, f), { disableYield: true })) +) + +/** + * Runs a channel and applies an effectful predicate to each output element + * until the predicate returns `false`. + * + * **Details** + * + * Returning `true` continues consuming the channel. Returning `false` stops + * consumption early. The returned effect completes with `void`. + * + * @category running + * @since 4.0.0 + */ +export const runForEachWhile: { + ( + f: (o: OutElem) => Effect.Effect + ): ( + self: Channel + ) => Effect.Effect + ( + self: Channel, + f: (o: OutElem) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Channel, + f: (o: OutElem) => Effect.Effect + ): Effect.Effect => + runWith(self, (pull) => + pull.pipe( + Effect.flatMap(f), + Effect.flatMap((cont) => (cont ? Effect.void : Cause.done())), + Effect.forever({ disableYield: true }) + )) +) + +/** + * Concatenates a channel's `Uint8Array` chunks into a single `Uint8Array`. + * + * **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] + * ``` + * + * **Gotchas** + * + * This materializes the full content in memory. The source channel must not + * reuse or mutate emitted buffers, which are retained until collection completes. + * + * @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.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class CollectError extends Data.TaggedError("CollectError")<{ + * readonly reason: string + * }> {} + * + * // Create a channel with elements + * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) + * + * // Collect all elements into an array + * const collectEffect = Channel.runCollect(numbersChannel) + * + * Effect.runSync(collectEffect) // => [1, 2, 3, 4, 5] + * ``` + * + * @category running + * @since 2.0.0 + */ +export const runCollect = ( + self: Channel +): Effect.Effect, OutErr, Env> => + runFold(self, () => [] as Array, (acc, o) => { + acc.push(o) + return acc + }) + +/** + * Runs a channel and outputs the done value. + * + * @category running + * @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`. + * + * **Details** + * + * Returns `Option.some` with the first output element, or `Option.none` if the + * channel completes without emitting output. + * + * @category running + * @since 4.0.0 + */ +export const runHead = ( + self: Channel +): Effect.Effect, OutErr, Env> => + Effect.suspend(() => { + let head = Option.none() + return runWith(self, (pull) => + pull.pipe( + Effect.asSome, + Effect.flatMap((head_) => { + head = head_ + return Cause.done() + }) + ), () => Effect.succeed(head)) + }) + +/** + * Runs a channel to completion and returns the last output element in an + * `Option`. + * + * **Details** + * + * Returns `Option.some` with the last emitted element, or `Option.none` if the + * channel completes without emitting output. + * + * @category running + * @since 4.0.0 + */ +export const runLast = ( + self: Channel +): Effect.Effect, OutErr, Env> => + Effect.suspend(() => { + const absent = Symbol() // Prevent boxing + let last: typeof absent | OutElem = absent + return runWith( + self, + (pull) => + Effect.forever( + Effect.flatMap(pull, (item) => { + last = item + return Effect.void + }), + { disableYield: true } + ), + () => last === absent ? Effect.succeedNone : Effect.succeedSome(last) + ) + }) + +/** + * Runs a channel and folds over all output elements with an accumulator. + * + * **Example** (Folding channel output) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class FoldError extends Data.TaggedError("FoldError")<{ + * readonly operation: string + * }> {} + * + * // Create a channel with numbers + * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) + * + * // Fold to calculate sum + * const sumEffect = Channel.runFold(numbersChannel, () => 0, (acc, n) => acc + n) + * + * Effect.runSync(sumEffect) // => 15 + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runFold: { + ( + initial: LazyArg, + f: (acc: Z, o: OutElem) => Z + ): ( + self: Channel + ) => Effect.Effect + ( + self: Channel, + initial: LazyArg, + f: (acc: Z, o: OutElem) => Z + ): Effect.Effect +} = dual(3, ( + self: Channel, + initial: LazyArg, + f: (acc: Z, o: OutElem) => Z +): Effect.Effect => + Effect.suspend(() => { + let state = initial() + return runWith( + self, + (pull) => + Effect.whileLoop({ + while: constTrue, + body: () => pull, + step: (value) => { + state = f(state, value) + } + }), + () => Effect.succeed(state) + ) + })) + +/** + * Runs a channel and effectfully folds all output elements with an accumulator. + * + * **When to use** + * + * Use when folding channel output needs effects, services, or an additional + * failure channel during accumulation. + * + * **Details** + * + * The initial accumulator is evaluated lazily. Each output element is passed to + * the effectful accumulator function. The returned effect succeeds with the + * final accumulator value. + * + * @category running + * @since 4.0.0 + */ +export const runFoldEffect: { + ( + initial: LazyArg, + f: (acc: Z, o: OutElem) => Effect.Effect + ): ( + self: Channel + ) => Effect.Effect + ( + self: Channel, + initial: LazyArg, + f: (acc: Z, o: OutElem) => Effect.Effect + ): Effect.Effect +} = dual(3, ( + self: Channel, + initial: LazyArg, + f: (acc: Z, o: OutElem) => Effect.Effect +): Effect.Effect => + Effect.suspend(() => { + let state = initial() + return runWith( + self, + (pull) => + Effect.whileLoop({ + while: constTrue, + body: constant(pull.pipe( + Effect.flatMap((o) => f(state, o)), + Effect.map((s) => { + state = s + }) + )), + step: constVoid + }), + () => Effect.succeed(state) + ) + })) + +/** + * Converts a channel to a scoped `Pull` for low-level consumption. + * + * **Details** + * + * The effect requires a `Scope`. The returned pull should be consumed only + * while that scope remains open. Pulls are serialized so only one pull is + * evaluated at a time. + * + * **Example** (Converting channels to pulls) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" + * + * class PullError extends Data.TaggedError("PullError")<{ + * readonly step: string + * }> {} + * + * // Create a channel + * const numbersChannel = Channel.fromIterable([1, 2, 3]) + * + * 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 + * @since 2.0.0 + */ +export const toPull: ( + self: Channel +) => Effect.Effect< + Pull.Pull, + never, + Env | Scope.Scope +> = Effect.fnUntraced( + function*( + self: Channel + ) { + const semaphore = Semaphore.makeUnsafe(1) + const context = yield* Effect.context() + const scope = Context.get(context, Scope.Scope) + const pull = yield* toTransform(self)(Cause.done(), scope) + return pull.pipe( + Effect.provideContext(context), + semaphore.withPermits(1) + ) + }, + // ensure errors are redirected to the pull effect + Effect.catchCause((cause) => Effect.succeed(Effect.failCause(cause))) +) as any + +/** + * Converts a channel to a Pull within an existing scope. + * + * **Example** (Converting channels to scoped pulls) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Scope } from "effect" + * + * class ScopedPullError extends Data.TaggedError("ScopedPullError")<{ + * readonly reason: string + * }> {} + * + * // Create a channel + * const numbersChannel = Channel.fromIterable([1, 2, 3]) + * + * // Convert to Pull with explicit scope + * const scopedPullEffect = Effect.gen(function*() { + * const scope = yield* Effect.scope + * const pull = yield* Channel.toPullScoped(numbersChannel, scope) + * return [yield* pull, yield* pull, yield* pull] + * }) + * await Effect.runPromise(Effect.scoped(scopedPullEffect)) // => [1, 2, 3] + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const toPullScoped = ( + self: Channel, + scope: Scope.Scope +): Effect.Effect, never, Env> => toTransform(self)(Cause.done(), scope) + +/** + * Runs a channel and offers each output element into a queue. + * + * **Details** + * + * When the channel completes, the queue is ended. When the channel fails, the + * queue is failed with the channel's cause. The returned effect itself + * completes with `void`. + * + * @category destructors + * @since 4.0.0 + */ +export const runIntoQueue: { + (queue: Queue.Queue): ( + self: Channel + ) => Effect.Effect + ( + self: Channel, + queue: Queue.Queue + ): Effect.Effect +} = dual( + (args) => isChannel(args[0]), + ( + self: Channel, + queue: Queue.Queue + ): Effect.Effect => + Effect.uninterruptibleMask((restore) => + runForEach(self, (value) => Queue.offer(queue, value)).pipe( + restore, + Effect.exit, + Effect.flatMap((exit) => { + if (Exit.isSuccess(exit)) { + Queue.endUnsafe(queue) + } else { + Queue.failCauseUnsafe(queue, exit.cause) + } + return Effect.void + }) + ) + ) +) + +/** + * Runs a channel that emits non-empty arrays and offers each array element into + * a queue. + * + * **Details** + * + * When the channel completes, the queue is ended. When the channel fails, the + * queue is failed with the channel's cause. The returned effect itself + * completes with `void`. + * + * @category destructors + * @since 4.0.0 + */ +export const runIntoQueueArray: { + (queue: Queue.Queue): ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env> + ) => Effect.Effect + ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + queue: Queue.Queue + ): Effect.Effect +} = dual( + (args) => isChannel(args[0]), + ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + queue: Queue.Queue + ): Effect.Effect => + Effect.uninterruptibleMask((restore) => + runForEach(self, (value) => Queue.offerAll(queue, value)).pipe( + restore, + Effect.exit, + Effect.flatMap((exit) => { + if (Exit.isSuccess(exit)) { + Queue.endUnsafe(queue) + } else { + Queue.failCauseUnsafe(queue, exit.cause) + } + return Effect.void + }) + ) + ) +) + +/** + * Creates a scoped queue and forks the channel to feed it for concurrent + * consumption. + * + * **Details** + * + * Output elements are offered to the queue. Channel completion and failure are + * signaled through the queue. The queue is shut down when the surrounding scope + * closes. + * + * **Example** (Converting channels to queues) + * + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Queue } from "effect" + * + * class QueueError extends Data.TaggedError("QueueError")<{ + * readonly operation: string + * }> {} + * + * // Create a channel with data + * const dataChannel = Channel.fromIterable([1, 2, 3, 4, 5]) + * + * // Convert to queue for concurrent processing + * 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 + * @since 2.0.0 + */ +export const toQueue: { + ( + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): ( + self: Channel + ) => Effect.Effect, never, Env | Scope.Scope> + ( + self: Channel, + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Effect.Effect, never, Env | Scope.Scope> +} = dual( + (args) => isChannel(args[0]), + Effect.fnUntraced(function*( + self: Channel, + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ) { + const scope = yield* Effect.scope + const queue = yield* Queue.make({ + capacity: typeof options.capacity === "number" ? options.capacity : undefined, + strategy: typeof options.capacity === "number" ? options.strategy : undefined + }) + yield* Scope.addFinalizer(scope, Queue.shutdown(queue)) + yield* Effect.forkIn(runIntoQueue(self, queue), scope) + return queue + }) +) + +/** + * Creates a scoped queue and forks an array-emitting channel to feed it. + * + * **Details** + * + * Each element inside emitted non-empty arrays is offered to the queue. Channel + * completion and failure are signaled through the queue. The queue is shut down + * when the surrounding scope closes. + * + * @category destructors + * @since 4.0.0 + */ +export const toQueueArray: { + ( + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env> + ) => Effect.Effect, never, Env | Scope.Scope> + ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Effect.Effect, never, Env | Scope.Scope> +} = dual( + (args) => isChannel(args[0]), + Effect.fnUntraced(function*( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ) { + const scope = yield* Effect.scope + const queue = yield* Queue.make({ + capacity: typeof options.capacity === "number" ? options.capacity : undefined, + strategy: typeof options.capacity === "number" ? options.strategy : undefined + }) + yield* Scope.addFinalizer(scope, Queue.shutdown(queue)) + yield* Effect.forkIn(runIntoQueueArray(self, queue), scope) + return queue + }) +) + +/** + * Converts a channel to a PubSub for concurrent consumption. + * + * **Details** + * + * `shutdownOnEnd` indicates whether the PubSub should be shut down when the + * channel ends. By default this is `true`. + * + * @category destructors + * @since 2.0.0 + */ +export const toPubSub: { + ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ): ( + self: Channel + ) => Effect.Effect, never, Env | Scope.Scope> + ( + self: Channel, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ): Effect.Effect, never, Env | Scope.Scope> +} = dual( + 2, + Effect.fnUntraced(function*( + self: Channel, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ) { + const pubsub = yield* makePubSub(options) + yield* Effect.forkScoped(runIntoPubSub(self, pubsub, { + shutdownOnEnd: options.shutdownOnEnd !== false + })) + return pubsub + }) +) + +/** + * Runs a channel and publishes each output element to a `PubSub`. + * + * **Details** + * + * The channel's output values are published as individual PubSub messages. Use + * `options.shutdownOnEnd` to shut down the PubSub when channel execution ends. + * + * @category destructors + * @since 4.0.0 + */ +export const runIntoPubSub: { + ( + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ): ( + self: Channel + ) => Effect.Effect + ( + self: Channel, + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ): Effect.Effect +} = dual( + (args) => isChannel(args[0]), + ( + self: Channel, + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ) => + runForEach(self, (value) => PubSub.publish(pubsub, value)).pipe( + options?.shutdownOnEnd === true ? Effect.ensuring(PubSub.shutdown(pubsub)) : identity_ + ) +) + +const makePubSub = ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } +) => + Effect.acquireRelease( + options.capacity === "unbounded" + ? PubSub.unbounded(options) + : options.strategy === "dropping" + ? PubSub.dropping(options) + : options.strategy === "sliding" + ? PubSub.sliding(options) + : PubSub.bounded(options), + PubSub.shutdown + ) + +/** + * Converts an array-emitting channel to a scoped `PubSub` for concurrent + * consumption. + * + * **Details** + * + * Each element inside emitted non-empty arrays is published as an individual + * PubSub message. `shutdownOnEnd` indicates whether the PubSub should be shut + * down when the channel ends. By default this is `true`. + * + * @category destructors + * @since 4.0.0 + */ +export const toPubSubArray: { + ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ): ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env> + ) => Effect.Effect, never, Env | Scope.Scope> + ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ): Effect.Effect, never, Env | Scope.Scope> +} = dual( + 2, + Effect.fnUntraced(function*( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ) { + const pubsub = yield* makePubSub(options) + yield* Effect.forkScoped(runIntoPubSubArray(self, pubsub, { + shutdownOnEnd: options.shutdownOnEnd !== false + })) + return pubsub + }) +) + +/** + * Runs an array-emitting channel and publishes each array element to a + * `PubSub`. + * + * **Details** + * + * Each element inside emitted non-empty arrays is published as an individual + * PubSub message. Use `options.shutdownOnEnd` to shut down the PubSub when + * channel execution ends. + * + * @category destructors + * @since 4.0.0 + */ +export const runIntoPubSubArray: { + ( + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ): ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env> + ) => Effect.Effect + ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ): Effect.Effect +} = dual( + (args) => isChannel(args[0]), + ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ) => + runForEach(self, (value) => PubSub.publishAll(pubsub, value)).pipe( + options?.shutdownOnEnd === true ? Effect.ensuring(PubSub.shutdown(pubsub)) : identity_ + ) +) + +/** + * Converts a channel to a scoped `PubSub` of `Take` values. + * + * **Details** + * + * Emitted non-empty arrays are published as output `Take` values. When the + * channel ends, its final `Exit` is published so subscribers can observe + * completion or failure. + * + * @category destructors + * @since 4.0.0 + */ +export const toPubSubTake: { + ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } + ): ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env> + ) => Effect.Effect< + PubSub.PubSub>, + never, + Env | Scope.Scope + > + ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } + ): Effect.Effect< + PubSub.PubSub>, + never, + Env | Scope.Scope + > +} = dual( + 2, + Effect.fnUntraced(function*( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env>, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } + ) { + const pubsub = yield* makePubSub>(options) + yield* runForEach(self, (value) => PubSub.publish(pubsub, value)).pipe( + Effect.onExit((exit) => PubSub.publish(pubsub, exit)), + Effect.forkScoped + ) + return pubsub + }) +) diff --git a/.repos/effect/packages/effect/src/ChannelSchema.ts b/.repos/effect/packages/effect/src/ChannelSchema.ts new file mode 100644 index 000000000..6f91859ec --- /dev/null +++ b/.repos/effect/packages/effect/src/ChannelSchema.ts @@ -0,0 +1,308 @@ +/** + * Schema adapters for `Channel` boundaries. The helpers encode typed channel + * chunks before they cross an encoded boundary, decode encoded chunks before + * application code receives them, and wrap bidirectional channels so callers + * work with schema-typed input and output while the inner channel uses encoded + * values. + * + * @since 4.0.0 + */ +import type * as Arr from "./Array.ts" +import * as Channel from "./Channel.ts" +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import * as Schema from "./Schema.ts" + +/** + * Creates a channel that encodes non-empty chunks of schema values into the + * schema's encoded representation. + * + * **When to use** + * + * Use to encode typed channel input into the schema's encoded representation + * before passing chunks to an encoded downstream boundary. + * + * **Details** + * + * Encoding failures are emitted as `SchemaError`, and any encoding services + * required by the schema become channel requirements. + * + * @see {@link encodeUnknown} for encoded output chunks that should be typed as `unknown` + * @see {@link decode} for the inverse channel that decodes encoded chunks into schema values + * + * @category constructors + * @since 4.0.0 + */ +export const encode = ( + schema: S +) => +(): Channel.Channel< + Arr.NonEmptyReadonlyArray, + IE | Schema.SchemaError, + Done, + Arr.NonEmptyReadonlyArray, + IE, + Done, + S["EncodingServices"] +> => { + const encode = Schema.encodeEffect(Schema.NonEmptyArray(schema)) + return Channel.fromTransform((upstream, _scope) => Effect.succeed(Effect.flatMap(upstream, (chunk) => encode(chunk)))) +} + +/** + * Creates an `encode` channel variant whose encoded output chunks are typed as + * `unknown`. + * + * **When to use** + * + * Use when a channel boundary should encode typed input chunks while the encoded + * output representation is intentionally untyped. + * + * @see {@link encode} for the variant that preserves the schema encoded type + * + * @category constructors + * @since 4.0.0 + */ +export const encodeUnknown: ( + schema: S +) => () => Channel.Channel< + Arr.NonEmptyReadonlyArray, + IE | Schema.SchemaError, + Done, + Arr.NonEmptyReadonlyArray, + IE, + Done, + S["EncodingServices"] +> = encode + +/** + * Creates a channel that decodes non-empty chunks from the schema's encoded + * representation into schema values. + * + * **When to use** + * + * Use to validate and decode encoded channel output into typed schema values + * before application code consumes it. + * + * **Details** + * + * Decoding failures are emitted as `SchemaError`, and any decoding services + * required by the schema become channel requirements. + * + * @see {@link decodeUnknown} for boundaries where the encoded input side is intentionally untyped + * @see {@link encode} for the inverse adapter that encodes typed schema values + * + * @category constructors + * @since 4.0.0 + */ +export const decode = ( + schema: S +) => +(): Channel.Channel< + Arr.NonEmptyReadonlyArray, + IE | Schema.SchemaError, + Done, + Arr.NonEmptyReadonlyArray, + IE, + Done, + S["DecodingServices"] +> => { + const decode = Schema.decodeEffect(Schema.NonEmptyArray(schema)) + return Channel.fromTransform((upstream, _scope) => Effect.succeed(Effect.flatMap(upstream, (chunk) => decode(chunk)))) +} + +/** + * Creates a `decode` channel variant for schema-decoding channel boundaries. + * + * **When to use** + * + * Use when you need an intentionally unknown or untyped encoded input while + * keeping only the decoded output statically typed according to the schema. + * + * **Details** + * + * The channel decodes non-empty encoded chunks into schema values, emits + * `SchemaError` when decoding fails, and requires the schema's decoding + * services. + * + * @see {@link decode} for the typed variant that preserves the schema's encoded type + * + * @category constructors + * @since 4.0.0 + */ +export const decodeUnknown: ( + schema: S +) => () => Channel.Channel< + Arr.NonEmptyReadonlyArray, + IE | Schema.SchemaError, + Done, + Arr.NonEmptyReadonlyArray, + IE, + Done, + S["DecodingServices"] +> = decode + +/** + * Wraps a channel so callers work with typed input and output chunks while the + * wrapped channel uses encoded chunks. + * + * **When to use** + * + * Use to expose typed input and output at a bidirectional channel boundary + * while the wrapped channel continues to operate on schema-encoded chunks. + * + * **Details** + * + * Values sent into the resulting channel are encoded with `inputSchema` before + * reaching the wrapped channel. Values emitted by the wrapped channel are + * decoded with `outputSchema` before they are emitted downstream. Schema + * failures are surfaced as `SchemaError`. + * + * @see {@link duplexUnknown} for the variant whose encoded side is intentionally untyped + * @see {@link encode} for encoding typed chunks at one-way channel boundaries + * @see {@link decode} for decoding encoded chunks at one-way channel boundaries + * + * @category combinators + * @since 4.0.0 + */ +export const duplex: { + (options: { + readonly inputSchema: In + readonly outputSchema: Out + }): ( + self: Channel.Channel< + Arr.NonEmptyReadonlyArray, + OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | InErr, + InDone, + R + > + ) => Channel.Channel< + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + InErr, + InDone, + R | In["EncodingServices"] | Out["DecodingServices"] + > + ( + self: Channel.Channel< + Arr.NonEmptyReadonlyArray, + OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | InErr, + InDone, + R + >, + options: { + readonly inputSchema: In + readonly outputSchema: Out + } + ): Channel.Channel< + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + InErr, + InDone, + R | In["EncodingServices"] | Out["DecodingServices"] + > +} = dual(2, ( + self: Channel.Channel< + Arr.NonEmptyReadonlyArray, + OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | InErr, + InDone, + R + >, + options: { + readonly inputSchema: In + readonly outputSchema: Out + } +): Channel.Channel< + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + InErr, + InDone, + R | In["EncodingServices"] | Out["DecodingServices"] +> => + encode(options.inputSchema)().pipe( + Channel.pipeTo(self), + Channel.pipeTo(decode(options.outputSchema)()) + )) + +/** + * Wraps a bidirectional channel whose encoded chunks are typed as `unknown`. + * + * **When to use** + * + * Use when you need a bidirectional channel to cross an encoded boundary whose + * chunk types are intentionally erased, while callers send and receive + * schema-typed chunks. + * + * **Details** + * + * The resulting channel accepts typed input chunks, encodes them with + * `inputSchema`, decodes unknown output chunks with `outputSchema`, and + * surfaces schema failures as `SchemaError`. + * + * @see {@link duplex} for the variant that preserves the schema encoded types on the wrapped channel + * + * @category combinators + * @since 4.0.0 + */ +export const duplexUnknown: { + (options: { + readonly inputSchema: In + readonly outputSchema: Out + }): ( + self: Channel.Channel< + Arr.NonEmptyReadonlyArray, + OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | InErr, + InDone, + R + > + ) => Channel.Channel< + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + InErr, + InDone, + R | In["EncodingServices"] | Out["DecodingServices"] + > + ( + self: Channel.Channel< + Arr.NonEmptyReadonlyArray, + OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | InErr, + InDone, + R + >, + options: { + readonly inputSchema: In + readonly outputSchema: Out + } + ): Channel.Channel< + Arr.NonEmptyReadonlyArray, + Schema.SchemaError | OutErr, + OutDone, + Arr.NonEmptyReadonlyArray, + InErr, + InDone, + R | In["EncodingServices"] | Out["DecodingServices"] + > +} = duplex diff --git a/.repos/effect/packages/effect/src/Chunk.ts b/.repos/effect/packages/effect/src/Chunk.ts new file mode 100644 index 000000000..7a75730a6 --- /dev/null +++ b/.repos/effect/packages/effect/src/Chunk.ts @@ -0,0 +1,2863 @@ +/** + * Stores many values in an immutable ordered collection. + * + * A `Chunk` is useful when you need to build or transform collections + * without changing the original collection. It is designed for efficient + * append, prepend, and concatenation. This module includes helpers for + * creating, reading, slicing, mapping, filtering, sorting, zipping, combining, + * and converting chunks to and from arrays and iterables. + * + * @since 2.0.0 + */ +import * as RA from "./Array.ts" +import type { NonEmptyReadonlyArray } from "./Array.ts" +import * as Equal from "./Equal.ts" +import * as Equivalence from "./Equivalence.ts" +import type * as Filter from "./Filter.ts" +import { format } from "./Formatter.ts" +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 type { NonEmptyIterable } from "./NonEmptyIterable.ts" +import type { Option } from "./Option.ts" +import * as O from "./Option.ts" +import * as Order from "./Order.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty, type Predicate, type Refinement } from "./Predicate.ts" +import * as R from "./Result.ts" +import type { Result } from "./Result.ts" +import type { Covariant, NoInfer } from "./Types.ts" + +const TypeId = "~effect/collections/Chunk" + +/** + * A Chunk is an immutable, ordered collection optimized for efficient concatenation and access patterns. + * + * **Example** (Inspecting chunk values) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk: Chunk.Chunk = Chunk.make(1, 2, 3) + * chunk.length // => 3 + * Chunk.toArray(chunk) // => [1, 2, 3] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Chunk extends Iterable, Equal.Equal, Pipeable, Inspectable { + readonly [TypeId]: { + readonly _A: Covariant + } + readonly length: number + right: Chunk + left: Chunk + backing: Backing + depth: number +} + +/** + * A non-empty Chunk guaranteed to contain at least one element. + * + * **Example** (Working with non-empty chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const nonEmptyChunk: Chunk.NonEmptyChunk = Chunk.make(1, 2, 3) + * Chunk.headNonEmpty(nonEmptyChunk) // => 1 + * Chunk.lastNonEmpty(nonEmptyChunk) // => 3 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface NonEmptyChunk extends Chunk, NonEmptyIterable {} + +/** + * Type lambda for Chunk, used for higher-kinded type operations. + * + * **Example** (Applying the Chunk type lambda) + * + * ```ts import.meta.vitest + * import type { Chunk, HKT } from "effect" + * + * // Create a Chunk type using the type lambda + * type NumberChunk = HKT.Kind + * // Equivalent to: Chunk + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export interface ChunkTypeLambda extends TypeLambda { + readonly type: Chunk +} + +type Backing = + | IArray + | IConcat + | ISingleton + | IEmpty + | ISlice + +interface IArray { + readonly _tag: "IArray" + readonly array: ReadonlyArray +} + +interface IConcat { + readonly _tag: "IConcat" + readonly left: Chunk + readonly right: Chunk +} + +interface ISingleton { + readonly _tag: "ISingleton" + readonly a: A +} + +interface IEmpty { + readonly _tag: "IEmpty" +} + +interface ISlice { + readonly _tag: "ISlice" + readonly chunk: Chunk + readonly offset: number + readonly length: number +} + +function copy( + src: ReadonlyArray, + srcPos: number, + dest: Array, + destPos: number, + len: number +) { + for (let i = srcPos; i < Math.min(src.length, srcPos + len); i++) { + dest[destPos + i - srcPos] = src[i]! + } + return dest +} + +const emptyArray: ReadonlyArray = [] + +/** + * Creates an `Equivalence` for chunks that compares chunk lengths and then + * compares corresponding elements with the provided element equivalence. + * + * **Example** (Comparing chunks for equivalence) + * + * ```ts import.meta.vitest + * import { Chunk, Equivalence } from "effect" + * + * const chunk1 = Chunk.make(1, 2, 3) + * const chunk2 = Chunk.make(1, 2, 3) + * const chunk3 = Chunk.make(1, 2, 4) + * + * const eq = Chunk.makeEquivalence(Equivalence.strictEqual()) + * eq(chunk1, chunk2) // => true + * eq(chunk1, chunk3) // => false + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence = (isEquivalent: Equivalence.Equivalence): Equivalence.Equivalence> => + Equivalence.make((self, that) => + self.length === that.length && toReadonlyArray(self).every((value, i) => isEquivalent(value, getUnsafe(that, i))) + ) + +const _equivalence = makeEquivalence(Equal.equals) + +const ChunkProto: Omit, "backing" | "depth" | "left" | "length" | "right"> = { + [TypeId]: { + _A: (_: never) => _ + }, + toString(this: Chunk) { + return `Chunk(${format(toReadonlyArray(this))})` + }, + toJSON(this: Chunk) { + return { + _id: "Chunk", + values: toJson(toReadonlyArray(this)) + } + }, + [NodeInspectSymbol](this: Chunk) { + return this.toJSON() + }, + [Equal.symbol](this: Chunk, that: unknown): boolean { + return isChunk(that) && _equivalence(this, that) + }, + [Hash.symbol](this: Chunk): number { + return Hash.array(toReadonlyArray(this)) + }, + [Symbol.iterator](this: Chunk): Iterator { + switch (this.backing._tag) { + case "IArray": { + return this.backing.array[Symbol.iterator]() + } + case "IEmpty": { + return emptyArray[Symbol.iterator]() + } + default: { + return toReadonlyArray(this)[Symbol.iterator]() + } + } + }, + pipe(this: Chunk) { + return pipeArguments(this, arguments) + } +} + +const makeChunk = (backing: Backing): Chunk => { + const chunk = Object.create(ChunkProto) + chunk.backing = backing + switch (backing._tag) { + case "IEmpty": { + chunk.length = 0 + chunk.depth = 0 + chunk.left = chunk + chunk.right = chunk + break + } + case "IConcat": { + chunk.length = backing.left.length + backing.right.length + chunk.depth = 1 + Math.max(backing.left.depth, backing.right.depth) + chunk.left = backing.left + chunk.right = backing.right + break + } + case "IArray": { + chunk.length = backing.array.length + chunk.depth = 0 + chunk.left = _empty + chunk.right = _empty + break + } + case "ISingleton": { + chunk.length = 1 + chunk.depth = 0 + chunk.left = _empty + chunk.right = _empty + break + } + case "ISlice": { + chunk.length = backing.length + chunk.depth = backing.chunk.depth + 1 + chunk.left = _empty + chunk.right = _empty + break + } + } + return chunk +} + +/** + * Checks whether `u` is a `Chunk` + * + * **Example** (Checking for chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3) + * const array = [1, 2, 3] + * + * Chunk.isChunk(chunk) // => true + * Chunk.isChunk(array) // => false + * Chunk.isChunk("string") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isChunk: { + (u: Iterable): u is Chunk + (u: unknown): u is Chunk +} = (u: unknown): u is Chunk => hasProperty(u, TypeId) + +const _empty = makeChunk({ _tag: "IEmpty" }) + +/** + * Creates an empty `Chunk`. + * + * **Example** (Creating an empty chunk) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.size(Chunk.empty()) // => 0 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty: () => Chunk = () => _empty + +/** + * Builds a `NonEmptyChunk` from an non-empty collection of elements. + * + * **Example** (Creating a non-empty chunk) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.toArray(Chunk.make(1, 2, 3, 4)) // => [1, 2, 3, 4] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = ]>(...as: As): NonEmptyChunk => + fromNonEmptyArrayUnsafe(as) + +/** + * Builds a `NonEmptyChunk` from a single element. + * + * **Example** (Creating a single-element chunk) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.toArray(Chunk.of("hello")) // => ["hello"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const of = (a: A): NonEmptyChunk => makeChunk({ _tag: "ISingleton", a }) as any + +/** + * Creates a new `Chunk` from an iterable collection of values. + * + * **Example** (Creating chunks from iterables) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.toArray(Chunk.fromIterable([1, 2, 3])) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = (self: Iterable): Chunk => + isChunk(self) ? self : fromArrayUnsafe(RA.fromIterable(self)) + +const copyToArray = (self: Chunk, array: Array, initial: number): void => { + switch (self.backing._tag) { + case "IArray": { + copy(self.backing.array, 0, array, initial, self.length) + break + } + case "IConcat": { + copyToArray(self.left, array, initial) + copyToArray(self.right, array, initial + self.left.length) + break + } + case "ISingleton": { + array[initial] = self.backing.a + break + } + case "ISlice": { + let i = 0 + let j = initial + while (i < self.length) { + array[j] = getUnsafe(self, i) + i += 1 + j += 1 + } + break + } + } +} + +const toArray_ = (self: Chunk): Array => toReadonlyArray(self).slice() + +/** + * Converts a `Chunk` into an `Array`. If the provided `Chunk` is non-empty + * (`NonEmptyChunk`), the function will return a `NonEmptyArray`, ensuring the + * non-empty property is preserved. + * + * **Example** (Converting chunks to mutable arrays) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3) + * const array = Chunk.toArray(chunk) + * array // => [1, 2, 3] + * Array.isArray(array) // => true + * + * // With empty chunk + * Chunk.toArray(Chunk.empty()) // => [] + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const toArray: >( + self: S +) => S extends NonEmptyChunk ? RA.NonEmptyArray> : Array> = toArray_ as any + +const toReadonlyArray_ = (self: Chunk): ReadonlyArray => { + switch (self.backing._tag) { + case "IEmpty": { + return emptyArray + } + case "IArray": { + return self.backing.array + } + default: { + const arr = new Array(self.length) + copyToArray(self, arr, 0) + self.backing = { + _tag: "IArray", + array: arr + } + self.left = _empty + self.right = _empty + self.depth = 0 + return arr + } + } +} + +/** + * Converts a `Chunk` into a `ReadonlyArray`. If the provided `Chunk` is + * non-empty (`NonEmptyChunk`), the function will return a + * `NonEmptyReadonlyArray`, ensuring the non-empty property is preserved. + * + * **Example** (Converting chunks to readonly arrays) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3) + * const readonlyArray = Chunk.toReadonlyArray(chunk) + * readonlyArray // => [1, 2, 3] + * + * // The result is read-only, modifications would cause TypeScript errors + * // readonlyArray[0] = 10 // TypeScript error + * + * // With empty chunk + * Chunk.toReadonlyArray(Chunk.empty()) // => [] + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const toReadonlyArray: >( + self: S +) => S extends NonEmptyChunk ? RA.NonEmptyReadonlyArray> : ReadonlyArray> = + toReadonlyArray_ as any + +const reverseChunk = (self: Chunk): Chunk => { + switch (self.backing._tag) { + case "IEmpty": + case "ISingleton": + return self + case "IArray": { + return makeChunk({ _tag: "IArray", array: RA.reverse(self.backing.array) }) + } + case "IConcat": { + return makeChunk({ _tag: "IConcat", left: reverse(self.backing.right), right: reverse(self.backing.left) }) + } + case "ISlice": + return fromArrayUnsafe(RA.reverse(toReadonlyArray(self))) + } +} + +/** + * Reverses the order of elements in a `Chunk`. + * + * **When to use** + * + * Use to read or process chunk elements in reverse order. + * + * **Details** + * + * If the input chunk is a `NonEmptyChunk`, the reversed chunk is also a + * `NonEmptyChunk`. + * + * **Example** (Reversing chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3) + * Chunk.toArray(Chunk.reverse(chunk)) // => [3, 2, 1] + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const reverse: >(self: S) => Chunk.With> = reverseChunk as any + +/** + * Gets the value at an index in a `Chunk` safely, returning `None` when the index is + * out of bounds. + * + * **Example** (Accessing elements safely) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make("a", "b", "c", "d") + * + * Chunk.get(chunk, 1) // => Option.some("b") + * Chunk.get(chunk, 10) // => Option.none() + * Chunk.get(chunk, -1) // => Option.none() + * + * // Using pipe syntax + * chunk.pipe(Chunk.get(2)) // => Option.some("c") + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const get: { + (index: number): (self: Chunk) => Option + (self: Chunk, index: number): Option +} = dual( + 2, + (self: Chunk, index: number): Option => + index < 0 || index >= self.length ? O.none() : O.some(getUnsafe(self, index)) +) + +/** + * Wraps an array into a chunk without copying. + * + * **When to use** + * + * Use when the input array can be shared with the resulting `Chunk` and avoiding + * a copy matters. + * + * **Gotchas** + * + * Mutating the source array after wrapping can mutate the resulting `Chunk`. + * + * **Example** (Creating chunks without copying arrays) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const array = [1, 2, 3, 4, 5] + * const chunk = Chunk.fromArrayUnsafe(array) + * Chunk.toArray(chunk) // => [1, 2, 3, 4, 5] + * + * // Warning: Since this doesn't copy the array, mutations affect the chunk + * array[0] = 999 + * Chunk.toArray(chunk) // => [999, 2, 3, 4, 5] + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const fromArrayUnsafe = (self: ReadonlyArray): Chunk => + self.length === 0 ? empty() : self.length === 1 ? of(self[0]) : makeChunk({ _tag: "IArray", array: self }) + +/** + * Wraps a non-empty array into a non-empty chunk without copying. + * + * **When to use** + * + * Use when the input array is already known to be non-empty, can be shared with + * the resulting `Chunk`, and avoiding a copy matters. + * + * **Gotchas** + * + * Mutating the source array after wrapping can mutate the resulting `Chunk`. + * + * **Example** (Creating non-empty chunks without copying arrays) + * + * ```ts import.meta.vitest + * import { Array, Chunk } from "effect" + * + * const nonEmptyArray = Array.make(1, 2, 3, 4, 5) + * const chunk = Chunk.fromNonEmptyArrayUnsafe(nonEmptyArray) + * Chunk.toArray(chunk) // => [1, 2, 3, 4, 5] + * + * // The result is guaranteed to be non-empty + * Chunk.isNonEmpty(chunk) // => true + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const fromNonEmptyArrayUnsafe = (self: NonEmptyReadonlyArray): NonEmptyChunk => + fromArrayUnsafe(self) as any + +/** + * Gets an element at the specified index without returning an `Option`. + * + * **When to use** + * + * Use when reading from a `Chunk` at an index known to be in bounds and direct + * element access is preferred over handling `Option.none`. + * + * **Gotchas** + * + * Throws if the index is out of bounds. + * + * **Example** (Accessing elements unsafely) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make("a", "b", "c", "d") + * + * Chunk.getUnsafe(chunk, 1) // => "b" + * Chunk.getUnsafe(chunk, 3) // => "d" + * + * // Use Chunk.get when the index may be out of bounds + * Option.isNone(Chunk.get(chunk, 10)) // => true + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const getUnsafe: { + (index: number): (self: Chunk) => A + (self: Chunk, index: number): A +} = dual(2, (self: Chunk, index: number): A => { + const i = Math.floor(index) + switch (self.backing._tag) { + case "IEmpty": { + throw new Error(`Index out of bounds: ${i}`) + } + case "ISingleton": { + if (index !== 0) { + throw new Error(`Index out of bounds: ${i}`) + } + return self.backing.a + } + case "IArray": { + if (i >= self.length || i < 0) { + throw new Error(`Index out of bounds: ${i}`) + } + return self.backing.array[i]! + } + case "IConcat": { + return i < self.left.length + ? getUnsafe(self.left, i) + : getUnsafe(self.right, i - self.left.length) + } + case "ISlice": { + return getUnsafe(self.backing.chunk, i + self.backing.offset) + } + } +}) + +/** + * Appends the specified element to the end of the `Chunk`. + * + * **When to use** + * + * Use to add one element after the existing chunk elements and return a + * `NonEmptyChunk`. + * + * **Example** (Appending an element) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3) + * Chunk.toArray(Chunk.append(chunk, 4)) // => [1, 2, 3, 4] + * + * // Appending to empty chunk + * const emptyChunk = Chunk.empty() + * Chunk.toArray(Chunk.append(emptyChunk, 42)) // => [42] + * ``` + * + * @see {@link prepend} for adding one element before the existing elements + * @see {@link appendAll} for appending all elements from another chunk + * + * @category combining + * @since 2.0.0 + */ +export const append: { + (a: A2): (self: Chunk) => NonEmptyChunk + (self: Chunk, a: A2): NonEmptyChunk +} = dual(2, (self: Chunk, a: A2): NonEmptyChunk => appendAll(self, of(a))) + +/** + * Prepends an element to the front of a `Chunk`, creating a new `NonEmptyChunk`. + * + * **Example** (Prepending an element) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(2, 3, 4) + * Chunk.toArray(Chunk.prepend(chunk, 1)) // => [1, 2, 3, 4] + * + * // Prepending to empty chunk + * const emptyChunk = Chunk.empty() + * Chunk.toArray(Chunk.prepend(emptyChunk, "first")) // => ["first"] + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const prepend: { + (elem: B): (self: Chunk) => NonEmptyChunk + (self: Chunk, elem: B): NonEmptyChunk +} = dual(2, (self: Chunk, elem: B): NonEmptyChunk => appendAll(of(elem), self)) + +/** + * Takes the first up to `n` elements from the chunk. + * + * **Example** (Taking elements from the start) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.toArray(Chunk.take(chunk, 3)) // => [1, 2, 3] + * ``` + * + * @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 => { + const n = Math.floor(_n) + if (n <= 0) { + return _empty + } else if (n >= self.length) { + return self + } else { + switch (self.backing._tag) { + case "ISlice": { + return makeChunk({ + _tag: "ISlice", + chunk: self.backing.chunk, + length: n, + offset: self.backing.offset + }) + } + case "IConcat": { + if (n > self.left.length) { + return makeChunk({ + _tag: "IConcat", + left: self.left, + right: take(self.right, n - self.left.length) + }) + } + + return take(self.left, n) + } + default: { + return makeChunk({ + _tag: "ISlice", + chunk: self, + offset: 0, + length: n + }) + } + } + } +}) + +/** + * Drops the first up to `n` elements from the chunk. + * + * **Example** (Dropping elements from the start) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.toArray(Chunk.drop(chunk, 2)) // => [3, 4, 5] + * ``` + * + * @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 => { + const n = Math.floor(_n) + if (n <= 0) { + return self + } else if (n >= self.length) { + return _empty + } else { + switch (self.backing._tag) { + case "ISlice": { + return makeChunk({ + _tag: "ISlice", + chunk: self.backing.chunk, + offset: self.backing.offset + n, + length: self.backing.length - n + }) + } + case "IConcat": { + if (n > self.left.length) { + return drop(self.right, n - self.left.length) + } + return makeChunk({ + _tag: "IConcat", + left: drop(self.left, n), + right: self.right + }) + } + default: { + return makeChunk({ + _tag: "ISlice", + chunk: self, + offset: n, + length: self.length - n + }) + } + } + } +}) + +/** + * Drops the last `n` elements. + * + * **Example** (Dropping elements from the end) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.toArray(Chunk.dropRight(chunk, 2)) // => [1, 2, 3] + * ``` + * + * @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))) + +/** + * Drops all elements so long as the predicate returns true. + * + * **Example** (Dropping elements while a predicate matches) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.toArray(Chunk.dropWhile(chunk, (n) => n < 3)) // => [3, 4, 5] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dropWhile: { + (predicate: Predicate>): (self: Chunk) => Chunk + (self: Chunk, predicate: Predicate): Chunk +} = dual(2, (self: Chunk, predicate: Predicate): Chunk => { + const arr = toReadonlyArray(self) + const len = arr.length + let i = 0 + while (i < len && predicate(arr[i]!)) { + i++ + } + return drop(self, i) +}) + +/** + * Prepends the specified prefix chunk to the beginning of the specified chunk. + * If either chunk is non-empty, the result is also a non-empty chunk. + * + * **Example** (Prepending all elements) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.make(1, 2).pipe( + * Chunk.prependAll(Chunk.make("a", "b")), + * Chunk.toArray + * ) // => ["a", "b", 1, 2] + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const prependAll: { + , T extends Chunk>( + that: T + ): (self: S) => Chunk.OrNonEmpty | Chunk.Infer> + (self: Chunk, that: NonEmptyChunk): NonEmptyChunk + (self: NonEmptyChunk, that: Chunk): NonEmptyChunk + (self: Chunk, that: Chunk): Chunk +} = dual(2, (self: NonEmptyChunk, that: Chunk): Chunk => appendAll(that, self)) + +/** + * Concatenates two chunks, combining their elements. + * If either chunk is non-empty, the result is also a non-empty chunk. + * + * **When to use** + * + * Use to concatenate two chunks when the second chunk's elements should come + * after the first. + * + * **Example** (Appending all elements) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.make(1, 2).pipe( + * Chunk.appendAll(Chunk.make("a", "b")), + * Chunk.toArray + * ) // => [1, 2, "a", "b"] + * ``` + * + * @see {@link prependAll} for concatenating chunks in the opposite order + * @see {@link append} for adding a single element to the end + * + * @category combining + * @since 2.0.0 + */ +export const appendAll: { + , T extends Chunk>( + that: T + ): (self: S) => Chunk.OrNonEmpty | Chunk.Infer> + (self: Chunk, that: NonEmptyChunk): NonEmptyChunk + (self: NonEmptyChunk, that: Chunk): NonEmptyChunk + (self: Chunk, that: Chunk): Chunk +} = dual(2, (self: Chunk, that: Chunk): Chunk => { + if (self.backing._tag === "IEmpty") { + return that + } + if (that.backing._tag === "IEmpty") { + return self + } + const diff = that.depth - self.depth + if (Math.abs(diff) <= 1) { + return makeChunk({ _tag: "IConcat", left: self, right: that }) + } else if (diff < -1) { + if (self.left.depth >= self.right.depth) { + const nr = appendAll(self.right, that) + return makeChunk({ _tag: "IConcat", left: self.left, right: nr }) + } else { + const nrr = appendAll(self.right.right, that) + if (nrr.depth === self.depth - 3) { + const nr = makeChunk({ _tag: "IConcat", left: self.right.left, right: nrr }) + return makeChunk({ _tag: "IConcat", left: self.left, right: nr }) + } else { + const nl = makeChunk({ _tag: "IConcat", left: self.left, right: self.right.left }) + return makeChunk({ _tag: "IConcat", left: nl, right: nrr }) + } + } + } else { + if (that.right.depth >= that.left.depth) { + const nl = appendAll(self, that.left) + return makeChunk({ _tag: "IConcat", left: nl, right: that.right }) + } else { + const nll = appendAll(self, that.left.left) + if (nll.depth === that.depth - 3) { + const nl = makeChunk({ _tag: "IConcat", left: nll, right: that.left.right }) + return makeChunk({ _tag: "IConcat", left: nl, right: that.right }) + } else { + const nr = makeChunk({ _tag: "IConcat", left: that.left.right, right: that.right }) + return makeChunk({ _tag: "IConcat", left: nll, right: nr }) + } + } + } +}) + +/** + * Returns a filtered and mapped subset of the elements. + * + * **Example** (Filtering and mapping values) + * + * ```ts import.meta.vitest + * import { Chunk, Result } from "effect" + * + * const chunk = Chunk.make("1", "2", "hello", "3", "world") + * const numbers = Chunk.filterMap(chunk, (str) => { + * const num = parseInt(str) + * return isNaN(num) ? Result.failVoid : Result.succeed(num) + * }) + * 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) + * }) + * Chunk.toArray(evenIndexNumbers) // => [1] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + (f: (input: A, i: number) => Result): (self: Chunk) => Chunk + (self: Chunk, f: (input: A, i: number) => Result): Chunk +} = dual( + 2, + (self: Chunk, f: (input: A, i: number) => Result): Chunk => { + const as = RA.fromIterable(self) + const out: Array = [] + for (let i = 0; i < as.length; i++) { + const result = f(as[i], i) + if (R.isSuccess(result)) { + out.push(result.success) + } + } + return fromArrayUnsafe(out) + } +) + +/** + * Returns a filtered subset of the elements. + * + * **Example** (Filtering values) + * + * ```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) + * 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") + * Chunk.toArray(numbers) // => [42, 100] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (refinement: Refinement, B>): (self: Chunk) => Chunk + (predicate: Predicate>): (self: Chunk) => Chunk + (self: Chunk, refinement: Refinement): Chunk + (self: Chunk, predicate: Predicate): Chunk +} = dual( + 2, + (self: Chunk, predicate: Predicate): Chunk => fromArrayUnsafe(RA.filter(self, predicate)) +) + +/** + * Transforms all elements of the chunk for as long as the specified function succeeds. + * + * **Example** (Filtering and mapping while values match) + * + * ```ts import.meta.vitest + * import { Chunk, Result } from "effect" + * + * const chunk = Chunk.make("1", "2", "hello", "3", "4") + * Chunk.toArray(Chunk.filterMapWhile(chunk, (s) => { + * const n = Number(s) + * return Number.isNaN(n) ? Result.failVoid : Result.succeed(n) + * })) // => [1, 2] + * + * 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 + * @since 2.0.0 + */ +export const filterMapWhile: { + (f: Filter.Filter): (self: Chunk) => Chunk + (self: Chunk, f: Filter.Filter): Chunk +} = dual(2, (self: Chunk, f: Filter.Filter): Chunk => { + const out: Array = [] + for (const a of self) { + const result = f(a) + if (R.isSuccess(result)) { + out.push(result.success) + } else { + break + } + } + return fromArrayUnsafe(out) +}) + +/** + * Filters out optional values + * + * **Example** (Compacting optional values) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(Option.some(1), Option.none(), Option.some(3)) + * Chunk.toArray(Chunk.compact(chunk)) // => [1, 3] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const compact = (self: Chunk>): Chunk => { + const out: Array = [] + for (const option of self) { + if (O.isSome(option)) { + out.push(option.value) + } + } + return fromArrayUnsafe(out) +} + +/** + * Applies a function to each element in a chunk and returns a new chunk containing the concatenated mapped elements. + * + * **Example** (Flat mapping chunks) + * + * ```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)) + * Chunk.toArray(duplicated) // => [1, 1, 2, 2, 3, 3] + * + * // Flattening nested arrays + * const words = Chunk.make("hello", "world") + * const letters = Chunk.flatMap( + * words, + * (word) => Chunk.fromIterable(word.split("")) + * ) + * Chunk.toArray(letters).join("") // => "helloworld" + * + * // With index parameter + * const indexed = Chunk.flatMap(chunk, (n, i) => Chunk.make(n + i)) + * Chunk.toArray(indexed) // => [1, 3, 5] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + , T extends Chunk>( + f: (a: Chunk.Infer, i: number) => T + ): (self: S) => Chunk.AndNonEmpty> + (self: NonEmptyChunk, f: (a: A, i: number) => NonEmptyChunk): NonEmptyChunk + (self: Chunk, f: (a: A, i: number) => Chunk): Chunk +} = dual(2, (self: Chunk, f: (a: A, i: number) => Chunk) => { + if (self.backing._tag === "ISingleton") { + return f(self.backing.a, 0) + } + let out: Chunk = _empty + let i = 0 + for (const k of self) { + out = appendAll(out, f(k, i++)) + } + return out +}) + +/** + * Iterates over each element of a `Chunk` and applies a function to it. + * + * **Details** + * + * This function processes every element of the given `Chunk`, calling the + * provided function `f` on each element. It does not return a new value; + * instead, it is primarily used for side effects, such as logging or + * accumulating data in an external variable. + * + * **Example** (Iterating over chunk values) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4) + * + * const values: Array = [] + * Chunk.forEach(chunk, (n) => values.push(`Value: ${n}`)) + * values // => ["Value: 1", "Value: 2", "Value: 3", "Value: 4"] + * + * // With index parameter + * 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 + * @since 2.0.0 + */ +export const forEach: { + (f: (a: A, index: number) => B): (self: Chunk) => void + (self: Chunk, f: (a: A, index: number) => B): void +} = dual(2, (self: Chunk, f: (a: A) => B): void => toReadonlyArray(self).forEach(f)) + +/** + * Flattens a chunk of chunks into a single chunk by concatenating all chunks. + * + * **Example** (Flattening nested chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const nested = Chunk.make( + * Chunk.make(1, 2), + * Chunk.make(3, 4, 5), + * Chunk.make(6) + * ) + * Chunk.toArray(Chunk.flatten(nested)) // => [1, 2, 3, 4, 5, 6] + * + * // With empty chunks + * const withEmpty = Chunk.make( + * Chunk.make(1, 2), + * Chunk.empty(), + * Chunk.make(3, 4) + * ) + * Chunk.toArray(Chunk.flatten(withEmpty)) // => [1, 2, 3, 4] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatten: >>(self: S) => Chunk.Flatten = flatMap(identity) as any + +/** + * Groups elements in chunks of up to `n` elements. + * + * **When to use** + * + * Use to divide a chunk into ordered, non-overlapping chunks with at most `n` + * elements each. + * + * **Details** + * + * 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. + * + * **Example** (Splitting into fixed-size chunks) + * + * ```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) + * + * 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) + * 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 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 gr: Array> = [] + let current: Array = [] + toReadonlyArray(self).forEach((a) => { + current.push(a) + if (current.length >= n) { + gr.push(fromArrayUnsafe(current)) + current = [] + } + }) + if (current.length > 0) { + gr.push(fromArrayUnsafe(current)) + } + return fromArrayUnsafe(gr) +}) + +/** + * Creates a `Chunk` of values that are included in both chunks. + * + * **Details** + * + * The order and references of result values are determined by the first chunk. + * + * **Example** (Intersecting chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk1 = Chunk.make(1, 2, 3, 4) + * const chunk2 = Chunk.make(3, 4, 5, 6) + * Chunk.toArray(Chunk.intersection(chunk1, chunk2)) // => [3, 4] + * + * // With strings + * const words1 = Chunk.make("hello", "world", "foo") + * const words2 = Chunk.make("world", "bar", "foo") + * Chunk.toArray(Chunk.intersection(words1, words2)) // => ["world", "foo"] + * + * // No intersection + * const chunk3 = Chunk.make(1, 2) + * const chunk4 = Chunk.make(3, 4) + * Chunk.toArray(Chunk.intersection(chunk3, chunk4)) // => [] + * ``` + * + * @category set operations + * @since 2.0.0 + */ +export const intersection: { + (that: Chunk): (self: Chunk) => Chunk + (self: Chunk, that: Chunk): Chunk +} = dual( + 2, + (self: Chunk, that: Chunk): Chunk => + fromArrayUnsafe(RA.intersection(toReadonlyArray(self), toReadonlyArray(that))) +) + +/** + * Determines if the chunk is empty. + * + * **Example** (Checking for empty chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.isEmpty(Chunk.empty()) // => true + * Chunk.isEmpty(Chunk.make(1, 2, 3)) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: Chunk): boolean => self.length === 0 + +/** + * Determines if the chunk is not empty. + * + * **Example** (Checking for non-empty chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.isNonEmpty(Chunk.empty()) // => false + * Chunk.isNonEmpty(Chunk.make(1, 2, 3)) // => true + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isNonEmpty = (self: Chunk): self is NonEmptyChunk => self.length > 0 + +/** + * Returns the first element of this chunk safely if it exists. + * + * **Example** (Getting the first element) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * Chunk.head(Chunk.empty()) // => Option.none() + * Chunk.head(Chunk.make(1, 2, 3)) // => Option.some(1) + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const head: (self: Chunk) => Option = get(0) + +/** + * Returns the first element of this chunk. + * + * **When to use** + * + * Use when you know the chunk is non-empty and need the first element directly + * without handling `Option.none`. + * + * **Gotchas** + * + * Throws an error if the chunk is empty. + * + * **Example** (Getting the first element unsafely) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4) + * Chunk.headUnsafe(chunk) // => 1 + * + * const singleElement = Chunk.make("hello") + * Chunk.headUnsafe(singleElement) // => "hello" + * + * // Use Chunk.head when the chunk may be empty + * Option.isNone(Chunk.head(Chunk.empty())) // => true + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const headUnsafe = (self: Chunk): A => getUnsafe(self, 0) + +/** + * Returns the first element of this non empty chunk. + * + * **Example** (Getting the first element of a non-empty chunk) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) + * Chunk.headNonEmpty(nonEmptyChunk) // => 1 + * + * const singleElement = Chunk.make("hello") + * Chunk.headNonEmpty(singleElement) // => "hello" + * + * // Type safety: this function only accepts NonEmptyChunk + * // Chunk.headNonEmpty(Chunk.empty()) // TypeScript error + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const headNonEmpty: (self: NonEmptyChunk) => A = headUnsafe + +/** + * Returns the last element of this chunk safely if it exists. + * + * **Example** (Getting the last element) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * Chunk.last(Chunk.empty()) // => Option.none() + * Chunk.last(Chunk.make(1, 2, 3)) // => Option.some(3) + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const last = (self: Chunk): Option => get(self, self.length - 1) + +/** + * Returns the last element of this chunk. + * + * **When to use** + * + * Use when you know the chunk is non-empty and need the last element directly + * without handling `Option.none`. + * + * **Gotchas** + * + * Throws an error if the chunk is empty. + * + * **Example** (Getting the last element unsafely) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4) + * Chunk.lastUnsafe(chunk) // => 4 + * + * const singleElement = Chunk.make("hello") + * Chunk.lastUnsafe(singleElement) // => "hello" + * + * // Use Chunk.last when the chunk may be empty + * Option.isNone(Chunk.last(Chunk.empty())) // => true + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const lastUnsafe = (self: Chunk): A => getUnsafe(self, self.length - 1) + +/** + * Returns the last element of this non empty chunk. + * + * **Example** (Getting the last element of a non-empty chunk) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) + * Chunk.lastNonEmpty(nonEmptyChunk) // => 4 + * + * const singleElement = Chunk.make("hello") + * Chunk.lastNonEmpty(singleElement) // => "hello" + * + * // Type safety: this function only accepts NonEmptyChunk + * // Chunk.lastNonEmpty(Chunk.empty()) // TypeScript error + * ``` + * + * @category getters + * @since 3.4.0 + */ +export const lastNonEmpty: (self: NonEmptyChunk) => A = lastUnsafe + +/** + * A namespace containing utility types for Chunk operations. + * + * **Example** (Working with Chunk utility types) + * + * ```ts import.meta.vitest + * import type { Chunk } from "effect" + * + * // Extract the element type from a Chunk + * declare const chunk: Chunk.Chunk + * type ElementType = Chunk.Chunk.Infer // string + * + * // Create a preserving non-emptiness + * declare const nonEmptyChunk: Chunk.NonEmptyChunk + * type WithString = Chunk.Chunk.With // Chunk.NonEmptyChunk + * ``` + * + * @since 2.0.0 + */ +export declare namespace Chunk { + /** + * Infers the element type of a Chunk. + * + * **Example** (Inferring element types) + * + * ```ts import.meta.vitest + * import type { Chunk } from "effect" + * + * declare const numberChunk: Chunk.Chunk + * declare const stringChunk: Chunk.Chunk + * + * type NumberType = Chunk.Chunk.Infer // number + * type StringType = Chunk.Chunk.Infer // string + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type Infer> = S extends Chunk ? A : never + + /** + * Constructs a Chunk type preserving non-emptiness. + * + * **Example** (Preserving non-emptiness) + * + * ```ts import.meta.vitest + * import type { Chunk } from "effect" + * + * declare const regularChunk: Chunk.Chunk + * declare const nonEmptyChunk: Chunk.NonEmptyChunk + * + * type WithString1 = Chunk.Chunk.With // Chunk.Chunk + * type WithString2 = Chunk.Chunk.With // Chunk.NonEmptyChunk + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type With, A> = S extends NonEmptyChunk ? NonEmptyChunk : Chunk + + /** + * Creates a non-empty Chunk if either input is non-empty. + * + * **Example** (Preserving non-emptiness from either input) + * + * ```ts import.meta.vitest + * import type { Chunk } from "effect" + * + * declare const emptyChunk: Chunk.Chunk + * declare const nonEmptyChunk: Chunk.NonEmptyChunk + * + * type Result1 = Chunk.Chunk.OrNonEmpty< + * typeof emptyChunk, + * typeof emptyChunk, + * string + * > // Chunk.Chunk + * type Result2 = Chunk.Chunk.OrNonEmpty< + * typeof emptyChunk, + * typeof nonEmptyChunk, + * string + * > // Chunk.NonEmptyChunk + * type Result3 = Chunk.Chunk.OrNonEmpty< + * typeof nonEmptyChunk, + * typeof emptyChunk, + * string + * > // Chunk.NonEmptyChunk + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type OrNonEmpty, T extends Chunk, A> = S extends NonEmptyChunk ? + NonEmptyChunk + : T extends NonEmptyChunk ? NonEmptyChunk + : Chunk + + /** + * Creates a non-empty Chunk only if both inputs are non-empty. + * + * **Example** (Requiring non-emptiness from both inputs) + * + * ```ts import.meta.vitest + * import type { Chunk } from "effect" + * + * declare const emptyChunk: Chunk.Chunk + * declare const nonEmptyChunk: Chunk.NonEmptyChunk + * + * type Result1 = Chunk.Chunk.AndNonEmpty< + * typeof emptyChunk, + * typeof emptyChunk, + * string + * > // Chunk.Chunk + * type Result2 = Chunk.Chunk.AndNonEmpty< + * typeof emptyChunk, + * typeof nonEmptyChunk, + * string + * > // Chunk.Chunk + * type Result3 = Chunk.Chunk.AndNonEmpty< + * typeof nonEmptyChunk, + * typeof nonEmptyChunk, + * string + * > // Chunk.NonEmptyChunk + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type AndNonEmpty, T extends Chunk, A> = S extends NonEmptyChunk ? + T extends NonEmptyChunk ? NonEmptyChunk + : Chunk : + Chunk + + /** + * Flattens a nested Chunk type. + * + * **Example** (Flattening nested chunk types) + * + * ```ts import.meta.vitest + * import type { Chunk } from "effect" + * + * declare const nestedChunk: Chunk.Chunk> + * declare const nestedNonEmpty: Chunk.NonEmptyChunk> + * + * type Flattened1 = Chunk.Chunk.Flatten // Chunk.Chunk + * type Flattened2 = Chunk.Chunk.Flatten // Chunk.NonEmptyChunk + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type Flatten>> = T extends NonEmptyChunk> ? NonEmptyChunk + : T extends Chunk> ? Chunk + : never +} + +/** + * Transforms the elements of a chunk using the specified mapping function. + * If the input chunk is non-empty, the resulting chunk will also be non-empty. + * + * **Example** (Mapping values) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.toArray(Chunk.map(Chunk.make(1, 2), (n) => n + 1)) // => [2, 3] + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + , B>(f: (a: Chunk.Infer, i: number) => B): (self: S) => Chunk.With + (self: NonEmptyChunk, f: (a: A, i: number) => B): NonEmptyChunk + (self: Chunk, f: (a: A, i: number) => B): Chunk +} = dual(2, (self: Chunk, f: (a: A, i: number) => B): Chunk => + self.backing._tag === "ISingleton" ? + of(f(self.backing.a, 0)) : + fromArrayUnsafe(pipe(toReadonlyArray(self), RA.map((a, i) => f(a, i))))) + +/** + * Maps over the chunk statefully, producing new elements of type `B`. + * + * **Example** (Mapping with accumulated state) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * const [finalState, mapped] = Chunk.mapAccum(chunk, 0, (state, current) => [ + * state + current, // accumulate sum + * state + current // output running sum + * ]) + * + * finalState // => 15 + * Chunk.toArray(mapped) // => [1, 3, 6, 10, 15] + * + * // Building a string with indices + * const words = Chunk.make("hello", "world", "effect") + * const [count, indexed] = Chunk.mapAccum(words, 0, (index, word) => [ + * index + 1, + * `${index}: ${word}` + * ]) + * count // => 3 + * Chunk.toArray(indexed) // => ["0: hello", "1: world", "2: effect"] + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const mapAccum: { + (s: S, f: (s: S, a: A) => readonly [S, B]): (self: Chunk) => [S, Chunk] + (self: Chunk, s: S, f: (s: S, a: A) => readonly [S, B]): [S, Chunk] +} = dual(3, (self: Chunk, s: S, f: (s: S, a: A) => readonly [S, B]): [S, Chunk] => { + const [s1, as] = RA.mapAccum(self, s, f) + return [s1, fromArrayUnsafe(as)] +}) + +/** + * Splits a chunk using a `Filter` into failures and successes. + * + * **Details** + * + * Returns `[excluded, satisfying]`. The filter receives `(element, index)`. + * + * **Example** (Partitioning with a Result) + * + * ```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}`) + * ) + * + * Chunk.toArray(excluded) // => ["negative:-2"] + * Chunk.toArray(satisfying) // => [1, 5] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const partition: { + ( + f: (input: NoInfer, i: number) => Result + ): (self: Chunk) => [excluded: Chunk, satisfying: Chunk] + ( + self: Chunk, + f: (input: A, i: number) => Result + ): [excluded: Chunk, satisfying: Chunk] +} = dual( + 2, + ( + self: Chunk, + f: (input: A, i: number) => Result + ): [excluded: Chunk, satisfying: Chunk] => { + const [excluded, satisfying] = RA.partition(self, f) + return [fromArrayUnsafe(excluded), fromArrayUnsafe(satisfying)] + } +) + +/** + * Separates a chunk of `Result` values into a chunk of failures and a chunk of + * successes. + * + * **Details** + * + * The returned tuple is `[failures, successes]`, preserving the original order + * within each side. + * + * **Example** (Separating failures and successes) + * + * ```ts import.meta.vitest + * import { Chunk, Result } from "effect" + * + * const chunk = Chunk.make( + * Result.succeed(1), + * Result.fail("error1"), + * Result.succeed(2), + * Result.fail("error2"), + * Result.succeed(3) + * ) + * + * const [errors, values] = Chunk.separate(chunk) + * 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) + * Chunk.toArray(noErrors) // => [] + * Chunk.toArray(allValues) // => [1, 2] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const separate = (self: Chunk>): [Chunk, Chunk] => + pipe( + RA.separate(toReadonlyArray(self)), + ([l, r]) => [fromArrayUnsafe(l), fromArrayUnsafe(r)] + ) + +/** + * Retrieves the size of the chunk. + * + * **Example** (Getting chunk size) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.size(Chunk.make(1, 2, 3)) // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: Chunk): number => self.length + +/** + * Sorts the elements of a `Chunk` in increasing order, creating a new `Chunk`. + * + * **Example** (Sorting chunks) + * + * ```ts import.meta.vitest + * import { Chunk, Order } from "effect" + * + * const numbers = Chunk.make(3, 1, 4, 1, 5, 9, 2, 6) + * Chunk.toArray(Chunk.sort(numbers, Order.Number)) // => [1, 1, 2, 3, 4, 5, 6, 9] + * + * // Reverse order + * 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") + * Chunk.toArray(Chunk.sort(words, Order.String)) // => ["apple", "banana", "cherry"] + * ``` + * + * @category sorting + * @since 2.0.0 + */ +export const sort: { + (O: Order.Order): (self: Chunk) => Chunk + (self: Chunk, O: Order.Order): Chunk +} = dual( + 2, + (self: Chunk, O: Order.Order): Chunk => fromArrayUnsafe(RA.sort(toReadonlyArray(self), O)) +) + +/** + * Sorts the elements of a `Chunk` based on a projection function. + * + * **Example** (Sorting chunks by a derived value) + * + * ```ts import.meta.vitest + * import { Chunk, Order } from "effect" + * + * const people = Chunk.make( + * { name: "Alice", age: 30 }, + * { name: "Bob", age: 25 }, + * { name: "Charlie", age: 35 } + * ) + * + * // Sort by age + * const byAge = Chunk.sortWith(people, (person) => person.age, Order.Number) + * Chunk.toArray(byAge).map((person) => person.name) // => ["Bob", "Alice", "Charlie"] + * + * // Sort by name + * const byName = Chunk.sortWith(people, (person) => person.name, Order.String) + * Chunk.toArray(byName).map((person) => person.name) // => ["Alice", "Bob", "Charlie"] + * + * // Sort by string length + * const words = Chunk.make("a", "abc", "ab") + * Chunk.toArray(Chunk.sortWith(words, (word) => word.length, Order.Number)) // => ["a", "ab", "abc"] + * ``` + * + * @category sorting + * @since 2.0.0 + */ +export const sortWith: { + (f: (a: A) => B, order: Order.Order): (self: Chunk) => Chunk + (self: Chunk, f: (a: A) => B, order: Order.Order): Chunk +} = dual( + 3, + (self: Chunk, f: (a: A) => B, order: Order.Order): Chunk => sort(self, Order.mapInput(order, f)) +) + +/** + * Returns two splits of this chunk at the specified index. + * + * **Example** (Splitting at an index) + * + * ```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) + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(after) // => [4, 5, 6] + * + * // Split at index 0 + * const [empty, all] = Chunk.splitAt(chunk, 0) + * Chunk.toArray(empty) // => [] + * Chunk.toArray(all) // => [1, 2, 3, 4, 5, 6] + * + * // Split beyond length + * const [allElements, empty2] = Chunk.splitAt(chunk, 10) + * Chunk.toArray(allElements) // => [1, 2, 3, 4, 5, 6] + * Chunk.toArray(empty2) // => [] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +export const splitAt: { + (n: number): (self: Chunk) => [beforeIndex: Chunk, fromIndex: Chunk] + (self: Chunk, n: number): [beforeIndex: Chunk, fromIndex: Chunk] +} = dual(2, (self: Chunk, n: number): [Chunk, Chunk] => [take(self, n), drop(self, n)]) + +/** + * Splits a `NonEmptyChunk` at `n`, returning a non-empty prefix and the + * remaining suffix. + * + * **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. + * + * **Example** (Splitting non-empty chunks at an index) + * + * ```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) + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(after) // => [4, 5, 6] + * + * // Split at 1 (minimum) + * const [first, rest] = Chunk.splitNonEmptyAt(nonEmptyChunk, 1) + * 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 + * ``` + * + * @category splitting + * @since 2.0.0 + */ +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)) + return _n >= self.length ? + [self, empty()] : + [take(self, _n), drop(self, _n)] +}) + +/** + * Splits a chunk into up to `n` chunks, distributing elements in order. + * + * **Details** + * + * The chunk size is derived from the input length and `n`; the final chunk may + * contain fewer elements than the others. + * + * **Example** (Splitting chunks into groups) + * + * ```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) + * 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) + * Chunk.toArray(chunks2).map(Chunk.toArray) // => [[1, 2, 3], [4, 5, 6], [7, 8]] + * + * // Split into 1 chunk + * const chunks3 = Chunk.split(chunk, 1) + * Chunk.toArray(chunks3).map(Chunk.toArray) // => [[1, 2, 3, 4, 5, 6, 7, 8, 9]] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +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)))) + +/** + * Splits this chunk on the first element that matches this predicate. + * Returns a tuple containing two chunks: the first one is before the match, and the second one is from the match onward. + * + * **Example** (Splitting at a matching element) + * + * ```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) + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(fromMatch) // => [4, 5, 6] + * + * // No match found + * const [all, empty] = Chunk.splitWhere(chunk, (n) => n > 10) + * 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) + * Chunk.toArray(emptyBefore) // => [] + * Chunk.toArray(allFromFirst) // => [1, 2, 3, 4, 5, 6] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +export const splitWhere: { + (predicate: Predicate>): (self: Chunk) => [beforeMatch: Chunk, fromMatch: Chunk] + (self: Chunk, predicate: Predicate): [beforeMatch: Chunk, fromMatch: Chunk] +} = dual(2, (self: Chunk, predicate: Predicate): [beforeMatch: Chunk, fromMatch: Chunk] => { + let i = 0 + for (const a of toReadonlyArray(self)) { + if (predicate(a)) { + break + } else { + i++ + } + } + return splitAt(self, i) +}) + +/** + * Returns every element after the first safely, or `None` when the chunk is empty. + * + * **Example** (Getting the tail safely) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4) + * Chunk.tail(chunk) // => Option.some(Chunk.make(2, 3, 4)) + * + * const singleElement = Chunk.make(1) + * Chunk.tail(singleElement) // => Option.some(Chunk.empty()) + * + * Chunk.tail(Chunk.empty()) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const tail = (self: Chunk): O.Option> => self.length > 0 ? O.some(drop(self, 1)) : O.none() + +/** + * Returns every element after the first from a non-empty chunk. + * + * **Example** (Getting the tail of a non-empty chunk) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) + * Chunk.toArray(Chunk.tailNonEmpty(nonEmptyChunk)) // => [2, 3, 4] + * + * const singleElement = Chunk.make(1) + * Chunk.toArray(Chunk.tailNonEmpty(singleElement)) // => [] + * + * // Type safety: this function only accepts NonEmptyChunk + * // Chunk.tailNonEmpty(Chunk.empty()) // TypeScript error + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const tailNonEmpty = (self: NonEmptyChunk): Chunk => drop(self, 1) + +/** + * Takes the last `n` elements. + * + * **Example** (Taking elements from the end) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) + * Chunk.toArray(Chunk.takeRight(chunk, 3)) // => [4, 5, 6] + * + * // Take more than available + * Chunk.toArray(Chunk.takeRight(chunk, 10)) // => [1, 2, 3, 4, 5, 6] + * + * // Take zero + * Chunk.toArray(Chunk.takeRight(chunk, 0)) // => [] + * ``` + * + * @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)) + +/** + * Takes all elements so long as the predicate returns true. + * + * **Example** (Taking elements while a predicate matches) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 3, 2, 1) + * Chunk.toArray(Chunk.takeWhile(chunk, (n) => n < 4)) // => [1, 2, 3] + * + * // Empty if first element doesn't match + * Chunk.toArray(Chunk.takeWhile(chunk, (n) => n > 5)) // => [] + * + * // Takes all if all match + * const small = Chunk.make(1, 2, 3) + * Chunk.toArray(Chunk.takeWhile(small, (n) => n < 10)) // => [1, 2, 3] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const takeWhile: { + (refinement: Refinement, B>): (self: Chunk) => Chunk + (predicate: Predicate>): (self: Chunk) => Chunk + (self: Chunk, refinement: Refinement): Chunk + (self: Chunk, predicate: Predicate): Chunk +} = dual(2, (self: Chunk, predicate: Predicate): Chunk => { + const out: Array = [] + for (const a of toReadonlyArray(self)) { + if (predicate(a)) { + out.push(a) + } else { + break + } + } + return fromArrayUnsafe(out) +}) + +/** + * Creates a Chunks of unique values, in order, from all given Chunks. + * + * **Example** (Unioning chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk1 = Chunk.make(1, 2, 3) + * const chunk2 = Chunk.make(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) + * Chunk.toArray(Chunk.union(withDupes1, withDupes2)) // => [1, 2, 3] + * ``` + * + * @category set operations + * @since 2.0.0 + */ +export const union: { + (that: Chunk): (self: Chunk) => Chunk + (self: Chunk, that: Chunk): Chunk +} = dual( + 2, + (self: Chunk, that: Chunk) => fromArrayUnsafe(RA.union(toReadonlyArray(self), toReadonlyArray(that))) +) + +/** + * Removes duplicate elements from a `Chunk`, preserving the first occurrence + * of each value. + * + * **Example** (Removing duplicate values) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 2, 3, 1, 4, 3) + * Chunk.toArray(Chunk.dedupe(chunk)) // => [1, 2, 3, 4] + * + * // Empty chunk + * const empty = Chunk.empty() + * Chunk.toArray(Chunk.dedupe(empty)) // => [] + * + * // No duplicates + * const unique = Chunk.make(1, 2, 3) + * Chunk.toArray(Chunk.dedupe(unique)) // => [1, 2, 3] + * ``` + * + * @category deduplication + * @since 2.0.0 + */ +export const dedupe = (self: Chunk): Chunk => fromArrayUnsafe(RA.dedupe(toReadonlyArray(self))) + +/** + * Deduplicates adjacent elements that are identical. + * + * **Example** (Removing adjacent duplicates) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 1, 2, 2, 2, 3, 1, 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") + * Chunk.toArray(Chunk.dedupeAdjacent(mixed)) // => ["a", "b", "a"] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dedupeAdjacent = (self: Chunk): Chunk => fromArrayUnsafe(RA.dedupeAdjacent(self)) + +/** + * Takes a `Chunk` of pairs and returns two corresponding `Chunk`s. + * + * **Details** + * + * This function is the reverse of `zip`. + * + * **Example** (Unzipping pairs) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const pairs = Chunk.make( + * [1, "a"] as const, + * [2, "b"] as const, + * [3, "c"] as const + * ) + * const [numbers, letters] = Chunk.unzip(pairs) + * 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) + * Chunk.toArray(emptyNums) // => [] + * Chunk.toArray(emptyStrs) // => [] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +export const unzip = (self: Chunk): [Chunk, Chunk] => { + const [left, right] = RA.unzip(self) + return [fromArrayUnsafe(left), fromArrayUnsafe(right)] +} + +/** + * Zips this chunk pointwise with the specified chunk using the specified combiner. + * + * **Example** (Zipping chunks with a function) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const numbers = Chunk.make(1, 2, 3) + * const letters = Chunk.make("a", "b", "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") + * Chunk.toArray(Chunk.zipWith(short, long, (n, l) => [n, l])) // => [[1, "a"], [2, "b"]] + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWith: { + (that: Chunk, f: (a: A, b: B) => C): (self: Chunk) => Chunk + (self: Chunk, that: Chunk, f: (a: A, b: B) => C): Chunk +} = dual( + 3, + (self: Chunk, that: Chunk, f: (a: A, b: B) => C): Chunk => + fromArrayUnsafe(RA.zipWith(self, that, f)) +) + +/** + * Zips this chunk pointwise with the specified chunk. + * + * **Example** (Zipping chunks) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const numbers = Chunk.make(1, 2, 3) + * const letters = Chunk.make("a", "b", "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") + * Chunk.toArray(Chunk.zip(short, long)) // => [[1, "a"], [2, "b"]] + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zip: { + (that: Chunk): (self: Chunk) => Chunk<[A, B]> + (self: Chunk, that: Chunk): Chunk<[A, B]> +} = dual( + 2, + (self: Chunk, that: Chunk): Chunk<[A, B]> => zipWith(self, that, (a, b) => [a, b]) +) + +/** + * Deletes the element at the specified index, creating a new `Chunk`. + * + * **Example** (Removing an element) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make("a", "b", "c", "d") + * Chunk.toArray(Chunk.remove(chunk, 1)) // => ["a", "c", "d"] + * + * // Remove first element + * Chunk.toArray(Chunk.remove(chunk, 0)) // => ["b", "c", "d"] + * + * // Index out of bounds returns same chunk + * Chunk.toArray(Chunk.remove(chunk, 10)) // => ["a", "b", "c", "d"] + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const remove: { + (i: number): (self: Chunk) => Chunk + (self: Chunk, i: number): Chunk +} = dual( + 2, + (self: Chunk, i: number): Chunk => fromArrayUnsafe(RA.remove(toReadonlyArray(self), i)) +) + +/** + * Applies a function to the element at the specified index safely, creating a new `Chunk`, + * or returns `None` if the index is out of bounds. + * + * **Example** (Modifying an element) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4) + * Chunk.modify(chunk, 1, (n) => n * 10) // => Option.some(Chunk.make(1, 20, 3, 4)) + * + * // Index out of bounds returns None + * chunk.pipe(Chunk.modify(10, (n) => n * 10)) // => Option.none() + * + * // Negative index returns None + * chunk.pipe(Chunk.modify(-1, (n) => n * 10)) // => Option.none() + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const modify: { + (i: number, f: (a: A) => B): (self: Chunk) => O.Option> + (self: Chunk, i: number, f: (a: A) => B): O.Option> +} = dual( + 3, + (self: Chunk, i: number, f: (a: A) => B): O.Option> => + pipe(RA.modify(toReadonlyArray(self), i, f), O.map(fromArrayUnsafe)) +) + +/** + * Changes the element at the specified index safely, creating a new `Chunk`, + * or returns `None` if the index is out of bounds. + * + * **Example** (Replacing an element) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make("a", "b", "c", "d") + * Chunk.replace(chunk, 1, "X") // => Option.some(Chunk.make("a", "X", "c", "d")) + * + * // Index out of bounds returns None + * chunk.pipe(Chunk.replace(10, "Y")) // => Option.none() + * + * // Negative index returns None + * chunk.pipe(Chunk.replace(-1, "Z")) // => Option.none() + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const replace: { + (i: number, b: B): (self: Chunk) => O.Option> + (self: Chunk, i: number, b: B): O.Option> +} = dual(3, (self: Chunk, i: number, b: B): O.Option> => modify(self, i, () => b)) + +/** + * Returns a non-empty `Chunk` of length `n` with element `i` initialized by `f(i)`. + * + * **Details** + * + * `n` is normalized to an integer greater than or equal to `1`. + * + * **Example** (Generating chunks from indices) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.toArray(Chunk.makeBy(5, (i) => i * 2)) // => [0, 2, 4, 6, 8] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const makeBy: { + (f: (i: number) => A): (n: number) => NonEmptyChunk + (n: number, f: (i: number) => A): NonEmptyChunk +} = dual(2, (n, f) => fromIterable(RA.makeBy(n, f))) + +/** + * Creates a non-empty `Chunk` of consecutive integers from `start` through + * `end`, inclusive. + * + * **Details** + * + * If `start` is greater than `end`, returns a single-element chunk containing + * `start`. + * + * **Example** (Creating a range) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * Chunk.toArray(Chunk.range(1, 5)) // => [1, 2, 3, 4, 5] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const range = (start: number, end: number): NonEmptyChunk => + start <= end ? makeBy(end - start + 1, (i) => start + i) : of(start) + +// ------------------------------------------------------------------------------------- +// re-exports from ReadonlyArray +// ------------------------------------------------------------------------------------- + +/** + * Returns a function that checks if a `Chunk` contains a given value using the default `Equivalence`. + * + * **Example** (Checking membership) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.contains(chunk, 3) // => true + * Chunk.contains(chunk, 6) // => false + * + * // Works with strings + * const words = Chunk.make("apple", "banana", "cherry") + * Chunk.contains(words, "banana") // => true + * Chunk.contains(words, "grape") // => false + * + * // Empty chunk + * Chunk.contains(Chunk.empty(), 1) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const contains: { + (a: A): (self: Chunk) => boolean + (self: Chunk, a: A): boolean +} = RA.contains + +/** + * Returns a function that checks if a `Chunk` contains a given value using a provided `isEquivalent` function. + * + * **Example** (Checking membership with custom equivalence) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make({ id: 1, name: "Alice" }, { id: 2, name: "Bob" }) + * + * // Custom equivalence by id + * const containsById = Chunk.containsWith<{ id: number; name: string }>((a, b) => + * a.id === b.id + * ) + * 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() + * ) + * containsCaseInsensitive(words, "apple") // => true + * containsCaseInsensitive(words, "grape") // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const containsWith: ( + isEquivalent: (self: A, that: A) => boolean +) => { + (a: A): (self: Chunk) => boolean + (self: Chunk, a: A): boolean +} = RA.containsWith + +/** + * Returns the first element that satisfies the specified + * predicate, or `None` if no such element exists. + * + * **Example** (Finding the first matching element) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.findFirst(chunk, (n) => n > 3) // => Option.some(4) + * + * // No match found + * Chunk.findFirst(chunk, (n) => n > 10) // => Option.none() + * + * // With type refinement + * const mixed = Chunk.make(1, "hello", 2, "world", 3) + * const firstString = Chunk.findFirst( + * mixed, + * (x): x is string => typeof x === "string" + * ) + * firstString // => Option.some("hello") + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findFirst: { + (refinement: Refinement, B>): (self: Chunk) => Option + (predicate: Predicate>): (self: Chunk) => Option + (self: Chunk, refinement: Refinement): Option + (self: Chunk, predicate: Predicate): Option +} = RA.findFirst + +/** + * Returns the first index for which a predicate holds. + * + * **Example** (Finding the first matching index) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.findFirstIndex(chunk, (n) => n > 3) // => Option.some(3) + * + * // No match found + * Chunk.findFirstIndex(chunk, (n) => n > 10) // => Option.none() + * + * // Find first even number + * Chunk.findFirstIndex(chunk, (n) => n % 2 === 0) // => Option.some(1) + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findFirstIndex: { + (predicate: Predicate): (self: Chunk) => O.Option + (self: Chunk, predicate: Predicate): O.Option +} = dual( + 2, + (self: Chunk, predicate: Predicate): O.Option => RA.findFirstIndex(self, predicate) +) + +/** + * Finds the last element for which a predicate holds. + * + * **Example** (Finding the last matching element) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.findLast(chunk, (n) => n < 4) // => Option.some(3) + * + * // No match found + * Chunk.findLast(chunk, (n) => n > 10) // => Option.none() + * + * // Find last even number + * Chunk.findLast(chunk, (n) => n % 2 === 0) // => Option.some(4) + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findLast: { + (refinement: Refinement, B>): (self: Chunk) => Option + (predicate: Predicate>): (self: Chunk) => Option + (self: Chunk, refinement: Refinement): Option + (self: Chunk, predicate: Predicate): Option +} = RA.findLast + +/** + * Returns the last index for which a predicate holds. + * + * **Example** (Finding the last matching index) + * + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.findLastIndex(chunk, (n) => n < 4) // => Option.some(2) + * + * // No match found + * Chunk.findLastIndex(chunk, (n) => n > 10) // => Option.none() + * + * // Find last even number index + * Chunk.findLastIndex(chunk, (n) => n % 2 === 0) // => Option.some(3) + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findLastIndex: { + (predicate: Predicate): (self: Chunk) => O.Option + (self: Chunk, predicate: Predicate): O.Option +} = dual( + 2, + (self: Chunk, predicate: Predicate): O.Option => RA.findLastIndex(self, predicate) +) + +/** + * Checks whether a predicate holds true for every `Chunk` element. + * + * **Example** (Checking every element) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const allPositive = Chunk.make(1, 2, 3, 4, 5) + * Chunk.every(allPositive, (n) => n > 0) // => true + * Chunk.every(allPositive, (n) => n > 3) // => false + * + * // Empty chunk returns 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 + * } + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const every: { + (refinement: Refinement, B>): (self: Chunk) => self is Chunk + (predicate: Predicate): (self: Chunk) => boolean + (self: Chunk, refinement: Refinement): self is Chunk + (self: Chunk, predicate: Predicate): boolean +} = dual( + 2, + (self: Chunk, refinement: Refinement): self is Chunk => + RA.fromIterable(self).every(refinement) +) + +/** + * Checks whether a predicate holds true for some `Chunk` element. + * + * **Example** (Checking for some matching element) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.some(chunk, (n) => n > 4) // => true + * Chunk.some(chunk, (n) => n > 10) // => false + * + * // Empty chunk returns false + * Chunk.some(Chunk.empty(), (n) => n > 0) // => false + * + * // Check for specific value + * const words = Chunk.make("apple", "banana", "cherry") + * Chunk.some(words, (word) => word.includes("ban")) // => true + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const some: { + (predicate: Predicate>): (self: Chunk) => self is NonEmptyChunk + (self: Chunk, predicate: Predicate): self is NonEmptyChunk +} = dual( + 2, + (self: Chunk, predicate: Predicate): self is NonEmptyChunk => RA.fromIterable(self).some(predicate) +) + +/** + * Joins the elements together with "sep" in the middle. + * + * **Example** (Joining chunks into a string) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make("apple", "banana", "cherry") + * Chunk.join(chunk, ", ") // => "apple, banana, cherry" + * + * // With different separator + * Chunk.join(chunk, " | ") // => "apple | banana | cherry" + * + * // Empty chunk + * Chunk.join(Chunk.empty(), ", ") // => "" + * + * // Single element + * Chunk.join(Chunk.make("hello"), ", ") // => "hello" + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const join: { + (sep: string): (self: Chunk) => string + (self: Chunk, sep: string): string +} = RA.join + +/** + * Reduces the elements of a chunk from left to right. + * + * **Example** (Reducing from the left) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4, 5) + * Chunk.reduce(chunk, 0, (acc, n) => acc + n) // => 15 + * + * // String concatenation with index + * const words = Chunk.make("a", "b", "c") + * Chunk.reduce(words, "", (acc, word, i) => acc + `${i}:${word} `).trimEnd() // => "0:a 1:b 2:c" + * + * // Find maximum + * Chunk.reduce(chunk, -Infinity, (acc, n) => Math.max(acc, n)) // => 5 + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + (b: B, f: (b: B, a: A, i: number) => B): (self: Chunk) => B + (self: Chunk, b: B, f: (b: B, a: A, i: number) => B): B +} = RA.reduce + +/** + * Reduces the elements of a chunk from right to left. + * + * **Example** (Reducing from the right) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk = Chunk.make(1, 2, 3, 4) + * Chunk.reduceRight(chunk, 0, (acc, n) => acc + n) // => 10 + * + * // String building (right to left) + * const words = Chunk.make("a", "b", "c") + * Chunk.reduceRight( + * words, + * "", + * (acc, word, i) => acc + `${i}:${word} ` + * ).trim() // => "2:c 1:b 0:a" + * + * // Subtract from right to left + * Chunk.reduceRight(chunk, 0, (acc, n) => n - acc) // => -2 + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduceRight: { + (b: B, f: (b: B, a: A, i: number) => B): (self: Chunk) => B + (self: Chunk, b: B, f: (b: B, a: A, i: number) => B): B +} = RA.reduceRight + +/** + * Creates a `Chunk` of values not included in the other given `Chunk` using the provided `isEquivalent` function. + * The order and references of result values are determined by the first `Chunk`. + * + * **Example** (Computing difference with custom equivalence) + * + * ```ts import.meta.vitest + * import { Chunk } from "effect" + * + * const chunk1 = Chunk.make({ id: 1, name: "Alice" }, { id: 2, name: "Bob" }) + * const chunk2 = Chunk.make({ id: 1, name: "Alice" }, { id: 3, name: "Charlie" }) + * + * // Custom equivalence by id + * const byId = Chunk.differenceWith<{ id: number; name: string }>((a, b) => + * a.id === b.id + * ) + * Chunk.toArray(byId(chunk1, chunk2)) // => [{ id: 2, name: "Bob" }] + * + * // String comparison case-insensitive + * const words1 = Chunk.make("Apple", "Banana", "Cherry") + * const words2 = Chunk.make("apple", "grape") + * const caseInsensitive = Chunk.differenceWith((a, b) => + * a.toLowerCase() === b.toLowerCase() + * ) + * Chunk.toArray(caseInsensitive(words1, words2)) // => ["Banana", "Cherry"] + * ``` + * + * @category filtering + * @since 3.2.0 + */ +export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): { + (that: Chunk): (self: Chunk) => Chunk + (self: Chunk, that: Chunk): Chunk +} => { + return dual( + 2, + (self: Chunk, that: Chunk): Chunk => fromArrayUnsafe(RA.differenceWith(isEquivalent)(self, that)) + ) +} + +/** + * Creates a `Chunk` of values not included in the other given `Chunk`. + * The order and references of result values are determined by the first `Chunk`. + * + * **Example** (Computing chunk difference) + * + * ```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) + * Chunk.toArray(Chunk.difference(chunk1, chunk2)) // => [1, 2, 5] + * + * // String difference + * const words1 = Chunk.make("apple", "banana", "cherry") + * const words2 = Chunk.make("banana", "grape") + * Chunk.toArray(Chunk.difference(words1, words2)) // => ["apple", "cherry"] + * + * // Empty second chunk returns original + * Chunk.toArray(Chunk.difference(chunk1, Chunk.empty())) // => [1, 2, 3, 4, 5] + * ``` + * + * @category filtering + * @since 3.2.0 + */ +export const difference: { + (that: Chunk): (self: Chunk) => Chunk + (self: Chunk, that: Chunk): Chunk +} = dual( + 2, + (self: Chunk, that: Chunk): Chunk => fromArrayUnsafe(RA.difference(self, that)) +) diff --git a/.repos/effect/packages/effect/src/Clock.ts b/.repos/effect/packages/effect/src/Clock.ts new file mode 100644 index 000000000..5434197be --- /dev/null +++ b/.repos/effect/packages/effect/src/Clock.ts @@ -0,0 +1,326 @@ +/** + * Service and helpers for reading time and sleeping inside Effect programs. + * 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 + */ +import type * as Context from "./Context.ts" +import type * as Duration from "./Duration.ts" +import type { Effect } from "./Effect.ts" +import * as effect from "./internal/effect.ts" + +/** + * Represents a time-based clock which provides functionality related to time + * and scheduling. + * + * **When to use** + * + * Use to define or provide a clock service for current-time and sleep + * operations. + * + * **Example** (Reading current time) + * + * ```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 + * return [currentTime, currentTimeNanos] as const + * }) + * + * await Effect.runPromise(Effect.provideService(clockOperations, Clock.Clock, testClock)) // => [1_000, 1_000_000_000n] + * ``` + * + * @category services + * @since 2.0.0 + */ +export interface Clock { + /** + * Returns the current Unix time in milliseconds unsafely. + * + * **When to use** + * + * 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 Unix time in milliseconds. + * + * **When to use** + * + * 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 Unix time in nanoseconds unsafely. + * + * **When to use** + * + * 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 Unix time in nanoseconds. + * + * **When to use** + * + * 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. + * + * **When to use** + * + * Use to delay an `Effect` workflow by a duration through this `Clock` service. + */ + sleep(duration: Duration.Duration): Effect +} + +/** + * Context reference for the active time service in the environment. + * + * **When to use** + * + * Use when you need to access or provide the full time service, including sleep + * operations, rather than a single timestamp accessor. + * + * **Example** (Accessing the Clock service) + * + * ```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 services + * @since 2.0.0 + */ +export const Clock: Context.Reference = effect.ClockRef + +/** + * Accesses the current Clock service and uses it to run the provided function. + * + * **When to use** + * + * Use when you need the full Clock service interface to perform multiple time + * operations or call unsafe variants within a single effect. + * + * **Example** (Accessing the current Clock service) + * + * ```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 = 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 accessors + * @since 2.0.0 + */ +export const clockWith: (f: (clock: Clock) => Effect) => Effect = effect.clockWith + +/** + * Returns an Effect that succeeds with the current Unix time in milliseconds. + * + * **When to use** + * + * 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 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 + * } + * + * 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 accessors + * @since 2.0.0 + */ +export const currentTimeMillis: Effect = effect.currentTimeMillis + +/** + * Returns an Effect that succeeds with the current Unix time in nanoseconds. + * + * **When to use** + * + * 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 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 + * } + * + * await Effect.runPromise(Effect.provideService(Clock.currentTimeNanos, Clock.Clock, testClock)) // => 1_000_000_000n + * ``` + * + * @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/.repos/effect/packages/effect/src/Combiner.ts b/.repos/effect/packages/effect/src/Combiner.ts new file mode 100644 index 000000000..75ab2d492 --- /dev/null +++ b/.repos/effect/packages/effect/src/Combiner.ts @@ -0,0 +1,303 @@ +/** + * Defines reusable rules for merging two values of the same type. + * + * A `Combiner` contains one operation, `combine(self, that)`, which returns + * the merged value. It does not define an initial value for reducing a + * collection; use a `Reducer` when you need that. This module includes the + * `Combiner` interface, a constructor for custom combining logic, and common + * combiners for choosing or ordering values. + * + * @since 4.0.0 + */ +import type * as Order from "./Order.ts" + +/** + * Represents a strategy for combining two values of the same type `A`. A + * `Combiner` contains a single `combine` method that takes two values and + * returns a merged result. It does not include an identity/empty value; use + * `Reducer` when you need one. + * + * **When to use** + * + * Use when you need to describe how two values of the same type + * merge, pass a reusable combining strategy to library functions like + * `Struct.makeCombiner` or `Option.makeCombinerFailFast`, or define the + * combining step for a `Reducer`. + * + * **Example** (Combining numbers with addition) + * + * ```ts import.meta.vitest + * import { Combiner } from "effect" + * + * const Sum = Combiner.make((self, that) => self + that) + * + * Sum.combine(3, 4) // => 7 + * ``` + * + * @see {@link make} – create a `Combiner` from a function + * @category models + * @since 4.0.0 + */ +export interface Combiner { + /** + * Combines two values into a new value. + * + * **When to use** + * + * Use to merge two values according to this combining strategy. + */ + readonly combine: (self: A, that: A) => A +} + +/** + * Creates a `Combiner` from a binary function. + * + * **When to use** + * + * Use when you have a custom combining operation that is not covered by + * the built-in constructors (`min`, `max`, `first`, `last`, `constant`). + * + * **Details** + * + * The returned combiner's `combine` method delegates to the provided function. + * Any purity, associativity, or mutation behavior comes from that function. + * + * **Example** (Multiplying numbers) + * + * ```ts import.meta.vitest + * import { Combiner } from "effect" + * + * const Product = Combiner.make((self, that) => self * that) + * + * Product.combine(3, 5) // => 15 + * ``` + * + * @see {@link Combiner} – the interface this creates + * @category constructors + * @since 4.0.0 + */ +export function make(combine: (self: A, that: A) => A): Combiner { + return { combine } +} + +/** + * Reverses the argument order of a combiner's `combine` method. + * + * **When to use** + * + * Use when you want the right-hand value to act as the accumulator, or need to + * reverse a non-commutative combiner such as string concatenation. + * + * **Details** + * + * Returns a new `Combiner` where `combine(self, that)` calls the original + * combiner as `combine(that, self)`. + * + * **Example** (Reversing string concatenation) + * + * ```ts import.meta.vitest + * import { Combiner, String } from "effect" + * + * const Prepend = Combiner.flip(String.ReducerConcat) + * + * Prepend.combine("a", "b") // => "ba" + * ``` + * + * @see {@link make} + * @category combinators + * @since 4.0.0 + */ +export function flip(combiner: Combiner): Combiner { + return make((self, that) => combiner.combine(that, self)) +} + +/** + * Creates a `Combiner` that returns the smaller of two values according to + * the provided `Order`. + * + * **When to use** + * + * Use when you want to accumulate the minimum value across a collection or + * build a `Reducer` that tracks the running minimum. + * + * **Details** + * + * The combiner compares values using the given `Order`. When values are equal, + * it returns `that` (the second argument). + * + * **Example** (Selecting the minimum of two numbers) + * + * ```ts import.meta.vitest + * import { Combiner, Number } from "effect" + * + * const Min = Combiner.min(Number.Order) + * + * Min.combine(3, 1) // => 1 + * Min.combine(1, 3) // => 1 + * ``` + * + * @see {@link max} + * @category constructors + * @since 4.0.0 + */ +export function min(order: Order.Order): Combiner { + return make((self, that) => order(self, that) === -1 ? self : that) +} + +/** + * Creates a `Combiner` that returns the larger of two values according to + * the provided `Order`. + * + * **When to use** + * + * Use when you want to accumulate the maximum value across a collection or + * build a `Reducer` that tracks the running maximum. + * + * **Details** + * + * The combiner compares values using the given `Order`. When values are equal, + * it returns `that` (the second argument). + * + * **Example** (Selecting the maximum of two numbers) + * + * ```ts import.meta.vitest + * import { Combiner, Number } from "effect" + * + * const Max = Combiner.max(Number.Order) + * + * Max.combine(3, 1) // => 3 + * Max.combine(1, 3) // => 3 + * ``` + * + * @see {@link min} + * @category constructors + * @since 4.0.0 + */ +export function max(order: Order.Order): Combiner { + return make((self, that) => order(self, that) === 1 ? self : that) +} + +/** + * Creates a `Combiner` that always returns the first (left) argument. + * + * **When to use** + * + * Use when you want "first write wins" semantics while merging values. + * + * **Details** + * + * `combine(self, that)` returns `self` and ignores `that`. + * + * **Example** (Keeping the first value) + * + * ```ts import.meta.vitest + * import { Combiner } from "effect" + * + * const First = Combiner.first() + * + * First.combine(1, 2) // => 1 + * ``` + * + * @see {@link last} + * @category constructors + * @since 4.0.0 + */ +export function first(): Combiner { + return make((self, _) => self) +} + +/** + * Creates a `Combiner` that always returns the last (right) argument. + * + * **When to use** + * + * Use when you want "last write wins" semantics while merging values. + * + * **Details** + * + * `combine(self, that)` returns `that` and ignores `self`. + * + * **Example** (Keeping the last value) + * + * ```ts import.meta.vitest + * import { Combiner } from "effect" + * + * const Last = Combiner.last() + * + * Last.combine(1, 2) // => 2 + * ``` + * + * @see {@link first} + * @category constructors + * @since 4.0.0 + */ +export function last(): Combiner { + return make((_, that) => that) +} + +/** + * Creates a `Combiner` that ignores both arguments and always returns the + * given constant value. + * + * **When to use** + * + * Use when you need a combiner that always returns a fixed value, including + * when a generic API requires a combiner but the result is predetermined. + * + * **Details** + * + * `combine(self, that)` returns the constant `a` and ignores both arguments. + * + * **Example** (Always returning zero) + * + * ```ts import.meta.vitest + * import { Combiner } from "effect" + * + * const Zero = Combiner.constant(0) + * + * Zero.combine(42, 99) // => 0 + * ``` + * + * @see {@link first} + * @see {@link last} + * @category constructors + * @since 4.0.0 + */ +export function constant(a: A): Combiner { + return make(() => a) +} + +/** + * Wraps a `Combiner` so that a separator value is inserted between every + * pair of combined elements. + * + * **When to use** + * + * Use when you need to inject a fixed separator between accumulated values, + * such as when building delimited strings, paths, or CSV-like output by + * repeated combination. + * + * **Details** + * + * `intercalate(middle)(combiner).combine(self, that)` is equivalent to + * `combiner.combine(self, combiner.combine(middle, that))`. This function is + * curried: first provide the separator, then the base combiner. + * + * **Example** (Joining strings with a separator) + * + * ```ts import.meta.vitest + * import { Combiner, String } from "effect" + * + * const commaSep = Combiner.intercalate(",")(String.ReducerConcat) + * + * commaSep.combine("a", "b") // => "a,b" + * ``` + * + * @see {@link make} + * @category combinators + * @since 4.0.0 + */ +export function intercalate(middle: A) { + return (combiner: Combiner): Combiner => + make((self, that) => combiner.combine(self, combiner.combine(middle, that))) +} diff --git a/.repos/effect/packages/effect/src/Config.ts b/.repos/effect/packages/effect/src/Config.ts new file mode 100644 index 000000000..9de373eb1 --- /dev/null +++ b/.repos/effect/packages/effect/src/Config.ts @@ -0,0 +1,1635 @@ +/** + * Descriptions of configuration values that can be read from a + * `ConfigProvider`. A `Config` explains which keys to read, how to decode + * and validate them, and how to combine defaults, fallbacks, nested paths, and + * multiple settings. Configs are also Effects, so they can be yielded in + * `Effect.gen` after a provider has been supplied. + * + * @since 4.0.0 + */ +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, 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" +import * as SchemaIssue from "./SchemaIssue.ts" +import * as SchemaParser from "./SchemaParser.ts" +import * as SchemaTransformation from "./SchemaTransformation.ts" + +const TypeId = "~effect/Config" + +/** + * Returns `true` if `u` is a `Config` instance. + * + * **When to use** + * + * Use when you need to distinguish a `Config` from an unknown value before + * calling `.parse` or {@link unwrap}. + * + * **Example** (Checking Config values) + * + * ```ts import.meta.vitest + * import { Config } from "effect" + * + * Config.isConfig(Config.string("HOST")) // => true + * Config.isConfig("not a config") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isConfig = (u: unknown): u is Config => Predicate.hasProperty(u, TypeId) + +/** + * Represents the error type produced when config loading or validation fails. + * + * **When to use** + * + * Use when you need to inspect config loading or validation failures. + * + * **Details** + * + * Wraps either: + * - A `SourceError` — the provider could not read data (I/O failure). + * - A `SchemaError` — the data was found but did not match the schema + * (wrong type, out of range, missing key, etc.). + * + * @see {@link orElse} – recover from a ConfigError + * @see {@link withDefault} – provide a fallback when relevant input is absent + * + * @category errors + * @since 4.0.0 + */ +export class ConfigError { + readonly _tag = "ConfigError" + readonly name: string = "ConfigError" + readonly cause: SourceError | Schema.SchemaError + constructor(cause: SourceError | Schema.SchemaError) { + this.cause = cause + } + get message() { + return this.cause.toString() + } + toString() { + return `ConfigError(${this.message})` + } +} + +/** + * A recipe for extracting a typed value `T` from a `ConfigProvider`. + * + * **When to use** + * + * Use to describe typed configuration that can be parsed from a provider or + * yielded inside `Effect.gen`. + * + * **Details** + * + * Key members: + * - `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. + * + * @see {@link schema} – the main way to create a Config + * + * @category models + * @since 2.0.0 + */ +export interface Config extends Effect.Effect { + readonly [TypeId]: typeof TypeId + 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 = { + ...Effectable.Prototype>({ + label: "Config", + evaluate(fiber) { + return this.parse(fiber.getRef(ConfigProvider.ConfigProvider)) + } + }), + [TypeId]: TypeId, + toJSON(this: Config) { + return { + _id: "Config" + } + } +} + +function make( + evaluator: Evaluator +): Config { + const self = Object.create(Proto) + 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. + * + * **When to use** + * + * Use when you need to transform a parsed config value with a function that + * cannot fail. + * + * **Example** (Uppercasing a string config) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const upper = Config.string("name").pipe( + * Config.map((s) => s.toUpperCase()) + * ) + * + * const provider = ConfigProvider.fromUnknown({ name: "alice" }) + * Effect.runSync(upper.parse(provider)) // => "ALICE" + * ``` + * + * @see {@link mapOrFail} – when the transformation can fail + * + * @category mapping + * @since 2.0.0 + */ +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(evaluateAt(self, provider, pathPrefix), (resolution) => + resolution._tag === "Resolved" + ? resolved(f(resolution.value), resolution.hasInput) + : resolution) + ) +}) + +/** + * Transforms the parsed value with a function that may fail. + * + * **When to use** + * + * Use when you need to transform a parsed config value with a function that can + * produce a `ConfigError` (e.g. parsing a URL, checking a range). + * + * **Example** (Wrapping a value in an effectful transformation) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const trimmed = Config.string("name").pipe( + * Config.mapOrFail((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 + * + * @category mapping + * @since 2.0.0 + */ +export const mapOrFail: { + (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(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)) + ) +}) + +/** + * Provides a fallback config when parsing fails with a `ConfigError`. + * + * **When to use** + * + * Use when you need to try an alternative config source after the primary one + * fails. + * + * **Details** + * + * 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.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * 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 semantic absence + * + * @category combinators + * @since 2.0.0 + */ +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.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) + }) + ) +}) + +/** + * Combines multiple configs into a single config that parses all of them. + * + * **When to use** + * + * Use when you need to group related configs into a tuple or named struct. + * + * **Details** + * + * 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 import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const dbConfig = Config.all({ + * 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 } + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export function all> | Record>>( + arg: Arg +): Config< + [Arg] extends [ReadonlyArray>] ? { + -readonly [K in keyof Arg]: [Arg[K]] extends [Config] ? A : never + } + : [Arg] extends [Iterable>] ? Array + : [Arg] extends [Record>] ? { + -readonly [K in keyof Arg]: [Arg[K]] extends [Config] ? A : never + } + : never +> { + const configs: Array> | Record> = Array.isArray(arg) + ? arg + : Symbol.iterator in arg + ? [...arg as any] + : arg + if (Array.isArray(configs)) { + return make((provider, pathPrefix) => + Effect.flatMapEager( + Effect.all(configs.map((config) => Effect.result(evaluateAt(config, provider, pathPrefix)))), + resolveArray + ) + ) as any + } else { + return make((provider, pathPrefix) => + Effect.flatMapEager( + Effect.all(Rec.map(configs, (config) => Effect.result(evaluateAt(config, provider, pathPrefix)))), + resolveRecord + ) + ) as any + } +} + +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 cannot resolve because none of its + * relevant input is present. + * + * **When to use** + * + * Use when you need to make a config key optional with a sensible default. + * + * **Gotchas** + * + * 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 import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const port = Config.number("port").pipe(Config.withDefault(3000)) + * + * const provider = ConfigProvider.fromUnknown({}) + * Effect.runSync(port.parse(provider)) // => 3000 + * ``` + * + * @see {@link option} – returns `Option` instead of a default value + * @see {@link orElse} – catches all errors, not just absent input + * + * @category combinators + * @since 2.0.0 + */ +export const withDefault: { + (defaultValue: A2): (self: Config) => Config + (self: Config, defaultValue: A2): Config +} = dual(2, (self: Config, defaultValue: A2): Config => { + 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 the + * config cannot resolve because none of its relevant input is present. + * + * **When to use** + * + * Use when you need to handle a config key that may or may not be present. + * + * **Gotchas** + * + * 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.meta.vitest + * import { Config, ConfigProvider, Effect, Option } from "effect" + * + * const maybePort = Config.option(Config.number("port")) + * + * const provider = ConfigProvider.fromUnknown({}) + * Effect.runSync(maybePort.parse(provider)) // => Option.none() + * ``` + * + * @see {@link withDefault} – provide a concrete fallback value instead + * + * @category combinators + * @since 2.0.0 + */ +export const option = (self: Config): Config> => + self.pipe(map(Option.some), withDefault(Option.none())) + +/** + * Extracts the successfully parsed value type from a `Config`. + * + * **When to use** + * + * Use to derive the parsed value type from an existing `Config` value when + * declaring reusable config-driven types. + * + * @see {@link Config} for the config type whose parsed value is extracted + * @see {@link Effect.Success} for extracting the success type from any `Effect` + * + * @category utility types + * @since 2.5.0 + */ +export type Success = [T] extends [Config] ? A : never + +/** + * Utility type that recursively replaces primitives with `Config` in a nested + * structure. + * + * **When to use** + * + * Use when typing the input of {@link unwrap} so callers can pass either a `Config` + * or a record of `Config`s. + * + * **Details** + * + * `Config.Wrap<{ key: string }>` becomes `{ key: Config } | Config<{ key: string }>` + * + * @see {@link unwrap} – construct a `Config` from a `Wrap` + * + * @category utility types + * @since 2.0.0 + */ +export type Wrap = [NonNullable] extends [infer T] ? [IsPlainObject] extends [true] ? + | { readonly [K in keyof A]: Wrap } + | Config + : Config + : Config + +type IsPlainObject = [A] extends [Record] + ? [keyof A] extends [never] ? false : [keyof A] extends [string] ? true : false + : false + +/** + * Constructs a `Config` from a value matching `Wrap`. + * + * **When to use** + * + * Use when accepting config from callers who may pass either a single `Config` or a + * record of individual `Config`s. + * + * **Details** + * + * If the input is already a `Config`, it is returned as-is. Otherwise, each + * key is recursively unwrapped and combined. + * + * **Example** (Unwrapping a record of configs) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * interface Options { + * key: string + * } + * + * 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 converting + * @since 2.0.0 + */ +export const unwrap = (wrapped: Wrap): Config => { + if (isConfig(wrapped)) return wrapped + return all(Rec.map(wrapped as Record>, (config) => unwrap(config))) as Config +} + +// ----------------------------------------------------------------------------- +// schema +// ----------------------------------------------------------------------------- + +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 = (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 hasProviderInput = ( + ast: SchemaAST.AST, + 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 matchesIndex = ast.indexSignatures.map((is) => SchemaParser._is(is.parameter)) + const materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { + if (cursor.node?._tag !== "Record") { + return undefined + } + 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) + } + } + 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 materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { + if (cursor.node?._tag !== "Array") { + return undefined + } + 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": + 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`. + * + * **When to use** + * + * Use when you need to read structured or schema-validated configuration. + * + * **Details** + * + * The optional `path` sets the local path segment(s) for the config lookup. + * It is appended to the logical path prefix accumulated from outer + * {@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 + * to this API. + * + * 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 import.meta.vitest + * import { Config, ConfigProvider, Effect, Schema } from "effect" + * + * const DbConfig = Config.schema( + * Schema.Struct({ + * host: Schema.String, + * port: Schema.Int + * }), + * "db" + * ) + * + * const provider = ConfigProvider.fromUnknown({ + * db: { host: "localhost", port: 5432 } + * }) + * + * Effect.runSync(DbConfig.parse(provider)) // => { host: "localhost", port: 5432 } + * ``` + * + * @see {@link string} / {@link number} / {@link boolean} – shortcuts for + * single-value configs + * + * @category schemas + * @since 4.0.0 + */ +export function schema(codec: Schema.ConstraintCodec, path?: string | ConfigProvider.Path): Config { + const codecStringTree = Schema.toCodecStringTree(codec) + 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 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 + ) + }) + ) + }) +} + +/** @internal */ +export const TrueValues = Schema.Literals(["true", "yes", "on", "1", "y"]) + +/** @internal */ +export const FalseValues = Schema.Literals(["false", "no", "off", "0", "n"]) + +/** + * 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.meta.vitest + * 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" + * } + * }) + * + * 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 separated or structural array input + * + * @category schemas + * @since 4.0.0 + */ +export const Record = (key: K, value: V, options?: { + readonly separator?: string | undefined + readonly keyValueSeparator?: string | undefined +}) => { + const record = Schema.Record(key, value) + const split = SchemaTransformation.splitKeyValue(options) + 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.Union([record, recordString]) +} + +const ArrayConfig = (value: V, options?: { + readonly separator?: string | undefined +}) => { + const array = Schema.Array(value) + const separator = options?.separator ?? "," + const arrayString = Schema.String.pipe( + Schema.decodeTo(Schema.toCodecStringTree(array), { + decode: SchemaGetter.split(options), + encode: SchemaGetter.passthrough, Schema.StringTree>({ strict: false }).compose( + SchemaGetter.transform((input) => input.join(separator)) + ) + }) + ) + + return Schema.Union([arrayString, array]) +} + +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. + * + * @see {@link Record} for separated or structural record input + * + * @category schemas + * @since 4.0.0 + */ + ArrayConfig as Array +} + +// ----------------------------------------------------------------------------- +// constructors +// ----------------------------------------------------------------------------- + +/** + * Creates a config that always fails with the given error. + * + * **When to use** + * + * Use when you need to re-raise a specific config error, such as inside + * {@link orElse}. + * + * @category constructors + * @since 2.0.0 + */ +export function fail(err: SourceError | Schema.SchemaError) { + return make(() => Effect.fail(evaluationFailure(new ConfigError(err), false))) +} + +/** + * Creates a config that always succeeds with the given value, ignoring the + * provider entirely. + * + * **When to use** + * + * Use when you need a hardcoded config value, such as inside {@link orElse} or + * tests. + * + * **Example** (Returning a constant fallback) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * 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(resolved(value, false))) +} + +/** + * Creates a config for a single string value. + * + * **When to use** + * + * Use when reading a single string env var or config key. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.String, name)`. + * + * **Example** (Reading a string config) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const host = Config.string("HOST") + * + * const provider = ConfigProvider.fromUnknown({ HOST: "localhost" }) + * Effect.runSync(host.parse(provider)) // => "localhost" + * ``` + * + * @see {@link nonEmptyString} – rejects empty strings + * @see {@link schema} – for more complex types + * + * @category constructors + * @since 2.0.0 + */ +export function string(name?: string) { + return schema(Schema.String, name) +} + +/** + * Creates a config for a non-empty string value. Fails if the value is an + * empty string. + * + * **When to use** + * + * Use to read a string config value that must contain at least one character. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.NonEmptyString, name)`. + * + * @see {@link string} for allowing empty strings + * + * @category constructors + * @since 3.7.0 + */ +export function nonEmptyString(name?: string) { + return schema(Schema.NonEmptyString, name) +} + +/** + * Creates a config for a numeric value (including `NaN`, `Infinity`). + * + * **When to use** + * + * Use when you need config input to accept JavaScript's full number domain, + * including NaN and infinities, rather than reject non-finite values. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.Number, name)`. + * + * @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) { + return schema(Schema.Number, name) +} + +/** + * Creates a config for a finite number (rejects `NaN` and `Infinity`). + * + * **When to use** + * + * Use to read a numeric config value that must be finite. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.Finite, name)`. + * + * @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) { + return schema(Schema.Finite, name) +} + +/** + * Creates a config for an integer value. Rejects floats. + * + * **When to use** + * + * Use to read a numeric config value that must be an integer. + * + * **Details** + * + * 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` + * + * @category constructors + * @since 4.0.0 + */ +export function int(name?: string) { + return schema(Schema.Int, name) +} + +/** + * Creates a config that only accepts a specific literal value. + * + * **When to use** + * + * Use to restrict a config to a single, specific literal value. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.Literal(literal), name)`. + * + * **Example** (Restricting to a literal) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * 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 + * @category constructors + * @since 2.0.0 + */ +export function literal(literal: L, name?: string) { + return schema(Schema.Literal(literal), name) +} + +/** + * Creates a config that only accepts one of the specified literal values. + * + * **When to use** + * + * Use to restrict a config to a fixed set of allowed literal values. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.Literals(literals), name)`. + * + * **Example** (Restricting to a set of literals) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * 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 + * + * @category constructors + * @since 4.0.0 + */ +export function literals>(literals: L, name?: string) { + return schema(Schema.Literals(literals), name) +} + +/** + * Creates a config for a boolean value parsed from common string + * representations. + * + * **When to use** + * + * Use to read boolean flags from string-like config sources. + * + * **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 import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const program = Config.boolean("FEATURE_FLAG") + * + * const provider = ConfigProvider.fromEnv({ + * env: { + * FEATURE_FLAG: "yes" + * } + * }) + * + * Effect.runSync( + * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) + * ) // => true + * ``` + * + * @see {@link Boolean} for the underlying boolean codec + * + * @category constructors + * @since 2.0.0 + */ +export function boolean(name?: string) { + return schema(Boolean, name) +} + +/** + * Creates a config for a `Duration` value parsed from a human-readable + * string. + * + * **When to use** + * + * Use to read time duration settings such as timeouts, intervals, or TTLs. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.DurationFromString, name)`. + * + * Accepts any string that `Duration.fromInput` can parse (e.g. + * `"10 seconds"`, `"500 millis"`, `"Infinity"`, `"-Infinity"`). + * + * **Example** (Reading a duration) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Duration, Effect } from "effect" + * + * const program = Config.duration("DURATION").pipe(Effect.map(Duration.toMillis)) + * + * const provider = ConfigProvider.fromEnv({ + * env: { + * DURATION: "10 seconds" + * } + * }) + * + * Effect.runSync( + * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) + * ) // => 10000 + * ``` + * + * @see {@link schema} for decoding configuration values with a custom codec + * + * @category constructors + * @since 2.5.0 + */ +export function duration(name?: string) { + return schema(Schema.DurationFromString, name) +} + +/** + * Creates a config for a port number (integer in 1–65535). + * + * **When to use** + * + * Use to read network port settings that must be valid port numbers. + * + * **Details** + * + * Shortcut for `Config.schema(Config.Port, name)`. + * + * **Example** (Reading a port) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const program = Config.port("PORT") + * + * const provider = ConfigProvider.fromEnv({ + * env: { + * PORT: "8080" + * } + * }) + * + * Effect.runSync( + * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) + * ) // => 8080 + * ``` + * + * @see {@link int} for integer config values outside the port range + * @see {@link Port} for the underlying port codec + * + * @category constructors + * @since 3.16.0 + */ +export function port(name?: string) { + return schema(Port, name) +} + +/** + * Creates a config for a log level string. + * + * **When to use** + * + * Use to read Effect log-level settings from configuration. + * + * **Details** + * + * Shortcut for `Config.schema(Config.LogLevel, name)`. + * + * Accepted values: `"All"`, `"Fatal"`, `"Error"`, `"Warn"`, `"Info"`, + * `"Debug"`, `"Trace"`, `"None"`. + * + * **Example** (Reading a log level) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const program = Config.logLevel("LOG_LEVEL") + * + * const provider = ConfigProvider.fromEnv({ + * env: { + * LOG_LEVEL: "Info" + * } + * }) + * + * Effect.runSync( + * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) + * ) // => "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) +} + +/** + * Creates a config for a redacted string value. The parsed result is wrapped + * in a `Redacted` container that hides the value from logs and `toString`. + * + * **When to use** + * + * Use to read secret string settings that should not be exposed in logs or + * string output. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.Redacted(Schema.String), name)`. + * + * **Example** (Reading a secret) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const program = Config.redacted("API_KEY").pipe(Effect.map(String)) + * + * const provider = ConfigProvider.fromEnv({ + * env: { + * API_KEY: "sk-1234567890abcdef" + * } + * }) + * + * Effect.runSync( + * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) + * ) // => "" + * ``` + * + * @see {@link string} for non-secret string settings + * + * @category constructors + * @since 2.0.0 + */ +export function redacted(name?: string) { + return schema(Schema.Redacted(Schema.String), name) +} + +/** + * Creates a config for a `URL` value parsed from a string. + * + * **When to use** + * + * Use to read configuration values that must be valid URL strings. + * + * **Details** + * + * This is a shortcut for `Config.schema(Schema.URL, name)`. + * + * **Gotchas** + * + * Fails if the string cannot be parsed by the `URL` constructor. + * + * **Example** (Reading a URL) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const program = Config.url("URL").pipe(Effect.map((url) => url.href)) + * + * const provider = ConfigProvider.fromEnv({ + * env: { + * URL: "https://example.com" + * } + * }) + * + * Effect.runSync( + * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) + * ) // => "https://example.com/" + * ``` + * + * @see {@link schema} for decoding configuration values with a custom codec + * + * @category constructors + * @since 3.11.0 + */ +export function url(name?: string) { + return schema(Schema.URL, name) +} + +/** + * Creates a config for a `Date` value parsed from a string. + * + * **When to use** + * + * Use to read date settings that must parse to valid `Date` values. + * + * **Details** + * + * Shortcut for `Config.schema(Schema.Date, name)`. + * + * **Gotchas** + * + * Fails with a `SchemaError` if the string produces an invalid `Date`. + * + * **Example** (Reading a date) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const createdAt = Config.date("CREATED_AT") + * + * const provider = ConfigProvider.fromUnknown({ CREATED_AT: "2024-01-15" }) + * 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.Date, name) +} + +/** + * Scopes a config under a named prefix. + * + * **When to use** + * + * Use when you need to group related config keys under a common namespace. + * + * **Details** + * + * The prefix is prepended to every key the inner config reads. With + * `fromUnknown` this means an extra object level; with `fromEnv` it means + * a `_`-separated prefix on env var names. + * + * Multiple `nested` calls compose: the outermost name becomes the + * outermost path segment. + * + * **Example** (Nesting a struct config under `"database"`) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const dbConfig = Config.all({ + * 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 } + * ``` + * + * **Example** (Reading env vars with a nested prefix) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const host = Config.string("host").pipe(Config.nested("database")) + * + * const provider = ConfigProvider.fromEnv({ + * env: { database_host: "localhost" } + * }) + * Effect.runSync(host.parse(provider)) // => "localhost" + * ``` + * + * @see {@link all} – combine multiple configs into a struct + * @see {@link schema} – read structured config from a schema + * + * @category combinators + * @since 2.0.0 + */ +export const nested: { + (name: string): (self: Config) => Config + (self: Config, name: string): Config +} = dual( + 2, + (self: Config, name: string): Config => + make((provider, pathPrefix) => evaluateAt(self, provider, [...pathPrefix, name])) +) diff --git a/.repos/effect/packages/effect/src/ConfigProvider.ts b/.repos/effect/packages/effect/src/ConfigProvider.ts new file mode 100644 index 000000000..a847dc743 --- /dev/null +++ b/.repos/effect/packages/effect/src/ConfigProvider.ts @@ -0,0 +1,1318 @@ +/** + * Data sources used by `Config` to load raw configuration values. A + * `ConfigProvider` reads paths from places such as environment variables, + * JavaScript objects, `.env` contents, or directories, and returns a uniform + * `Node` shape that config schemas can decode. The module also includes helpers + * for composing providers, changing paths, and installing providers through + * layers. + * + * @since 4.0.0 + */ + +import * as Context from "./Context.ts" +import * as Data from "./Data.ts" +import * as Effect from "./Effect.ts" +import * as FileSystem from "./FileSystem.ts" +import { format } from "./Formatter.ts" +import { dual, flow } from "./Function.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as Layer from "./Layer.ts" +import * as Path_ from "./Path.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { PlatformError } from "./PlatformError.ts" +import * as Predicate from "./Predicate.ts" +import type { Scope } from "./Scope.ts" +import * as Str from "./String.ts" + +/** + * A discriminated union describing the shape of a configuration value at a + * given path. + * + * **When to use** + * + * Use when implementing a custom `ConfigProvider` by returning raw + * nodes from the `get` callback passed to {@link make}, or when inspecting raw + * provider output before schema parsing. + * + * **Details** + * + * `Value` is a terminal string leaf. `Record` is an object-like container + * whose immediate child keys are known and may carry an optional co-located + * `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 + * + * @category models + * @since 4.0.0 + */ +export type Node = + /** A terminal string value */ + | { + readonly _tag: "Value" + readonly value: string + } + /** An object; keys are unordered */ + | { + readonly _tag: "Record" + readonly keys: ReadonlySet + readonly value: string | undefined + } + /** An array-like container; length is the number of elements */ + | { + readonly _tag: "Array" + readonly length: number + readonly value: string | undefined + } + +/** + * Creates a `Value` node representing a terminal string leaf. + * + * **When to use** + * + * Use when building nodes inside a custom `ConfigProvider`'s `get` + * callback. + * + * **Details** + * + * The function returns a new plain object. + * + * **Example** (Creating a value node) + * + * ```ts import.meta.vitest + * import { ConfigProvider } from "effect" + * + * ConfigProvider.makeValue("3000") // => { _tag: "Value", value: "3000" } + * ``` + * + * @see {@link makeRecord} – for object-like containers + * @see {@link makeArray} – for array-like containers + * + * @category constructors + * @since 4.0.0 + */ +export function makeValue(value: string): Node { + return { _tag: "Value", value } +} + +/** + * Creates a `Record` node representing an object-like container with known + * child keys. + * + * **When to use** + * + * Use when you need to describe a directory or JSON object inside a custom + * provider. + * + * **Details** + * + * The optional `value` allows a node to be both a container and a leaf at the + * same time (for example, an env var `A=x` that also has children `A_FOO` and + * `A_BAR`). + * + * **Example** (Creating a record node) + * + * ```ts import.meta.vitest + * import { ConfigProvider } from "effect" + * + * const node = ConfigProvider.makeRecord(new Set(["host", "port"])) + * node._tag // => "Record" + * if (node._tag === "Record") { + * node.keys // => new Set(["host", "port"]) + * node.value // => undefined + * } + * ``` + * + * @see {@link makeValue} – for terminal leaves + * @see {@link makeArray} – for array-like containers + * + * @category constructors + * @since 4.0.0 + */ +export function makeRecord(keys: ReadonlySet, value?: string): Node { + return { _tag: "Record", keys, value } +} + +/** + * Creates an `Array` node representing an indexed container with a known + * length. + * + * **When to use** + * + * Use when you need to describe a JSON array or numerically indexed env vars + * inside a custom provider. + * + * **Details** + * + * The optional `value` allows a node to be both a container and a leaf at the + * same time. + * + * **Example** (Creating an array node) + * + * ```ts import.meta.vitest + * import { ConfigProvider } from "effect" + * + * ConfigProvider.makeArray(3) // => { _tag: "Array", length: 3, value: undefined } + * ``` + * + * @see {@link makeValue} – for terminal leaves + * @see {@link makeRecord} – for object-like containers + * + * @category constructors + * @since 4.0.0 + */ +export function makeArray(length: number, value?: string): Node { + return { _tag: "Array", length, value } +} + +/** + * Typed error indicating that a configuration source could not be read. + * + * **When to use** + * + * Use when you need to report that a custom provider's underlying store is + * unreachable or produced an I/O error while reading configuration data. + * + * **Gotchas** + * + * Do not use `SourceError` for "key not found". That case is represented by + * returning `undefined` from `load`. + * + * **Example** (Failing with a SourceError) + * + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" + * + * const provider = ConfigProvider.make((_path) => + * Effect.fail( + * 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 errors + * @since 4.0.0 + */ +export class SourceError extends Data.TaggedError("SourceError")<{ + readonly message: string + readonly cause?: unknown +}> {} + +/** + * An ordered sequence of string or numeric segments that addresses a node in + * the configuration tree. String segments name object keys; numeric segments + * index into arrays. + * + * **When to use** + * + * Use to address raw configuration nodes when implementing or transforming a + * `ConfigProvider`. + * + * **Example** (A typical config path) + * + * ```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 + * @since 4.0.0 + */ +export type Path = ReadonlyArray + +/** + * The core interface for loading raw configuration data. + * + * **When to use** + * + * Use to type-annotate variables that hold a provider or to implement a + * custom provider via {@link make}. + * + * **Details** + * + * `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", 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 services + * @since 4.0.0 + */ +export interface ConfigProvider extends Pipeable { + /** + * 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 + + /** + * 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 +} + +/** + * Context reference for the active raw configuration provider, registered in the context with a + * default value of `fromEnv()`. Because it is a `Context.Reference`, it is + * available without explicit provision; `Config` schemas automatically resolve + * it. + * + * **When to use** + * + * Use to override the active raw configuration provider for an entire program, + * or retrieve the current provider inside an Effect. + * + * **Example** (Providing a custom provider) + * + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" + * + * const provider = ConfigProvider.fromUnknown({ port: 8080 }) + * + * const program = Effect.gen(function*() { + * const current = yield* ConfigProvider.ConfigProvider + * return current + * }).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 4.0.0 + */ +export const ConfigProvider: Context.Reference = Context.Reference( + "effect/ConfigProvider", + { defaultValue: () => fromEnv() } +) + +const Proto = { + ...PipeInspectableProto, + toJSON(this: ConfigProvider) { + return { + _id: "ConfigProvider" + } + } +} + +const identityPath = (path: Path): Path => path + +function makeProvider( + load: (path: Path) => Effect.Effect, + mapInput: (f: (path: Path) => Path) => ConfigProvider +): ConfigProvider { + const self = Object.create(Proto) + self.load = load + self.mapInput = mapInput + return self +} + +function makeSource( + get: (path: Path) => Effect.Effect, + transform: (path: Path) => Path +): ConfigProvider { + return makeProvider( + (path) => get(transform(path)), + (f) => makeSource(get, flow(transform, f)) + ) +} + +function makeOrElse(first: ConfigProvider, second: ConfigProvider): ConfigProvider { + 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)) + ) +} + +/** + * Creates a `ConfigProvider` from a raw lookup function. + * + * **When to use** + * + * Use when implementing a provider backed by a custom store, such as a + * database, remote API, or in-memory map. + * + * **Details** + * + * The `get` callback receives a `Path` and must return + * `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 import.meta.vitest + * import { ConfigProvider, Effect } from "effect" + * + * const data: Record = { + * host: "localhost", + * port: "5432" + * } + * + * const provider = ConfigProvider.make((path) => { + * const key = path.join(".") + * const value = data[key] + * return Effect.succeed( + * 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 4.0.0 + */ +export function make(get: (path: Path) => Effect.Effect): ConfigProvider { + return makeSource(get, identityPath) +} + +/** + * Returns a provider that falls back to `that` when `self` returns `undefined` + * for a path. + * + * **When to use** + * + * Use to layer multiple config sources, such as env vars plus a defaults file, + * or provide partial overrides on top of a base config. + * + * **Details** + * + * Each provider keeps its own path transformations. If the combined provider + * is later transformed with {@link mapInput} or {@link nested}, the + * transformation is applied to both sides. + * + * **Gotchas** + * + * The fallback only runs when the path is not found (`undefined`). A + * `SourceError` from `self` is not caught; it propagates immediately. + * + * **Example** (Falling back to a default provider) + * + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" + * + * const envProvider = ConfigProvider.fromEnv({ + * env: { HOST: "prod.example.com" } + * }) + * const defaults = ConfigProvider.fromUnknown({ HOST: "localhost", PORT: "3000" }) + * + * const combined = ConfigProvider.orElse(envProvider, defaults) + * + * const host = Effect.runSync(combined.load(["HOST"])) + * const port = Effect.runSync(combined.load(["PORT"])) + * const values = [host?.value, port?.value] // => ["prod.example.com", "3000"] + * ``` + * + * @see {@link layerAdd} – install a fallback provider via a Layer + * + * @category combinators + * @since 2.0.0 + */ +export const orElse: { + (that: ConfigProvider): (self: ConfigProvider) => ConfigProvider + (self: ConfigProvider, that: ConfigProvider): ConfigProvider +} = dual( + 2, + (self: ConfigProvider, that: ConfigProvider): ConfigProvider => makeOrElse(self, that) +) + +/** + * Transforms the path segments before they reach the underlying store. + * + * **When to use** + * + * Use when you need to rename, re-case, or otherwise transform config path + * segments before lookup. + * + * **Details** + * + * The function `f` receives the whole path produced by earlier provider + * transformations and must return a new path. Lookup path transformations + * compose in application order: the existing transformation runs first, then + * `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.meta.vitest + * import { ConfigProvider, Effect } from "effect" + * + * const provider = ConfigProvider.fromEnv({ + * env: { APP_HOST: "localhost" } + * }) + * + * const upper = ConfigProvider.mapInput(provider, (path) => + * path.map((seg) => + * 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` + * @see {@link nested} – for prepending a prefix instead of transforming + * + * @category combinators + * @since 4.0.0 + */ +export const mapInput: { + (f: (path: Path) => Path): (self: ConfigProvider) => ConfigProvider + (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider +} = dual( + 2, + (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider => self.mapInput(f) +) + +/** + * Converts all string path segments to `CONSTANT_CASE` before lookup. + * + * **When to use** + * + * Use to bridge camelCase schema keys to `SCREAMING_SNAKE_CASE` + * environment variables. + * + * **Details** + * + * 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.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 + * + * @category combinators + * @since 2.0.0 + */ +export const constantCase: (self: ConfigProvider) => ConfigProvider = mapInput((path) => + path.map((seg) => typeof seg === "number" ? seg : Str.configCase(seg)) +) + +/** + * Scopes a provider so that all lookups are prefixed with the given path + * segments. + * + * **When to use** + * + * Use to namespace config under a prefix like `"app"` or `"database"`, or + * reuse the same provider shape for multiple sub-configs. + * + * **Details** + * + * Accepts a single string or a full `Path` array. For providers composed with + * {@link orElse}, the prefix is applied to each operand. Supports both + * data-last and data-first calling conventions. + * + * **Gotchas** + * + * Ordering matters when composing with {@link mapInput} or + * {@link constantCase}. Later provider transformations run after earlier ones: + * a later `nested` becomes the outer prefix, and a later `mapInput` sees the + * whole path produced by previous transformations. + * + * **Example** (Nesting under a prefix) + * + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" + * + * const provider = ConfigProvider.fromEnv({ + * env: { APP_HOST: "localhost", APP_PORT: "3000" } + * }) + * + * // 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 + * + * @category combinators + * @since 2.0.0 + */ +export const nested: { + (prefix: string | Path): (self: ConfigProvider) => ConfigProvider + (self: ConfigProvider, prefix: string | Path): ConfigProvider +} = dual( + 2, + (self: ConfigProvider, prefix: string | Path): ConfigProvider => { + const path = typeof prefix === "string" ? [prefix] : prefix + return mapInput(self, (input) => [...path, ...input]) + } +) + +/** + * Provides a layer that installs a `ConfigProvider` as the active provider for + * all downstream effects, replacing any previously installed provider. + * + * **When to use** + * + * Use to set the config source for an entire application or test suite. + * + * **Details** + * + * Accepts either a plain `ConfigProvider` or an `Effect` that produces one. + * When given an Effect, it is evaluated once when the layer is built. + * + * **Example** (Reading config from a JSON object) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Layer } from "effect" + * + * const TestLayer = ConfigProvider.layer( + * ConfigProvider.fromUnknown({ port: 8080 }) + * ) + * + * const program = Effect.gen(function*() { + * const port = yield* Config.number("port") + * return port + * }) + * + * Effect.runSync(Effect.provide(program, TestLayer)) // => 8080 + * ``` + * + * @see {@link layerAdd} – add a provider without replacing the existing one + * + * @category layers + * @since 4.0.0 + */ +export const layer = ( + self: ConfigProvider | Effect.Effect +): Layer.Layer> => + Effect.isEffect(self) ? Layer.effect(ConfigProvider)(self) : Layer.succeed(ConfigProvider)(self) + +/** + * Creates a Layer that composes a new `ConfigProvider` with the currently + * active one, rather than replacing it. + * + * **When to use** + * + * Use to add defaults that should only apply when the primary provider has no + * value for a path, or override specific keys while keeping the rest from the + * existing provider by setting `asPrimary: true`. + * + * **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. + * + * **Example** (Adding default values) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Layer } from "effect" + * + * const defaults = ConfigProvider.fromUnknown({ + * HOST: "localhost", + * PORT: "3000" + * }) + * + * // 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 + * @see {@link orElse} – compose providers without layers + * + * @category layers + * @since 4.0.0 + */ +export const layerAdd = ( + self: ConfigProvider | Effect.Effect, + options?: { + readonly asPrimary?: boolean | undefined + } | undefined +): Layer.Layer> => + Layer.effect(ConfigProvider)( + Effect.gen(function*() { + const current = yield* ConfigProvider + const configProvider = Effect.isEffect(self) ? yield* self : self + return options?.asPrimary ? orElse(configProvider, current) : orElse(current, configProvider) + }) + ) + +/** + * Creates a `ConfigProvider` backed by an in-memory JavaScript value + * (typically a parsed JSON object). + * + * **When to use** + * + * Use when you need deterministic config from an in-memory JavaScript value, + * such as in tests, embedded config, or parsed JSON. + * + * **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`. + * + * 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 import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const provider = ConfigProvider.fromUnknown({ + * database: { + * host: "localhost", + * port: 5432 + * } + * }) + * + * const host = Config.string("host").parse( + * provider.pipe(ConfigProvider.nested("database")) + * ) + * + * Effect.runSync(host) // => "localhost" + * ``` + * + * @see {@link fromEnv} – for environment variables + * @see {@link make} – for custom backing stores + * + * @category constructors + * @since 4.0.0 + */ +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, preserveEmptyStrings: boolean): Node | undefined { + let cur: unknown = root + + for (const seg of path) { + if (cur === null || cur === undefined) return undefined + + if (Array.isArray(cur)) { + if (typeof seg !== "number" || !Number.isInteger(seg) || seg < 0 || seg >= cur.length) return undefined + cur = cur[seg] + continue + } + + if (Predicate.isObject(cur)) { + if (typeof seg !== "string") return undefined + if (!Object.hasOwn(cur, seg)) return undefined + cur = cur[seg] + continue + } + + // cannot descend + return undefined + } + + return describeUnknown(cur, preserveEmptyStrings) +} + +function describeUnknown(u: unknown, preserveEmptyStrings: boolean): Node | undefined { + if (u === undefined || u === null) return undefined + if (typeof u === "string") return stringNode(u, preserveEmptyStrings) + if (typeof u === "number" || typeof u === "boolean" || typeof u === "bigint") { + return makeValue(String(u)) + } + if (Array.isArray(u)) return makeArray(u.length) + if (Predicate.isObject(u)) { + return makeRecord(new Set(Object.keys(u))) + } + // unknown values + 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. + * + * **Details** + * + * Path segments are joined with `_` for direct lookup, and env var names are + * also split on `_` to build a trie for child key discovery. This means + * `DATABASE_HOST=localhost` is accessible at both path `["DATABASE_HOST"]` + * and `["DATABASE", "HOST"]`. If all immediate children of a trie node have + * purely numeric names, the node is reported as an `Array`; otherwise as a + * `Record`. + * + * 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 import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" + * + * const provider = ConfigProvider.fromEnv({ + * env: { + * DATABASE_HOST: "localhost", + * DATABASE_PORT: "5432" + * } + * }) + * + * const host = Config.string("HOST").parse( + * provider.pipe(ConfigProvider.nested("DATABASE")) + * ) + * + * 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 constructors + * @since 2.0.0 + */ +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 + } + return fromEnvRecord(env, { preserveEmptyStrings: options?.preserveEmptyStrings }) +} + +type EnvTrieNode = { + children?: Record +} + +function buildEnvTrie(env: Record): EnvTrieNode { + const trie: EnvTrieNode = {} + + for (const [name, value] of Object.entries(env)) { + if (value === undefined) continue + + // Split on "_" and keep empty segments (no special handling for "__") + const segments = name.split("_") + + let node = trie + for (const seg of segments) { + const children = node.children ??= Object.create(null) + node = children[seg] ??= {} + } + } + + return trie +} + +const NUMERIC_INDEX = /^(0|[1-9][0-9]*)$/ + +function nodeAtEnv( + trie: EnvTrieNode, + env: Record, + path: Path, + preserveEmptyStrings: boolean +): Node | undefined { + const key = path.map(String).join("_") + const leafValue = emptyStringAsMissing(Object.hasOwn(env, key) ? env[key] : undefined, preserveEmptyStrings) + + const trieNode = trieNodeAt(trie, path) + const children = trieNode?.children ? Object.keys(trieNode.children) : [] + + if (children.length === 0) { + return leafValue === undefined ? undefined : makeValue(leafValue) + } + + const allNumeric = children.every((k) => NUMERIC_INDEX.test(k)) + if (allNumeric) { + const length = Math.max(...children.map((k) => parseInt(k, 10))) + 1 + return makeArray(length, leafValue) + } + + return makeRecord(new Set(children), leafValue) +} + +function trieNodeAt(root: EnvTrieNode, path: Path): EnvTrieNode | undefined { + if (path.length === 0) return root + + // Convert path segments to strings and navigate through the trie + let node: EnvTrieNode | undefined = root + for (const seg of path) { + node = node?.children?.[String(seg)] + if (!node) return undefined + } + return node +} + +/** + * Creates a `ConfigProvider` by parsing the string contents of a `.env` file. + * + * **When to use** + * + * Use when you already have the `.env` contents as a string, such as contents + * fetched from a remote store or embedded in a test. + * + * **Details** + * + * Supports `export` prefixes, single/double/backtick quoting, inline comments, + * 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 fromEnvRecord} with the parsed key-value pairs. + * + * **Example** (Parsing .env contents) + * + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" + * + * const contents = ` + * HOST=localhost + * PORT=3000 + * # this is a comment + * ` + * + * 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 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 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 = Object.create(null) + + // Convert line breaks to same format + lines = lines.replace(/\r\n?/gm, "\n") + + let match: RegExpExecArray | null + while ((match = DOT_ENV_LINE.exec(lines)) != null) { + const key = match[1] + + // Default undefined or null to empty string + let value = match[2] || "" + + // Remove whitespace + value = value.trim() + + // Check if double quoted + const maybeQuote = value[0] + + // Remove surrounding quotes + value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2") + + // Expand newlines if double quoted + if (maybeQuote === "\"") { + value = value.replace(/\\n/g, "\n") + value = value.replace(/\\r/g, "\r") + } + + // Add to object + obj[key] = value + } + + return obj +} + +function dotEnvExpand(parsed: Record): Record { + const newParsed: Record = Object.create(null) + + for (const configKey of Object.keys(parsed)) { + // resolve escape sequences + newParsed[configKey] = interpolate(parsed[configKey], parsed).replace(/\\\$/g, "$") + } + + return newParsed +} + +function interpolate(envValue: string, parsed: Record): string { + // find the last unescaped dollar sign in the + // value so that we can evaluate it + const lastUnescapedDollarSignIndex = searchLast(envValue, /(?!(?<=\\))\$/g) + + // If we couldn't match any unescaped dollar sign + // let's return the string as is + if (lastUnescapedDollarSignIndex === -1) return envValue + + // This is the right-most group of variables in the string + const rightMostGroup = envValue.slice(lastUnescapedDollarSignIndex) + + /** + * This finds the inner most variable/group divided + * by variable name and default value (if present) + * ( + * (?!(?<=\\))\$ // only match dollar signs that are not escaped + * {? // optional opening curly brace + * ([\w]+) // match the variable name + * (?::-([^}\\]*))? // match an optional default value + * }? // optional closing curly brace + * ) + */ + const matchGroup = /((?!(?<=\\))\${?([\w]+)(?::-([^}\\]*))?}?)/ + const match = rightMostGroup.match(matchGroup) + + if (match !== null) { + const [_, group, variableName, defaultValue] = match + const value = Object.hasOwn(parsed, variableName) && parsed[variableName] !== "" + ? parsed[variableName] + : defaultValue ?? "" + + return interpolate( + envValue.replace(group, value), + parsed + ) + } + + return envValue +} + +function searchLast(str: string, rgx: RegExp): number { + const matches = Array.from(str.matchAll(rgx)) + return matches.length > 0 ? matches.slice(-1)[0].index : -1 +} + +/** + * Creates a `ConfigProvider` by reading and parsing a `.env` file from the + * file system. + * + * **When to use** + * + * Use to load environment config from a `.env` file at application startup. + * + * **Details** + * + * 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.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 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 + * @see {@link fromEnv} – read from the runtime environment + * + * @category constructors + * @since 4.0.0 + */ +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 fromDotEnvContents(content, options) + } +) + +/** + * Creates a `ConfigProvider` that reads configuration from a directory tree + * on disk, where each file is a leaf value and each directory is a container. + * + * **When to use** + * + * Use when you expose each config key as a file under a directory, such as + * Kubernetes ConfigMap or Secret volume mounts. + * + * **Details** + * + * 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.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 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 constructors + * @since 4.0.0 + */ +export const fromDir: (options?: { + readonly rootPath?: string | undefined + readonly preserveEmptyStrings?: boolean | undefined +}) => Effect.Effect< + ConfigProvider, + never, + Path_.Path | FileSystem.FileSystem +> = Effect.fnUntraced(function*(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) => stringNode(content.trim(), preserveEmptyStrings)) + ) + + // If not a file, try reading as a *directory* + const asDirectory = fs.readDirectory(fullPath).pipe( + Effect.map((entries) => makeRecord(new Set(entries.map((entry) => platformPath.basename(entry))))) + ) + + return asFile.pipe( + Effect.catch((fileCause) => + asDirectory.pipe( + Effect.catch((dirCause) => + isNotFound(fileCause) && isNotFound(dirCause) + ? Effect.succeed(undefined) + : Effect.fail(isNotFound(fileCause) ? dirCause : fileCause) + ) + ) + ), + Effect.mapError((cause: PlatformError) => + new SourceError({ + message: `Failed to read file at ${platformPath.join(rootPath, ...path.map(String))}`, + cause + }) + ) + ) + }) +}) + +const isNotFound = (cause: PlatformError) => cause.reason._tag === "NotFound" diff --git a/.repos/effect/packages/effect/src/Console.ts b/.repos/effect/packages/effect/src/Console.ts new file mode 100644 index 000000000..83264a74b --- /dev/null +++ b/.repos/effect/packages/effect/src/Console.ts @@ -0,0 +1,853 @@ +/** + * Wraps console operations in Effect. + * + * The `Console` service exposes common console methods such as logging, + * warnings, errors, groups, counters, tables, and timers. Because console access + * goes through a service, programs can use custom console implementations in + * tests or other environments. This module also includes scoped helpers that + * close console groups or timers automatically. + * + * @since 2.0.0 + */ +import type * as Context from "./Context.ts" +import type * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import * as core from "./internal/core.ts" +import * as effect from "./internal/effect.ts" +import type { Scope } from "./Scope.ts" + +/** + * Represents a console interface for logging, debugging, timing, and grouping output. + * + * @category services + * @since 2.0.0 + */ +export interface Console { + assert(condition: boolean, ...args: ReadonlyArray): void + clear(): void + count(label?: string): void + countReset(label?: string): void + debug(...args: ReadonlyArray): void + dir(item: any, options?: any): void + dirxml(...args: ReadonlyArray): void + error(...args: ReadonlyArray): void + group(...args: ReadonlyArray): void + groupCollapsed(...args: ReadonlyArray): void + groupEnd(): void + info(...args: ReadonlyArray): void + log(...args: ReadonlyArray): void + table(tabularData: any, properties?: ReadonlyArray): void + time(label?: string): void + timeEnd(label?: string): void + timeLog(label?: string, ...args: ReadonlyArray): void + trace(...args: ReadonlyArray): void + warn(...args: ReadonlyArray): void +} + +/** + * Context reference for the current console service in the Effect system, allowing access to the active console implementation from within the Effect context. + * + * **When to use** + * + * Use when you need an effect to run against a provided console implementation, + * such as tests or alternate runtimes, rather than the default console. + * + * **Details** + * + * When no override is provided, the reference resolves to `globalThis.console`. + * + * **Example** (Accessing the current console) + * + * ```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 services + * @since 2.0.0 + */ +export const Console: Context.Reference = effect.ConsoleRef + +/** + * Creates an Effect that provides access to the current console service and lets you perform operations with it within an Effect context. + * + * **Example** (Accessing the current console service) + * + * ```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 + * @since 2.0.0 + */ +export const consoleWith = (f: (console: Console) => Effect.Effect): Effect.Effect => + core.withFiber((fiber) => f(fiber.getRef(Console))) + +/** + * Writes the supplied assertion message to the console as an error when `condition` is false; when `condition` is true, no console output is produced. + * + * **Example** (Logging failed assertions) + * + * ```ts import.meta.vitest + * import { Console, Effect } from "effect" + * + * const errors: Array = [] + * 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 + * @since 2.0.0 + */ +export const assert = (condition: boolean, ...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.assert(condition, ...args) + }) + ) + +/** + * Runs the current console service's clear operation. + * + * **When to use** + * + * Use to request that the active console implementation clear its visible + * output. + * + * **Gotchas** + * + * The clearing behavior depends on the active console implementation and host + * environment. + * + * **Example** (Clearing console output) + * + * ```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 + * @since 2.0.0 + */ +export const clear: Effect.Effect = consoleWith((console) => + effect.sync(() => { + console.clear() + }) +) + +/** + * Logs and increments the counter associated with `label`, using the console's default counter when no label is provided. + * + * **Example** (Counting repeated calls) + * + * ```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") + * yield* Console.count() + * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["my-counter: 1", "my-counter: 2", "default: 1"] + * ``` + * + * @category accessors + * @since 2.0.0 + */ +export const count = (label?: string): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.count(label) + }) + ) + +/** + * Resets the counter associated with the specified label back to zero. + * + * **Example** (Resetting a counter) + * + * ```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") + * yield* Console.countReset("my-counter") + * 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 + * @since 2.0.0 + */ +export const countReset = (label?: string): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.countReset(label) + }) + ) + +/** + * Writes a debug message through the current `Console` service. + * + * **Details** + * + * The arguments are passed to the service's `debug` method when the returned + * Effect is executed. Any filtering behavior depends on the active console + * implementation. + * + * **Example** (Writing debug messages) + * + * ```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 + * @since 2.0.0 + */ +export const debug = (...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.debug(...args) + }) + ) + +/** + * Displays an interactive list of the properties of the specified object, optionally using console-specific inspection options for debugging complex data structures. + * + * **Example** (Inspecting an object) + * + * ```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 }) + * }) + * + * 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 + * @since 2.0.0 + */ +export const dir = (item: any, options?: any): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.dir(item, options) + }) + ) + +/** + * Displays an interactive tree of descendant XML or HTML elements, which is particularly useful for inspecting DOM elements in browser environments. + * + * **Example** (Inspecting XML-like data) + * + * ```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(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["Ada"] + * ``` + * + * @category accessors + * @since 2.0.0 + */ +export const dirxml = (...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.dirxml(...args) + }) + ) + +/** + * Writes an error-level message to the console, typically displayed with error + * styling by the active console implementation. + * + * **Example** (Writing error messages) + * + * ```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:", { + * code: 500, + * 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 + * @since 2.0.0 + */ +export const error = (...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.error(...args) + }) + ) + +/** + * Creates a scoped console group, optionally collapsed and labeled, and closes it automatically when the Effect scope is finalized. + * + * **Example** (Grouping scoped output) + * + * ```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*() { + * yield* Console.group({ label: "User Processing" }) + * yield* Console.log("Loading user data...") + * yield* Console.log("Validating user...") + * yield* Console.log("User processed successfully") + * }) + * ) + * }) + * + * 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 + * @since 2.0.0 + */ +export const group = ( + options?: { label?: string | undefined; collapsed?: boolean | undefined } | undefined +): Effect.Effect => + consoleWith((console) => + effect.acquireRelease( + effect.sync(() => { + if (options?.collapsed) { + console.groupCollapsed(options.label) + } else { + console.group(options?.label) + } + }), + () => + effect.sync(() => { + console.groupEnd() + }) + ) + ) + +/** + * Writes an informational message to the console, typically displayed with info + * styling by the active console implementation. + * + * **Example** (Writing informational messages) + * + * ```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:", { + * port: 3000, + * 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 + * @since 2.0.0 + */ +export const info = (...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.info(...args) + }) + ) + +/** + * Logs a general-purpose message to the console. + * + * **Example** (Writing log messages) + * + * ```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 + * @since 2.0.0 + */ +export const log = (...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.log(...args) + }) + ) + +/** + * Displays tabular data as a formatted table in the console, optionally limited to selected properties. + * + * **Example** (Displaying tabular data) + * + * ```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" }, + * { name: "Jane", age: 25, city: "London" }, + * { name: "Bob", age: 35, city: "Paris" } + * ] + * 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 + * @since 2.0.0 + */ +export const table = (tabularData: any, properties?: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.table(tabularData, properties) + }) + ) + +/** + * Starts a scoped timer for `label` and automatically ends it when the Effect scope is finalized. + * + * **Example** (Timing scoped work) + * + * ```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* 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 + * @since 2.0.0 + */ +export const time = (label?: string | undefined): Effect.Effect => + consoleWith((console) => + effect.acquireRelease( + effect.sync(() => { + console.time(label) + }), + () => + effect.sync(() => { + console.timeEnd(label) + }) + ) + ) + +/** + * Logs the elapsed time for an existing timer without stopping it, allowing progress reports for long-running operations. + * + * **Example** (Logging timer progress) + * + * ```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]), + * 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* Console.timeLog("long-operation", "Halfway done") + * // 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 + * @since 2.0.0 + */ +export const timeLog = (label?: string, ...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.timeLog(label, ...args) + }) + ) + +/** + * Writes the current stack trace to the console to show how the current point in + * the code was reached. + * + * **Example** (Writing stack traces) + * + * ```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 + * @since 2.0.0 + */ +export const trace = (...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.trace(...args) + }) + ) + +/** + * Writes a warning-level message to the console, typically displayed with + * warning styling by the active console implementation. + * + * **Example** (Writing warning messages) + * + * ```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 + * @since 2.0.0 + */ +export const warn = (...args: ReadonlyArray): Effect.Effect => + consoleWith((console) => + effect.sync(() => { + console.warn(...args) + }) + ) + +/** + * Runs an Effect inside an optionally labeled or collapsed console group, starting the group before execution and ending it after the Effect completes. + * + * **Example** (Wrapping an effect in a group) + * + * ```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*() { + * yield* Console.log("Step 1: Initialize") + * yield* Console.log("Step 2: Process") + * yield* Console.log("Step 3: Complete") + * }), + * { 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 + * @since 2.0.0 + */ +export const withGroup = dual< + ( + options?: { + readonly label?: string | undefined + readonly collapsed?: boolean | undefined + } + ) => (self: Effect.Effect) => Effect.Effect, + ( + self: Effect.Effect, + options?: { + readonly label?: string | undefined + readonly collapsed?: boolean | undefined + } + ) => Effect.Effect +>((args) => core.isEffect(args[0]), (self, options) => + consoleWith((console) => + effect.acquireUseRelease( + effect.sync(() => { + if (options?.collapsed) { + console.groupCollapsed(options.label) + } else { + console.group(options?.label) + } + }), + () => self, + () => + effect.sync(() => { + console.groupEnd() + }) + ) + )) + +/** + * Runs an Effect with a console timer, starting the timer before execution and ending it after the Effect completes. + * + * **Example** (Timing an effect) + * + * ```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* 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 + * @since 2.0.0 + */ +export const withTime = dual< + (label?: string) => (self: Effect.Effect) => Effect.Effect, + (self: Effect.Effect, label?: string) => Effect.Effect +>((args) => core.isEffect(args[0]), (self, label) => + consoleWith((console) => + effect.acquireUseRelease( + effect.sync(() => { + console.time(label) + }), + () => self, + () => + effect.sync(() => { + console.timeEnd(label) + }) + ) + )) diff --git a/.repos/effect/packages/effect/src/Context.ts b/.repos/effect/packages/effect/src/Context.ts new file mode 100644 index 000000000..3db89c975 --- /dev/null +++ b/.repos/effect/packages/effect/src/Context.ts @@ -0,0 +1,1331 @@ +/** + * Stores Effect services in typed maps. + * + * A `Context` holds service implementations under `Context.Service` or + * `Context.Reference` keys, and its type records which keys are present. + * Effects use contexts as their environment, so services can be provided once + * instead of passed through every function call. This module includes helpers + * for creating keys, building contexts, adding and reading services, merging + * contexts, and selecting or removing services. + * + * @since 4.0.0 + */ +import type { Effect, EffectIterator } from "./Effect.ts" +import * as Effectable from "./Effectable.ts" +import * as Equal from "./Equal.ts" +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 * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Types from "./Types.ts" + +/** + * String literal type used as the runtime type identifier for `Context` + * service keys. + * + * @category type IDs + * @since 4.0.0 + */ +export type ServiceTypeId = "~effect/Context/Service" + +/** + * Runtime type identifier attached to `Context` service keys and used by + * `isKey` to recognize them. + * + * @category type IDs + * @since 4.0.0 + */ +export const ServiceTypeId: ServiceTypeId = "~effect/Context/Service" + +/** + * Typed identifier for a service stored in a `Context`. + * + * **When to use** + * + * Use as the typed handle for storing, retrieving, and requiring a specific + * service in a `Context`. + * + * **Details** + * + * `Identifier` tracks the requirement in Effect types, while `Shape` is the + * service implementation retrieved by the key. A key is also an Effect value, + * so yielding it inside `Effect.gen` retrieves the service from the current + * fiber context. + * + * @see {@link Service} for creating required service keys + * @see {@link Reference} for creating service keys with default values + * + * @category models + * @since 4.0.0 + */ +export interface Key extends Effect { + readonly [ServiceTypeId]: ServiceTypeId + readonly Service: Shape + readonly Identifier: Identifier + readonly key: string +} + +/** + * Context key with helper methods for working with a service. + * + * **Details** + * + * `context` creates a one-service `Context`, `use` and `useSync` retrieve the + * service from the current Effect context before applying a function, and `of` + * is a type-level helper for service values. + * + * **Example** (Defining a service key) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * // Define an identifier for a database service + * const Database = Context.Service<{ query: (sql: string) => string }>( + * "Database" + * ) + * + * // The key can be used to store and retrieve services + * const context = Context.make(Database, { query: (sql) => `Result: ${sql}` }) + * Context.get(context, Database).query("SELECT 1") // => "Result: SELECT 1" + * ``` + * + * @category services + * @since 4.0.0 + */ +export interface Service extends Key { + of(this: void, self: Shape): Shape + context(self: Shape): Context + use(f: (service: Shape) => Effect): Effect + useSync(f: (service: Shape) => A): Effect +} + +/** + * Class-style service key produced by `Context.Service()("Id")`. + * + * **When to use** + * + * Use when declaring a service as a class so the class value can serve as the + * `Context` key. + * + * **Details** + * + * The class itself is the `Context` key, and its string `key` identifies the + * service at runtime. + * + * @see {@link Service} for creating function-style keys or class-style service keys + * + * @category services + * @since 4.0.0 + */ +export interface ServiceClass + extends Service +{ + new(_: never): ServiceClass.Shape + readonly key: Identifier +} + +/** + * Namespace containing helper types for class-style `Context.Service` + * declarations. + * + * @since 4.0.0 + */ +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 services + * @since 4.0.0 + */ + export interface Shape { + readonly [ServiceTypeId]: typeof ServiceTypeId + readonly key: Identifier + readonly Service: Service + } +} + +/** + * Creates a `Context` service key. + * + * **When to use** + * + * Use when you need to define a context service key for a dependency that must + * be provided by the surrounding context. + * + * **Details** + * + * Call `Context.Service("Key")` for a function-style key, or use the two-stage + * form `Context.Service()("Key")` for class-style service + * declarations. The returned key can be yielded as an Effect and passed to + * `Context.make`, `Context.add`, and the Context getter functions. + * + * **Gotchas** + * + * The string key is the runtime identity of the service. Reusing the same key + * string for unrelated services makes them occupy the same slot in a + * `Context`. + * + * **Example** (Creating service keys) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * // Create a simple service + * const Database = Context.Service<{ + * query: (sql: string) => string + * }>("Database") + * + * // Create a service class + * class Config extends Context.Service()("Config") {} + * + * // Use the services to create contexts + * const db = Context.make(Database, { + * 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 services + * @since 4.0.0 + */ +export const Service: { + ( + key: string, + options?: { + /** @internal */ + readonly fiberCached?: boolean | undefined + } | undefined + ): Service + (): < + const Identifier extends string, + E, + R = Types.unassigned, + Args extends ReadonlyArray = never + >( + id: Identifier, + options?: { + readonly make?: ((...args: Args) => Effect) | Effect | undefined + /** @internal */ + readonly fiberCached?: boolean | undefined + } | undefined + ) => + & ServiceClass + & ([Types.unassigned] extends [R] ? unknown + : { readonly make: [Args] extends [never] ? Effect : (...args: Args) => Effect }) + (): < + const Identifier extends string, + Make extends Effect | ((...args: any) => Effect) + >( + id: Identifier, + options: { + readonly make: Make + /** @internal */ + readonly fiberCached?: boolean | undefined + } + ) => + & ServiceClass< + Self, + Identifier, + Make extends + Effect | ((...args: infer _Args) => Effect) ? _A + : never + > + & { readonly make: Make } +} = function() { + function KeyClass() {} + const self = KeyClass as any as Types.Mutable> + Object.setPrototypeOf(self, ServiceProto) + 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 = { + [ServiceTypeId]: ServiceTypeId, + ...Effectable.Prototype>({ + label: "Service", + evaluate(fiber) { + return exitSucceed(get(fiber.context, this)) + } + }), + toJSON(this: Service) { + return { + _id: "Service", + key: this.key + } + }, + of(this: void, self: Service): Service { + return self + }, + context( + this: Service, + self: Shape + ): Context { + return make(this, self) + }, + use(this: Service, f: (service: any) => Effect): Effect { + return withFiber((fiber) => f(get(fiber.context, this))) + }, + useSync(this: Service, f: (service: any) => A): Effect { + return withFiber((fiber) => exitSucceed(f(get(fiber.context, this)))) + } +} + +const cacheKeys = new Set() + +const ReferenceTypeId = "~effect/Context/Reference" as const + +/** + * Service key with a lazily computed default value. + * + * **Details** + * + * When a `Reference` is requested from a `Context` that does not contain an + * override, Context getters that resolve references return the cached default + * value instead of failing. + * + * **Example** (Defining a reference with a default value) + * + * ```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) => { 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 services + * @since 3.11.0 + */ +export interface Reference extends Service { + readonly [ReferenceTypeId]: typeof ReferenceTypeId + readonly defaultValue: () => Shape + [Symbol.iterator](): EffectIterator> + new(_: never): {} +} + +/** + * Namespace containing utility types for `Context` service keys. + * + * **Example** (Extracting service types) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const Database = Context.Service<{ + * query: (sql: string) => string + * }>("Database") + * + * // Extract service type from a key + * type DatabaseService = Context.Service.Shape + * + * // Extract identifier type from a key + * type DatabaseId = Context.Service.Identifier + * + * Database.key // => "Database" + * ``` + * + * @since 2.0.0 + */ +export declare namespace Service { + /** + * Type that matches any `Context` service key regardless of its identifier or + * service shape. + * + * **Example** (Typing any service key) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * // Any represents any possible service type + * const services: Array = [ + * Context.Service<{ log: (msg: string) => void }>("Logger"), + * Context.Service<{ query: (sql: string) => string }>("Database") + * ] + * services.map((service) => service.key) // => ["Logger", "Database"] + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Any = Key | Key + + /** + * Extracts the service implementation type stored behind a `Context` service + * key. + * + * **Example** (Extracting a service shape) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const Database = Context.Service<{ query: (sql: string) => string }>( + * "Database" + * ) + * + * // Extract the service shape from the service + * type DatabaseService = Context.Service.Shape + * // DatabaseService is { query: (sql: string) => string } + * Database.key // => "Database" + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Shape = T extends Key ? S : never + + /** + * Extracts the identifier, or requirement type, associated with a `Context` + * service key. + * + * **Example** (Extracting a service identifier) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const Database = Context.Service<{ query: (sql: string) => string }>( + * "Database" + * ) + * + * // Extract the identifier type from a key + * type DatabaseId = Context.Service.Identifier + * // DatabaseId is the identifier type + * Database.key // => "Database" + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type Identifier = T extends Key ? I : never +} + +const TypeId = "~effect/Context" as const + +/** + * Immutable collection of service implementations used for dependency + * injection in Effect programs. + * + * **Details** + * + * The type parameter tracks the service identifiers available in the context. + * At runtime, services are stored by each key's string `key`. + * + * **Example** (Creating a context with multiple services) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * // Create a context with multiple services + * const Logger = Context.Service<{ log: (msg: string) => void }>("Logger") + * const Database = Context.Service<{ query: (sql: string) => string }>( + * "Database" + * ) + * + * 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 + * @since 2.0.0 + */ +export interface Context extends Equal.Equal, Pipeable, Inspectable { + readonly [TypeId]: { + readonly _Services: Types.Contravariant + } + readonly mapUnsafe: ReadonlyMap +} + +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 +} + +/** + * Creates a `Context` from an existing service map. + * + * **When to use** + * + * Use when constructing a low-level `Context` from a trusted map whose lifecycle + * you control. + * + * **Gotchas** + * + * 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 import.meta.vitest + * import { Context } from "effect" + * + * // Create a context from a Map (unsafe) + * const map = new Map([ + * ["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 => + makeImpl(undefined, mapUnsafe, undefined, 0) + +const Proto: Omit< + ContextImpl, + "cacheRoot" | "base" | "overlay" | "depth" | "_flat" | "baseHits" +> = { + get mapUnsafe() { + return flatten(this as any as ContextImpl) + }, + ...PipeInspectableProto, + [TypeId]: { + _Services: (_: never) => _ + }, + toJSON(this: Context) { + return { + _id: "Context", + services: Array.from(this.mapUnsafe).map(([key, value]) => ({ key, value })) + } + }, + [Equal.symbol](this: Context, that: unknown): boolean { + 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 + }, + [Hash.symbol](this: Context): number { + return Hash.number(this.mapUnsafe.size) + } +} + +/** @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`. + * + * **When to use** + * + * Use to narrow an unknown value before passing it to APIs that require a + * `Context`. + * + * **Details** + * + * This checks the runtime `Context` marker and does not inspect which services + * the context contains. + * + * **Gotchas** + * + * This guard only proves that the value is a `Context`; it does not prove that + * any specific service is present. + * + * **Example** (Checking for contexts) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * Context.isContext(Context.empty()) // => true + * ``` + * + * @see {@link isKey} for checking service keys + * @see {@link isReference} for checking references with defaults + * + * @category guards + * @since 2.0.0 + */ +export const isContext = (u: unknown): u is Context => hasProperty(u, TypeId) + +/** + * Checks whether the provided argument is a `Key`. + * + * **Example** (Checking for keys) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * Context.isKey(Context.Service("Service")) // => true + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isKey = (u: unknown): u is Key => hasProperty(u, ServiceTypeId) + +/** + * Checks whether the provided argument is a `Reference`. + * + * **Example** (Checking for references) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const LoggerRef = Context.Reference("Logger", { + * defaultValue: () => ({ log: (_msg: string) => {} }) + * }) + * + * Context.isReference(LoggerRef) // => true + * Context.isReference(Context.Service("Key")) // => false + * ``` + * + * @category guards + * @since 3.11.0 + */ +export const isReference = (u: Key): u is Reference => !!(u as Reference)[ReferenceTypeId] + +/** + * Returns an empty `Context`. + * + * **Example** (Creating an empty context) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * Context.empty().mapUnsafe.size // => 0 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (): Context => emptyContext +const emptyContext = makeUnsafe(new Map()) + +/** + * Creates a new `Context` with a single service associated to the key. + * + * **Example** (Creating a context with one service) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * + * const context = Context.make(Port, { PORT: 8080 }) + * + * Context.get(context, Port).PORT // => 8080 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = ( + key: Key, + service: Types.NoInfer +): Context => makeUnsafe(new Map([[key.key, service]])) + +/** + * Adds a service to a given `Context`. + * + * **When to use** + * + * Use when you need to store a known service value in a `Context`. + * + * **Details** + * + * If the context already contains the same service key, the new service + * replaces the previous one. + * + * **Example** (Adding a service to a context) + * + * ```ts import.meta.vitest + * import { Context, pipe } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") + * + * const someContext = Context.make(Port, { PORT: 8080 }) + * + * const context = pipe( + * someContext, + * Context.add(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 combining + * @since 2.0.0 + */ +export const add: { + ( + key: Key, + service: Types.NoInfer + ): (self: Context) => Context + ( + self: Context, + key: Key, + service: Types.NoInfer + ): Context +} = dual(3, ( + self: Context, + key: Key, + service: Types.NoInfer +): 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`. + * + * **When to use** + * + * Use when you need to add or omit a `Context` service based on an `Option`. + * + * **Details** + * + * When `service` is `Option.some`, the value is stored for the key. When it is + * `Option.none`, the key is removed from the returned `Context`. + * + * **Example** (Adding optional services) + * + * ```ts import.meta.vitest + * import { Context, Option } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * + * const withPort = Context.empty().pipe( + * Context.addOrOmit(Port, Option.some({ PORT: 8080 })) + * ) + * + * 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 combining + * @since 4.0.0 + */ +export const addOrOmit: { + ( + key: Key, + service: Option.Option> + ): (self: Context) => Context> + ( + self: Context, + key: Key, + service: Option.Option> + ): Context> +} = dual(3, ( + self: Context, + key: Key, + service: Option.Option> +): 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 + * key is absent. + * + * **When to use** + * + * Use when you need a fallback for a missing `Context.Service` key while still + * resolving `Context.Reference` defaults. + * + * **Details** + * + * If the key is a `Context.Reference` and no override is stored in the + * context, its cached default value is returned instead of the fallback. + * + * **Gotchas** + * + * The fallback is not evaluated for missing `Context.Reference` keys because + * references resolve to their default value. + * + * **Example** (Falling back for missing services) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const Logger = Context.Service<{ log: (msg: string) => void }>("Logger") + * const Database = Context.Service<{ query: (sql: string) => string }>( + * "Database" + * ) + * + * const context = Context.make(Logger, { log: (_msg: string) => {} }) + * + * const logger = Context.getOrElse(context, Logger, () => ({ log: () => {} })) + * const database = Context.getOrElse( + * context, + * Database, + * () => ({ query: () => "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 + * + * @category getters + * @since 3.7.0 + */ +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 => { + const value = lookup(self, key.key) + if (value !== notFound) return value as any + return isReference(key) ? getDefaultValue(key) : orElse() +}) + +/** + * Returns the service currently stored for a key, or `undefined` when the key + * is absent. + * + * **When to use** + * + * Use when you need to read the service stored for a key without resolving + * `Context.Reference` defaults. + * + * **Gotchas** + * + * This is a raw lookup and does not resolve default values for + * `Context.Reference` keys. + * + * @see {@link getOption} for a reference-aware optional lookup + * + * @category getters + * @since 4.0.0 + */ +export const getOrUndefined: { + (key: Key): (self: Context) => S | undefined + (self: Context, key: Key): S | undefined +} = dual( + 2, + (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. + * + * **When to use** + * + * Use when you need to read a service from a context whose type does not prove + * the service is present. + * + * **Details** + * + * If the key is a `Context.Reference` and no override is stored in the + * context, its cached default value is returned. For absent non-reference keys, + * this function throws a runtime error. + * + * **Example** (Getting services unsafely) + * + * ```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 }) + * + * Context.getUnsafe(context, Port).PORT // => 8080 + * Context.getOption(context, Timeout) // => Option.none() + * ``` + * + * @see {@link get} for type-checked service access + * @see {@link getOption} for optional service access + * + * @category unsafe + * @since 4.0.0 + */ +export const getUnsafe: { + (service: Key): (self: Context) => S + (self: Context, services: Key): S +} = dual( + 2, + (self: Context, service: Key): S => { + const value = lookup(self, service.key) + if (value === notFound) { + if (isReference(service)) return getDefaultValue(service as any) + throw serviceNotFoundError(service) + } + return value as any + } +) + +/** + * Gets a service from the context that corresponds to the given key. + * + * **When to use** + * + * Use when you need type-checked access to a service already included in the + * context type. + * + * **Example** (Getting a service from a context) + * + * ```ts import.meta.vitest + * import { Context, pipe } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") + * + * const context = pipe( + * Context.make(Port, { PORT: 8080 }), + * Context.add(Timeout, { TIMEOUT: 5000 }) + * ) + * + * Context.get(context, Timeout).TIMEOUT // => 5000 + * ``` + * + * @see {@link getOption} for optional service access + * @see {@link getOrElse} for fallback values + * + * @category getters + * @since 2.0.0 + */ +export const get: { + (service: Key): (self: Context) => S + (self: Context, service: Key): S +} = getUnsafe + +const defaultValueCacheKey = "~effect/Context/defaultValue" as const + +const getDefaultValue = (ref: Reference) => { + if (defaultValueCacheKey in ref) { + return ref[defaultValueCacheKey] as any + } + return (ref as any)[defaultValueCacheKey] = ref.defaultValue() +} + +const serviceNotFoundError = (service: Key) => { + const error = new Error( + `Service not found${service.key ? `: ${String(service.key)}` : ""}` + ) + if (error.stack) { + const lines = error.stack.split("\n") + lines.splice(1, 3) + error.stack = lines.join("\n") + } + return error +} + +/** + * Gets the service for a key safely wrapped in an `Option`. + * + * **When to use** + * + * Use when you need to read a `Context` service as an `Option` so absence is + * represented as data. + * + * **Details** + * + * Returns `Option.some` when the service is stored in the context. If the key + * is a `Context.Reference` and no override is stored, returns `Option.some` of + * the cached default value. Missing non-reference keys return `Option.none`. + * + * **Example** (Getting optional services) + * + * ```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 }) + * + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() + * ``` + * + * @see {@link getOrElse} for returning a fallback value directly + * + * @category getters + * @since 2.0.0 + */ +export const getOption: { + (service: Key): (self: Context) => Option.Option + (self: Context, service: Key): Option.Option +} = dual(2, (self: Context, service: Key): Option.Option => { + 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() +}) + +/** + * Merges two `Context`s into one. + * + * **When to use** + * + * Use when you need to combine two contexts. + * + * **Details** + * + * When both contexts contain the same service key, the service from `that` + * overrides the service from `self`. + * + * **Example** (Merging two contexts) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") + * + * const firstContext = Context.make(Port, { PORT: 8080 }) + * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 }) + * + * const context = Context.merge(firstContext, secondContext) + * + * 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 + * + * @category combining + * @since 2.0.0 + */ +export const merge: { + (that: Context): (self: Context) => Context + (self: Context, that: Context): Context +} = 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 withFlat(self, (map) => that.mapUnsafe.forEach((value, key) => map.set(key, value))) +}) + +/** + * Merges any number of `Context`s into one. + * + * **When to use** + * + * Use when you need to combine a variadic list of contexts. + * + * **Details** + * + * When multiple contexts contain the same service key, the service from the + * last context with that key is kept. + * + * **Example** (Merging multiple contexts) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") + * const Host = Context.Service<{ HOST: string }>("Host") + * + * const firstContext = Context.make(Port, { PORT: 8080 }) + * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 }) + * const thirdContext = Context.make(Host, { HOST: "localhost" }) + * + * const context = Context.mergeAll( + * firstContext, + * secondContext, + * thirdContext + * ) + * + * context.mapUnsafe.size // => 3 + * ``` + * + * @see {@link merge} for merging two contexts + * + * @category combining + * @since 3.12.0 + */ +export const mergeAll = >( + ...ctxs: [...{ [K in keyof T]: Context }] +): Context => { + const map = new Map() + for (let i = 0; i < ctxs.length; i++) { + ctxs[i].mapUnsafe.forEach((value, key) => { + map.set(key, value) + }) + } + return makeUnsafe(map) +} + +/** + * Returns a new `Context` that contains only the specified services. + * + * **When to use** + * + * Use when you want to keep an allowlist of services in a `Context`. + * + * **Example** (Picking services from a context) + * + * ```ts import.meta.vitest + * import { Context, Option, pipe } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") + * + * const someContext = pipe( + * Context.make(Port, { PORT: 8080 }), + * Context.add(Timeout, { TIMEOUT: 5000 }) + * ) + * + * const context = pipe(someContext, Context.pick(Port)) + * + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() + * ``` + * + * @see {@link omit} for removing selected services + * + * @category filtering + * @since 2.0.0 + */ +export const pick = >>( + ...services: S +) => +(self: Context): Context> => { + const keep = new Set(services.map((key) => key.key)) + return withFlat(self, (map) => + map.forEach((_, key) => { + if (!keep.has(key)) map.delete(key) + })) +} + +/** + * Returns a new `Context` with the specified service keys removed. + * + * **When to use** + * + * Use when you want to remove a denylist of services from a `Context`. + * + * **Example** (Omitting services from a context) + * + * ```ts import.meta.vitest + * import { Context, Option, pipe } from "effect" + * + * const Port = Context.Service<{ PORT: number }>("Port") + * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") + * + * const someContext = pipe( + * Context.make(Port, { PORT: 8080 }), + * Context.add(Timeout, { TIMEOUT: 5000 }) + * ) + * + * const context = pipe(someContext, Context.omit(Timeout)) + * + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() + * ``` + * + * @see {@link pick} for keeping selected services + * + * @category filtering + * @since 2.0.0 + */ +export const omit = >>( + ...keys: S +) => +(self: Context): Context>> => + withFlat(self, (map) => { + for (let i = 0; i < keys.length; i++) { + map.delete(keys[i].key) + } + }) + +/** + * Creates a context key with a default value. + * + * **When to use** + * + * Use when you need to define a context key with a lazily computed default + * value. + * + * **Details** + * + * `Context.Reference` allows you to create a key that can hold a value. You + * can provide a default value for the service, which will automatically be used + * when the context is accessed, or override it with a custom implementation + * when needed. The default value is computed lazily and cached on the + * reference. + * + * **Example** (Creating references with default values) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * + * // Create a reference with a default value + * const messages: Array = [] + * const LoggerRef = Context.Reference("Logger", { + * defaultValue: () => ({ log: (msg: string) => messages.push(`Default: ${msg}`) }) + * }) + * + * // The reference provides the default value when accessed from an empty context + * const context = Context.empty() + * const logger = Context.get(context, LoggerRef) + * + * // You can also override the default value + * const customContext = Context.make(LoggerRef, { + * 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 services + * @since 3.11.0 + */ +export const Reference: ( + key: string, + options: { + readonly defaultValue: () => Service + /** @internal */ + readonly fiberCached?: boolean | undefined + } +) => Reference = Service as any diff --git a/.repos/effect/packages/effect/src/Cron.ts b/.repos/effect/packages/effect/src/Cron.ts new file mode 100644 index 000000000..259c6a6f2 --- /dev/null +++ b/.repos/effect/packages/effect/src/Cron.ts @@ -0,0 +1,1318 @@ +/** + * Utilities for recurring calendar schedules written as cron expressions or + * explicit field constraints. A `Cron` value stores allowed seconds, minutes, + * hours, days of month, months, weekdays, and an optional time zone. The module + * can create or parse schedules, compare them, test whether a date matches, and + * find previous or next scheduled occurrences. + * + * @since 2.0.0 + */ +import * as Arr from "./Array.ts" +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 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" +import * as dateTime from "./internal/dateTime.ts" +import * as N from "./Number.ts" +import * as Option from "./Option.ts" +import { type Pipeable, pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import * as Result from "./Result.ts" +import * as String from "./String.ts" +import type { Mutable } from "./Types.ts" + +const TypeId = "~effect/time/Cron" + +/** + * Represents a cron schedule with time constraints and timezone information. + * + * **When to use** + * + * Use to represent a recurring calendar schedule that can be matched against + * dates or used to compute scheduled occurrences. + * + * **Details** + * + * A `Cron` instance defines when a scheduled task should run, supporting + * seconds, minutes, hours, days, months, and weekday constraints. It also + * supports timezone-aware scheduling. + * + * **Example** (Creating a cron schedule) + * + * ```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: [], + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * weekdays: [1, 2, 3, 4, 5], // Monday to Friday + * tz: DateTime.zoneMakeNamedUnsafe("UTC") + * }) + * + * // Check if a date matches the schedule + * Cron.match(weekdayMorning, "2023-06-05T09:00:00Z") // => true + * ``` + * + * @see {@link make} for creating a schedule from explicit field constraints + * @see {@link parse} for creating a schedule from a cron expression string + * @see {@link match} for testing a date against a schedule + * @see {@link next} for finding the next scheduled occurrence + * + * @category models + * @since 2.0.0 + */ +export interface Cron extends Pipeable, Equal.Equal, Inspectable { + readonly [TypeId]: typeof TypeId + readonly tz: Option.Option + readonly seconds: ReadonlySet + readonly minutes: ReadonlySet + readonly hours: ReadonlySet + readonly days: ReadonlySet + readonly months: ReadonlySet + readonly weekdays: ReadonlySet + /** @internal */ + readonly and: boolean + /** @internal */ + readonly first: { + readonly second: number + readonly minute: number + readonly hour: number + readonly day: number + readonly month: number + readonly weekday: number + } + /** @internal */ + readonly last: { + readonly second: number + readonly minute: number + readonly hour: number + readonly day: number + readonly month: number + readonly weekday: number + } + /** @internal */ + readonly next: { + readonly second: ReadonlyArray + readonly minute: ReadonlyArray + readonly hour: ReadonlyArray + readonly day: ReadonlyArray + readonly month: ReadonlyArray + readonly weekday: ReadonlyArray + } + /** @internal */ + readonly prev: { + readonly second: ReadonlyArray + readonly minute: ReadonlyArray + readonly hour: ReadonlyArray + readonly day: ReadonlyArray + readonly month: ReadonlyArray + readonly weekday: ReadonlyArray + } +} + +function toPojo(cron: Cron): Record { + const out: Record = { + tz: cron.tz, + and: cron.and, + seconds: Arr.fromIterable(cron.seconds), + minutes: Arr.fromIterable(cron.minutes), + hours: Arr.fromIterable(cron.hours), + days: Arr.fromIterable(cron.days), + months: Arr.fromIterable(cron.months), + weekdays: Arr.fromIterable(cron.weekdays) + } + return out +} + +const CronProto = { + [TypeId]: TypeId, + [Equal.symbol](this: Cron, that: unknown) { + return isCron(that) && equals(this, that) + }, + [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))), + Hash.combine(Hash.array(Arr.fromIterable(this.days))), + Hash.combine(Hash.array(Arr.fromIterable(this.months))), + Hash.combine(Hash.array(Arr.fromIterable(this.weekdays))) + ) + }, + toObject(this: Cron) { + return toPojo(this) + }, + toString(this: Cron) { + return `Cron(${formatValue(toPojo(this))})` + }, + toJSON(this: Cron) { + const out = toPojo(this) + out["_id"] = "Cron" + return out + }, + [NodeInspectSymbol](this: Cron) { + return this.toJSON() + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +/** + * Checks whether a given value is a Cron instance. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a `Cron` schedule. + * + * **Details** + * + * This function is a type guard that determines whether the provided + * value is a valid Cron instance by checking for the presence of the + * Cron type identifier. + * + * **Example** (Checking cron values) + * + * ```ts import.meta.vitest + * import { Cron } from "effect" + * + * const cron = Cron.make({ + * minutes: [0], + * hours: [9], + * days: [1, 15], + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * weekdays: [1, 2, 3, 4, 5] + * }) + * + * Cron.isCron(cron) // => true + * Cron.isCron({}) // => false + * Cron.isCron("not a cron") // => false + * ``` + * + * @see {@link make} for constructing a `Cron` value directly + * @see {@link parse} for constructing a `Cron` value from a string + * + * @category guards + * @since 2.0.0 + */ +export const isCron = (u: unknown): u is Cron => hasProperty(u, TypeId) + +/** + * Creates a Cron instance from time constraints. + * + * **When to use** + * + * Use to build a cron schedule from explicit sets of allowed time-field values. + * + * **Details** + * + * Constructs a cron schedule by specifying which seconds, minutes, hours, + * 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.meta.vitest + * import { Cron, DateTime } from "effect" + * + * const utc = DateTime.zoneMakeNamedUnsafe("UTC") + * + * // Every day at midnight + * const midnight = Cron.make({ + * minutes: [0], + * hours: [0], + * 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 + * ], + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * 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: [], + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * 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 + * + * @category constructors + * @since 2.0.0 + */ +export const make = (values: { + readonly seconds?: Iterable | undefined + readonly minutes: Iterable + readonly hours: Iterable + 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 = 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) + const minutes = Array.from(o.minutes) + const hours = Array.from(o.hours) + const days = Array.from(o.days) + const months = Array.from(o.months) + const weekdays = Array.from(o.weekdays) + + o.first = { + second: seconds[0] ?? 0, + minute: minutes[0] ?? 0, + hour: hours[0] ?? 0, + day: days[0] ?? 1, + month: (months[0] ?? 1) - 1, + weekday: weekdays[0] ?? 0 + } + + o.last = { + second: seconds[seconds.length - 1] ?? 59, + minute: minutes[minutes.length - 1] ?? 59, + hour: hours[hours.length - 1] ?? 23, + day: days[days.length - 1] ?? 31, + month: (months[months.length - 1] ?? 12) - 1, + weekday: weekdays[weekdays.length - 1] ?? 6 + } + + o.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: 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 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" +): (values: ReadonlyArray) => Array => +(values) => { + const result = new Array(size).fill(undefined) + if (values.length === 0) { + return result + } + + let current: number | undefined = undefined + + if (dir === "next") { + let index = values.length - 1 + for (let i = size - 1; i >= 0; i--) { + while (index >= 0 && values[index] >= i) { + current = values[index--] + } + result[i] = current + } + } else { + let index = 0 + for (let i = 0; i < size; i++) { + while (index < values.length && values[index] <= i) { + current = values[index++] + } + result[i] = current + } + } + + return result +} + +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/time/Cron/CronParseError" + +/** + * Represents an error that occurs when parsing a cron expression fails. + * + * **When to use** + * + * Use to handle invalid cron expression failures returned by `parse`. + * + * **Details** + * + * This error provides information about what went wrong during parsing, + * including the error message and optionally the input that caused the error. + * + * **Example** (Handling cron parse failures) + * + * ```ts import.meta.vitest + * import { Cron, Result } from "effect" + * + * 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 errors + * @since 4.0.0 + */ +export class CronParseError extends Data.TaggedError("CronParseError")<{ + readonly message: string + readonly input?: string +}> { + readonly [CronParseErrorTypeId]: typeof CronParseErrorTypeId = CronParseErrorTypeId +} + +/** + * Checks whether a given value is a CronParseError instance. + * + * **When to use** + * + * Use to narrow an unknown failure before handling it as a cron parse error. + * + * **Details** + * + * This function is a type guard that determines whether the provided + * value is a CronParseError by checking for the presence of the + * CronParseError type identifier. + * + * **Example** (Checking cron parse errors) + * + * ```ts import.meta.vitest + * import { Cron, Result } from "effect" + * + * 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 + * @see {@link parse} for producing `CronParseError` values on invalid input + * + * @category guards + * @since 4.0.0 + */ +export const isCronParseError = (u: unknown): u is CronParseError => hasProperty(u, CronParseErrorTypeId) + +/** + * Parses a cron expression safely into a `Cron` instance, returning a `Result` + * instead of throwing. + * + * **When to use** + * + * Use to parse cron expressions from configuration or user input while handling + * invalid input as a `Result`. + * + * **Details** + * + * 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`. 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 import.meta.vitest + * import { Cron, Result } from "effect" + * + * // At 04:00 on every day-of-month from 8 through 14. + * 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 + * @see {@link make} for constructing a schedule from explicit field constraints + * + * @category constructors + * @since 2.0.0 + */ +export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Result => { + 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 })) + } + + if (segments.length === 5) { + segments.unshift("0") + } + + const [seconds, minutes, hours, days, months, weekdays] = segments + const zone = tz === undefined || dateTime.isTimeZone(tz) ? + Result.succeed(tz) : + Result.fromOption( + dateTime.zoneFromString(tz), + () => new CronParseError({ message: `Invalid time zone in cron expression`, input: tz }) + ) + + return Result.all({ + tz: zone, + seconds: parseSegment(seconds, secondOptions), + minutes: parseSegment(minutes, minuteOptions), + hours: parseSegment(hours, hourOptions), + days: parseSegment(days, dayOptions), + months: parseSegment(months, monthOptions), + weekdays: parseSegment(weekdays, weekdayOptions) + }).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 + }) + )) +} + +/** + * Parses a cron expression into a `Cron` instance, throwing on failure. + * + * **When to use** + * + * Use when you expect the input to be valid and want to avoid handling the + * `Result` type. + * + * **Example** (Parsing cron expressions unsafely) + * + * ```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 * *", "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 + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use to test whether a specific date/time satisfies a cron schedule. + * + * **Details** + * + * 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 import.meta.vitest + * import { Cron, Result } from "effect" + * + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *", "UTC")) + * + * // Check if specific dates match + * 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 + * @see {@link prev} for finding the previous matching date/time + * + * @category predicates + * @since 2.0.0 + */ +export const match = (cron: Cron, date: DateTime.DateTime.Input): boolean => { + const parts = dateTime.makeZonedUnsafe(date, { + timeZone: Option.getOrUndefined(cron.tz) + }).pipe(dateTime.toParts) + + if (cron.seconds.size !== 0 && !cron.seconds.has(parts.second)) { + return false + } + + if (cron.minutes.size !== 0 && !cron.minutes.has(parts.minute)) { + return false + } + + if (cron.hours.size !== 0 && !cron.hours.has(parts.hour)) { + return false + } + + if (cron.months.size !== 0 && !cron.months.has(parts.month)) { + return false + } + + if (cron.days.size === 0 && cron.weekdays.size === 0) { + 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) + } + + if (cron.days.size === 0) { + return cron.weekdays.has(parts.weekDay) + } + + return cron.days.has(parts.day) || cron.weekdays.has(parts.weekDay) +} + +const daysInMonth = (date: Date): number => + new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0)).getUTCDate() + +/** + * Returns the next scheduled date/time for the given Cron instance. + * + * **When to use** + * + * Use to find the next occurrence of a cron schedule after a specific date/time + * or after the current time. + * + * **Details** + * + * Searches for the next date and time when the cron schedule should trigger, + * starting after the specified date/time or after the current time when no + * date is provided. + * + * **Example** (Finding the next occurrence) + * + * ```ts import.meta.vitest + * import { Cron, Result } from "effect" + * + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *", "UTC")) + * + * // Get next run after a specific date + * Cron.next(cron, "2021-01-01T00:00:00Z").toISOString() // => "2021-01-08T04:00:00.000Z" + * ``` + * + * @see {@link prev} for finding the previous scheduled occurrence + * @see {@link sequence} for iterating future scheduled occurrences + * + * @category getters + * @since 2.0.0 + */ +export const next = (cron: Cron, now?: DateTime.DateTime.Input): Date => { + return stepCron(cron, now, "next") +} + +/** + * Returns the previous scheduled date/time for the given Cron instance. + * + * **When to use** + * + * Use to find the most recent occurrence of a cron schedule before a specific + * date/time or before the current time. + * + * **Details** + * + * When no date/time is provided, the search starts from the current time. + * + * **Gotchas** + * + * The search is strict: if the supplied date/time already matches the schedule, + * the result is the earlier occurrence. + * + * @see {@link next} for finding the next scheduled occurrence + * + * @category getters + * @since 3.20.0 + */ +export const prev = (cron: Cron, now?: DateTime.DateTime.Input): Date => { + return stepCron(cron, now, "prev") +} + +const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, direction: "next" | "prev"): Date => { + const tz = Option.getOrUndefined(cron.tz) + const zoned = dateTime.makeZonedUnsafe(now ?? new Date(), { + timeZone: tz + }) + + const reverse = direction === "prev" + const tick = reverse ? -1 : 1 + const table = cron[direction] + const boundary = reverse ? cron.last : cron.first + + const needsStep = reverse ? + (next: number, current: number) => next < current : + (next: number, current: number) => next > current + + const utc = tz !== undefined && dateTime.isTimeZoneNamed(tz) && tz.id === "UTC" + const adjustDst = utc ? constVoid : (current: Date) => { + const adjusted = dateTime.makeZonedUnsafe(current, { + timeZone: zoned.zone, + adjustForTimeZone: true, + disambiguation: reverse ? "later" : undefined + }).pipe(dateTime.toDate) + + const drift = current.getTime() - adjusted.getTime() + if (reverse ? drift !== 0 : drift > 0) { + current.setTime(reverse ? adjusted.getTime() : current.getTime() + drift) + } + } + + const result = dateTime.mutate(zoned, (current) => { + current.setUTCSeconds(current.getUTCSeconds() + tick, 0) + + for (let i = 0; i < 10_000; i++) { + if (cron.seconds.size !== 0) { + const currentSecond = current.getUTCSeconds() + const nextSecond = table.second[currentSecond] + if (nextSecond === undefined) { + current.setUTCMinutes(current.getUTCMinutes() + tick, boundary.second) + adjustDst(current) + continue + } + if (needsStep(nextSecond, currentSecond)) { + current.setUTCSeconds(nextSecond) + adjustDst(current) + continue + } + } + + if (cron.minutes.size !== 0) { + const currentMinute = current.getUTCMinutes() + const nextMinute = table.minute[currentMinute] + if (nextMinute === undefined) { + current.setUTCHours(current.getUTCHours() + tick, boundary.minute, boundary.second) + adjustDst(current) + continue + } + if (needsStep(nextMinute, currentMinute)) { + current.setUTCMinutes(nextMinute, boundary.second) + adjustDst(current) + continue + } + } + + if (cron.hours.size !== 0) { + const currentHour = current.getUTCHours() + const nextHour = table.hour[currentHour] + if (nextHour === undefined) { + current.setUTCDate(current.getUTCDate() + tick) + current.setUTCHours(boundary.hour, boundary.minute, boundary.second) + adjustDst(current) + continue + } + if (needsStep(nextHour, currentHour)) { + current.setUTCHours(nextHour, boundary.minute, boundary.second) + adjustDst(current) + continue + } + } + + if (cron.weekdays.size !== 0 || cron.days.size !== 0) { + 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 + } + } 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 + } + } + + 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 + } + } + } + + if (cron.months.size !== 0) { + const currentMonth = current.getUTCMonth() + 1 + const nextMonth = table.month[currentMonth] + const clampBoundaryDay = (targetMonthIndex: number): number => { + const maxDayInMonth = daysInMonth(new Date(Date.UTC(current.getUTCFullYear(), targetMonthIndex + 1, 0))) + 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) + current.setUTCMonth(boundary.month, clampBoundaryDay(boundary.month)) + current.setUTCHours(boundary.hour, boundary.minute, boundary.second) + adjustDst(current) + continue + } + if (needsStep(nextMonth, currentMonth)) { + const targetMonthIndex = nextMonth - 1 + current.setUTCMonth(targetMonthIndex, clampBoundaryDay(targetMonthIndex)) + current.setUTCHours(boundary.hour, boundary.minute, boundary.second) + adjustDst(current) + continue + } + } + + return + } + + throw new Error("Unable to find cron date") + }) + + return dateTime.toDateUtc(result) +} + +/** + * Returns an infinite iterator that yields dates matching the Cron schedule. + * + * **When to use** + * + * Use to lazily iterate future occurrences of a cron schedule. + * + * **Details** + * + * The iterator generates an infinite sequence of dates when the cron schedule + * should trigger, starting after the specified date/time or after the current + * time when no date is provided. + * + * **Example** (Iterating scheduled occurrences) + * + * ```ts import.meta.vitest + * import { Cron, Result } from "effect" + * + * const cron = Result.getOrThrow(Cron.parse("0 0 9 * * 1-5", "UTC")) // 9 AM weekdays + * + * // Get first 5 occurrences + * 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 + * + * @category sequencing + * @since 2.0.0 + */ +export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): IterableIterator { + while (true) { + yield now = next(cron, now) + } +} + +/** + * Equivalence instance for comparing the timezone, field restrictions, and + * day-matching mode of two `Cron` schedules. + * + * **When to use** + * + * Use to compare cron schedules through APIs that accept an equivalence + * relation. + * + * **Details** + * + * This comparison checks the optional timezone, the `and` day-matching mode, + * seconds, minutes, hours, days, months, and weekdays. + * + * **Example** (Comparing schedules with equivalence) + * + * ```ts import.meta.vitest + * import { Cron } from "effect" + * + * const cron1 = Cron.make({ + * minutes: [0, 30], + * hours: [9], + * days: [1, 15], + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * weekdays: [1, 2, 3, 4, 5] + * }) + * + * const cron2 = Cron.make({ + * minutes: [30, 0], // Different order + * hours: [9], + * days: [15, 1], // Different order + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * weekdays: [1, 2, 3, 4, 5] + * }) + * + * Cron.Equivalence(cron1, cron2) // => true + * ``` + * + * @see {@link equals} for directly comparing two `Cron` values + * + * @category instances + * @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) && + restrictionsEquals(self.days, that.days) && + restrictionsEquals(self.months, that.months) && + restrictionsEquals(self.weekdays, that.weekdays) +) + +const restrictionsArrayEquals = Equ.Array(Equ.strictEqual()) +const restrictionsEquals = (self: ReadonlySet, that: ReadonlySet): boolean => + restrictionsArrayEquals(Arr.fromIterable(self), Arr.fromIterable(that)) + +/** + * Checks whether two `Cron` instances have equal timezone values, field + * restrictions, and day-matching modes. + * + * **When to use** + * + * Use to directly compare two cron schedules, including their timezones and + * day-matching modes. + * + * **Details** + * + * The comparison checks the optional timezone, the `and` day-matching mode, + * seconds, minutes, hours, days, months, and weekdays. + * + * **Example** (Checking schedule equality) + * + * ```ts import.meta.vitest + * import { Cron } from "effect" + * + * const cron1 = Cron.make({ + * minutes: [0], + * hours: [9], + * days: [1, 15], + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * weekdays: [1, 2, 3, 4, 5] + * }) + * + * const cron2 = Cron.make({ + * minutes: [0], + * hours: [9], + * days: [1, 15], + * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + * weekdays: [1, 2, 3, 4, 5] + * }) + * + * Cron.equals(cron1, cron2) // => true + * Cron.equals(cron1)(cron2) // => true + * ``` + * + * @see {@link Equivalence} for the reusable equivalence instance + * + * @category predicates + * @since 2.0.0 + */ +export const equals: { + (that: Cron): (self: Cron) => boolean + (self: Cron, that: Cron): boolean +} = dual(2, (self: Cron, that: Cron): boolean => Equivalence(self, that)) + +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 = { + min: 0, + max: 59 +} + +const minuteOptions: SegmentOptions = { + min: 0, + max: 59 +} + +const hourOptions: SegmentOptions = { + min: 0, + max: 23 +} + +const dayOptions: SegmentOptions = { + min: 1, + max: 31 +} + +const monthOptions: SegmentOptions = { + min: 1, + max: 12, + aliases: { + jan: 1, + feb: 2, + mar: 3, + apr: 4, + may: 5, + jun: 6, + jul: 7, + aug: 8, + sep: 9, + oct: 10, + nov: 11, + dec: 12 + } +} + +const weekdayOptions: SegmentOptions = { + min: 0, + max: 7, + normalize: (value) => value === 7 ? 0 : value, + aliases: { + sun: 0, + mon: 1, + tue: 2, + wed: 3, + thu: 4, + fri: 5, + sat: 6 + } +} + +const parseSegment = ( + input: string, + options: SegmentOptions +): Result.Result => { + const values = new Set() + const fields = input.split(",") + 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 })) + } + if (step < 1) { + 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 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) { + add(i) + } + } else { + const [left, right] = splitRange(raw, options.aliases) + if (!Number.isInteger(left)) { + return Result.fail(new CronParseError({ message: `Expected a positive integer`, input })) + } + if (left < options.min || left > options.max) { + return Result.fail( + new CronParseError({ message: `Expected a value between ${options.min} and ${options.max}`, input }) + ) + } + + if (right === undefined) { + 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 })) + } + if (right < options.min || right > options.max) { + return Result.fail( + new CronParseError({ message: `Expected a value between ${options.min} and ${options.max}`, input }) + ) + } + if (left > right) { + return Result.fail(new CronParseError({ message: `Invalid value range`, input })) + } + + for (let i = left; i <= right; i += step ?? 1) { + add(i) + } + } + } + } + + return Result.succeed({ values, wildcard }) +} + +const splitStep = (input: string): [string, number | undefined] => { + const separator = input.indexOf("/") + if (separator !== -1) { + const step = input.slice(separator + 1) + return [input.slice(0, separator), decimalRegex.test(step) ? Number(step) : NaN] + } + + return [input, undefined] +} + +const splitRange = (input: string, aliases?: Record): [number, number | undefined] => { + const separator = input.indexOf("-") + if (separator !== -1) { + return [aliasOrValue(input.slice(0, separator), aliases), aliasOrValue(input.slice(separator + 1), aliases)] + } + + return [aliasOrValue(input, aliases), undefined] +} + +function aliasOrValue(field: string, aliases?: Record): number { + return aliases?.[String.toLowerCase(field)] ?? (decimalRegex.test(field) ? Number(field) : NaN) +} + +const decimalRegex = /^\d+$/ diff --git a/.repos/effect/packages/effect/src/Crypto.ts b/.repos/effect/packages/effect/src/Crypto.ts new file mode 100644 index 000000000..9dfc90590 --- /dev/null +++ b/.repos/effect/packages/effect/src/Crypto.ts @@ -0,0 +1,288 @@ +/** + * Defines a platform-independent service for cryptographic operations. + * + * Runtime packages provide concrete implementations backed by the host + * platform's cryptography APIs. This module defines the service interface and a + * constructor from random-byte and digest primitives. The service provides + * secure random bytes and numbers, UUIDv4 and UUIDv7 generation, shuffling, and + * SHA message digests. + * + * @since 4.0.0 + */ +import * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import * as Uuid from "./internal/uuid.ts" +import * as PlatformError from "./PlatformError.ts" + +const TypeId = "~effect/platform/Crypto" + +/** + * Digest algorithms supported by the platform `Crypto` service. + * + * **Gotchas** + * + * SHA-1 is included for interoperability with existing protocols. Do not use + * SHA-1 for new security-sensitive designs. + * + * **Example** (Using a digest algorithm) + * + * ```ts import.meta.vitest + * import { Crypto } from "effect" + * + * const algorithm: Crypto.DigestAlgorithm = "SHA-256" + * ``` + * + * @category models + * @since 4.0.0 + */ +export type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512" + +/** + * Platform-agnostic cryptographic operations. + * + * **Details** + * + * `Crypto` implementations must use cryptographically secure platform APIs. + * The random generator helpers are derived by the `make` constructor from + * the random methods on this service. + * + * **Example** (Using cryptographic operations) + * + * ```ts import.meta.vitest + * import { Crypto, Effect, Layer } from "effect" + * + * const TestCrypto = Layer.succeed( + * Crypto.Crypto, + * Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const crypto = yield* Crypto.Crypto + * const bytes = yield* crypto.randomBytes(16) + * const uuidv4 = yield* crypto.randomUUIDv4 + * const hash = yield* crypto.digest("SHA-256", bytes) + * return [bytes.length, uuidv4.length, hash.length] + * }) + * + * await Effect.runPromise(Effect.provide(program, TestCrypto)) // => [16, 36, 16] + * ``` + * + * @category services + * @since 4.0.0 + */ +export interface Crypto { + readonly [TypeId]: typeof TypeId + + /** + * Generates a random integer in the range Number.MIN_SAFE_INTEGER to + * Number.MAX_SAFE_INTEGER (both inclusive). + */ + nextIntUnsafe(): number + + /** + * Generates a random number in the range 0 (inclusive) to 1 (exclusive). + */ + nextDoubleUnsafe(): number + + /** + * Generates cryptographically secure random bytes. + */ + randomBytes(size: number): Effect.Effect + + /** + * Computes a cryptographic digest for the supplied data. + */ + digest( + algorithm: DigestAlgorithm, + data: Uint8Array + ): Effect.Effect + + /** + * Generates a cryptographically secure random number between 0 (inclusive) + * and 1 (exclusive). + */ + readonly random: Effect.Effect + + /** + * Generates a cryptographically secure random boolean. + */ + readonly randomBoolean: Effect.Effect + + /** + * Generates a cryptographically secure random integer between + * `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` (both inclusive). + */ + readonly randomInt: Effect.Effect + + /** + * Generates a cryptographically secure random number between `min` + * (inclusive) and `max` (exclusive). + */ + randomBetween(min: number, max: number): Effect.Effect + + /** + * Generates a cryptographically secure random integer between `min` and `max`. + * + * **Details** + * + * The lower bound is rounded up with `Math.ceil` and the upper bound is + * rounded down with `Math.floor`. By default the range is inclusive; set + * `options.halfOpen: true` to exclude the upper bound. + */ + randomIntBetween(min: number, max: number, options?: { + readonly halfOpen?: boolean | undefined + }): Effect.Effect + + /** + * Uses the cryptographically secure random generator to shuffle the supplied + * iterable. + */ + randomShuffle(elements: Iterable): Effect.Effect> + + /** + * Generates a cryptographically secure UUIDv4 string. + */ + readonly randomUUIDv4: Effect.Effect + + /** + * Generates a cryptographically secure UUIDv7 string. + */ + readonly randomUUIDv7: Effect.Effect +} + +/** + * Service tag for platform cryptography. + * + * **When to use** + * + * Use when you need to provide or retrieve the full platform cryptography + * service from an effect's context. + * + * **Details** + * + * Providing this service supplies platform-agnostic cryptographic operations + * such as hashing, UUID generation, and secure random values. + * + * @see {@link make} for constructing a Crypto service from primitive operations + * + * @category services + * @since 4.0.0 + */ +export const Crypto: Context.Service = Context.Service("effect/Crypto") + +/** + * Creates a `Crypto` service from the primitive implementation, deriving the + * random generator helpers and UUID generation from those primitives. + * + * **When to use** + * + * Use to build a Crypto service for a platform integration, test layer, or + * custom runtime from primitive random-byte and digest operations. + * + * **Details** + * + * The constructor derives random numbers, booleans, integer ranges, shuffling, + * and UUID generation from `impl.randomBytes`. Digest operations delegate to + * `impl.digest`. + * + * **Gotchas** + * + * `impl.randomBytes` must return cryptographically secure bytes of the + * requested length. UUID formatting mutates the byte array returned for UUID + * generation, so the implementation should return a fresh array for each call. + * + * **Example** (Creating a Crypto service) + * + * ```ts import.meta.vitest + * import { Crypto, Effect } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const make = ( + impl: { + readonly randomBytes: (size: number) => Uint8Array + readonly digest: ( + algorithm: DigestAlgorithm, + data: Uint8Array + ) => Effect.Effect + } +): Crypto => { + const randomBytesUnsafe = impl.randomBytes + + const randomBytes: Crypto["randomBytes"] = (size) => Effect.map(validateSize("randomBytes", size), randomBytesUnsafe) + + 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 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, + randomBytes, + nextDoubleUnsafe, + nextIntUnsafe, + digest: impl.digest, + random: Effect.sync(() => nextDoubleUnsafe()), + randomBoolean: Effect.sync(() => nextDoubleUnsafe() > 0.5), + randomInt: Effect.sync(() => nextIntUnsafe()), + randomBetween: (min, max) => Effect.sync(() => nextDoubleUnsafe() * (max - min) + min), + randomIntBetween(min, max, options) { + const extra = options?.halfOpen === true ? 0 : 1 + return Effect.sync(() => { + const minInt = Math.ceil(min) + const maxInt = Math.floor(max) + return Math.floor(nextDoubleUnsafe() * (maxInt - minInt + extra)) + minInt + }) + }, + randomShuffle: (elements) => + Effect.sync(() => { + const buffer = Array.from(elements) + for (let i = buffer.length - 1; i >= 1; i = i - 1) { + const index = Math.min(i, Math.floor(nextDoubleUnsafe() * (i + 1))) + const value = buffer[i]! + buffer[i] = buffer[index]! + buffer[index] = value + } + return buffer + }), + randomUUIDv4: Effect.sync(() => Uuid.v4String(randomBytesUnsafe(16))), + randomUUIDv7: Effect.clockWith((clock) => + Effect.succeed(Uuid.v7String(clock.currentTimeMillisUnsafe(), randomBytesUnsafe(16))) + ) + }) +} + +const validateSize = (method: string, size: number): Effect.Effect => + Number.isSafeInteger(size) && size >= 0 + ? Effect.succeed(size) + : Effect.fail(PlatformError.badArgument({ + module: "Crypto", + method, + description: "size must be a non-negative safe integer" + })) diff --git a/.repos/effect/packages/effect/src/Data.ts b/.repos/effect/packages/effect/src/Data.ts new file mode 100644 index 000000000..2228bb0e9 --- /dev/null +++ b/.repos/effect/packages/effect/src/Data.ts @@ -0,0 +1,765 @@ +/** + * Defines helpers for small immutable data models. + * + * This module helps create plain classes, tagged classes, tagged unions, and + * typed errors with readonly fields. Tagged values carry a `_tag` field, which + * makes them easy to narrow with pattern matching or simple checks. These + * helpers are commonly used for domain values and errors in Effect programs. + * + * @since 2.0.0 + */ +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" +import type { Unify } from "./Unify.ts" + +/** + * Provides a base class for immutable data types. + * + * **When to use** + * + * Use when you need a lightweight immutable value type with `.pipe()` support. + * + * **Details** + * + * Extend `Class` with a type parameter to declare fields. The constructor + * accepts those fields as a single object argument. When there are no fields + * the argument is optional. Instances are `Readonly` and `Pipeable`. + * + * **Example** (Defining a value class) + * + * ```ts import.meta.vitest + * import { Data, Equal } from "effect" + * + * class Person extends Data.Class<{ readonly name: string }> {} + * + * Equal.equals(new Person({ name: "Mike" }), new Person({ name: "Mike" })) // => true + * ``` + * + * @see {@link TaggedClass} — adds a `_tag` field + * @see {@link Error} — yieldable error variant + * + * @category constructors + * @since 2.0.0 + */ +export const Class: new = {}>( + args: Types.VoidIfEmpty<{ readonly [P in keyof A]: A[P] }> +) => Readonly & Pipeable.Pipeable = class extends Pipeable.Class { + constructor(props: object | undefined) { + super() + if (props) { + InternalRecord.assignProperties(this, props) + } + } +} as any + +/** + * Provides a base class for immutable data types with a `_tag` discriminator. + * + * **When to use** + * + * Use when you need a single-variant tagged type or an ad-hoc discriminator. + * + * **Details** + * + * Like {@link Class}, but the resulting instances also carry a + * `readonly _tag: Tag` property. The `_tag` is excluded from the constructor + * argument. + * + * **Example** (Defining a tagged class) + * + * ```ts import.meta.vitest + * import { Data } from "effect" + * + * class Person extends Data.TaggedClass("Person")<{ + * readonly name: string + * }> {} + * + * new Person({ name: "Mike" })._tag // => "Person" + * ``` + * + * @see {@link Class} — without a `_tag` + * @see {@link TaggedError} — tagged error variant + * @see {@link TaggedEnum} — multi-variant unions + * + * @category constructors + * @since 2.0.0 + */ +export const TaggedClass = ( + tag: Tag +): new = {}>( + args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }> +) => Readonly & { readonly _tag: Tag } & Pipeable.Pipeable => + class extends Class { + readonly _tag = tag + } as any + +/** + * Transforms a record of variant definitions into a discriminated union type. + * + * **When to use** + * + * Use when you have two or more variants that share a common `_tag` discriminator. + * + * **Details** + * + * Each key in the record becomes a variant with `readonly _tag` set to that + * key. Use with {@link taggedEnum} to get constructors and matchers. + * + * **Gotchas** + * + * Variant records must **not** include a `_tag` property; it is added automatically. + * + * **Example** (Defining a tagged enum) + * + * ```ts import.meta.vitest + * import { Data } from "effect" + * + * type HttpError = Data.TaggedEnum<{ + * BadRequest: { readonly status: 400; readonly message: string } + * NotFound: { readonly status: 404 } + * }> + * + * // Equivalent to: + * // | { readonly _tag: "BadRequest"; readonly status: 400; readonly message: string } + * // | { readonly _tag: "NotFound"; readonly status: 404 } + * + * const { BadRequest, NotFound } = Data.taggedEnum() + * + * BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" + * ``` + * + * @see {@link taggedEnum} — constructors and matchers for a `TaggedEnum` + * @see {@link TaggedEnum.WithGenerics} — generic tagged enums + * @see {@link TaggedEnum.Constructor} — the constructor object type + * + * @category models + * @since 2.0.0 + */ +export type TaggedEnum< + A extends Record> & UntaggedChildren +> = keyof A extends infer Tag ? Tag extends keyof A ? Types.Simplify< + { readonly _tag: Tag } & { readonly [K in keyof A[Tag]]: A[Tag][K] } + > + : never + : never + +type ChildrenAreTagged = keyof A extends infer K ? K extends keyof A ? "_tag" extends keyof A[K] ? true + : false + : never + : never + +type UntaggedChildren = true extends ChildrenAreTagged + ? "It looks like you're trying to create a tagged enum, but one or more of its members already has a `_tag` property." + : unknown + +/** + * Namespace for `TaggedEnum` utility types. + * + * **When to use** + * + * Use to reference utility types for constructing, extracting, and matching + * `TaggedEnum` variants. + * + * **Details** + * + * Provides helper types for: + * - Generic tagged enums ({@link TaggedEnum.WithGenerics}, {@link TaggedEnum.Kind}) + * - Extracting constructor arguments ({@link TaggedEnum.Args}) and variant + * values ({@link TaggedEnum.Value}) + * - Full constructor objects ({@link TaggedEnum.Constructor}) + * + * @since 2.0.0 + */ +export declare namespace TaggedEnum { + /** + * Defines a tagged enum shape that accepts generic type parameters. + * + * **When to use** + * + * Use when variant payloads need to be parameterized, such as `Result`. + * + * **Details** + * + * Extend this interface and set `taggedEnum` to your union type, using + * `this["A"]`, `this["B"]`, etc. as placeholders for the generics. The + * `Count` parameter declares how many generics are used (up to 4). + * + * **Example** (Defining a generic tagged enum) + * + * ```ts import.meta.vitest + * import { Data } from "effect" + * + * type MyResult = Data.TaggedEnum<{ + * Failure: { readonly error: E } + * Success: { readonly value: A } + * }> + * + * interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { + * readonly taggedEnum: MyResult + * } + * + * const { Failure, Success } = Data.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 + * @see {@link taggedEnum} — constructors and matchers + * + * @category models + * @since 2.0.0 + */ + export interface WithGenerics { + readonly taggedEnum: { readonly _tag: string } + readonly numberOfGenerics: Count + + readonly A: unknown + readonly B: unknown + readonly C: unknown + readonly D: unknown + } + + /** + * Applies concrete type arguments to a `WithGenerics` definition, producing + * the resulting tagged union type. + * + * **When to use** + * + * Use to refer to a specific instantiation of a generic tagged enum in type signatures. + * + * **Example** (Applying generics) + * + * ```ts import.meta.vitest + * import type { Data } from "effect" + * + * type Option = Data.TaggedEnum<{ + * None: {} + * Some: { readonly value: A } + * }> + * interface OptionDef extends Data.TaggedEnum.WithGenerics<1> { + * readonly taggedEnum: Option + * } + * + * // 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 + * + * @category utility types + * @since 2.0.0 + */ + export type Kind< + Z extends WithGenerics, + A = unknown, + B = unknown, + C = unknown, + D = unknown + > = (Z & { + readonly A: A + readonly B: B + readonly C: C + readonly D: D + })["taggedEnum"] + + /** + * Extracts the constructor argument type for a specific variant of a tagged + * union. + * + * **When to use** + * + * Use to derive the argument object expected by a constructor for one tagged + * union variant. + * + * **Details** + * + * Returns `void` if the variant has no fields beyond `_tag`. + * + * **Example** (Extracting variant args) + * + * ```ts import.meta.vitest + * import type { Data } from "effect" + * + * type Result = + * | { readonly _tag: "Ok"; readonly value: number } + * | { readonly _tag: "Err"; readonly error: string } + * + * type OkArgs = Data.TaggedEnum.Args + * // { readonly value: number } + * + * type ErrArgs = Data.TaggedEnum.Args + * // { readonly error: string } + * ``` + * + * @see {@link Value} — extracts the full variant type (including `_tag`) + * + * @category utility types + * @since 2.0.0 + */ + export type Args< + A extends { readonly _tag: string }, + K extends A["_tag"], + E = Extract + > = { + readonly [K in keyof E as K extends "_tag" ? never : K]: E[K] + } extends infer T ? Types.VoidIfEmpty + : never + + /** + * Extracts the full variant type (including `_tag`) for a specific tag. + * + * **When to use** + * + * Use to select one full tagged-union variant by its `_tag` value. + * + * **Example** (Extracting a variant type) + * + * ```ts import.meta.vitest + * import type { Data } from "effect" + * + * type Result = + * | { readonly _tag: "Ok"; readonly value: number } + * | { readonly _tag: "Err"; readonly error: string } + * + * type OkVariant = Data.TaggedEnum.Value + * // { readonly _tag: "Ok"; readonly value: number } + * ``` + * + * @see {@link Args} — extracts fields without `_tag` + * + * @category utility types + * @since 2.0.0 + */ + export type Value< + A extends { readonly _tag: string }, + K extends A["_tag"] + > = Extract + + /** + * The full constructors-and-matchers object type returned by {@link taggedEnum}. + * + * **When to use** + * + * Use when you want to annotate an exported constructor bundle so downstream + * code keeps exact variant constructors and exhaustive matching. + * + * **Details** + * + * Includes: + * - A constructor function for each variant (keyed by tag name) + * - `$is(tag)` — returns a type-guard that checks only the `_tag` field; + * safe when the tag is globally unique and the value was produced by your + * constructors. For untrusted input, validate with the `Schema` module first. + * - `$match` — exhaustive pattern matching (data-last or data-first) + * + * **Example** (Using the constructor object) + * + * ```ts import.meta.vitest + * import { Data } from "effect" + * + * type Shape = + * | { readonly _tag: "Circle"; readonly radius: number } + * | { readonly _tag: "Rect"; readonly w: number; readonly h: number } + * + * const { Circle, Rect, $is, $match } = Data.taggedEnum() + * + * const shape = Circle({ radius: 10 }) + * + * if ($is("Circle")(shape)) { + * shape.radius // => 10 + * } + * + * $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 utility types + * @since 3.1.0 + */ + export type Constructor = Types.Simplify< + { + readonly [Tag in A["_tag"]]: ConstructorFrom< + Extract, + "_tag" + > + } & { + readonly $is: ( + tag: Tag + ) => (u: unknown) => u is Extract + readonly $match: { + < + Cases extends { + readonly [Tag in A["_tag"]]: ( + args: Extract + ) => any + } + >( + cases: Cases + ): (value: A) => Unify> + < + Cases extends { + readonly [Tag in A["_tag"]]: ( + args: Extract + ) => any + } + >( + value: A, + cases: Cases + ): Unify> + } + } + > + + /** + * Function type that constructs a tagged-union variant from its fields, + * excluding the keys listed in `Tag`. + * + * **When to use** + * + * Use to type an individual constructor for one tagged-union variant. + * + * **Details** + * + * The constructor returns the full variant type `A`. If no fields remain + * after excluding `Tag` keys, the constructor argument type becomes `void`. + * + * @category utility types + * @since 4.0.0 + */ + export type ConstructorFrom = ( + args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends Tag ? never : P]: A[P] }> + ) => A + + /** + * Type-guard and pattern-matching interface for generic tagged enums. + * + * **When to use** + * + * Use to type the `$is` and `$match` helpers for generic tagged enums. + * + * **Details** + * + * This is the `$is` / `$match` portion of the object returned by + * {@link taggedEnum} when used with a {@link WithGenerics} definition. + * + * @see {@link Constructor} — the non-generic equivalent + * + * @category models + * @since 3.2.0 + */ + export interface GenericMatchers> { + readonly $is: ( + tag: Tag + ) => { + >( + u: T + ): u is T & { readonly _tag: Tag } + (u: unknown): u is Extract, { readonly _tag: Tag }> + } + readonly $match: { + < + A, + B, + C, + D, + Cases extends { + readonly [Tag in Z["taggedEnum"]["_tag"]]: ( + args: Extract< + TaggedEnum.Kind, + { readonly _tag: Tag } + > + ) => any + } + >( + cases: Cases + ): ( + self: TaggedEnum.Kind + ) => Unify> + < + A, + B, + C, + D, + Cases extends { + readonly [Tag in Z["taggedEnum"]["_tag"]]: ( + args: Extract< + TaggedEnum.Kind, + { readonly _tag: Tag } + > + ) => any + } + >( + self: TaggedEnum.Kind, + cases: Cases + ): Unify> + } + } +} + +/** + * Creates constructors and matchers for a `TaggedEnum` type. + * + * **When to use** + * + * Use when you model a closed union with plain data objects and want + * construction, tag checks, and exhaustive matching from the same definition. + * + * **Details** + * + * Returns an object with: + * - One constructor per variant (keyed by tag name) + * - `$is(tag)` — returns a type-guard function that checks only the `_tag` field + * - `$match` — exhaustive pattern matching (data-first or data-last) + * + * **Gotchas** + * + * - Constructors produce **plain objects**, not class instances. + * - `$is(tag)` only checks the `_tag` field, not the full structure. It relies + * on the tag being globally unique and the value being produced by your + * constructors. For untrusted input, validate with the `Schema` module first. + * + * **Example** (Creating and matching tagged enum values) + * + * ```ts import.meta.vitest + * import { Data } from "effect" + * + * type HttpError = Data.TaggedEnum<{ + * BadRequest: { readonly message: string } + * NotFound: { readonly url: string } + * }> + * + * const { BadRequest, NotFound, $is, $match } = Data.taggedEnum() + * + * const err = NotFound({ url: "/missing" }) + * + * $is("NotFound")(err) // => true + * + * $match(err, { + * BadRequest: (e) => e.message, + * NotFound: (e) => `${e.url} not found` + * }) // => "/missing not found" + * ``` + * + * **Example** (Defining a generic tagged enum) + * + * ```ts import.meta.vitest + * import { Data } from "effect" + * + * type MyResult = Data.TaggedEnum<{ + * Failure: { readonly error: E } + * Success: { readonly value: A } + * }> + * interface MyResultDef extends Data.TaggedEnum.WithGenerics<2> { + * readonly taggedEnum: MyResult + * } + * const { Failure, Success } = Data.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 + * @see {@link TaggedEnum.Constructor} — the returned object type + * @see {@link TaggedEnum.WithGenerics} — generic enum support + * + * @category constructors + * @since 2.0.0 + */ +export const taggedEnum: { + >(): Types.Simplify< + { + readonly [Tag in Z["taggedEnum"]["_tag"]]: ( + args: TaggedEnum.Args< + TaggedEnum.Kind, + Tag, + Extract, { readonly _tag: Tag }> + > + ) => TaggedEnum.Value, Tag> + } & TaggedEnum.GenericMatchers + > + + >(): Types.Simplify< + { + readonly [Tag in Z["taggedEnum"]["_tag"]]: ( + args: TaggedEnum.Args< + TaggedEnum.Kind, + Tag, + Extract, { readonly _tag: Tag }> + > + ) => TaggedEnum.Value, Tag> + } & TaggedEnum.GenericMatchers + > + + >(): Types.Simplify< + { + readonly [Tag in Z["taggedEnum"]["_tag"]]: ( + args: TaggedEnum.Args< + TaggedEnum.Kind, + Tag, + Extract, { readonly _tag: Tag }> + > + ) => TaggedEnum.Value, Tag> + } & TaggedEnum.GenericMatchers + > + + >(): Types.Simplify< + { + readonly [Tag in Z["taggedEnum"]["_tag"]]: ( + args: TaggedEnum.Args< + TaggedEnum.Kind, + Tag, + Extract, { readonly _tag: Tag }> + > + ) => TaggedEnum.Value, Tag> + } & TaggedEnum.GenericMatchers + > + + (): TaggedEnum.Constructor +} = () => + new Proxy( + {}, + { + get(_target, tag, _receiver) { + if (tag === "$is") { + return Predicate.isTagged + } else if (tag === "$match") { + return taggedMatch + } + return (props: any) => ({ ...props, _tag: tag }) + } + } + ) as any + +function taggedMatch< + A extends { readonly _tag: string }, + Cases extends { + readonly [K in A["_tag"]]: (args: Extract) => any + } +>(self: A, cases: Cases): ReturnType +function taggedMatch< + A extends { readonly _tag: string }, + Cases extends { + readonly [K in A["_tag"]]: (args: Extract) => any + } +>(cases: Cases): (value: A) => ReturnType +function taggedMatch< + A extends { readonly _tag: string }, + Cases extends { + readonly [K in A["_tag"]]: (args: Extract) => any + } +>(): any { + if (arguments.length === 1) { + const cases = arguments[0] as Cases + return function(value: A): ReturnType { + return cases[value._tag as A["_tag"]](value as any) + } + } + const value = arguments[0] as A + const cases = arguments[1] as Cases + return cases[value._tag as A["_tag"]](value as any) +} + +/** + * Provides a base class for yieldable errors. + * + * **When to use** + * + * Use when you need yieldable errors that do **not** need tag-based + * discrimination. + * + * **Details** + * + * Extends `Cause.YieldableError`, so instances can be yielded inside + * `Effect.gen` to fail the enclosing effect. Fields are passed as a single + * object; when there are no fields the argument is optional. If a `message` + * field is provided, it becomes the error's `.message`. + * + * **Example** (Defining a yieldable error) + * + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" + * + * class NetworkError extends Data.Error<{ + * readonly code: number + * readonly message: string + * }> {} + * + * const program = Effect.gen(function*() { + * return yield* new NetworkError({ code: 500, message: "timeout" }) + * }) + * + * Effect.runSync(Effect.exit(program)) // => Exit.fail(new NetworkError({ code: 500, message: "timeout" })) + * ``` + * + * @see {@link TaggedError} — adds a `_tag` for `Effect.catchTag` + * @see {@link Class} — non-error data class + * + * @category constructors + * @since 2.0.0 + */ +export const Error: new = {}>( + args: Types.VoidIfEmpty<{ readonly [P in keyof A]: A[P] }> +) => Cause.YieldableError & Readonly = core.Error + +/** + * Creates a tagged error class with a `_tag` discriminator. + * + * **When to use** + * + * Use when you need domain errors with discriminated-union handling. + * + * **Details** + * + * Like {@link Error}, but instances also carry a `readonly _tag` property, + * enabling `Effect.catchTag` and `Effect.catchTags` for tag-based recovery. + * The `_tag` is excluded from the constructor argument. Yielding an instance + * inside `Effect.gen` fails the effect with this error. + * + * **Example** (Recovering by tag) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class NotFound extends Data.TaggedError("NotFound")<{ + * readonly resource: string + * }> {} + * + * class Forbidden extends Data.TaggedError("Forbidden")<{ + * readonly reason: string + * }> {} + * + * const program = Effect.gen(function*() { + * return yield* new NotFound({ resource: "/users/42" }) + * }) + * + * const recovered = program.pipe( + * Effect.catchTag("NotFound", (e) => + * Effect.succeed(`missing: ${e.resource}`)) + * ) + * + * await Effect.runPromise(recovered) // => "missing: /users/42" + * ``` + * + * @see {@link Error} — without a `_tag` + * @see {@link TaggedClass} — tagged class that is not an error + * + * @category constructors + * @since 2.0.0 + */ +export const TaggedError: ( + tag: Tag +) => new = {}>( + args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }> +) => Cause.YieldableError & { readonly _tag: Tag } & Readonly = core.TaggedError as any diff --git a/.repos/effect/packages/effect/src/DateTime.ts b/.repos/effect/packages/effect/src/DateTime.ts new file mode 100644 index 000000000..c121861a2 --- /dev/null +++ b/.repos/effect/packages/effect/src/DateTime.ts @@ -0,0 +1,2874 @@ +/** + * Works with absolute instants, UTC date-times, zoned date-times, and time + * zones. + * + * A `DateTime` always represents an absolute point in time with epoch + * milliseconds. It may also carry a `TimeZone` for calendar parts, formatting, + * and zone-aware transformations. This module includes constructors, time-zone + * helpers, comparisons, date arithmetic, current-time effects, and formatting + * functions. + * + * @since 3.6.0 + */ +import type { IllegalArgumentError } from "./Cause.ts" +import * as Context from "./Context.ts" +import type * as Duration from "./Duration.ts" +import * as Effect from "./Effect.ts" +import type * as Equ from "./Equivalence.ts" +import { dual, flow, type LazyArg } from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import * as Internal from "./internal/dateTime.ts" +import { provideService } from "./internal/effect.ts" +import * as Layer from "./Layer.ts" +import type * as Option from "./Option.ts" +import type * as order from "./Order.ts" +import type { Pipeable } from "./Pipeable.ts" + +const TypeId = Internal.TypeId +const TimeZoneTypeId = Internal.TimeZoneTypeId + +/** + * A `DateTime` represents a point in time. It can optionally have a time zone + * associated with it. + * + * @category models + * @since 3.6.0 + */ +export type DateTime = Utc | Zoned + +/** + * Represents a `DateTime` stored as an absolute UTC instant with no associated + * time zone. + * + * **Details** + * + * Use `DateTime.isUtc` to narrow a `DateTime` to this variant. + * + * @category models + * @since 3.6.0 + */ +export interface Utc extends DateTime.Proto { + readonly _tag: "Utc" + readonly epochMilliseconds: number + partsUtc: DateTime.PartsWithWeekday | undefined +} + +/** + * Represents a `DateTime` with an associated `TimeZone`. + * + * **Details** + * + * A zoned value still represents an absolute instant through + * `epochMilliseconds`, while the time zone is used for wall-clock parts, + * formatting, and zone-aware transformations. + * + * @category models + * @since 3.6.0 + */ +export interface Zoned extends DateTime.Proto { + readonly _tag: "Zoned" + readonly epochMilliseconds: number + readonly zone: TimeZone + adjustedEpochMilliseconds: number | undefined + partsAdjusted: DateTime.PartsWithWeekday | undefined + partsUtc: DateTime.PartsWithWeekday | undefined +} + +/** + * Companion namespace containing the public helper types used by `DateTime` + * constructors, parts APIs, formatting, and date/time arithmetic. + * + * @since 3.6.0 + */ +export declare namespace DateTime { + /** + * Input accepted by `DateTime.make`, `DateTime.makeUnsafe`, and the zoned + * constructors. + * + * **Details** + * + * Includes existing `DateTime` values, partial date parts, epoch-millisecond + * objects, epoch milliseconds, JavaScript `Date` instances, and parseable date + * strings. + * + * @category models + * @since 3.6.0 + */ + export type Input = DateTime | Partial | Instant | InstantWithZone | Date | number | string + + /** + * Type-level helper used by constructors to preserve a zoned input. + * + * **Details** + * + * When the input type is `DateTime.Zoned`, the result type is + * `DateTime.Zoned`; otherwise the result type is `DateTime.Utc`. + * + * @category models + * @since 3.6.0 + */ + export type PreserveZone = A extends Zoned ? Zoned : Utc + + /** + * Date and time unit name accepted by `DateTime` rounding and arithmetic + * APIs. + * + * **Details** + * + * Includes both singular units, such as `"day"`, and plural units, such as + * `"days"`. + * + * @category models + * @since 3.6.0 + */ + export type Unit = UnitSingular | UnitPlural + + /** + * Singular date and time unit names used by rounding APIs such as + * `DateTime.startOf`, `DateTime.endOf`, and `DateTime.nearest`. + * + * @category models + * @since 3.6.0 + */ + export type UnitSingular = + | "millisecond" + | "second" + | "minute" + | "hour" + | "day" + | "week" + | "month" + | "year" + + /** + * Plural date and time unit names used by `DateTime.PartsForMath` for + * amount-based arithmetic. + * + * @category models + * @since 3.6.0 + */ + export type UnitPlural = + | "milliseconds" + | "seconds" + | "minutes" + | "hours" + | "days" + | "weeks" + | "months" + | "years" + + /** + * Calendar and time components of a `DateTime`, including the weekday. + * + * **Details** + * + * `month` is one-based (`1` for January through `12` for December), and + * `weekDay` follows JavaScript `Date#getUTCDay` numbering (`0` for Sunday + * through `6` for Saturday). + * + * @category models + * @since 3.6.0 + */ + export interface PartsWithWeekday { + readonly millisecond: number + readonly second: number + readonly minute: number + readonly hour: number + readonly day: number + readonly weekDay: number + readonly month: number + readonly year: number + } + + /** + * Calendar and time components of a `DateTime`, without weekday information. + * + * **Details** + * + * `month` is one-based (`1` for January through `12` for December). + * + * @category models + * @since 3.6.0 + */ + export interface Parts { + readonly millisecond: number + readonly second: number + readonly minute: number + readonly hour: number + readonly day: number + readonly month: number + readonly year: number + } + + /** + * Plural amount fields accepted by `DateTime.add` and `DateTime.subtract`. + * + * **Details** + * + * Each field represents the number of units to add or subtract for that part. + * + * @category models + * @since 3.6.0 + */ + export interface PartsForMath { + readonly milliseconds: number + readonly seconds: number + readonly minutes: number + readonly hours: number + readonly days: number + readonly weeks: number + readonly months: number + readonly years: number + } + + /** + * Object input representing an absolute instant as milliseconds since the Unix + * epoch. + * + * @category models + * @since 4.0.0 + */ + export interface Instant { + readonly epochMilliseconds: number + } + + /** + * Object input representing an absolute instant plus a time zone identifier. + * + * **Details** + * + * `DateTime.makeZoned` and `DateTime.makeZonedUnsafe` use `timeZoneId` when + * no explicit `timeZone` option is supplied. + * + * @category models + * @since 4.0.0 + */ + export interface InstantWithZone { + readonly timeZoneId: string + readonly epochMilliseconds: number + } + + /** + * Shared protocol implemented by all `DateTime` values. + * + * **Details** + * + * Provides the `DateTime` type identifier along with pipe and inspection + * support. + * + * @category models + * @since 3.6.0 + */ + export interface Proto extends Pipeable, Inspectable { + readonly [TypeId]: typeof TypeId + } +} + +/** + * Represents a time zone used by `DateTime.Zoned`. + * + * **Details** + * + * A `TimeZone` is either a fixed offset from UTC or a named IANA time zone. + * + * @category models + * @since 3.6.0 + */ +export type TimeZone = TimeZone.Offset | TimeZone.Named + +/** + * Companion namespace containing the public variant and protocol types for + * `TimeZone`. + * + * @since 3.6.0 + */ +export declare namespace TimeZone { + /** + * Shared protocol implemented by all `TimeZone` values. + * + * **Details** + * + * Provides the `TimeZone` type identifier and inspection support. + * + * @category models + * @since 3.6.0 + */ + export interface Proto extends Inspectable { + readonly [TimeZoneTypeId]: typeof TimeZoneTypeId + } + + /** + * Fixed-offset time zone. + * + * **Details** + * + * The `offset` is measured in milliseconds from UTC. Positive offsets are + * ahead of UTC, and negative offsets are behind UTC. + * + * @category models + * @since 3.6.0 + */ + export interface Offset extends Proto { + readonly _tag: "Offset" + readonly offset: number + } + + /** + * Named IANA time zone. + * + * **Details** + * + * The `id` field contains the resolved time zone identifier, such as + * `"Europe/London"` or `"America/New_York"`. + * + * @category models + * @since 3.6.0 + */ + export interface Named extends Proto { + readonly _tag: "Named" + readonly id: string + /** @internal */ + readonly format: Intl.DateTimeFormat + } +} + +/** + * A `Disambiguation` is used to resolve ambiguities when a `DateTime` is + * ambiguous, such as during a daylight saving time transition. + * + * **Details** + * + * For more information, see the [Temporal documentation](https://tc39.es/proposal-temporal/docs/timezone.html#ambiguity-due-to-dst-or-other-time-zone-offset-changes) + * + * - `"compatible"`: (default) Behavior matching Temporal API and legacy JavaScript Date and moment.js. + * For repeated times, chooses the earlier occurrence. For gap times, chooses the later interpretation. + * + * - `"earlier"`: For repeated times, always choose the earlier occurrence. + * For gap times, choose the time before the gap. + * + * - `"later"`: For repeated times, always choose the later occurrence. + * For gap times, choose the time after the gap. + * + * - `"reject"`: Throw an `RangeError` when encountering ambiguous or non-existent times. + * + * **Example** (Resolving ambiguous local times) + * + * ```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, hour: 1, minute: 30 } + * const timeZone = DateTime.zoneMakeNamedUnsafe("America/New_York") + * + * const earlier = DateTime.makeZoned(ambiguousTime, { + * timeZone, + * adjustForTimeZone: true, + * disambiguation: "earlier" + * }) + * // Earlier occurrence (DST time): 2025-11-02T05:30:00.000Z + * + * const later = DateTime.makeZoned(ambiguousTime, { + * timeZone, + * adjustForTimeZone: true, + * disambiguation: "later" + * }) + * // 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, hour: 2, minute: 30 } + * + * const beforeGap = DateTime.makeZoned(gapTime, { + * timeZone, + * adjustForTimeZone: true, + * disambiguation: "earlier" + * }) + * // Time before gap: 2025-03-09T06:30:00.000Z (01:30 EST) + * + * 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 + * @since 3.18.0 + */ +export type Disambiguation = "compatible" | "earlier" | "later" | "reject" + +// ============================================================================= +// guards +// ============================================================================= + +/** + * Checks whether a value is a `DateTime`. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a `DateTime`. + * + * @see {@link isUtc} for narrowing a known `DateTime` to UTC + * @see {@link isZoned} for narrowing a known `DateTime` to zoned + * + * @category guards + * @since 3.6.0 + */ +export const isDateTime: (u: unknown) => u is DateTime = Internal.isDateTime + +/** + * Checks whether a value is a `TimeZone`. + * + * **When to use** + * + * Use to narrow unknown input to any `TimeZone` before passing it to APIs that + * accept either fixed-offset or named time zones. + * + * @see {@link isTimeZoneOffset} for narrowing to fixed-offset time zones + * @see {@link isTimeZoneNamed} for narrowing to named time zones + * + * @category guards + * @since 3.6.0 + */ +export const isTimeZone: (u: unknown) => u is TimeZone = Internal.isTimeZone + +/** + * Checks whether a value is an offset-based `TimeZone`. + * + * **When to use** + * + * Use when you need to narrow an unknown or union `TimeZone` value to the + * fixed-offset variant before reading its offset in milliseconds. + * + * @see {@link isTimeZone} for checking either time zone variant + * @see {@link isTimeZoneNamed} for narrowing to named time zones + * + * @category guards + * @since 3.6.0 + */ +export const isTimeZoneOffset: (u: unknown) => u is TimeZone.Offset = Internal.isTimeZoneOffset + +/** + * Checks whether a value is a named `TimeZone` (IANA time zone). + * + * **When to use** + * + * Use to narrow an unknown value to the `TimeZone.Named` variant before + * reading named-zone fields such as `id`. + * + * @see {@link isTimeZone} for checking either time zone variant + * @see {@link isTimeZoneOffset} for narrowing to fixed-offset time zones + * + * @category guards + * @since 3.6.0 + */ +export const isTimeZoneNamed: (u: unknown) => u is TimeZone.Named = Internal.isTimeZoneNamed + +/** + * Checks whether a `DateTime` is a UTC `DateTime` (no time zone information). + * + * **When to use** + * + * Use to narrow a `DateTime` before passing it to code that requires a UTC + * value without an associated time zone. + * + * @see {@link isZoned} for narrowing to zoned date-times + * @see {@link match} for handling both UTC and zoned cases + * + * @category guards + * @since 3.6.0 + */ +export const isUtc: (self: DateTime) => self is Utc = Internal.isUtc + +/** + * Checks whether a `DateTime` is a zoned `DateTime` (has time zone information). + * + * **When to use** + * + * Use to narrow a known `DateTime` before reading its zone or passing it to + * APIs that require `DateTime.Zoned`. + * + * @see {@link isUtc} for narrowing to UTC date-times + * @see {@link match} for handling both UTC and zoned cases + * + * @category guards + * @since 3.6.0 + */ +export const isZoned: (self: DateTime) => self is Zoned = Internal.isZoned + +// ============================================================================= +// instances +// ============================================================================= + +/** + * Provides an `Equivalence` for comparing two `DateTime` values for equality. + * + * **Details** + * + * Two `DateTime` values are considered equivalent if they represent the same + * point in time, regardless of their time zone. + * + * **Example** (Comparing DateTime values for equivalence) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { + * timeZone: "Europe/London" + * }) + * + * DateTime.Equivalence(utc, zoned) // => true + * ``` + * + * @category instances + * @since 3.6.0 + */ +export const Equivalence: Equ.Equivalence = Internal.Equivalence + +/** + * Provides an `Order` for comparing and sorting `DateTime` values. + * + * **Details** + * + * `DateTime` values are ordered by their epoch milliseconds, so earlier times + * come before later times regardless of time zone. + * + * **Example** (Sorting DateTime values chronologically) + * + * ```ts import.meta.vitest + * import { Array, DateTime } from "effect" + * + * const dates = [ + * DateTime.makeUnsafe("2024-03-01"), + * DateTime.makeUnsafe("2024-01-01"), + * DateTime.makeUnsafe("2024-02-01") + * ] + * + * Array.sort(dates, DateTime.Order).map(DateTime.formatIsoDateUtc) // => ["2024-01-01", "2024-02-01", "2024-03-01"] + * ``` + * + * @category instances + * @since 3.6.0 + */ +export const Order: order.Order = Internal.Order + +/** + * Returns a `DateTime` constrained between a minimum and maximum value. + * + * **Details** + * + * If the `DateTime` is before the minimum, the minimum is returned. + * If the `DateTime` is after the maximum, the maximum is returned. + * Otherwise, the original `DateTime` is returned. + * + * **Example** (Clamping DateTime values) + * + * ```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") + * + * DateTime.clamp(date, { minimum: min, maximum: max }) // => DateTime.makeUnsafe("2024-12-31") + * ``` + * + * @category ordering + * @since 3.6.0 + */ +export const clamp: { + ( + options: { readonly minimum: Min; readonly maximum: Max } + ): (self: A) => A | Min | Max + ( + self: A, + options: { readonly minimum: Min; readonly maximum: Max } + ): A | Min | Max +} = Internal.clamp + +// ============================================================================= +// constructors +// ============================================================================= + +/** + * Create a `DateTime` from a `Date`. + * + * **Details** + * + * If the `Date` is invalid, an `IllegalArgumentError` will be thrown. + * + * **Example** (Creating DateTime values from Dates) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * DateTime.fromDateUnsafe(new Date("2024-01-01T12:00:00Z")) // => DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromDateUnsafe: (date: Date) => Utc = Internal.fromDateUnsafe + +/** + * Create a `DateTime` from supported input values. + * + * **When to use** + * + * Use when creating a `DateTime` from trusted input and construction failures + * should throw an `IllegalArgumentError` instead of returning `Option.none`. + * + * **Details** + * + * - A `DateTime` + * - A `Date` instance (invalid dates will throw an `IllegalArgumentError`) + * - The `number` of milliseconds since the Unix epoch + * - An object with the parts of a date + * - A `string` that can be parsed by `Date.parse` + * + * **Example** (Creating DateTime values unsafely) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * // from Date + * DateTime.makeUnsafe(new Date("2024-01-01T12:00:00Z")) // => DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * + * // from parts + * DateTime.makeUnsafe({ year: 2024 }) // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * + * // from string + * DateTime.makeUnsafe("2024-01-01") // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * ``` + * + * @category constructors + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use when the date/time input and zone options are trusted and invalid or + * rejected ambiguous times should throw instead of returning `Option.none`. + * + * **Details** + * + * The input is treated as UTC and then the time zone is attached, unless + * `adjustForTimeZone` is set to `true`. In that case, the input is treated as + * already in the time zone. + * + * When `adjustForTimeZone` is true and ambiguous times occur during DST transitions, + * the `disambiguation` option controls how to resolve the ambiguity: + * - `compatible` (default): Choose earlier time for repeated times, later for gaps + * - `earlier`: Always choose the earlier of two possible times + * - `later`: Always choose the later of two possible times + * - `reject`: Throw an error when ambiguous times are encountered + * + * **Example** (Creating zoned DateTime values unsafely) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const zoned = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { + * timeZone: "Europe/London" + * }) + * + * DateTime.formatIsoZoned(zoned) // => "2024-06-15T15:30:00.000+01:00[Europe/London]" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeZonedUnsafe: (input: DateTime.Input, options?: { + readonly timeZone?: number | string | TimeZone | undefined + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined +}) => Zoned = Internal.makeZonedUnsafe + +/** + * Creates a `DateTime.Zoned` safely from an input and a time zone. + * + * **Details** + * + * By default, the input is interpreted as a UTC instant and the time zone is + * attached without changing that instant. When `adjustForTimeZone` is `true`, + * the input is interpreted as wall-clock time in the target zone. + * + * When `adjustForTimeZone` is `true`, `disambiguation` controls + * daylight-saving gaps and repeated times: + * + * - `"compatible"` (default): chooses the earlier occurrence for repeated + * times and the later interpretation for gaps + * - `"earlier"`: chooses the earlier possible instant + * - `"later"`: chooses the later possible instant + * - `"reject"`: rejects ambiguous or nonexistent wall-clock times + * + * Returns `Some` when construction succeeds, or `None` when the input, time + * zone, or disambiguation cannot be resolved. + * + * **Example** (Creating optional zoned DateTime values) + * + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" + * + * const result = DateTime.makeZoned("2024-06-15T14:30:00Z", { + * timeZone: "Europe/London" + * }) + * + * result.pipe(Option.map(DateTime.formatIsoZoned)) // => Option.some("2024-06-15T15:30:00.000+01:00[Europe/London]") + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const makeZoned: ( + input: DateTime.Input, + options?: { + readonly timeZone?: number | string | TimeZone | undefined + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + } +) => Option.Option = Internal.makeZoned + +/** + * Creates a `DateTime` safely from supported input values. + * + * **Details** + * + * - A `DateTime` + * - A JavaScript `Date` + * - The number of milliseconds since the Unix epoch + * - An object with date and time parts + * - A string that can be parsed as a date + * + * Returns `Some` with the constructed `DateTime` when the input is valid, or + * `None` when construction would fail, including invalid `Date` instances or + * unparseable strings. + * + * **Example** (Creating optional DateTime values) + * + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" + * + * // from Date + * DateTime.make(new Date("2024-01-01T12:00:00Z")) // => Option.some(DateTime.makeUnsafe("2024-01-01T12:00:00Z")) + * + * // from parts + * DateTime.make({ year: 2024 }) // => Option.some(DateTime.makeUnsafe("2024-01-01T00:00:00Z")) + * + * // from string + * DateTime.make("2024-01-01") // => Option.some(DateTime.makeUnsafe("2024-01-01T00:00:00Z")) + * + * DateTime.make("not a date") // => Option.none() + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const make: (input: A) => Option.Option> = Internal.make + +/** + * Parses an ISO zoned date-time string into a `DateTime.Zoned` safely. + * + * **Details** + * + * Accepts named-zone strings such as + * `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]` and offset-only strings such as + * `YYYY-MM-DDTHH:mm:ss.sss+HH:MM`. Returns `None` when the input cannot be + * parsed. + * + * **Example** (Parsing zoned DateTime strings) + * + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" + * + * DateTime.makeZonedFromString( + * "2024-01-01T12:00:00+02:00[Europe/Berlin]" + * ).pipe(Option.map(DateTime.formatIsoZoned)) // => Option.some("2024-01-01T11:00:00.000+01:00[Europe/Berlin]") + * + * DateTime.makeZonedFromString("2024-01-01T12:00:00Z") // => Option.none() + * DateTime.makeZonedFromString("invalid") // => Option.none() + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const makeZonedFromString: (input: string) => Option.Option = Internal.makeZonedFromString + +/** + * Gets the current time using the `Clock` service and converts it to a `DateTime`. + * + * **Example** (Getting the current DateTime) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" + * + * await Effect.runPromise(Effect.map(DateTime.now, DateTime.isDateTime)) // => true + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const now: Effect.Effect = Internal.now + +/** + * Gets the current time from the `Clock` service and returns it as a + * JavaScript `Date`. + * + * **Example** (Getting the current Date) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" + * + * await Effect.runPromise(Effect.map(DateTime.nowAsDate, (now) => now instanceof Date)) // => true + * ``` + * + * @category constructors + * @since 3.14.0 + */ +export const nowAsDate: Effect.Effect = Internal.nowAsDate + +/** + * Gets the current time using `Date.now`. + * + * **When to use** + * + * Use when synchronous wall-clock access outside an Effect program is + * acceptable and testability through the `Clock` service is not needed. + * + * **Details** + * + * This is a synchronous version of `now` that directly uses `Date.now()` + * instead of the Effect `Clock` service. + * + * **Example** (Getting the current DateTime unsafely) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * Number.isFinite(DateTime.toEpochMillis(DateTime.nowUnsafe())) // => true + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const nowUnsafe: LazyArg = Internal.nowUnsafe + +// ============================================================================= +// time zones +// ============================================================================= + +/** + * Converts a `DateTime` to a UTC `DateTime`. + * + * **When to use** + * + * Use to represent the same instant in UTC instead of its current time zone. + * + * **Details** + * + * The returned value keeps the same epoch milliseconds and changes only the + * `DateTime` representation to UTC. + * + * **Example** (Converting DateTime values to UTC) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const now = DateTime.makeZonedUnsafe({ year: 2024 }, { + * timeZone: "Europe/London" + * }) + * + * // set as UTC + * const utc: DateTime.Utc = DateTime.toUtc(now) + * utc // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * ``` + * + * @category converting + * @since 3.13.0 + */ +export const toUtc: (self: DateTime) => Utc = Internal.toUtc + +/** + * Sets the time zone of a `DateTime`, returning a new `DateTime.Zoned`. + * + * **Example** (Setting time zones) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") + * const zoned: DateTime.Zoned = DateTime.setZone(DateTime.makeUnsafe("2024-01-01"), zone) + * + * DateTime.isZoned(zoned) // => true + * ``` + * + * @category transforming + * @since 3.6.0 + */ +export const setZone: { + (zone: TimeZone, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): (self: DateTime) => Zoned + (self: DateTime, zone: TimeZone, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): Zoned +} = Internal.setZone + +/** + * Adds a fixed offset time zone to a `DateTime`. + * + * **Details** + * + * The offset is in milliseconds. + * + * **Example** (Setting fixed-offset time zones) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const zoned: DateTime.Zoned = DateTime.setZoneOffset(dateTime, 3 * 60 * 60 * 1000) + * + * DateTime.zoneToString(zoned.zone) // => "+03:00" + * ``` + * + * @category transforming + * @since 3.6.0 + */ +export const setZoneOffset: { + (offset: number, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): (self: DateTime) => Zoned + (self: DateTime, offset: number, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): Zoned +} = Internal.setZoneOffset + +/** + * Attempts to create a named time zone from an IANA time zone identifier. + * + * **When to use** + * + * Use when the IANA zone id is trusted and invalid zones should throw instead + * of returning `Option.none` or failing in `Effect`. + * + * **Details** + * + * If the time zone is invalid, an `IllegalArgumentError` will be thrown. + * + * **Example** (Creating named time zones unsafely) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * 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 constructors + * @since 4.0.0 + */ +export const zoneMakeNamedUnsafe: (zoneId: string) => TimeZone.Named = Internal.zoneMakeNamedUnsafe + +/** + * Create a fixed offset time zone. + * + * **Details** + * + * The offset is specified in milliseconds from UTC. Positive values are + * ahead of UTC, negative values are behind UTC. + * + * **Example** (Creating fixed-offset time zones) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * // Create a time zone with +3 hours offset + * const zone = DateTime.zoneMakeOffset(3 * 60 * 60 * 1000) + * + * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { + * timeZone: zone + * }) + * DateTime.formatIsoZoned(dt) // => "2024-01-01T15:00:00.000+03:00" + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const zoneMakeOffset: (offset: number) => TimeZone.Offset = Internal.zoneMakeOffset + +/** + * Creates a named time zone safely from an IANA time zone identifier. + * + * **Details** + * + * If the time zone is invalid, `None` will be returned. + * + * **Example** (Creating optional named time zones) + * + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" + * + * DateTime.zoneMakeNamed("Europe/London").pipe(Option.map(DateTime.zoneToString)) // => Option.some("Europe/London") + * DateTime.zoneMakeNamed("Invalid/Zone") // => Option.none() + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const zoneMakeNamed: (zoneId: string) => Option.Option = Internal.zoneMakeNamed + +/** + * Creates a named time zone effectfully from an IANA time zone identifier. + * + * **When to use** + * + * Use when invalid IANA zone ids should fail in the Effect error channel + * instead of returning `Option.none` or throwing. + * + * **Example** (Creating named time zones effectfully) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const zone = yield* DateTime.zoneMakeNamedEffect("Europe/London") + * const now = yield* DateTime.now + * return DateTime.setZone(now, zone) + * }) + * + * DateTime.zoneToString((await Effect.runPromise(program)).zone) // => "Europe/London" + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const zoneMakeNamedEffect: (zoneId: string) => Effect.Effect = + Internal.zoneMakeNamedEffect + +/** + * Create a named time zone from the system's local time zone. + * + * **Details** + * + * This uses the system's configured time zone, which may vary depending + * on the runtime environment. + * + * **Example** (Creating local time zones) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * DateTime.isTimeZoneNamed(DateTime.zoneMakeLocal()) // => true + * ``` + * + * @category constructors + * @since 3.6.0 + */ +export const zoneMakeLocal: () => TimeZone.Named = Internal.zoneMakeLocal + +/** + * Tries to parse a `TimeZone` from a string safely. + * + * **Details** + * + * Supports both IANA time zone identifiers and offset formats like "+03:00". + * + * **Example** (Parsing time zones) + * + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" + * + * 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 decoding + * @since 3.6.0 + */ +export const zoneFromString: (zone: string) => Option.Option = Internal.zoneFromString + +/** + * Formats a `TimeZone` as a string. + * + * **Example** (Formatting time zones) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)) // => "+03:00" + * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Europe/London")) // => "Europe/London" + * ``` + * + * @category encoding + * @since 3.6.0 + */ +export const zoneToString: (self: TimeZone) => string = Internal.zoneToString + +/** + * Sets the time zone of a `DateTime` safely from an IANA time zone identifier. If the + * time zone is invalid, `None` will be returned. + * + * **Example** (Setting named time zones safely) + * + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" + * + * 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 transforming + * @since 3.6.0 + */ +export const setZoneNamed: { + (zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): (self: DateTime) => Option.Option + (self: DateTime, zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): Option.Option +} = Internal.setZoneNamed + +/** + * Sets the time zone of a `DateTime` from an IANA time zone identifier. If the + * time zone is invalid, an `IllegalArgumentError` will be thrown. + * + * **Example** (Setting named time zones unsafely) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const zoned = DateTime.setZoneNamedUnsafe(dateTime, "Europe/London") + * + * DateTime.zoneToString(zoned.zone) // => "Europe/London" + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export const setZoneNamedUnsafe: { + (zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): (self: DateTime) => Zoned + (self: DateTime, zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: Disambiguation | undefined + }): Zoned +} = Internal.setZoneNamedUnsafe + +// ============================================================================= +// comparisons +// ============================================================================= + +/** + * Computes the difference between two `DateTime` values, returning a + * `Duration` representing the amount of time between them. + * + * **Details** + * + * If `other` is *after* `self`, the result will be a positive `Duration`. If + * `other` is *before* `self`, the result will be a negative `Duration`. If they + * are equal, the result will be a `Duration` of zero. + * + * **Example** (Measuring distance between DateTime values) + * + * ```ts import.meta.vitest + * import { DateTime, Duration } from "effect" + * + * const start = DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * const end = DateTime.add(start, { minutes: 1 }) + * + * DateTime.distance(start, end) // => Duration.minutes(1) + * ``` + * + * @category comparisons + * @since 3.6.0 + */ +export const distance: { + (other: DateTime): (self: DateTime) => Duration.Duration + (self: DateTime, other: DateTime): Duration.Duration +} = Internal.distance + +/** + * Returns the earlier of two `DateTime` values. + * + * **Example** (Selecting the earlier DateTime) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const date1 = DateTime.makeUnsafe("2024-01-01") + * const date2 = DateTime.makeUnsafe("2024-02-01") + * + * DateTime.min(date1, date2) // => DateTime.makeUnsafe("2024-01-01") + * ``` + * + * @category comparisons + * @since 3.6.0 + */ +export const min: { + (that: That): (self: Self) => Self | That + (self: Self, that: That): Self | That +} = Internal.min + +/** + * Returns the later of two `DateTime` values. + * + * **Example** (Selecting the later DateTime) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const date1 = DateTime.makeUnsafe("2024-01-01") + * const date2 = DateTime.makeUnsafe("2024-02-01") + * + * DateTime.max(date1, date2) // => DateTime.makeUnsafe("2024-02-01") + * ``` + * + * @category comparisons + * @since 3.6.0 + */ +export const max: { + (that: That): (self: Self) => Self | That + (self: Self, that: That): Self | That +} = Internal.max + +/** + * Checks whether the first `DateTime` is after the second `DateTime`. + * + * **Example** (Checking whether a DateTime is later) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const date1 = DateTime.makeUnsafe("2024-02-01") + * const date2 = DateTime.makeUnsafe("2024-01-01") + * + * DateTime.isGreaterThan(date1, date2) // => true + * DateTime.isGreaterThan(date2, date1) // => false + * ``` + * + * @category comparisons + * @since 4.0.0 + */ +export const isGreaterThan: { + (that: DateTime): (self: DateTime) => boolean + (self: DateTime, that: DateTime): boolean +} = Internal.isGreaterThan + +/** + * Checks whether the first `DateTime` is after or equal to the second `DateTime`. + * + * **Example** (Checking whether a DateTime is later or equal) + * + * ```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") + * + * DateTime.isGreaterThanOrEqualTo(date1, date2) // => true + * DateTime.isGreaterThanOrEqualTo(date3, date1) // => true + * DateTime.isGreaterThanOrEqualTo(date1, date3) // => false + * ``` + * + * @category comparisons + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo: { + (that: DateTime): (self: DateTime) => boolean + (self: DateTime, that: DateTime): boolean +} = Internal.isGreaterThanOrEqualTo + +/** + * Checks whether the first `DateTime` is before the second `DateTime`. + * + * **Example** (Checking whether a DateTime is earlier) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const date1 = DateTime.makeUnsafe("2024-01-01") + * const date2 = DateTime.makeUnsafe("2024-02-01") + * + * DateTime.isLessThan(date1, date2) // => true + * DateTime.isLessThan(date2, date1) // => false + * ``` + * + * @category comparisons + * @since 4.0.0 + */ +export const isLessThan: { + (that: DateTime): (self: DateTime) => boolean + (self: DateTime, that: DateTime): boolean +} = Internal.isLessThan + +/** + * Checks whether the first `DateTime` is before or equal to the second `DateTime`. + * + * **Example** (Checking whether a DateTime is earlier or equal) + * + * ```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") + * + * DateTime.isLessThanOrEqualTo(date1, date2) // => true + * DateTime.isLessThanOrEqualTo(date1, date3) // => true + * DateTime.isLessThanOrEqualTo(date3, date1) // => false + * ``` + * + * @category comparisons + * @since 4.0.0 + */ +export const isLessThanOrEqualTo: { + (that: DateTime): (self: DateTime) => boolean + (self: DateTime, that: DateTime): boolean +} = Internal.isLessThanOrEqualTo + +/** + * Checks whether a `DateTime` is between two other `DateTime` values (inclusive). + * + * **Example** (Checking whether a DateTime is within bounds) + * + * ```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") + * + * DateTime.between(date, { minimum: min, maximum: max }) // => true + * ``` + * + * @category comparisons + * @since 3.6.0 + */ +export const between: { + (options: { minimum: DateTime; maximum: DateTime }): (self: DateTime) => boolean + (self: DateTime, options: { minimum: DateTime; maximum: DateTime }): boolean +} = Internal.between + +/** + * Checks effectfully if a `DateTime` is in the future compared to the current time. + * + * **Details** + * + * This is an effectful operation that uses the current time from the `Clock` service. + * + * **Example** (Checking future DateTime values effectfully) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" + * + * const futureDate = DateTime.makeUnsafe(1) + * await Effect.runPromise(Effect.provide(DateTime.isFuture(futureDate), TestClock.layer())) // => true + * ``` + * + * @category comparisons + * @since 3.6.0 + */ +export const isFuture: (self: DateTime) => Effect.Effect = Internal.isFuture + +/** + * Checks synchronously if a `DateTime` is in the future compared to the current time. + * + * **When to use** + * + * Use when checking whether a `DateTime` is in the future with a synchronous + * live-clock read and `Clock`-based testability is not needed. + * + * **Details** + * + * This is a synchronous version that uses `Date.now()` directly. + * + * **Example** (Checking future DateTime values unsafely) + * + * ```ts + * import { DateTime } from "effect" + * + * const oneHourFromNow = DateTime.add(DateTime.nowUnsafe(), { hours: 1 }) + * DateTime.isFutureUnsafe(oneHourFromNow) + * ``` + * + * @category comparisons + * @since 4.0.0 + */ +export const isFutureUnsafe: (self: DateTime) => boolean = Internal.isFutureUnsafe + +/** + * Checks effectfully if a `DateTime` is in the past compared to the current time. + * + * **Details** + * + * This is an effectful operation that uses the current time from the `Clock` service. + * + * **Example** (Checking past DateTime values effectfully) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" + * + * const pastDate = DateTime.makeUnsafe(-1) + * await Effect.runPromise(Effect.provide(DateTime.isPast(pastDate), TestClock.layer())) // => true + * ``` + * + * @category comparisons + * @since 3.6.0 + */ +export const isPast: (self: DateTime) => Effect.Effect = Internal.isPast + +/** + * Checks synchronously if a `DateTime` is in the past compared to the current time. + * + * **When to use** + * + * Use when checking whether a `DateTime` is in the past with a synchronous + * live-clock read and `Clock`-based testability is not needed. + * + * **Details** + * + * This is a synchronous version that uses `Date.now()` directly. + * + * **Example** (Checking past DateTime values unsafely) + * + * ```ts + * import { DateTime } from "effect" + * + * const oneHourAgo = DateTime.subtract(DateTime.nowUnsafe(), { hours: 1 }) + * DateTime.isPastUnsafe(oneHourAgo) + * ``` + * + * @category comparisons + * @since 4.0.0 + */ +export const isPastUnsafe: (self: DateTime) => boolean = Internal.isPastUnsafe + +// ============================================================================= +// conversions +// ============================================================================= + +/** + * Gets the UTC `Date` of a `DateTime`. + * + * **Details** + * + * This always returns the UTC representation, ignoring any time zone information. + * + * **Example** (Converting DateTime values to UTC Dates) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { + * timeZone: "Europe/London" + * }) + * + * DateTime.toDateUtc(dt).toISOString() // => "2024-01-01T12:00:00.000Z" + * ``` + * + * @category converting + * @since 3.6.0 + */ +export const toDateUtc: (self: DateTime) => Date = Internal.toDateUtc + +/** + * Converts a `DateTime` to a `Date`, applying the time zone first. + * + * **Details** + * + * For `DateTime.Zoned`, this adjusts for the time zone before converting. + * For `DateTime.Utc`, this is equivalent to `toDateUtc`. + * + * **Example** (Converting DateTime values to Dates) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { + * timeZone: "Europe/London" + * }) + * + * DateTime.toDate(utc).toISOString() // => "2024-01-01T12:00:00.000Z" + * DateTime.toDate(zoned).toISOString() // => "2024-01-01T12:00:00.000Z" + * ``` + * + * @category converting + * @since 3.6.0 + */ +export const toDate: (self: DateTime) => Date = Internal.toDate + +/** + * Computes the time zone offset of a `DateTime.Zoned` in milliseconds. + * + * **Details** + * + * Returns the offset from UTC in milliseconds. Positive values indicate + * time zones ahead of UTC, negative values indicate time zones behind UTC. + * + * **Example** (Reading zoned offsets) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { + * timeZone: "Europe/London" + * }) + * + * DateTime.zonedOffset(zoned) // => 0 + * ``` + * + * @category converting + * @since 3.6.0 + */ +export const zonedOffset: (self: Zoned) => number = Internal.zonedOffset + +/** + * Formats the time zone offset of a `DateTime.Zoned` as an ISO string. + * + * **Details** + * + * The offset is formatted as "±HH:MM". + * + * **Example** (Formatting zoned offsets) + * + * ```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 + * }) + * + * DateTime.zonedOffsetIso(zoned) // => "+03:00" + * ``` + * + * @category converting + * @since 3.6.0 + */ +export const zonedOffsetIso: (self: Zoned) => string = Internal.zonedOffsetIso + +/** + * Gets the milliseconds since the Unix epoch of a `DateTime`. + * + * **Details** + * + * This returns the UTC timestamp regardless of any time zone information. + * + * **Example** (Reading epoch milliseconds) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * DateTime.toEpochMillis(dt) // => 1704067200000 + * ``` + * + * @category converting + * @since 3.6.0 + */ +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 import.meta.vitest + * import { DateTime } from "effect" + * + * // returns "2024-01-01T00:00:00Z" + * DateTime.makeZonedUnsafe("2024-01-01T05:00:00Z", { + * timeZone: "Pacific/Auckland", + * adjustForTimeZone: true + * }).pipe( + * DateTime.removeTime, + * DateTime.formatIso + * ) // => "2024-01-01T00:00:00.000Z" + * ``` + * + * @category converting + * @since 3.6.0 + */ +export const removeTime: (self: DateTime) => Utc = Internal.removeTime + +// ============================================================================= +// parts +// ============================================================================= + +/** + * Gets the time-zone-adjusted parts of a `DateTime` as an object. + * + * **Details** + * + * The parts will be time zone adjusted if the `DateTime` is zoned. + * + * **Example** (Reading DateTime parts) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T12:30:45.123Z") + * const parts = DateTime.toParts(dt) + * + * const selectedParts = [parts.year, parts.month, parts.day, parts.hour] // => [2024, 1, 1, 12] + * ``` + * + * @category getters + * @since 3.6.0 + */ +export const toParts: (self: DateTime) => DateTime.PartsWithWeekday = Internal.toParts + +/** + * Gets the UTC parts of a `DateTime` as an object. + * + * **Details** + * + * The parts will always be in UTC, ignoring any time zone information. + * + * **Example** (Reading UTC DateTime parts) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:30:45.123Z", { + * timeZone: "Europe/London" + * }) + * const parts = DateTime.toPartsUtc(zoned) + * + * const selectedParts = [parts.year, parts.month, parts.day, parts.hour] // => [2024, 1, 1, 12] + * ``` + * + * @category getters + * @since 3.6.0 + */ +export const toPartsUtc: (self: DateTime) => DateTime.PartsWithWeekday = Internal.toPartsUtc + +/** + * Gets one UTC part of a `DateTime` as a number. + * + * **Details** + * + * The part will be in the UTC time zone. + * + * **Example** (Reading UTC DateTime parts by key) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dateTime = DateTime.makeUnsafe({ year: 2024 }) + * DateTime.getPartUtc(dateTime, "year") // => 2024 + * ``` + * + * @category getters + * @since 3.6.0 + */ +export const getPartUtc: { + (part: keyof DateTime.PartsWithWeekday): (self: DateTime) => number + (self: DateTime, part: keyof DateTime.PartsWithWeekday): number +} = Internal.getPartUtc + +/** + * Gets one time-zone-adjusted part of a `DateTime` as a number. + * + * **Details** + * + * The part will be time zone adjusted. + * + * **Example** (Reading DateTime parts by key) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dateTime = DateTime.makeZonedUnsafe({ year: 2024 }, { + * timeZone: "Europe/London" + * }) + * DateTime.getPart(dateTime, "year") // => 2024 + * ``` + * + * @category getters + * @since 3.6.0 + */ +export const getPart: { + (part: keyof DateTime.PartsWithWeekday): (self: DateTime) => number + (self: DateTime, part: keyof DateTime.PartsWithWeekday): number +} = Internal.getPart + +/** + * Sets time-zone-adjusted parts on a `DateTime`. + * + * **Details** + * + * The date will be time zone adjusted for `DateTime.Zoned`. + * + * **Example** (Updating DateTime parts) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { timeZone: "UTC" }) + * const updated = DateTime.setParts(dt, { + * year: 2025, + * month: 6, + * day: 15 + * }) + * + * updated // => DateTime.makeZonedUnsafe("2025-06-15T12:00:00Z", { timeZone: "UTC" }) + * ``` + * + * @category transforming + * @since 3.6.0 + */ +export const setParts: { + (parts: Partial): (self: A) => A + (self: A, parts: Partial): A +} = Internal.setParts + +/** + * Sets UTC parts on a `DateTime`. + * + * **Details** + * + * The parts are always interpreted as UTC, ignoring any time zone information. + * + * **Example** (Updating UTC DateTime parts) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * const updated = DateTime.setPartsUtc(dt, { + * year: 2025, + * hour: 18 + * }) + * + * updated // => DateTime.makeUnsafe("2025-01-01T18:00:00Z") + * ``` + * + * @category transforming + * @since 3.6.0 + */ +export const setPartsUtc: { + (parts: Partial): (self: A) => A + (self: A, parts: Partial): A +} = Internal.setPartsUtc + +// ============================================================================= +// current time zone +// ============================================================================= + +/** + * Context service that supplies the ambient `TimeZone` for APIs that work in + * the current zone, such as `DateTime.setZoneCurrent` and + * `DateTime.nowInCurrentZone`. + * + * **Details** + * + * Provide it with `DateTime.withCurrentZone`, one of the `withCurrentZone*` + * helpers, or one of the `layerCurrentZone*` layers. + * + * **Example** (Accessing the current time zone service) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * return DateTime.zoneToString(yield* DateTime.CurrentTimeZone) + * }) + * + * // Provide a time zone + * const layer = DateTime.layerCurrentZoneNamed("Europe/London") + * await Effect.runPromise(Effect.provide(program, layer)) // => "Europe/London" + * ``` + * + * @category services + * @since 3.11.0 + */ +export class CurrentTimeZone extends Context.Service()( + "effect/DateTime/CurrentTimeZone" +) {} + +/** + * Sets the time zone of a `DateTime` to the current time zone, which is + * determined by the `CurrentTimeZone` service. + * + * **Example** (Setting the current time zone) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * 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 accessors + * @since 3.6.0 + */ +export const setZoneCurrent = (self: DateTime): Effect.Effect => + Effect.map(CurrentTimeZone, (zone) => setZone(self, zone)) + +/** + * Provides the `CurrentTimeZone` to an effect. + * + * **Example** (Providing the current time zone) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * const zone = DateTime.zoneMakeNamedUnsafe("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.withCurrentZone(zone))) // => "Europe/London" + * ``` + * + * @category providing services + * @since 3.6.0 + */ +export const withCurrentZone: { + (value: TimeZone): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, value: TimeZone): Effect.Effect> +} = provideService(CurrentTimeZone) + +/** + * Provides the `CurrentTimeZone` to an effect, using the system's local time + * zone. + * + * **Example** (Providing the local time zone) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * await Effect.runPromise(Effect.gen(function*() { + * return DateTime.isZoned(yield* DateTime.nowInCurrentZone) + * }).pipe(DateTime.withCurrentZoneLocal)) // => true + * ``` + * + * @category providing services + * @since 3.6.0 + */ +export const withCurrentZoneLocal = ( + effect: Effect.Effect +): Effect.Effect> => + Effect.provideServiceEffect(effect, CurrentTimeZone, Effect.sync(zoneMakeLocal)) + +/** + * Provides the `CurrentTimeZone` to an effect, using an offset. + * + * **Example** (Providing a fixed-offset time zone) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * 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 providing services + * @since 3.6.0 + */ +export const withCurrentZoneOffset: { + (offset: number): ( + effect: Effect.Effect + ) => Effect.Effect> + (effect: Effect.Effect, offset: number): Effect.Effect> +} = dual( + 2, + (effect: Effect.Effect, offset: number): Effect.Effect> => + Effect.provideService(effect, CurrentTimeZone, zoneMakeOffset(offset)) +) + +/** + * Provides the `CurrentTimeZone` to an effect using an IANA time zone + * identifier. + * + * **Details** + * + * If the time zone is invalid, it will fail with an `IllegalArgumentError`. + * + * **Example** (Providing a named time zone) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * 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 providing services + * @since 3.6.0 + */ +export const withCurrentZoneNamed: { + (zone: string): ( + effect: Effect.Effect + ) => Effect.Effect> + ( + effect: Effect.Effect, + zone: string + ): Effect.Effect> +} = dual( + 2, + ( + effect: Effect.Effect, + zone: string + ): Effect.Effect> => + Effect.provideServiceEffect(effect, CurrentTimeZone, zoneMakeNamedEffect(zone)) +) + +/** + * Gets the current time as a `DateTime.Zoned`, using the `CurrentTimeZone`. + * + * **Example** (Getting the current time in the current zone) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * await Effect.runPromise(Effect.gen(function*() { + * return DateTime.zoneToString((yield* DateTime.nowInCurrentZone).zone) + * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))) // => "Europe/London" + * ``` + * + * @category accessors + * @since 3.6.0 + */ +export const nowInCurrentZone: Effect.Effect = Effect.flatMap(now, setZoneCurrent) + +// ============================================================================= +// mapping +// ============================================================================= + +/** + * Modifies a `DateTime` with a mutable local `Date` copy. + * + * **When to use** + * + * Use to adjust calendar fields in the `DateTime`'s own time zone with an + * existing `Date` mutation API. + * + * **Details** + * + * The `Date` will first have the time zone applied if possible, and then be + * converted back to a `DateTime` within the same time zone. + * + * Supports `disambiguation` when the new wall clock time is ambiguous. + * + * **Example** (Mutating DateTime values with Dates) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * + * DateTime.mutate(dt, (date) => { + * date.setHours(15) // Set to 3 PM + * date.setMinutes(30) // Set to 30 minutes + * }) + * ``` + * + * @category mapping + * @since 3.6.0 + */ +export const mutate: { + ( + f: (date: Date) => void, + options?: { + readonly disambiguation?: Disambiguation | undefined + } + ): (self: A) => A + ( + self: A, + f: (date: Date) => void, + options?: { + readonly disambiguation?: Disambiguation | undefined + } + ): A +} = Internal.mutate + +/** + * Modifies a `DateTime` with a mutable UTC `Date` copy. + * + * **When to use** + * + * Use to adjust the instant with an existing `Date` mutation API that works on + * UTC calendar fields. + * + * **Example** (Mutating DateTime values with UTC Dates) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { + * timeZone: "Europe/London" + * }) + * + * const modified = DateTime.mutateUtc(dt, (date) => { + * date.setUTCHours(18) // Set UTC time to 6 PM + * }) + * + * modified // => DateTime.makeZonedUnsafe("2024-01-01T18:00:00Z", { timeZone: "Europe/London" }) + * ``` + * + * @category mapping + * @since 3.6.0 + */ +export const mutateUtc: { + (f: (date: Date) => void): (self: A) => A + (self: A, f: (date: Date) => void): A +} = Internal.mutateUtc + +/** + * Transforms a `DateTime` by applying a function to the number of milliseconds + * since the Unix epoch. + * + * **Example** (Mapping epoch milliseconds) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * // add 10 milliseconds + * const result = DateTime.makeUnsafe(0).pipe( + * DateTime.mapEpochMillis((millis) => millis + 10) + * ) + * result // => DateTime.makeUnsafe(10) + * ``` + * + * @category mapping + * @since 3.6.0 + */ +export const mapEpochMillis: { + (f: (millis: number) => number): (self: A) => A + (self: A, f: (millis: number) => number): A +} = Internal.mapEpochMillis + +/** + * Applies a function to a JavaScript `Date` representing the `DateTime` and + * returns the function's result. + * + * **Details** + * + * The callback receives the time-zone-adjusted wall-clock date for + * `DateTime.Zoned` values. Use `DateTime.withDateUtc` when the callback should + * receive the UTC instant. + * + * **Example** (Applying time zone adjusted Dates) + * + * ```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 + * @since 3.6.0 + */ +export const withDate: { + (f: (date: Date) => A): (self: DateTime) => A + (self: DateTime, f: (date: Date) => A): A +} = Internal.withDate + +/** + * Applies a function to a JavaScript `Date` representing the `DateTime`'s UTC + * instant and returns the function's result. + * + * **Details** + * + * This ignores any associated time zone. Use `DateTime.withDate` when the + * callback should receive the time-zone-adjusted wall-clock date. + * + * **Example** (Applying UTC Dates) + * + * ```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 + * @since 3.6.0 + */ +export const withDateUtc: { + (f: (date: Date) => A): (self: DateTime) => A + (self: DateTime, f: (date: Date) => A): A +} = Internal.withDateUtc + +/** + * Pattern match on a `DateTime` to handle `Utc` and `Zoned` cases differently. + * + * **Example** (Pattern matching DateTime variants) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt1 = DateTime.makeUnsafe("2024-01-01T12:00:00Z") // Utc + * const dt2 = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { + * timeZone: "Europe/London" + * }) // Zoned + * + * const result1 = DateTime.match(dt1, { + * onUtc: (utc) => `UTC: ${DateTime.formatIso(utc)}`, + * onZoned: (zoned) => `Zoned: ${DateTime.formatIsoZoned(zoned)}` + * }) + * + * const result2 = DateTime.match(dt2, { + * onUtc: (utc) => `UTC: ${DateTime.formatIso(utc)}`, + * onZoned: (zoned) => `Zoned: ${DateTime.formatIsoZoned(zoned)}` + * }) + * + * result1 // => "UTC: 2024-01-01T12:00:00.000Z" + * result2 // => "Zoned: 2024-06-15T15:30:00.000+01:00[Europe/London]" + * ``` + * + * @category mapping + * @since 3.6.0 + */ +export const match: { + (options: { + readonly onUtc: (_: Utc) => A + readonly onZoned: (_: Zoned) => B + }): (self: DateTime) => A | B + (self: DateTime, options: { + readonly onUtc: (_: Utc) => A + readonly onZoned: (_: Zoned) => B + }): A | B +} = Internal.match + +// ============================================================================= +// math +// ============================================================================= + +/** + * Adds the given `Duration` to a `DateTime`. + * + * **When to use** + * + * Use to move a `DateTime` by an elapsed duration such as minutes, seconds, or + * milliseconds. + * + * **Details** + * + * The duration is converted to milliseconds and added to the epoch + * milliseconds. Zoned values keep their original time zone. + * + * **Gotchas** + * + * This is elapsed-time arithmetic, not calendar-aware local date arithmetic. + * Use `add` when adding days, weeks, months, or years should account for the + * date/time zone rules. + * + * **Example** (Adding durations) + * + * ```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 + * @see {@link subtractDuration} for subtracting an elapsed duration + * + * @category math + * @since 3.6.0 + */ +export const addDuration: { + (duration: Duration.Input): (self: A) => A + (self: A, duration: Duration.Input): A +} = Internal.addDuration + +/** + * Subtracts the given `Duration` from a `DateTime`. + * + * **Example** (Subtracting durations) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * // subtract 5 minutes + * DateTime.makeUnsafe(0).pipe( + * DateTime.subtractDuration("5 minutes") + * ) // => DateTime.makeUnsafe(-300000) + * ``` + * + * @category math + * @since 3.6.0 + */ +export const subtractDuration: { + (duration: Duration.Input): (self: A) => A + (self: A, duration: Duration.Input): A +} = Internal.subtractDuration + +/** + * Adds the given `amount` of `unit` to a `DateTime`. + * + * **Details** + * + * The time zone is taken into account when adding days, weeks, months, and + * years. + * + * **Example** (Adding date and time parts) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * // add 5 minutes + * DateTime.makeUnsafe(0).pipe( + * DateTime.add({ minutes: 5 }) + * ) // => DateTime.makeUnsafe(300000) + * ``` + * + * @category math + * @since 3.6.0 + */ +export const add: { + (parts: Partial): (self: A) => A + (self: A, parts: Partial): A +} = Internal.add + +/** + * Subtracts the given `amount` of `unit` from a `DateTime`. + * + * **Example** (Subtracting date and time parts) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * // subtract 5 minutes + * DateTime.makeUnsafe(0).pipe( + * DateTime.subtract({ minutes: 5 }) + * ) // => DateTime.makeUnsafe(-300000) + * ``` + * + * @category math + * @since 3.6.0 + */ +export const subtract: { + (parts: Partial): (self: A) => A + (self: A, parts: Partial): A +} = Internal.subtract + +/** + * Converts a `DateTime` to the start of the given `part`. + * + * **Details** + * + * If the part is `week`, the `weekStartsOn` option can be used to specify the + * day of the week that the week starts on. The default is 0 (Sunday). + * + * **Example** (Rounding down DateTime values) + * + * ```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.makeUnsafe("2024-01-01T00:00:00Z") + * ``` + * + * @category math + * @since 3.6.0 + */ +export const startOf: { + ( + part: DateTime.UnitSingular, + options?: { readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined } + ): (self: A) => A + ( + self: A, + part: DateTime.UnitSingular, + options?: { readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined } + ): A +} = Internal.startOf + +/** + * Converts a `DateTime` to the end of the given `part`. + * + * **Details** + * + * If the part is `week`, the `weekStartsOn` option can be used to specify the + * day of the week that the week starts on. The default is 0 (Sunday). + * + * **Example** (Rounding up DateTime values) + * + * ```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.makeUnsafe("2024-01-01T23:59:59.999Z") + * ``` + * + * @category math + * @since 3.6.0 + */ +export const endOf: { + ( + part: DateTime.UnitSingular, + options?: { readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined } + ): (self: A) => A + ( + self: A, + part: DateTime.UnitSingular, + options?: { readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined } + ): A +} = Internal.endOf + +/** + * Converts a `DateTime` to the nearest given `part`. + * + * **Details** + * + * If the part is `week`, the `weekStartsOn` option can be used to specify the + * day of the week that the week starts on. The default is 0 (Sunday). + * + * **Example** (Rounding DateTime values to nearest units) + * + * ```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.makeUnsafe("2024-01-02T00:00:00Z") + * ``` + * + * @category math + * @since 3.6.0 + */ +export const nearest: { + ( + part: DateTime.UnitSingular, + options?: { readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined } + ): (self: A) => A + ( + self: A, + part: DateTime.UnitSingular, + options?: { readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined } + ): A +} = Internal.nearest + +// ============================================================================= +// formatting +// ============================================================================= + +/** + * Formats a `DateTime` with `Intl.DateTimeFormat`. + * + * **Details** + * + * Unless a `timeZone` option is supplied, UTC values are formatted in UTC and + * zoned values are formatted in their named zone or fixed-offset zone. + * + * Fixed-offset zones depend on runtime support for offset `timeZone` + * identifiers. When unsupported, formatting falls back to UTC with the + * `DateTime` adjusted to the offset. + * + * **Example** (Formatting DateTime values with Intl options) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { + * timeZone: "Europe/London" + * }) + * + * DateTime.format(dt, { + * dateStyle: "full", + * timeStyle: "short", + * locale: "en-US" + * }) // => "Saturday, June 15, 2024 at 3:30 PM" + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const format: { + ( + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: string | undefined + } + | undefined + ): (self: DateTime) => string + ( + self: DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: string | undefined + } + | undefined + ): string +} = Internal.format + +/** + * Formats a `DateTime` with `Intl.DateTimeFormat` using the system local time + * zone and locale. + * + * **Example** (Formatting DateTime values locally) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-06-15T14:30:00Z") + * + * // Uses system local time zone and locale + * DateTime.formatLocal(dt, { + * year: "numeric", + * month: "long", + * day: "numeric", + * hour: "2-digit", + * minute: "2-digit" + * }) + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const formatLocal: { + ( + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: string | undefined + } + | undefined + ): (self: DateTime) => string + ( + self: DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: string | undefined + } + | undefined + ): string +} = Internal.formatLocal + +/** + * Formats a `DateTime` with `Intl.DateTimeFormat` using the UTC time zone. + * + * **Details** + * + * This forces the time zone to be UTC. + * + * **Example** (Formatting DateTime values in UTC) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { + * timeZone: "Europe/London" + * }) + * + * // Force UTC formatting regardless of time zone + * DateTime.formatUtc(dt, { + * year: "numeric", + * month: "2-digit", + * day: "2-digit", + * hour: "2-digit", + * minute: "2-digit", + * timeZoneName: "short" + * }) + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const formatUtc: { + ( + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: string | undefined + } + | undefined + ): (self: DateTime) => string + ( + self: DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: string | undefined + } + | undefined + ): string +} = Internal.formatUtc + +/** + * Formats a `DateTime` as a string using the `Intl.DateTimeFormat` API. + * + * **When to use** + * + * Use when you already have an `Intl.DateTimeFormat` and want it to control the + * locale, time zone, and formatting options. + * + * **Details** + * + * The formatter receives the `DateTime` epoch milliseconds. Any time zone + * conversion comes from the supplied formatter. + * + * **Example** (Formatting DateTime values with custom formatters) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-06-15T14:30:00Z") + * + * // Create a custom formatter + * const formatter = new Intl.DateTimeFormat("de-DE", { + * year: "numeric", + * month: "long", + * day: "numeric", + * hour: "2-digit", + * minute: "2-digit", + * timeZone: "Europe/Berlin" + * }) + * + * DateTime.formatIntl(dt, formatter).length > 0 // => true + * ``` + * + * @see {@link formatUtc} for formatting with options forced to UTC + * @see {@link formatIso} for stable ISO formatting + * + * @category formatting + * @since 3.6.0 + */ +export const formatIntl: { + (format: Intl.DateTimeFormat): (self: DateTime) => string + (self: DateTime, format: Intl.DateTimeFormat): string +} = Internal.formatIntl + +/** + * Formats a `DateTime` as a UTC ISO string. + * + * **Details** + * + * Always returns the UTC representation in ISO 8601 format, ignoring any time zone. + * + * **Example** (Formatting DateTime values as ISO strings) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * 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" + * }) + * DateTime.formatIso(zoned) // => "2024-01-01T12:30:45.123Z" + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const formatIso: (self: DateTime) => string = Internal.formatIso + +/** + * Formats a `DateTime` as a time zone adjusted ISO date string. + * + * **Details** + * + * Returns only the date part (YYYY-MM-DD) after applying time zone adjustments. + * + * **Example** (Formatting DateTime values as ISO dates) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T23:30:00Z") + * DateTime.formatIsoDate(dt) // => "2024-01-01" + * + * const zoned = DateTime.makeZonedUnsafe("2024-01-01T23:30:00Z", { + * timeZone: "Pacific/Auckland" // UTC+12/13 + * }) + * DateTime.formatIsoDate(zoned) // => "2024-01-02" + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const formatIsoDate: (self: DateTime) => string = Internal.formatIsoDate + +/** + * Formats a `DateTime` as a UTC ISO date string. + * + * **Details** + * + * Returns only the date part (YYYY-MM-DD) in UTC, ignoring any time zone. + * + * **Example** (Formatting DateTime values as UTC ISO dates) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T23:30:00Z") + * DateTime.formatIsoDateUtc(dt) // => "2024-01-01" + * + * const zoned = DateTime.makeZonedUnsafe("2024-01-01T23:30:00Z", { + * timeZone: "Pacific/Auckland" + * }) + * DateTime.formatIsoDateUtc(zoned) // => "2024-01-01" + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const formatIsoDateUtc: (self: DateTime) => string = Internal.formatIsoDateUtc + +/** + * Formats a `DateTime.Zoned` as an ISO string with an offset. + * + * **Details** + * + * For `DateTime.Utc`, returns the same as `formatIso`. For `DateTime.Zoned`, + * includes the time zone offset in the format. + * + * **Example** (Formatting DateTime values with offsets) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * 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) + * }) + * DateTime.formatIsoOffset(zoned) // => "2024-01-01T15:00:00.000+03:00" + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const formatIsoOffset: (self: DateTime) => string = Internal.formatIsoOffset + +/** + * Formats a `DateTime.Zoned` as a string. + * + * **Details** + * + * It uses the format: `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]`. + * + * **Example** (Formatting zoned DateTime values) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const zoned = DateTime.makeZonedUnsafe("2024-06-15T14:30:45.123Z", { + * timeZone: "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) + * }) + * + * DateTime.formatIsoZoned(offsetZone) // => "2024-06-15T17:30:45.123+03:00" + * ``` + * + * @category formatting + * @since 3.6.0 + */ +export const formatIsoZoned: (self: Zoned) => string = Internal.formatIsoZoned + +/** + * Create a Layer from the given time zone. + * + * **Details** + * + * This layer provides the `CurrentTimeZone` service with the specified time zone. + * + * **Example** (Providing current time zone layers) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") + * const layer = DateTime.layerCurrentZone(zone) + * + * const program = Effect.gen(function*() { + * const now = yield* DateTime.nowInCurrentZone + * return DateTime.zoneToString(now.zone) + * }) + * + * // Use the layer to provide the time zone + * await Effect.runPromise(Effect.provide(program, layer)) // => "Europe/London" + * ``` + * + * @category layers + * @since 3.6.0 + */ +export const layerCurrentZone: (resource: NoInfer) => Layer.Layer = Layer.succeed( + CurrentTimeZone +) + +/** + * Create a Layer from the given time zone offset. + * + * **Details** + * + * This layer provides the `CurrentTimeZone` service with a fixed offset time zone. + * + * **Example** (Providing fixed-offset time zone layers) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * // Create a layer for UTC+3 + * const layer = DateTime.layerCurrentZoneOffset(3 * 60 * 60 * 1000) + * + * const program = Effect.gen(function*() { + * const now = yield* DateTime.nowInCurrentZone + * return DateTime.zoneToString(now.zone) + * }) + * + * await Effect.runPromise(Effect.provide(program, layer)) // => "+03:00" + * ``` + * + * @category layers + * @since 3.6.0 + */ +export const layerCurrentZoneOffset = (offset: number): Layer.Layer => + Layer.succeed(CurrentTimeZone)(Internal.zoneMakeOffset(offset)) + +/** + * Create a Layer from the given IANA time zone identifier. + * + * **Details** + * + * This layer provides the `CurrentTimeZone` service with a named time zone. + * If the time zone identifier is invalid, the layer will fail. + * + * **Example** (Providing named time zone layers) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * const layer = DateTime.layerCurrentZoneNamed("Europe/London") + * + * const program = Effect.gen(function*() { + * const now = yield* DateTime.nowInCurrentZone + * return DateTime.zoneToString(now.zone) + * }) + * + * await Effect.runPromise(Effect.provide(program, layer)) // => "Europe/London" + * ``` + * + * @category layers + * @since 3.6.0 + */ +export const layerCurrentZoneNamed: (zoneId: string) => Layer.Layer< + CurrentTimeZone, + IllegalArgumentError +> = flow(Internal.zoneMakeNamedEffect, Layer.effect(CurrentTimeZone)) + +/** + * Create a Layer from the system's local time zone. + * + * **Details** + * + * This layer provides the `CurrentTimeZone` service using the system's + * configured local time zone. + * + * **Example** (Providing local time zone layers) + * + * ```ts import.meta.vitest + * import { DateTime, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const now = yield* DateTime.nowInCurrentZone + * return DateTime.isZoned(now) + * }) + * + * // Use the system's local time zone + * await Effect.runPromise(Effect.provide(program, DateTime.layerCurrentZoneLocal)) // => true + * ``` + * + * @category layers + * @since 3.6.0 + */ +export const layerCurrentZoneLocal: Layer.Layer = Layer.sync(CurrentTimeZone)(zoneMakeLocal) diff --git a/.repos/effect/packages/effect/src/Deferred.ts b/.repos/effect/packages/effect/src/Deferred.ts new file mode 100644 index 000000000..afe2ab3a9 --- /dev/null +++ b/.repos/effect/packages/effect/src/Deferred.ts @@ -0,0 +1,920 @@ +/** + * One-time coordination cells for Effect programs. A `Deferred` starts + * empty, can be completed exactly once with a success, failure, defect, or + * interruption, and lets any number of fibers wait for that result. Awaiting a + * `Deferred` suspends the fiber instead of blocking an operating-system thread, + * and every waiter observes the same completion. + * + * @since 2.0.0 + */ +import type * as Cause from "./Cause.ts" +import type { Effect } from "./Effect.ts" +import type * as Exit from "./Exit.ts" +import { dual, identity, type LazyArg } from "./Function.ts" +import * as core from "./internal/core.ts" +import * as internalEffect from "./internal/effect.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 * as Types from "./Types.ts" + +const TypeId = "~effect/Deferred" + +/** + * A `Deferred` represents an asynchronous variable that can be set exactly + * once, with the ability for an arbitrary number of fibers to suspend (by + * calling `Deferred.await`) and automatically resume when the variable is set. + * + * **When to use** + * + * Use to coordinate multiple fibers around a value or failure that will be + * supplied exactly once. + * + * **Example** (Creating a Deferred for inter-fiber communication) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred: Deferred.Deferred = yield* Deferred.make() + * const producer = yield* Effect.forkChild( + * Effect.gen(function*() { + * yield* Deferred.succeed(deferred, "Hello, World!") + * }) + * ) + * + * const consumer = yield* Effect.forkChild(Deferred.await(deferred)) + * yield* Fiber.join(producer) + * return yield* Fiber.join(consumer) + * }) + * + * await Effect.runPromise(program) // => "Hello, World!" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Deferred extends Deferred.Variance, Pipeable { + effect?: Effect + resumes?: Array<(effect: Effect) => void> | undefined +} + +/** + * Checks whether a value is a `Deferred`. + * + * **When to use** + * + * Use to validate unknown values at runtime boundaries before treating them as + * `Deferred` values. + * + * @category guards + * @since 4.0.0 + */ +export const isDeferred = (u: unknown): u is Deferred => hasProperty(u, TypeId) + +/** + * Companion namespace containing type-level metadata for `Deferred`. + * + * **When to use** + * + * Use to reference type-level metadata associated with `Deferred`. + * + * @since 2.0.0 + */ +export declare namespace Deferred { + /** + * Type-level variance marker for the value and error channels of `Deferred`. + * + * **When to use** + * + * Use to carry the value and error type parameters for `Deferred` in Effect's + * type machinery. + * + * **Details** + * + * This interface is part of the public type structure and is not intended to + * be constructed directly. + * + * @category models + * @since 2.0.0 + */ + export interface Variance { + readonly [TypeId]: { + readonly _A: Types.Invariant + readonly _E: Types.Invariant + } + } +} + +const DeferredProto = { + [TypeId]: { + _A: identity, + _E: identity + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +/** + * Creates an empty `Deferred` synchronously outside the `Effect` runtime. + * + * **When to use** + * + * Use to allocate a `Deferred` synchronously when direct allocation outside + * `Effect` is required. + * + * **Example** (Creating a Deferred unsafely) + * + * ```ts import.meta.vitest + * import { Deferred } from "effect" + * + * const deferred = Deferred.makeUnsafe() + * 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 +} + +/** + * Creates a new `Deferred`. + * + * **When to use** + * + * Use to allocate an empty `Deferred` inside an `Effect` workflow. + * + * **Example** (Creating a Deferred) + * + * ```ts import.meta.vitest + * import { Deferred, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* Deferred.make() + * yield* Deferred.succeed(deferred, 42) + * return yield* Deferred.await(deferred) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (): Effect> => internalEffect.sync(() => makeUnsafe()) + +const _await = (self: Deferred): Effect => + internalEffect.callback((resume) => { + if (self.effect) return resume(self.effect) + self.resumes ??= [] + self.resumes.push(resume) + return internalEffect.sync(() => { + // 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) + }) + }) + +export { + /** + * Retrieves the value of the `Deferred`, suspending the fiber running the + * workflow until the result is available. + * + * **When to use** + * + * Use to wait for a `Deferred` to be completed and resume with its success, + * failure, defect, or interruption. + * + * **Details** + * + * Awaiters observe the completion effect stored in the `Deferred`. + * + * **Example** (Awaiting a Deferred value) + * + * ```ts import.meta.vitest + * import { Deferred, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* Deferred.make() + * yield* Deferred.succeed(deferred, 42) + * + * return yield* Deferred.await(deferred) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @see {@link complete} for completing from an effect and memoizing its result + * @see {@link completeWith} for completing with an effect directly + * + * @category getters + * @since 2.0.0 + */ + _await as await +} + +/** + * Runs the supplied `Effect` and attempts to complete the `Deferred` with its + * memoized result. + * + * **When to use** + * + * Use when completing a `Deferred` should run an effect once and share its + * result with all awaiters. + * + * **Details** + * + * The returned effect succeeds with `true` when this call completed the + * `Deferred`, or `false` if it was already completed. + * + * **Example** (Completing a Deferred from an effect) + * + * ```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)) + * const value = yield* Deferred.await(deferred) + * return [completed, value] + * }) + * + * await Effect.runPromise(program) // => [true, 42] + * ``` + * + * @see {@link completeWith} for storing an effect directly without memoizing its result + * + * @category completion + * @since 2.0.0 + */ +export const complete: { + (effect: Effect): (self: Deferred) => Effect + (self: Deferred, effect: Effect): Effect +} = dual( + 2, + (self: Deferred, effect: Effect): Effect => + internalEffect.suspend(() => self.effect ? internalEffect.succeed(false) : into(effect, self)) +) + +/** + * Attempts to complete the `Deferred` with the specified effect directly. + * + * **When to use** + * + * Use to store an already environment-free effect as the completion without + * running it during completion. + * + * **Details** + * + * The returned effect succeeds with `true` when this call completed the + * `Deferred`, or `false` if it was already completed. + * + * **Gotchas** + * + * The supplied effect is not memoized by `completeWith`; each awaiter may run + * the stored effect independently. + * + * **Example** (Completing a Deferred with an effect) + * + * ```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)) + * const value = yield* Deferred.await(deferred) + * return [completed, value] + * }) + * + * await Effect.runPromise(program) // => [true, 42] + * ``` + * + * @see {@link complete} for running an effect once and sharing its result + * @see {@link done} for completing from an already computed `Exit` + * + * @category completion + * @since 2.0.0 + */ +export const completeWith: { + (effect: Effect): (self: Deferred) => Effect + (self: Deferred, effect: Effect): Effect +} = dual( + 2, + (self: Deferred, effect: Effect): Effect => + internalEffect.sync(() => doneUnsafe(self, effect)) +) + +/** + * Completes the `Deferred` with the specified `Exit` value, which will be + * propagated to all fibers waiting on the value of the `Deferred`. + * + * **When to use** + * + * Use to complete a `Deferred` from an already computed `Exit`. + * + * **Details** + * + * The returned effect succeeds with `true` when this call completed the + * `Deferred`, or `false` if it was already completed. + * + * **Example** (Completing a Deferred with an Exit) + * + * ```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)) + * 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 + * @see {@link completeWith} for storing an effect directly + * @see {@link succeed} for completing with a success value + * @see {@link failCause} for completing with a failure cause + * + * @category completion + * @since 2.0.0 + */ +export const done: { + (exit: Exit.Exit): (self: Deferred) => Effect + (self: Deferred, exit: Exit.Exit): Effect +} = completeWith as any + +/** + * Attempts to complete the `Deferred` with the specified error. + * + * **When to use** + * + * Use to complete a `Deferred` with a typed failure value. + * + * **Details** + * + * Fibers waiting on the `Deferred` fail with that error only if this call + * completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Failing a Deferred with an error) + * + * ```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") + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Operation failed")] + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const fail: { + (error: E): (self: Deferred) => Effect + (self: Deferred, error: E): Effect +} = dual(2, (self: Deferred, error: E): Effect => done(self, core.exitFail(error))) + +/** + * Computes an error when the returned effect is run, then attempts to complete + * the `Deferred` with that error. + * + * **When to use** + * + * Use to lazily compute a typed failure value when the `Deferred` completion + * effect runs. + * + * **Details** + * + * Fibers waiting on the `Deferred` fail with the computed error only if this + * call completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Failing a Deferred with a lazy error) + * + * ```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") + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Lazy error")] + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const failSync: { + (evaluate: LazyArg): (self: Deferred) => Effect + (self: Deferred, evaluate: LazyArg): Effect +} = dual( + 2, + (self: Deferred, evaluate: LazyArg): Effect => + internalEffect.suspend(() => fail(self, evaluate())) +) + +/** + * Attempts to complete the `Deferred` with the specified `Cause`. + * + * **When to use** + * + * Use to complete a `Deferred` with a full failure cause. + * + * **Details** + * + * Fibers waiting on the `Deferred` observe that cause only if this call + * completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Failing a Deferred with a Cause) + * + * ```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")) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Operation failed"))] + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const failCause: { + (cause: Cause.Cause): (self: Deferred) => Effect + (self: Deferred, cause: Cause.Cause): Effect +} = dual( + 2, + (self: Deferred, cause: Cause.Cause): Effect => done(self, core.exitFailCause(cause)) +) + +/** + * Computes a `Cause` when the returned effect is run, then attempts to + * complete the `Deferred` with that cause. + * + * **When to use** + * + * Use to lazily compute a full failure cause when the `Deferred` completion + * effect runs. + * + * **Details** + * + * Fibers waiting on the `Deferred` observe the computed cause only if this + * call completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Failing a Deferred with a lazy Cause) + * + * ```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")) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Lazy error"))] + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const failCauseSync: { + (evaluate: LazyArg>): (self: Deferred) => Effect + (self: Deferred, evaluate: LazyArg>): Effect +} = dual( + 2, + (self: Deferred, evaluate: LazyArg>): Effect => + internalEffect.suspend(() => failCause(self, evaluate())) +) + +/** + * Attempts to complete the `Deferred` with a defect. + * + * **When to use** + * + * Use to complete a `Deferred` with an unexpected defect. + * + * **Details** + * + * Fibers waiting on the `Deferred` die with that defect only if this call + * completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Killing a Deferred with a defect) + * + * ```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, defect) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.die(defect)] + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const die: { + (defect: unknown): (self: Deferred) => Effect + (self: Deferred, defect: unknown): Effect +} = dual(2, (self: Deferred, defect: unknown): Effect => done(self, core.exitDie(defect))) + +/** + * Computes a defect when the returned effect is run, then attempts to complete + * the `Deferred` with that defect. + * + * **When to use** + * + * Use to lazily compute an unexpected defect when the completion effect runs. + * + * **Details** + * + * Fibers waiting on the `Deferred` die with the computed defect only if this + * call completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Killing a Deferred with a lazy defect) + * + * ```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, () => defect) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.die(defect)] + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const dieSync: { + (evaluate: LazyArg): (self: Deferred) => Effect + (self: Deferred, evaluate: LazyArg): Effect +} = dual( + 2, + (self: Deferred, evaluate: LazyArg): Effect => + internalEffect.suspend(() => die(self, evaluate())) +) + +/** + * Attempts to complete the `Deferred` with interruption by the current fiber. + * + * **When to use** + * + * Use to complete a `Deferred` as interrupted by the current fiber. + * + * **Details** + * + * Fibers waiting on the `Deferred` are interrupted with the current fiber id + * only if this call completes it. The returned effect succeeds with `true` + * when this call completed the `Deferred`, or `false` if it was already + * completed. + * + * **Example** (Interrupting a Deferred) + * + * ```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) + * 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 + * @since 2.0.0 + */ +export const interrupt = (self: Deferred): Effect => + core.withFiber((fiber) => interruptWith(self, fiber.id)) + +/** + * Attempts to complete the `Deferred` with interruption by the specified + * `FiberId`. + * + * **When to use** + * + * Use to complete a `Deferred` as interrupted by a specific fiber id. + * + * **Details** + * + * Fibers waiting on the `Deferred` are interrupted with that fiber id only if + * this call completes it. The returned effect succeeds with `true` when this + * call completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Interrupting a Deferred with a fiber id) + * + * ```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) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.interrupt(42)] + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const interruptWith: { + (fiberId: number): (self: Deferred) => Effect + (self: Deferred, fiberId: number): Effect +} = dual( + 2, + (self: Deferred, fiberId: number): Effect => + failCause(self, internalEffect.causeInterrupt(fiberId)) +) + +/** + * Returns `true` if this `Deferred` has already been completed with a value or + * an error, `false` otherwise. + * + * **When to use** + * + * Use to check completion status inside an `Effect` workflow. + * + * **Example** (Checking Deferred completion) + * + * ```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) + * yield* Deferred.succeed(deferred, 42) + * const afterCompletion = yield* Deferred.isDone(deferred) + * return [beforeCompletion, afterCompletion] + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isDone = (self: Deferred): Effect => internalEffect.sync(() => isDoneUnsafe(self)) + +/** + * Returns whether this `Deferred` has already been completed synchronously. + * + * **When to use** + * + * Use to check `Deferred` completion synchronously in code that cannot return + * an `Effect`, such as low-level integration code. + * + * @see {@link isDone} for checking completion inside `Effect` + * @see {@link poll} for reading the completed effect when available + * + * @category predicates + * @since 4.0.0 + */ +export const isDoneUnsafe = (self: Deferred): boolean => self.effect !== undefined + +/** + * Returns the current completion effect as an `Option`. This returns + * `Option.some(effect)` when the `Deferred` is completed, `Option.none()` + * otherwise. + * + * **When to use** + * + * Use to inspect whether a `Deferred` is already completed and retrieve its + * stored completion effect when available. + * + * **Example** (Polling Deferred completion) + * + * ```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) + * yield* Deferred.succeed(deferred, 42) + * const afterCompletion = yield* Deferred.poll(deferred) + * const afterValue = yield* Effect.transposeOption(afterCompletion) + * return [beforeCompletion, afterValue] + * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export function poll(self: Deferred): Effect>> { + return internalEffect.sync(() => Option.fromUndefinedOr(self.effect)) +} + +/** + * Attempts to complete the `Deferred` with the specified value. + * + * **When to use** + * + * Use to complete a `Deferred` with a successful value. + * + * **Details** + * + * Fibers waiting on the `Deferred` receive the value only if this call + * completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Completing a Deferred with a value) + * + * ```ts import.meta.vitest + * import { Deferred, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* Deferred.make() + * yield* Deferred.succeed(deferred, 42) + * + * return yield* Deferred.await(deferred) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const succeed: { + (value: A): (self: Deferred) => Effect + (self: Deferred, value: A): Effect +} = dual(2, (self: Deferred, value: A): Effect => done(self, core.exitSucceed(value))) + +/** + * Computes a value when the returned effect is run, then attempts to complete + * the `Deferred` with that value. + * + * **When to use** + * + * Use to lazily compute a successful value when the `Deferred` completion + * effect runs. + * + * **Details** + * + * Fibers waiting on the `Deferred` receive the computed value only if this call + * completes it. The returned effect succeeds with `true` when this call + * completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Completing a Deferred with a lazy value) + * + * ```ts import.meta.vitest + * import { Deferred, Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* Deferred.make() + * yield* Deferred.sync(deferred, () => 42) + * return yield* Deferred.await(deferred) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const sync: { + (evaluate: LazyArg): (self: Deferred) => Effect + (self: Deferred, evaluate: LazyArg): Effect +} = dual( + 2, + (self: Deferred, evaluate: LazyArg): Effect => + internalEffect.suspend(() => succeed(self, evaluate())) +) + +/** + * Attempts to complete the `Deferred` synchronously with the specified + * completion effect. + * + * **When to use** + * + * Use to complete a `Deferred` synchronously in low-level code that already has + * the completion effect. + * + * **Details** + * + * This mutates the `Deferred` directly and should be reserved for low-level + * code; prefer the effectful completion APIs when possible. Returns `true` if + * this call completed the `Deferred`, or `false` if it was already completed. + * + * **Example** (Completing a Deferred unsafely) + * + * ```ts import.meta.vitest + * import { Deferred, Effect } from "effect" + * + * const deferred = Deferred.makeUnsafe() + * Deferred.doneUnsafe(deferred, Effect.succeed(42)) // => true + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const doneUnsafe = (self: Deferred, effect: Effect): boolean => { + if (self.effect) return false + self.effect = effect + if (self.resumes) { + // 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 +} + +/** + * Runs an `Effect` and attempts to complete a `Deferred` with the effect's + * result. + * + * **When to use** + * + * Use to pipe an effect result into a `Deferred` while preserving success, + * failure, defects, and interruption. + * + * **Details** + * + * If the effect succeeds, fails, dies, or is interrupted, that result is used + * as the attempted completion. The returned effect cannot fail; it succeeds + * with `true` if it completed the `Deferred`, or `false` if the `Deferred` was + * already completed. + * + * **Example** (Completing a Deferred from an effect result) + * + * ```ts import.meta.vitest + * import { Deferred, Effect } from "effect" + * + * const successEffect = Effect.succeed(42) + * + * const program = Effect.gen(function*() { + * const deferred = yield* Deferred.make() + * const isCompleted = yield* Deferred.into(successEffect, deferred) + * const value = yield* Deferred.await(deferred) + * return [isCompleted, value] + * }) + * + * await Effect.runPromise(program) // => [true, 42] + * ``` + * + * @category completion + * @since 4.0.0 + */ +export const into: { + (deferred: Deferred): (self: Effect) => Effect + (self: Effect, deferred: Deferred): Effect +} = dual( + 2, + (self: Effect, deferred: Deferred): Effect => + internalEffect.uninterruptibleMask((restore) => + internalEffect.flatMap( + internalEffect.exit(restore(self)), + (exit) => done(deferred, exit) + ) + ) +) diff --git a/.repos/effect/packages/effect/src/Differ.ts b/.repos/effect/packages/effect/src/Differ.ts new file mode 100644 index 000000000..ba636ba64 --- /dev/null +++ b/.repos/effect/packages/effect/src/Differ.ts @@ -0,0 +1,32 @@ +/** + * Interfaces for describing changes to a value as patches. A + * `Differ` compares an old and new value, creates a patch for the + * difference, combines patches in order, and applies a patch to an old value to + * produce an updated value. + * + * @since 4.0.0 + */ + +/** + * Describes how to compute, combine, and apply patches for values of type `T`. + * + * **When to use** + * + * Use to model patch-based updates for a value type when callers need to + * compute a patch from two values, combine patches, and apply a patch later. + * + * **Details** + * + * A `Differ` provides an empty patch, computes the patch between two values, + * combines patches, and applies a patch to an old value to produce an updated + * value. + * + * @category models + * @since 2.0.0 + */ +export interface Differ { + readonly empty: Patch + diff(oldValue: T, newValue: T): Patch + combine(first: Patch, second: Patch): Patch + patch(oldValue: T, patch: Patch): T +} diff --git a/.repos/effect/packages/effect/src/Duration.ts b/.repos/effect/packages/effect/src/Duration.ts new file mode 100644 index 000000000..4f4e7fa1e --- /dev/null +++ b/.repos/effect/packages/effect/src/Duration.ts @@ -0,0 +1,1798 @@ +/** + * Represents immutable spans of time. + * + * A `Duration` can be finite, positive infinity, or negative infinity. It is + * the standard representation for delays, timeouts, intervals, and + * time-to-live values across Effect APIs. This module includes constructors + * from common input shapes, unit conversions, comparisons, arithmetic, + * formatting, and reusable reducer or combiner helpers. + * + * @since 2.0.0 + */ +import * as Combiner from "./Combiner.ts" +import * as Equal from "./Equal.ts" +import type * as Equ from "./Equivalence.ts" +import { dual, identity } from "./Function.ts" +import * as Hash from "./Hash.ts" +import type * as Inspectable from "./Inspectable.ts" +import { NodeInspectSymbol } from "./Inspectable.ts" +import * as Option from "./Option.ts" +import * as order from "./Order.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty, isNumber } from "./Predicate.ts" +import * as Reducer from "./Reducer.ts" + +const TypeId = "~effect/time/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) +const bigint1e6 = BigInt(1_000_000) +const bigint1e9 = BigInt(1_000_000_000) + +const roundTiesAwayFromZero = (input: number): bigint => + BigInt(input < 0 ? Math.ceil(input - 0.5) : Math.floor(input + 0.5)) + +const roundMillisToNanos = (millis: number): bigint => roundTiesAwayFromZero(millis * 1_000_000) + +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 + const absolute = nanos < bigint0 ? -nanos : nanos + return [ + Number(sign * (absolute / bigint1e9)), + Number(sign * (absolute % bigint1e9)) + ] +} + +/** + * Represents a span of time with high precision, supporting operations from + * nanoseconds to weeks. + * + * **When to use** + * + * Use to model elapsed time, delays, timeouts, schedule intervals, and cache + * TTLs as immutable duration values. + * + * @see {@link Input} for values accepted by APIs that decode duration-like + * inputs + * @see {@link DurationValue} for the tagged representation exposed by the + * `value` field + * + * @category models + * @since 2.0.0 + */ +export interface Duration extends Equal.Equal, Pipeable, Inspectable.Inspectable { + readonly [TypeId]: typeof TypeId + readonly value: DurationValue +} + +/** + * Tagged representation of a `Duration` value. + * + * **When to use** + * + * Use when modeling or inspecting the exact tagged representation stored in a + * `Duration`, including finite millisecond or nanosecond values and infinite + * sentinels. + * + * **Details** + * + * A duration is represented as milliseconds, nanoseconds, positive infinity, + * or negative infinity. + * + * @see {@link Duration} for the public type whose `value` field contains this + * representation + * @see {@link match} for pattern matching without reading `value` directly + * + * @category models + * @since 2.0.0 + */ +export type DurationValue = + | { _tag: "Millis"; millis: number } + | { _tag: "Nanos"; nanos: bigint } + | { _tag: "Infinity" } + | { _tag: "NegativeInfinity" } + +/** + * Valid time units that can be used in duration string representations. + * + * **When to use** + * + * Use when typing the unit portion of duration string inputs accepted by + * `Duration.Input`. + * + * @see {@link Input} for the full duration input union + * + * @category models + * @since 2.0.0 + */ +export type Unit = + | "nano" + | "nanos" + | "micro" + | "micros" + | "milli" + | "millis" + | "second" + | "seconds" + | "minute" + | "minutes" + | "hour" + | "hours" + | "day" + | "days" + | "week" + | "weeks" + +/** + * Valid input types that can be converted to a Duration. + * + * **When to use** + * + * Use when an API should accept any value that Effect can convert into a + * `Duration`, including existing durations, millisecond numbers, nanosecond + * bigints, high-resolution tuples, duration strings, infinity strings, or + * duration objects. + * + * **Details** + * + * String inputs accept values like `"10 seconds"`, `"500 millis"`, + * `"Infinity"`, and `"-Infinity"`. Finite fractional values that are + * normalized to nanoseconds are rounded to the nearest nanosecond, with ties + * away from zero. + * + * @see {@link fromInput} for safe conversion to `Option` + * @see {@link fromInputUnsafe} for throwing conversion + * @see {@link DurationObject} for object-shaped duration input + * @see {@link Unit} for supported string units + * + * @category models + * @since 4.0.0 + */ +export type Input = + | Duration + | number // millis + | bigint // nanos + | readonly [seconds: number, nanos: number] + | `${number} ${Unit}` + | "Infinity" + | "-Infinity" + | DurationObject + +/** + * An object with optional duration components that can be combined to create + * a Duration. All fields are optional and additive. + * + * **Details** + * + * Compatible with Temporal.Duration-like objects. + * + * **Example** (Combining duration object fields) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * 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 + * @since 4.0.0 + */ +export interface DurationObject { + readonly weeks?: number | undefined + readonly days?: number | undefined + readonly hours?: number | undefined + readonly minutes?: number | undefined + readonly seconds?: number | undefined + readonly milliseconds?: number | undefined + readonly microseconds?: number | undefined + readonly nanoseconds?: number | undefined +} + +const DURATION_REGEXP = /^(-?\d+(?:\.\d+)?)\s+(nanos?|micros?|millis?|seconds?|minutes?|hours?|days?|weeks?)$/ + +/** + * Decodes a `Duration.Input` into a `Duration`. + * + * **When to use** + * + * Use when the input has already been validated or comes from a trusted source + * and throwing is acceptable for invalid duration syntax. + * + * **Gotchas** + * + * If the input is not a valid `Duration.Input`, it throws an error. + * + * **Example** (Decoding duration inputs) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const fromInputUnsafe = (input: Input): Duration => { + switch (typeof input) { + case "number": + return millis(input) + case "bigint": + return nanos(input) + case "string": { + if (input === "Infinity") { + return infinity + } + if (input === "-Infinity") { + return negativeInfinity + } + const match = DURATION_REGEXP.exec(input) + if (!match) break + const [_, valueStr, unit] = match + if (unit === "nano" || unit === "nanos") { + return nanos(parseNanos(valueStr, bigint1)) + } + if (unit === "micro" || unit === "micros") { + return nanos(parseNanos(valueStr, bigint1e3)) + } + const value = Number(valueStr) + switch (unit) { + case "milli": + case "millis": + return millis(value) + case "second": + case "seconds": + return seconds(value) + case "minute": + case "minutes": + return minutes(value) + case "hour": + case "hours": + return hours(value) + case "day": + case "days": + return days(value) + case "week": + case "weeks": + return weeks(value) + } + break + } + case "object": { + if (input === null) break + if (TypeId in input) return input as Duration + if (Array.isArray(input)) { + if (input.length !== 2 || !input.every(isNumber)) { + return invalid(input) + } + if (Number.isNaN(input[0]) || Number.isNaN(input[1])) { + return zero + } + if (input[0] === -Infinity || input[1] === -Infinity) { + return negativeInfinity + } + if (input[0] === Infinity || input[1] === Infinity) { + return infinity + } + return make(roundTiesAwayFromZero(input[0] * 1_000_000_000 + input[1])) + } + const obj = input as DurationObject + let millis = 0 + // we can use truthy checks here, because 0 can be ignored + if (obj.weeks) millis += obj.weeks * 604_800_000 + if (obj.days) millis += obj.days * 86_400_000 + if (obj.hours) millis += obj.hours * 3_600_000 + if (obj.minutes) millis += obj.minutes * 60_000 + if (obj.seconds) millis += obj.seconds * 1_000 + if (obj.milliseconds) millis += obj.milliseconds + if (!obj.microseconds && !obj.nanoseconds) return make(millis) + return make(roundTiesAwayFromZero( + millis * 1_000_000 + (obj.microseconds ?? 0) * 1_000 + (obj.nanoseconds ?? 0) + )) + } + } + return invalid(input) +} + +const invalid = (input: unknown): never => { + throw new Error(`Invalid Input: ${input}`) +} + +/** + * Decodes a `Input` value into a `Duration` safely, returning + * `Option.none()` if decoding fails. + * + * **Example** (Safely decoding duration inputs) + * + * ```ts import.meta.vitest + * import { Duration, Option } from "effect" + * + * Duration.fromInput(1000) // => Option.some(Duration.seconds(1)) + * Duration.fromInput("invalid" as any) // => Option.none() + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromInput: (u: Input) => Option.Option = Option.liftThrowable( + fromInputUnsafe +) + +const zeroDurationValue: DurationValue = { _tag: "Millis", millis: 0 } +const infinityDurationValue: DurationValue = { _tag: "Infinity" } +const negativeInfinityDurationValue: DurationValue = { _tag: "NegativeInfinity" } + +const DurationProto: Omit = { + [TypeId]: TypeId, + [Hash.symbol](this: Duration) { + // 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) + }, + toString(this: Duration) { + switch (this.value._tag) { + case "Infinity": + return "Infinity" + case "NegativeInfinity": + return "-Infinity" + case "Nanos": + return `${this.value.nanos} nanos` + case "Millis": + return `${this.value.millis} millis` + } + }, + toJSON(this: Duration) { + switch (this.value._tag) { + case "Millis": + return { _id: "Duration", _tag: "Millis", millis: this.value.millis } + case "Nanos": + return { _id: "Duration", _tag: "Nanos", nanos: String(this.value.nanos) } + case "Infinity": + return { _id: "Duration", _tag: "Infinity" } + case "NegativeInfinity": + return { _id: "Duration", _tag: "NegativeInfinity" } + } + }, + [NodeInspectSymbol]() { + return this.toJSON() + }, + pipe() { + return pipeArguments(this, arguments) + } +} as const + +const make = (input: number | bigint): Duration => { + const duration = Object.create(DurationProto) + if (typeof input === "number") { + if (isNaN(input) || input === 0 || Object.is(input, -0)) { + duration.value = zeroDurationValue + } else if (!Number.isFinite(input)) { + duration.value = input > 0 ? infinityDurationValue : negativeInfinityDurationValue + } else if (!Number.isInteger(input)) { + duration.value = { _tag: "Nanos", nanos: roundMillisToNanos(input) } + } else { + duration.value = { _tag: "Millis", millis: input } + } + } else if (input === bigint0) { + duration.value = zeroDurationValue + } else { + duration.value = { _tag: "Nanos", nanos: input } + } + return duration +} + +/** + * Checks whether a value is a Duration. + * + * **Example** (Checking for durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isDuration(Duration.seconds(1)) // => true + * Duration.isDuration(1000) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isDuration = (u: unknown): u is Duration => hasProperty(u, TypeId) + +/** + * Checks whether a Duration is finite (not infinite). + * + * **Example** (Checking finite durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isFinite(Duration.seconds(5)) // => true + * Duration.isFinite(Duration.infinity) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isFinite = (self: Duration): boolean => + self.value._tag !== "Infinity" && self.value._tag !== "NegativeInfinity" + +/** + * Checks whether a Duration is zero. + * + * **Example** (Checking for zero durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isZero(Duration.zero) // => true + * Duration.isZero(Duration.seconds(1)) // => false + * ``` + * + * @category predicates + * @since 3.5.0 + */ +export const isZero = (self: Duration): boolean => { + switch (self.value._tag) { + case "Millis": + return self.value.millis === 0 + case "Nanos": + return self.value.nanos === bigint0 + case "Infinity": + case "NegativeInfinity": + return false + } +} + +/** + * Returns `true` if the duration is negative (strictly less than zero). + * + * **Example** (Checking for negative durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isNegative(Duration.seconds(-5)) // => true + * Duration.isNegative(Duration.zero) // => false + * Duration.isNegative(Duration.negativeInfinity) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isNegative = (self: Duration): boolean => { + switch (self.value._tag) { + case "Millis": + return self.value.millis < 0 + case "Nanos": + return self.value.nanos < bigint0 + case "NegativeInfinity": + return true + case "Infinity": + return false + } +} + +/** + * Returns `true` if the duration is positive (strictly greater than zero). + * + * **Example** (Checking for positive durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isPositive(Duration.seconds(5)) // => true + * Duration.isPositive(Duration.zero) // => false + * Duration.isPositive(Duration.infinity) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isPositive = (self: Duration): boolean => { + switch (self.value._tag) { + case "Millis": + return self.value.millis > 0 + case "Nanos": + return self.value.nanos > bigint0 + case "Infinity": + return true + case "NegativeInfinity": + return false + } +} + +/** + * Returns the absolute value of the duration. + * + * **Example** (Taking absolute duration values) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.abs(Duration.seconds(-5)) // => Duration.seconds(5) + * Duration.abs(Duration.negativeInfinity) // => Duration.infinity + * ``` + * + * @category math + * @since 4.0.0 + */ +export const abs = (self: Duration): Duration => { + switch (self.value._tag) { + case "Infinity": + case "NegativeInfinity": + return infinity + case "Millis": + return self.value.millis < 0 ? make(-self.value.millis) : self + case "Nanos": + return self.value.nanos < bigint0 ? make(-self.value.nanos) : self + } +} + +/** + * Returns the negated duration. + * + * **Example** (Negating durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.negate(Duration.seconds(5)) // => Duration.seconds(-5) + * Duration.negate(Duration.infinity) // => Duration.negativeInfinity + * ``` + * + * @category math + * @since 4.0.0 + */ +export const negate = (self: Duration): Duration => { + switch (self.value._tag) { + case "Infinity": + return negativeInfinity + case "NegativeInfinity": + return infinity + case "Millis": + return self.value.millis === 0 ? self : make(-self.value.millis) + case "Nanos": + return self.value.nanos === bigint0 ? self : make(-self.value.nanos) + } +} + +/** + * A Duration representing zero time. + * + * **Example** (Referencing the zero duration) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.zero) // => 0 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const zero: Duration = make(0) + +/** + * A Duration representing infinite time. + * + * **Example** (Referencing infinite duration) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.infinity) // => Infinity + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const infinity: Duration = make(Infinity) + +/** + * A Duration representing negative infinite time. + * + * **Example** (Referencing negative infinite duration) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.negativeInfinity) // => -Infinity + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const negativeInfinity: Duration = make(-Infinity) + +/** + * Creates a Duration from nanoseconds. + * + * **Example** (Creating durations from nanoseconds) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.nanos(500_000_000n) // => Duration.nanos(500_000_000n) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const nanos = (nanos: bigint): Duration => make(nanos) + +/** + * Creates a Duration from microseconds. + * + * **Example** (Creating durations from microseconds) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.micros(500_000n) // => Duration.nanos(500_000_000n) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const micros = (micros: bigint): Duration => make(micros * bigint1e3) + +/** + * Creates a Duration from milliseconds. + * + * **Example** (Creating durations from milliseconds) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.millis(1000)) // => 1000 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const millis = (millis: number): Duration => make(millis) + +/** + * Creates a Duration from seconds. + * + * **Example** (Creating durations from seconds) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.seconds(30)) // => 30_000 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const seconds = (seconds: number): Duration => make(seconds * 1000) + +/** + * Creates a Duration from minutes. + * + * **Example** (Creating durations from minutes) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.minutes(5)) // => 300_000 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const minutes = (minutes: number): Duration => make(minutes * 60_000) + +/** + * Creates a Duration from hours. + * + * **Example** (Creating durations from hours) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.hours(2)) // => 7_200_000 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const hours = (hours: number): Duration => make(hours * 3_600_000) + +/** + * Creates a Duration from days. + * + * **Example** (Creating durations from days) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.days(1)) // => 86_400_000 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const days = (days: number): Duration => make(days * 86_400_000) + +/** + * Creates a Duration from weeks. + * + * **Example** (Creating durations from weeks) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.weeks(1)) // => 604_800_000 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const weeks = (weeks: number): Duration => make(weeks * 604_800_000) + +/** + * Converts a Duration to milliseconds. + * + * **Example** (Converting durations to milliseconds) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMillis(Duration.seconds(5)) // => 5000 + * Duration.toMillis(Duration.minutes(2)) // => 120_000 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const toMillis = (self: Input): number => + match(fromInputUnsafe(self), { + onMillis: identity, + onNanos: (nanos) => Number(nanos) / 1_000_000, + onInfinity: () => Infinity, + onNegativeInfinity: () => -Infinity + }) + +/** + * Converts a Duration to seconds. + * + * **Example** (Converting durations to seconds) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toSeconds(Duration.millis(5000)) // => 5 + * Duration.toSeconds(Duration.minutes(2)) // => 120 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const toSeconds = (self: Input): number => + match(fromInputUnsafe(self), { + onMillis: (millis) => millis / 1_000, + onNanos: (nanos) => Number(nanos) / 1_000_000_000, + onInfinity: () => Infinity, + onNegativeInfinity: () => -Infinity + }) + +/** + * Converts a Duration to minutes. + * + * **Example** (Converting durations to minutes) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toMinutes(Duration.seconds(120)) // => 2 + * Duration.toMinutes(Duration.hours(1)) // => 60 + * ``` + * + * @category getters + * @since 3.8.0 + */ +export const toMinutes = (self: Input): number => + match(fromInputUnsafe(self), { + onMillis: (millis) => millis / 60_000, + onNanos: (nanos) => Number(nanos) / 60_000_000_000, + onInfinity: () => Infinity, + onNegativeInfinity: () => -Infinity + }) + +/** + * Converts a Duration to hours. + * + * **Example** (Converting durations to hours) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toHours(Duration.minutes(120)) // => 2 + * Duration.toHours(Duration.days(1)) // => 24 + * ``` + * + * @category getters + * @since 3.8.0 + */ +export const toHours = (self: Input): number => + match(fromInputUnsafe(self), { + onMillis: (millis) => millis / 3_600_000, + onNanos: (nanos) => Number(nanos) / 3_600_000_000_000, + onInfinity: () => Infinity, + onNegativeInfinity: () => -Infinity + }) + +/** + * Converts a Duration to days. + * + * **Example** (Converting durations to days) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toDays(Duration.hours(48)) // => 2 + * Duration.toDays(Duration.weeks(1)) // => 7 + * ``` + * + * @category getters + * @since 3.8.0 + */ +export const toDays = (self: Input): number => + match(fromInputUnsafe(self), { + onMillis: (millis) => millis / 86_400_000, + onNanos: (nanos) => Number(nanos) / 86_400_000_000_000, + onInfinity: () => Infinity, + onNegativeInfinity: () => -Infinity + }) + +/** + * Converts a Duration to weeks. + * + * **Example** (Converting durations to weeks) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toWeeks(Duration.days(14)) // => 2 + * Duration.toWeeks(Duration.days(7)) // => 1 + * ``` + * + * @category getters + * @since 3.8.0 + */ +export const toWeeks = (self: Input): number => + match(fromInputUnsafe(self), { + onMillis: (millis) => millis / 604_800_000, + onNanos: (nanos) => Number(nanos) / 604_800_000_000_000, + onInfinity: () => Infinity, + onNegativeInfinity: () => -Infinity + }) + +/** + * Gets the duration in nanoseconds as a bigint. + * + * **When to use** + * + * Use when the duration is known to be finite and you need the nanosecond value + * as a `bigint`. + * + * **Details** + * + * Millisecond-backed fractional durations are rounded to the nearest + * nanosecond, with ties away from zero. + * + * **Gotchas** + * + * If the duration is infinite, it throws an error. + * + * **Example** (Reading nanoseconds unsafely) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toNanosUnsafe(Duration.seconds(2)) // => 2_000_000_000n + * + * // Duration.toNanosUnsafe(Duration.infinity) + * // throws Error: "Cannot convert infinite duration to nanos" + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const toNanosUnsafe = (input: Input): bigint => { + const self = fromInputUnsafe(input) + switch (self.value._tag) { + case "Infinity": + case "NegativeInfinity": + throw new Error("Cannot convert infinite duration to nanos") + case "Nanos": + return self.value.nanos + case "Millis": + return roundMillisToNanos(self.value.millis) + } +} + +/** + * Gets the duration in nanoseconds safely as an `Option`. + * + * **Details** + * + * If the duration is infinite, returns `Option.none()`. + * + * **Example** (Safely reading nanoseconds) + * + * ```ts import.meta.vitest + * import { Duration, Option } from "effect" + * + * Duration.toNanos(Duration.seconds(1)) // => Option.some(1_000_000_000n) + * Duration.toNanos(Duration.infinity) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const toNanos: (self: Input) => Option.Option = Option.liftThrowable(toNanosUnsafe) + +/** + * Converts a Duration to high-resolution time format [seconds, nanoseconds]. + * + * **Example** (Converting durations to high-resolution time) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.toHrTime(Duration.millis(1500)) // => [1, 500_000_000] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const toHrTime = (input: Input): [seconds: number, nanos: number] => { + const self = fromInputUnsafe(input) + switch (self.value._tag) { + case "Infinity": + return [Infinity, 0] + case "NegativeInfinity": + return [-Infinity, 0] + case "Nanos": + return nanosToHrTime(self.value.nanos) + case "Millis": + return nanosToHrTime(roundMillisToNanos(self.value.millis)) + } +} + +/** + * Pattern matches on the representation of a `Duration`. + * + * **Details** + * + * Provide handlers for millisecond-backed values, nanosecond-backed values, + * and positive infinity. Use `onNegativeInfinity` to handle negative infinity + * separately; otherwise negative infinity is handled by `onInfinity`. + * + * **Example** (Pattern matching on duration representations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.match(Duration.seconds(5), { + * onMillis: (millis) => `${millis} milliseconds`, + * onNanos: (nanos) => `${nanos} nanoseconds`, + * onInfinity: () => "infinite" + * }) // => "5000 milliseconds" + * ``` + * + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + ( + options: { + readonly onMillis: (millis: number) => A + readonly onNanos: (nanos: bigint) => B + readonly onInfinity: () => C + readonly onNegativeInfinity?: () => D + } + ): (self: Duration) => A | B | C | D + ( + self: Duration, + options: { + readonly onMillis: (millis: number) => A + readonly onNanos: (nanos: bigint) => B + readonly onInfinity: () => C + readonly onNegativeInfinity?: () => D + } + ): A | B | C | D +} = dual(2, ( + self: Duration, + options: { + readonly onMillis: (millis: number) => A + readonly onNanos: (nanos: bigint) => B + readonly onInfinity: () => C + readonly onNegativeInfinity?: () => D + } +): A | B | C | D => { + switch (self.value._tag) { + case "Millis": + return options.onMillis(self.value.millis) + case "Nanos": + return options.onNanos(self.value.nanos) + case "Infinity": + return options.onInfinity() + case "NegativeInfinity": + return (options.onNegativeInfinity ?? options.onInfinity as unknown as () => D)() + } +}) + +/** + * Pattern matches on two `Duration`s, providing handlers that receive both values. + * + * **Example** (Pattern matching on duration pairs) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.matchPair(Duration.seconds(3), Duration.seconds(2), { + * onMillis: (a, b) => a + b, + * onNanos: (a, b) => Number(a + b), + * onInfinity: () => Infinity + * }) // => 5000 + * ``` + * + * @category pattern matching + * @since 4.0.0 + */ +export const matchPair: { + ( + that: Duration, + options: { + readonly onMillis: (self: number, that: number) => A + readonly onNanos: (self: bigint, that: bigint) => B + readonly onInfinity: (self: Duration, that: Duration) => C + } + ): (self: Duration) => A | B | C + ( + self: Duration, + that: Duration, + options: { + readonly onMillis: (self: number, that: number) => A + readonly onNanos: (self: bigint, that: bigint) => B + readonly onInfinity: (self: Duration, that: Duration) => C + } + ): A | B | C +} = dual(3, ( + self: Duration, + that: Duration, + options: { + readonly onMillis: (self: number, that: number) => A + readonly onNanos: (self: bigint, that: bigint) => B + readonly onInfinity: (self: Duration, that: Duration) => C + } +): A | B | C => { + if ( + self.value._tag === "Infinity" || self.value._tag === "NegativeInfinity" || + that.value._tag === "Infinity" || that.value._tag === "NegativeInfinity" + ) return options.onInfinity(self, that) + if (self.value._tag === "Millis") { + return that.value._tag === "Millis" + ? options.onMillis(self.value.millis, that.value.millis) + : options.onNanos(toNanosUnsafe(self), that.value.nanos) + } else { + return options.onNanos(self.value.nanos, toNanosUnsafe(that)) + } +}) + +/** + * Provides an `Order` instance for comparing `Duration` values. + * + * **Details** + * + * `NegativeInfinity` < any finite value < `Infinity`. + * + * **Example** (Sorting durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * const durations = [ + * Duration.seconds(3), + * Duration.seconds(1), + * Duration.seconds(2) + * ] + * durations.sort((a, b) => Duration.Order(a, b)).map(Duration.toSeconds) // => [1, 2, 3] + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Order: order.Order = order.make((self, that) => + matchPair(self, that, { + onMillis: (self, that) => (self < that ? -1 : self > that ? 1 : 0), + onNanos: (self, that) => (self < that ? -1 : self > that ? 1 : 0), + onInfinity: (self, that) => { + if (self.value._tag === that.value._tag) return 0 + if (self.value._tag === "Infinity") return 1 + if (self.value._tag === "NegativeInfinity") return -1 + // self is finite + if (that.value._tag === "Infinity") return -1 + // that is NegativeInfinity + return 1 + } + }) +) + +/** + * Returns `true` if a `Duration` is greater than or equal to `minimum` and + * less than or equal to `maximum`, according to `Duration.Order`. + * + * **When to use** + * + * Use to test whether a duration is inside an inclusive range. + * + * **Details** + * + * Both bounds are inclusive and compared with `Duration.Order`. + * + * **Gotchas** + * + * The bounds are not normalized. If `minimum` is greater than `maximum`, the + * predicate returns `false` for every duration. + * + * **Example** (Checking duration ranges) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.between(Duration.seconds(3), { + * minimum: Duration.seconds(2), + * maximum: Duration.seconds(5) + * }) // => true + * ``` + * + * @see {@link clamp} for constraining a duration to a range + * @see {@link isGreaterThanOrEqualTo} for checking only the lower bound + * @see {@link isLessThanOrEqualTo} for checking only the upper bound + * + * @category predicates + * @since 2.0.0 + */ +export const between: { + (options: { minimum: Duration; maximum: Duration }): (self: Duration) => boolean + (self: Duration, options: { minimum: Duration; maximum: Duration }): boolean +} = order.isBetween(Order) + +/** + * Provides an `Equivalence` instance for comparing `Duration` values. + * + * **Example** (Comparing durations for equivalence) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) // => true + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Equivalence: Equ.Equivalence = (self, that) => + matchPair(self, that, { + onMillis: (self, that) => self === that, + onNanos: (self, that) => self === that, + onInfinity: (self, that) => self.value._tag === that.value._tag + }) + +/** + * Returns the smaller of two Durations. + * + * **Example** (Selecting the shorter duration) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.min(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(3) + * ``` + * + * @category ordering + * @since 2.0.0 + */ +export const min: { + (that: Duration): (self: Duration) => Duration + (self: Duration, that: Duration): Duration +} = order.min(Order) + +/** + * Returns the larger of two Durations. + * + * **Example** (Selecting the longer duration) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.max(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(5) + * ``` + * + * @category ordering + * @since 2.0.0 + */ +export const max: { + (that: Duration): (self: Duration) => Duration + (self: Duration, that: Duration): Duration +} = order.max(Order) + +/** + * Returns a `Duration` constrained between a minimum and maximum value. + * + * **Example** (Clamping durations to a range) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.clamp(Duration.seconds(10), { + * minimum: Duration.seconds(2), + * maximum: Duration.seconds(5) + * }) // => Duration.seconds(5) + * ``` + * + * @category ordering + * @since 2.0.0 + */ +export const clamp: { + (options: { minimum: Duration; maximum: Duration }): (self: Duration) => Duration + (self: Duration, options: { minimum: Duration; maximum: Duration }): Duration +} = order.clamp(Order) + +/** + * Divides a `Duration` by a finite, non-zero number safely. + * + * **Details** + * + * Returns `Option.none()` for zero, negative zero, or non-finite divisors. For + * nanosecond-backed durations, also returns `Option.none()` when the divisor + * cannot be converted to a `bigint`, such as a fractional divisor. + * + * **Example** (Safely dividing durations) + * + * ```ts import.meta.vitest + * import { Duration, Option } from "effect" + * + * Duration.divide(Duration.seconds(10), 2) // => Option.some(Duration.seconds(5)) + * Duration.divide(Duration.seconds(10), 0) // => Option.none() + * ``` + * + * @category math + * @since 2.4.19 + */ +export const divide: { + (by: number): (self: Duration) => Option.Option + (self: Duration, by: number): Option.Option +} = dual( + 2, + (self: Duration, by: number): Option.Option => { + if (!Number.isFinite(by)) return Option.none() + if (by === 0 || Object.is(by, -0)) return Option.none() + return match(self, { + onMillis: (millis) => Option.some(make(millis / by)), + onNanos: (nanos) => { + try { + return Option.some(make(nanos / BigInt(by))) + } catch { + return Option.none() + } + }, + onInfinity: () => Option.some(by > 0 ? infinity : negativeInfinity), + onNegativeInfinity: () => Option.some(by > 0 ? negativeInfinity : infinity) + }) + } +) + +/** + * Divides a `Duration` by a number using fallback rules instead of returning + * an `Option`. + * + * **When to use** + * + * Use when dividing a `Duration` should return `Duration.zero` or signed + * infinity for invalid cases instead of forcing callers to handle `Option.none`. + * + * **Details** + * + * Non-finite divisors return `Duration.zero`. Division by positive or negative + * zero can produce signed infinity for non-zero finite durations, while zero + * or infinite durations divided by zero produce `Duration.zero`. + * Nanosecond-backed durations return `Duration.zero` when the divisor cannot + * be converted to a `bigint`. + * + * **Example** (Dividing durations unsafely) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.divideUnsafe(Duration.seconds(10), 2) // => Duration.seconds(5) + * Duration.divideUnsafe(Duration.seconds(10), 0) // => Duration.infinity + * ``` + * + * @category math + * @since 4.0.0 + */ +export const divideUnsafe: { + (by: number): (self: Duration) => Duration + (self: Duration, by: number): Duration +} = dual( + 2, + (self: Duration, by: number): Duration => { + if (!Number.isFinite(by)) return zero + return match(self, { + onMillis: (millis) => make(millis / by), + onNanos: (nanos) => { + if (Object.is(by, 0) || Object.is(by, -0)) { + if (nanos === bigint0) return zero + // match IEEE 754: same sign → +infinity, different sign → -infinity + const positiveNanos = nanos > bigint0 + const positiveZero = Object.is(by, 0) + return (positiveNanos === positiveZero) ? infinity : negativeInfinity + } + try { + return make(nanos / BigInt(by)) + } catch { + return zero + } + }, + onInfinity: () => by > 0 ? infinity : by < 0 ? negativeInfinity : zero, + onNegativeInfinity: () => by > 0 ? negativeInfinity : by < 0 ? infinity : zero + }) + } +) + +/** + * Returns a `Duration` multiplied by a number. + * + * **Details** + * + * For nanosecond-backed durations, the multiplier must be convertible to a + * `bigint`; fractional or non-finite multipliers can throw. Infinite + * durations return positive infinity, negative infinity, or zero depending on + * the multiplier sign. + * + * **Example** (Multiplying durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.times(Duration.seconds(5), 2) // => Duration.seconds(10) + * ``` + * + * @category math + * @since 2.0.0 + */ +export const times: { + (times: number): (self: Duration) => Duration + (self: Duration, times: number): Duration +} = dual( + 2, + (self: Duration, times: number): Duration => + match(self, { + onMillis: (millis) => make(millis * times), + onNanos: (nanos) => make(nanos * BigInt(times)), + onInfinity: () => times > 0 ? infinity : times < 0 ? negativeInfinity : zero, + onNegativeInfinity: () => times > 0 ? negativeInfinity : times < 0 ? infinity : zero + }) +) + +/** + * Subtracts one Duration from another. The result can be negative. + * + * **Details** + * + * Infinity subtraction follows signed-infinity arithmetic. Subtracting the + * same infinity from itself returns zero. Positive infinity minus negative + * infinity or any finite duration remains positive infinity. Negative infinity + * minus positive infinity or any finite duration remains negative infinity. + * Finite durations minus positive infinity produce negative infinity, and + * finite durations minus negative infinity produce positive infinity. + * + * **Example** (Subtracting durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.subtract(Duration.seconds(10), Duration.seconds(3)) // => Duration.seconds(7) + * ``` + * + * @category math + * @since 2.0.0 + */ +export const subtract: { + (that: Duration): (self: Duration) => Duration + (self: Duration, that: Duration): Duration +} = dual( + 2, + (self: Duration, that: Duration): Duration => + matchPair(self, that, { + onMillis: (self, that) => make(self - that), + onNanos: (self, that) => make(self - that), + onInfinity: (self, that) => { + const s = self.value._tag + const t = that.value._tag + if (s === "Infinity") return t === "Infinity" ? zero : infinity + if (s === "NegativeInfinity") return t === "NegativeInfinity" ? zero : negativeInfinity + return t === "Infinity" ? negativeInfinity : infinity + } + }) +) + +/** + * Adds two Durations together. + * + * **Details** + * + * Infinity addition follows these rules: + * + * - infinity + infinity = infinity + * - infinity + negativeInfinity = zero + * - infinity + finite = infinity + * - negativeInfinity + negativeInfinity = negativeInfinity + * - negativeInfinity + finite = negativeInfinity + * + * **Example** (Adding durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.sum(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(8) + * ``` + * + * @category math + * @since 2.0.0 + */ +export const sum: { + (that: Duration): (self: Duration) => Duration + (self: Duration, that: Duration): Duration +} = dual( + 2, + (self: Duration, that: Duration): Duration => + matchPair(self, that, { + onMillis: (self, that) => make(self + that), + onNanos: (self, that) => make(self + that), + onInfinity: (self, that) => { + const s = self.value._tag + const t = that.value._tag + if (s === "Infinity" && t === "NegativeInfinity") return zero + if (s === "NegativeInfinity" && t === "Infinity") return zero + if (s === "Infinity" || t === "Infinity") return infinity + if (s === "NegativeInfinity" || t === "NegativeInfinity") return negativeInfinity + // unreachable, but satisfy TS + return zero + } + }) +) + +/** + * Checks whether the first Duration is less than the second. + * + * **Example** (Comparing durations with less than) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThan: { + (that: Duration): (self: Duration) => boolean + (self: Duration, that: Duration): boolean +} = order.isLessThan(Order) + +/** + * Checks whether the first Duration is less than or equal to the second. + * + * **Example** (Comparing durations with less than or equal) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isLessThanOrEqualTo( + * Duration.seconds(5), + * Duration.seconds(5) + * ) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThanOrEqualTo: { + (that: Duration): (self: Duration) => boolean + (self: Duration, that: Duration): boolean +} = order.isLessThanOrEqualTo(Order) + +/** + * Checks whether the first Duration is greater than the second. + * + * **Example** (Comparing durations with greater than) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThan: { + (that: Duration): (self: Duration) => boolean + (self: Duration, that: Duration): boolean +} = order.isGreaterThan(Order) + +/** + * Checks whether the first Duration is greater than or equal to the second. + * + * **Example** (Comparing durations with greater than or equal) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.isGreaterThanOrEqualTo( + * Duration.seconds(5), + * Duration.seconds(5) + * ) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo: { + (that: Duration): (self: Duration) => boolean + (self: Duration, that: Duration): boolean +} = order.isGreaterThanOrEqualTo(Order) + +/** + * Checks whether two Durations are equal. + * + * **Example** (Checking duration equality) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.equals(Duration.seconds(5), Duration.millis(5000)) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const equals: { + (that: Duration): (self: Duration) => boolean + (self: Duration, that: Duration): boolean +} = dual(2, (self: Duration, that: Duration): boolean => Equivalence(self, that)) + +/** + * Decomposes a `Duration` into normalized signed components. + * + * **Details** + * + * Finite durations are returned as `{ days, hours, minutes, seconds, millis, + * nanos }`. Infinite durations return every component as `Infinity` or + * `-Infinity`. + * + * **Example** (Decomposing durations into parts) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * // Create a complex duration by adding multiple parts + * const duration = Duration.sum( + * Duration.sum( + * Duration.sum(Duration.days(1), Duration.hours(2)), + * Duration.sum(Duration.minutes(30), Duration.seconds(45)) + * ), + * Duration.millis(123) + * ) + * 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)) + * Duration.parts(complex) // => ({ days: 1, hours: 2, minutes: 30, seconds: 0, millis: 0, nanos: 0 }) + * ``` + * + * @category converting + * @since 3.8.0 + */ +export const parts = (self: Duration): { + days: number + hours: number + minutes: number + seconds: number + millis: number + nanos: number +} => { + if (self.value._tag === "Infinity") { + return { + days: Infinity, + hours: Infinity, + minutes: Infinity, + seconds: Infinity, + millis: Infinity, + nanos: Infinity + } + } + if (self.value._tag === "NegativeInfinity") { + return { + days: -Infinity, + hours: -Infinity, + minutes: -Infinity, + seconds: -Infinity, + millis: -Infinity, + nanos: -Infinity + } + } + + const n = toNanosUnsafe(self) + const neg = n < bigint0 + const a = neg ? -n : n + const ms = a / bigint1e6 + const sec = ms / bigint1e3 + const min = sec / bigint60 + const hr = min / bigint60 + const d = hr / bigint24 + const sign = neg ? -1 : 1 + + return { + days: sign * Number(d), + hours: sign * Number(hr % bigint24), + minutes: sign * Number(min % bigint60), + seconds: sign * Number(sec % bigint60), + millis: sign * Number(ms % bigint1e3), + nanos: sign * Number(a % bigint1e6) + } +} + +/** + * Converts a `Duration` to a human readable string. + * + * **Example** (Formatting durations) + * + * ```ts import.meta.vitest + * import { Duration } from "effect" + * + * Duration.format(Duration.millis(1000)) // => "1s" + * Duration.format(Duration.millis(1001)) // => "1s 1ms" + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const format = (self: Duration): string => { + if (self.value._tag === "Infinity") { + return "Infinity" + } + if (self.value._tag === "NegativeInfinity") { + return "-Infinity" + } + if (isZero(self)) { + return "0" + } + if (isNegative(self)) { + return "-" + format(abs(self)) + } + + const fragments = parts(self) + const pieces = [] + if (fragments.days !== 0) { + pieces.push(`${fragments.days}d`) + } + + if (fragments.hours !== 0) { + pieces.push(`${fragments.hours}h`) + } + + if (fragments.minutes !== 0) { + pieces.push(`${fragments.minutes}m`) + } + + if (fragments.seconds !== 0) { + pieces.push(`${fragments.seconds}s`) + } + + if (fragments.millis !== 0) { + pieces.push(`${fragments.millis}ms`) + } + + if (fragments.nanos !== 0) { + pieces.push(`${fragments.nanos}ns`) + } + + return pieces.join(" ") +} + +/** + * Reducer for summing `Duration`s. + * + * **When to use** + * + * Use to sum many `Duration` values through APIs that consume a `Reducer`. + * + * **Details** + * + * `ReducerSum` uses `sum` and starts from `zero`, so `combineAll([])` returns + * `zero`. + * + * @see {@link sum} for adding two duration values directly + * @see {@link CombinerMax} for keeping the longest duration instead of summing + * @see {@link CombinerMin} for keeping the shortest duration instead of summing + * + * @category math + * @since 4.0.0 + */ +export const ReducerSum: Reducer.Reducer = Reducer.make(sum, zero) + +/** + * Combiner that returns the maximum `Duration`. + * + * **When to use** + * + * Use to keep the longest `Duration` when an API consumes a `Combiner`. + * + * @see {@link CombinerMin} for keeping the shortest `Duration` + * @see {@link max} for comparing two `Duration` values directly + * + * @category math + * @since 4.0.0 + */ +export const CombinerMax: Combiner.Combiner = Combiner.max(Order) + +/** + * Combiner that returns the minimum `Duration`. + * + * **When to use** + * + * Use to keep the shortest `Duration` through APIs that consume a `Combiner`. + * + * @see {@link CombinerMax} for keeping the longest `Duration` + * @see {@link min} for comparing two `Duration` values directly + * + * @category math + * @since 4.0.0 + */ +export const CombinerMin: Combiner.Combiner = Combiner.min(Order) diff --git a/.repos/effect/packages/effect/src/Effect.ts b/.repos/effect/packages/effect/src/Effect.ts new file mode 100644 index 000000000..3afd6618b --- /dev/null +++ b/.repos/effect/packages/effect/src/Effect.ts @@ -0,0 +1,15382 @@ +/** + * Describes workflows that run only when executed by the Effect runtime. + * + * An `Effect` can succeed with an `A`, fail with an `E`, and require + * services `R`. Creating an effect does not perform the work; it builds a value + * that can be composed, provided with services, retried, interrupted, run + * concurrently, or inspected by the runtime. This module is the main API for + * creating effects, combining them, handling failures, managing resources, and + * running effect programs. + * + * @since 2.0.0 + */ +import type * as Arr from "./Array.ts" +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 * as ExecutionPlan from "./ExecutionPlan.ts" +import * as Exit from "./Exit.ts" +import type { Fiber } from "./Fiber.ts" +import type * as Filter from "./Filter.ts" +import { constant, dual, type LazyArg } from "./Function.ts" +import type { TypeLambda } from "./HKT.ts" +import type { Inspectable } from "./Inspectable.ts" +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" +import type { Logger } from "./Logger.ts" +import type { Severity } from "./LogLevel.ts" +import * as Metric from "./Metric.ts" +import type { Option } from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import type * as Predicate from "./Predicate.ts" +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 { Scheduler } from "./Scheduler.ts" +import type { Scope } from "./Scope.ts" +import type { + AnySpan, + ParentSpan, + Span, + SpanLink, + SpanOptions, + SpanOptionsNoTrace, + TraceOptions, + Tracer +} from "./Tracer.ts" +import type { TxRef } from "./TxRef.ts" +import type { + Concurrency, + Covariant, + EqualsWith, + ExcludeReason, + ExcludeTag, + ExtractReason, + ExtractTag, + NarrowReason, + NoInfer, + OmitReason, + ReasonOf, + ReasonTags, + Simplify, + Tags, + unassigned +} from "./Types.ts" +import type * as Unify from "./Unify.ts" +import { internalCall } from "./Utils.ts" + +/** + * Type-level identifier for `Effect` values. + * + * @category type IDs + * @since 4.0.0 + */ +export type TypeId = "~effect/Effect" + +/** + * Runtime identifier used to recognize `Effect` values. + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId: TypeId = core.EffectTypeId + +/** + * The `Effect` interface defines a value that lazily describes a workflow or + * job. The workflow requires some context `R`, and may fail with an error of + * type `E`, or succeed with a value of type `A`. + * + * **When to use** + * + * Use when you need to represent a lazy, composable workflow that can require + * services, fail with a typed error, or succeed with a typed value. + * + * **Details** + * + * `Effect` values model resourceful interaction with the outside world, + * including synchronous, asynchronous, concurrent, and parallel interaction. + * They use a fiber-based concurrency model, with built-in support for + * scheduling, fine-grained interruption, structured concurrency, and high + * scalability. + * + * To run an `Effect` value, you need a `Runtime`, which is a type that is + * capable of executing `Effect` values. + * + * @category models + * @since 2.0.0 + */ +export interface Effect extends Pipeable, Inspectable { + readonly [TypeId]: Variance + [Symbol.iterator](): EffectIterator> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: EffectUnify + [Unify.ignoreSymbol]?: {} +} + +/** + * Type-level unification support for `Effect` values. + * + * @category models + * @since 2.0.0 + */ +export interface EffectUnify { + Effect?: () => A[Unify.typeSymbol] extends + | Effect + | infer _ ? Effect + : never +} + +/** + * Type lambda used to represent `Effect` in higher-kinded APIs. + * + * @category utility types + * @since 2.0.0 + */ +export interface EffectTypeLambda extends TypeLambda { + readonly type: Effect +} + +/** + * Variance interface for Effect, encoding the type parameters' variance. + * + * @category models + * @since 2.0.0 + */ +export interface Variance { + _A: Covariant + _E: Covariant + _R: Covariant +} + +/** + * Extracts the success type from an `Effect`. + * + * **When to use** + * + * Use to derive the value produced by an existing effect when declaring + * reusable type aliases, service interfaces, or function signatures. + * + * @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 utility types + * @since 2.0.0 + */ +export type Success = T extends Effect ? _A + : never + +/** + * Extracts the error type from an `Effect`. + * + * **When to use** + * + * Use to derive the error type from an existing `Effect` type when declaring + * helper types, wrappers, or APIs that preserve the effect's failure channel. + * + * **Details** + * + * Non-`Effect` inputs resolve to `never`. + * + * @see {@link Success} for extracting the success value type instead + * @see {@link Services} for extracting the required services type instead + * + * @category utility types + * @since 2.0.0 + */ +export type Error = T extends Effect ? _E + : never + +/** + * Extracts the required services type from an `Effect`. + * + * **When to use** + * + * Use to derive the context requirements of a generic or inferred `Effect` + * without restating its `R` type parameter. + * + * @see {@link Success} for extracting the success value type instead + * @see {@link Error} for extracting the failure type instead + * + * @category utility types + * @since 4.0.0 + */ +export type Services = T extends Effect ? _R + : never + +/** + * Checks whether a value is an `Effect`. + * + * **Example** (Checking whether a value is an Effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * Effect.isEffect(Effect.succeed(1)) // => true + * Effect.isEffect("hello") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isEffect: (u: unknown) => u is Effect = core.isEffect + +/** + * Iterator interface for Effect generators, enabling Effect values to work with generator functions. + * + * **When to use** + * + * Use when defining or typing `[Symbol.iterator]()` for values typed as + * `Effect`s so `yield*` can pass their success type back into `Effect.gen`. + * + * @see {@link gen} for writing generator-based `Effect` programs that consume this iterator protocol + * + * @category models + * @since 4.0.0 + */ +export interface EffectIterator> { + next( + ...args: ReadonlyArray + ): IteratorResult> +} + +// ======================================================================== +// Collecting +// ======================================================================== + +/** + * Namespace containing type utilities for the `Effect.all` function, which handles + * collecting multiple effects into various output structures. + * + * @since 2.0.0 + */ +export declare namespace All { + /** + * Alias for any `Effect` value accepted by `Effect.all`. + * + * @category utility types + * @since 2.0.0 + */ + export type EffectAny = Effect + + /** + * Computes the return type for `Effect.all` when collecting an iterable. + * + * @category utility types + * @since 2.0.0 + */ + export type ReturnIterable< + T extends Iterable, + Discard extends boolean, + Mode extends boolean = false + > = [T] extends [Iterable>] ? Effect< + Discard extends true ? void : Array : A>, + Mode extends true ? never : E, + R + > + : never + + /** + * Computes the return type for `Effect.all` when collecting a tuple. + * + * @category utility types + * @since 2.0.0 + */ + export type ReturnTuple< + T extends ReadonlyArray, + Discard extends boolean, + Mode extends boolean = false + > = Effect< + Discard extends true ? void + : T[number] extends never ? [] + : { + -readonly [K in keyof T]: T[K] extends Effect< + infer _A, + infer _E, + infer _R + > ? Mode extends true ? Result.Result<_A, _E> : _A + : never + }, + Mode extends true ? never + : T[number] extends never ? never + : T[number] extends Effect ? _E + : never, + T[number] extends never ? never + : T[number] extends Effect ? _R + : never + > extends infer X ? X + : never + + /** + * Computes the return type for `Effect.all` when collecting a record. + * + * @category utility types + * @since 2.0.0 + */ + export type ReturnObject = [T] extends [ + Record + ] ? Effect< + Discard extends true ? void + : { + -readonly [K in keyof T]: [T[K]] extends [ + Effect + ] ? 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 + > + : never + + /** + * Detects whether `Effect.all` should discard collected values. + * + * @category utility types + * @since 2.0.0 + */ + export type IsDiscard = [Extract] extends [ + never + ] ? false + : true + + /** + * Detects whether `Effect.all` should collect results in `Result` mode. + * + * @category utility types + * @since 4.0.0 + */ + export type IsResult = [Extract] extends [never] ? false : true + + /** + * Computes the return type for `Effect.all` from its input and options. + * + * @category utility types + * @since 2.0.0 + */ + export type Return< + Arg extends Iterable | Record, + O extends { + readonly concurrency?: Concurrency | undefined + readonly discard?: boolean | undefined + readonly mode?: "default" | "result" | undefined + } + > = [Arg] extends [ReadonlyArray] ? ReturnTuple, IsResult> + : [Arg] extends [Iterable] ? ReturnIterable, IsResult> + : [Arg] extends [Record] ? ReturnObject, IsResult> + : never +} + +/** + * Combines an iterable or record of effects into one effect whose success shape + * follows the input. + * + * **When to use** + * + * Use to run a known collection of effects and collect results in the same + * tuple, iterable, or record shape. + * + * **Details** + * + * Tuple and iterable inputs collect results in order. Record inputs collect + * results under the same keys. By default, the combined effect fails on the + * first failure; with concurrent execution, effects that have already started + * may be interrupted, while effects not yet started are skipped. + * + * Options: + * + * Use `concurrency` to control sequential or concurrent execution. Use + * `mode: "result"` to run every effect and collect each success or failure as a + * `Result` in the same output shape. Use `discard: true` to ignore successful + * values and return `void`. + * + * **Example** (Collecting tuple results in order) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const tupleOfEffects = [ + * Effect.succeed(42), + * Effect.succeed("Hello") + * ] as const + * + * // ┌─── Effect<[number, string], never, never> + * // ▼ + * const resultsAsTuple = Effect.all(tupleOfEffects) + * + * await Effect.runPromise(resultsAsTuple) // => [42, "Hello"] + * ``` + * + * **Example** (Collecting iterable results in order) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const iterableOfEffects: Iterable> = [1, 2, 3].map( + * Effect.succeed + * ) + * + * // ┌─── Effect + * // ▼ + * const resultsAsArray = Effect.all(iterableOfEffects) + * + * await Effect.runPromise(resultsAsArray) // => [1, 2, 3] + * ``` + * + * **Example** (Collecting struct results by key) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const structOfEffects = { + * a: Effect.succeed(42), + * b: Effect.succeed("Hello") + * } + * + * // ┌─── Effect<{ a: number; b: string; }, never, never> + * // ▼ + * const resultsAsStruct = Effect.all(structOfEffects) + * + * await Effect.runPromise(resultsAsStruct) // => { a: 42, b: "Hello" } + * ``` + * + * **Example** (Collecting record results by key) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const recordOfEffects: Record> = { + * key1: Effect.succeed(1), + * key2: Effect.succeed(2) + * } + * + * // ┌─── Effect<{ [x: string]: number; }, never, never> + * // ▼ + * const resultsAsRecord = Effect.all(recordOfEffects) + * + * await Effect.runPromise(resultsAsRecord) // => { key1: 1, key2: 2 } + * ``` + * + * **Example** (Stopping on the first failure) + * + * ```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(record)), + * Effect.fail("Task2: Oh no!").pipe(Effect.tap(record)), + * // Won't execute due to earlier failure + * Effect.succeed("Task3").pipe(Effect.tap(record)) + * ]) + * + * 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 combining + * @since 2.0.0 + */ +export const all: < + const Arg extends + | Iterable> + | Record>, + O extends { + readonly concurrency?: Concurrency | undefined + readonly discard?: boolean | undefined + readonly mode?: "default" | "result" | undefined + } +>( + arg: Arg, + options?: O +) => All.Return = internal.all + +/** + * Applies an effectful function to each element and partitions failures and + * successes. + * + * **Details** + * + * The returned tuple is `[excluded, satisfying]`, where `excluded` contains + * all failures and `satisfying` contains all successes. + * + * This function runs every effect and never fails. Use `concurrency` to control + * parallelism. + * + * **Example** (Separating successes and failures) + * + * ```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) + * ) + * + * await Effect.runPromise(program) // => [['0 is even', '2 is even'], [1, 3]] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const partition: { + ( + f: (a: A, i: number) => Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): (elements: Iterable) => Effect<[excluded: Array, satisfying: Array], never, R> + ( + elements: Iterable, + f: (a: A, i: number) => Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): 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. + * + * **Details** + * + * This function always evaluates every element. If at least one effect fails, + * all failures are returned as a non-empty array and successes are discarded. + * If all effects succeed, it returns all collected successes. + * + * Use `discard: true` to ignore successful values while still validating all + * elements. + * + * **Example** (Validating every element) + * + * ```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) + * ) + * + * await Effect.runPromiseExit(program) // => Exit.fail(["0 is even", "2 is even"]) + * ``` + * + * @category validation + * @since 2.0.0 + */ +export const validate: { + ( + f: (a: A, i: number) => Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: false | undefined + } | undefined + ): (elements: Iterable) => Effect, Arr.NonEmptyArray, R> + ( + f: (a: A, i: number) => Effect, + options: { + readonly concurrency?: Concurrency | undefined + readonly discard: true + } + ): (elements: Iterable) => Effect, R> + ( + elements: Iterable, + f: (a: A, i: number) => Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: false | undefined + } | undefined + ): Effect, Arr.NonEmptyArray, R> + ( + elements: Iterable, + f: (a: A, i: number) => Effect, + options: { + readonly concurrency?: Concurrency | undefined + readonly discard: true + } + ): Effect, R> +} = internal.validate + +/** + * Returns the first element that satisfies an effectful predicate. + * + * **Details** + * + * The predicate receives the element and its index. Evaluation short-circuits + * as soon as an element matches. + * + * **Example** (Finding the first successful match) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.findFirst([1, 2, 3, 4], (n) => Effect.succeed(n > 2)) + * + * await Effect.runPromise(program) // => Option.some(3) + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findFirst: { + ( + predicate: (a: NoInfer, i: number) => Effect + ): (elements: Iterable) => Effect, E, R> + ( + elements: Iterable, + predicate: (a: NoInfer, i: number) => Effect + ): Effect, E, R> +} = internal.findFirst + +/** + * Returns the first value that passes an effectful `FilterEffect`. + * + * **When to use** + * + * Use when you need to find the first element that satisfies an effectful + * filter returning a `Result`, which also transforms the matching element. + * + * **Details** + * + * The filter receives the element and index. Evaluation short-circuits on the + * first `Result.succeed` and returns the transformed value in `Option.some`. + * + * @see {@link findFirst} for the simpler effectful predicate-based variant + * + * @category searching + * @since 4.0.0 + */ +export const findFirstFilter: { + ( + filter: (input: NoInfer, i: number) => Effect, E, R> + ): (elements: Iterable) => Effect, E, R> + ( + elements: Iterable, + filter: (input: NoInfer, i: number) => Effect, E, R> + ): Effect, E, R> +} = internal.findFirstFilter + +/** + * Executes an effectful operation for each element in an `Iterable`. + * + * **When to use** + * + * Use to traverse an iterable with an effectful function while preserving + * element order in the collected results. + * + * **Details** + * + * The `forEach` function applies a provided operation to each element in the + * iterable, producing a new effect that returns an array of results. + * + * If any effect fails, the iteration stops immediately (short-circuiting), and + * the error is propagated. + * + * Concurrency: + * + * The `concurrency` option controls how many operations are performed + * concurrently. By default, the operations are performed sequentially. + * + * Discarding Results: + * + * If the `discard` option is set to `true`, the intermediate results are not + * collected, and the final result of the operation is `void`. + * + * **Example** (Mapping over an iterable with effects) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const result = Effect.forEach( + * [1, 2, 3, 4, 5], + * (n, index) => + * Effect.sync(() => { output.push(`Currently at index ${index}`) }).pipe(Effect.as(n * 2)) + * ) + * + * 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.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) => + * Effect.sync(() => { output.push(`Currently at index ${index}`) }).pipe(Effect.as(n * 2)), + * { discard: true } + * ) + * + * 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 sequencing + * @since 2.0.0 + */ +export const forEach: { + = Iterable, const Discard extends boolean = false>( + f: (a: A, i: number) => Effect, + options?: { readonly concurrency?: Concurrency | undefined; readonly discard?: Discard | undefined } | undefined + ): ( + 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, + options?: { readonly concurrency?: Concurrency | undefined; readonly discard?: Discard | undefined } | undefined + ): 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 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) => void output.push(`Current count: ${n}`) + * }) + * + * await Effect.runPromise(program) + * output // => ["Current count: 1", "Current count: 2", "Current count: 3", "Current count: 4", "Current count: 5"] + * ``` + * + * @category repetition + * @since 2.0.0 + */ +export const whileLoop: (options: { + readonly while: LazyArg + readonly body: LazyArg> + readonly step: (a: A) => void +}) => Effect = internal.whileLoop + +// ----------------------------------------------------------------------------- +// Creating Effects +// ----------------------------------------------------------------------------- + +/** + * Creates an `Effect` that represents an asynchronous computation guaranteed to + * succeed. + * + * **When to use** + * + * Use to convert a `Promise` into an `Effect` when the async operation is + * guaranteed to succeed and will not reject. + * + * **Details** + * + * An optional `AbortSignal` can be provided to allow for interruption of the + * wrapped `Promise` API. + * + * **Gotchas** + * + * The `Promise` must not reject. If it rejects, the rejection is treated as a + * defect, not as a typed failure. Use `tryPromise` when rejection is expected. + * + * Interruption aborts the provided `AbortSignal`, but the underlying + * asynchronous operation only stops if it observes that signal. + * + * **Example** (Wrapping a non-rejecting Promise) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const succeedAsync = (message: string) => + * Effect.promise(() => Promise.resolve(message)) + * + * // ┌─── Effect + * // ▼ + * 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. + * @category constructors + * @since 2.0.0 + */ +export const promise: ( + evaluate: (signal: AbortSignal) => PromiseLike +) => Effect = internal.promise + +/** + * Creates an `Effect` from an asynchronous computation that may throw or + * reject, mapping failures into the error channel. + * + * **When to use** + * + * Use when you need to perform asynchronous operations that might fail, such + * as fetching data from an API, and want thrown exceptions or rejected promises + * captured as Effect errors. + * + * **Details** + * + * The promise thunk is evaluated when the effect runs. If it returns a promise + * that resolves, the resolved value becomes the success value. If the thunk + * throws before returning a promise, or if the returned promise rejects, the + * thrown or rejected value is mapped into the error channel. + * + * Passing the thunk directly maps failures to {@link Cause.UnknownError}. + * Passing `{ try, catch }` uses `catch` to map failures to an error of type + * `E`. + * + * The thunk receives an `AbortSignal` that is aborted if the effect is + * interrupted. The underlying asynchronous operation only stops if it observes + * that signal. + * + * **Gotchas** + * + * If `catch` throws while mapping the error, that thrown value is treated as a + * defect. Return the error value you want in the error channel instead of + * throwing it. + * + * **Example** (Wrapping a fetch request that may fail) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const getTodo = (id: number) => + * Effect.tryPromise(() => Promise.resolve({ id, completed: false })) + * + * // ┌─── 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 import.meta.vitest + * import { Data, Effect } from "effect" + * + * class TodoFetchError extends Data.TaggedError("TodoFetchError")<{ readonly cause: unknown }> {} + * + * const getTodo = (id: number) => + * Effect.tryPromise({ + * try: () => Promise.reject(`Todo ${id} is unavailable`), + * // remap the error + * catch: (cause) => new TodoFetchError({ cause }) + * }) + * + * // ┌─── Effect + * // ▼ + * 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 + +/** + * Creates an `Effect` that always succeeds with a given value. + * + * **When to use** + * + * Use when an effect should complete successfully with a specific value without any errors + * or external dependencies. + * + * **Example** (Creating a successful effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // Creating an effect that represents a successful scenario + * // + * // ┌─── Effect + * // ▼ + * const success = Effect.succeed(42) + * Effect.runSync(success) // => 42 + * ``` + * + * @see {@link fail} to create an effect that represents a failure. + * @category constructors + * @since 2.0.0 + */ +export const succeed: (value: A) => Effect = internal.succeed + +/** + * Returns an effect which succeeds with `None`. + * + * **Example** (Succeeding with Option.none) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.succeedNone + * + * Effect.runSync(program) // => Option.none() + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const succeedNone: Effect> = internal.succeedNone + +/** + * Returns an effect which succeeds with the value wrapped in a `Some`. + * + * **Example** (Succeeding with Option.some) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.succeedSome(42) + * + * Effect.runSync(program) // => Option.some(42) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const succeedSome: (value: A) => Effect> = internal.succeedSome + +/** + * Creates an `Effect` lazily, delaying construction until it is needed. + * + * **When to use** + * + * Use when you need to defer the evaluation of an effect until it is required. + * + * **Details** + * + * `suspend` takes a thunk that represents an effect and delays creating it + * until the suspended effect is evaluated. This is useful for optimizing + * expensive computations, managing circular dependencies such as recursive + * functions, and helping TypeScript unify return types when branches construct + * different effects. Any side effects or scoped captures inside the thunk are + * re-executed on each invocation. + * + * **Example** (Lazily evaluating side effects) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * let i = 0 + * + * const bad = Effect.succeed(i++) + * + * const good = Effect.suspend(() => Effect.succeed(i++)) + * + * Effect.runSync(bad) // => 0 + * Effect.runSync(bad) // => 0 + * + * Effect.runSync(good) // => 1 + * Effect.runSync(good) // => 2 + * ``` + * + * **Example** (Suspending recursive Fibonacci evaluation) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const blowsUp = (n: number): Effect.Effect => + * n < 2 + * ? Effect.succeed(1) + * : Effect.zipWith(blowsUp(n - 1), blowsUp(n - 2), (a, b) => a + b) + * + * // console.log(Effect.runSync(blowsUp(32))) + * // crash: JavaScript heap out of memory + * + * const allGood = (n: number): Effect.Effect => + * n < 2 + * ? Effect.succeed(1) + * : Effect.zipWith( + * Effect.suspend(() => allGood(n - 1)), + * Effect.suspend(() => allGood(n - 2)), + * (a, b) => a + b + * ) + * + * Effect.runSync(allGood(16)) // => 1597 + * ``` + * + * **Example** (Helping TypeScript infer recursive effect types) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // Without suspend, TypeScript may struggle with type inference. + * // Inferred type: + * // (a: number, b: number) => + * // Effect | Effect + * const withoutSuspend = (a: number, b: number) => + * b === 0 + * ? Effect.fail(new Error("Cannot divide by zero")) + * : Effect.succeed(a / b) + * + * // Using suspend to unify return types. + * // Inferred type: + * // (a: number, b: number) => Effect + * const withSuspend = (a: number, b: number) => + * Effect.suspend(() => + * b === 0 + * ? Effect.fail(new Error("Cannot divide by zero")) + * : Effect.succeed(a / b) + * ) + * + * Effect.runSync(withSuspend(6, 2)) // => 3 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const suspend: ( + effect: LazyArg> +) => Effect = internal.suspend + +/** + * Creates an `Effect` that represents a synchronous side-effectful computation. + * + * **When to use** + * + * Use when you need to wrap a synchronous side-effectful operation that is not + * expected to throw. + * + * **Details** + * + * The provided function is evaluated lazily when the effect runs. + * + * **Gotchas** + * + * The function must not throw. If it throws, the thrown value is treated as a + * defect, not as a typed failure. Use `try` when throwing is expected. + * + * **Example** (Capturing synchronous logging in an Effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const log = (message: string) => + * Effect.sync(() => { + * 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. + * @category constructors + * @since 2.0.0 + */ +export const sync: (thunk: LazyArg) => Effect = internal.sync + +const void_: Effect = internal.void +export { + /** + * Returns an effect that succeeds with `void`. + * + * @category constructors + * @since 2.0.0 + */ + void_ as void +} + +const undefined_: Effect = internal.undefined +export { + /** + * Returns an effect that succeeds with `undefined`. + * + * @category constructors + * @since 4.0.0 + */ + undefined_ as undefined +} + +/** + * Creates an `Effect` from a callback-based asynchronous API. + * + * **When to use** + * + * Use when you need to integrate APIs that complete through callbacks instead + * of returning a `Promise`. + * + * **Details** + * + * The registration function receives a `resume` callback and, when requested, + * an `AbortSignal`. Call `resume` at most once with the effect that should + * complete the fiber; later calls are ignored. Return an optional cleanup + * effect from the registration function to run if the fiber is interrupted. + * + * **Example** (Integrating callback APIs) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const fromCallback = (message: string) => + * Effect.callback((resume) => { + * queueMicrotask(() => { + * void output.push(message) + * resume(Effect.void) + * }) + * }) + * + * await Effect.runPromise(fromCallback("callback completed")) + * output // => ["callback completed"] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const callback: ( + register: ( + this: Scheduler, + resume: (effect: Effect) => void, + signal: AbortSignal + ) => void | Effect +) => Effect = internal.callback + +/** + * Returns an effect that will never produce anything. The moral equivalent of + * `while(true) {}`, only without the wasted CPU cycles. + * + * **Example** (Creating a never-ending effect) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.timeoutOption(Effect.never, 0) + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const never: Effect = internal.never + +/** + * Effect that succeeds with an empty record `{}`, used as the starting point + * for do notation chains. + * + * **Example** (Starting do notation) + * + * ```ts import.meta.vitest + * import { Effect, pipe } from "effect" + * + * const program = pipe( + * Effect.Do, + * Effect.bind("x", () => Effect.succeed(2)), + * 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 constructors + * @since 2.0.0 + */ +export const Do: Effect<{}> = internal.Do + +/** + * Gives a name to the success value of an `Effect`, creating a single-key + * record used in do notation pipelines. + * + * **When to use** + * + * Use to start a do-notation pipeline from an existing `Effect` when its + * success value should become the first named field in the accumulated record. + * + * @see {@link Do} for starting from an empty accumulated record + * @see {@link bind} for adding fields produced by effects + * + * @category mapping + * @since 2.0.0 + */ +export const bindTo: { + (name: N): (self: Effect) => Effect<{ [K in N]: A }, E, R> + (self: Effect, name: N): Effect<{ [K in N]: A }, E, R> +} = internal.bindTo + +const let_: { + , B>( + name: N, + f: (a: NoInfer) => B + ): ( + self: Effect + ) => Effect & Record>, E, R> + , E, R, B, N extends string>( + self: Effect, + name: N, + f: (a: NoInfer) => B + ): Effect & Record>, E, R> +} = internal.let + +export { + /** + * Adds a computed plain value to the do notation record. + * + * **When to use** + * + * Use to add a derived, synchronous value to a do-notation pipeline when it + * depends on fields already accumulated in the record and does not need to run + * another `Effect`. + * + * **Details** + * + * The new field is added with object spreading. If the name already exists in + * the record, the computed value replaces it in the returned type. + * + * @see {@link bind} for adding fields produced by effects + * @see {@link bindTo} for naming an existing success value + * @see {@link Do} for starting from an empty accumulated record + * @see {@link gen} for sequencing without accumulating a record + * + * @category mapping + * @since 2.0.0 + */ + let_ as let +} + +/** + * Adds an `Effect` value to the do notation record under a given name. + * + * **When to use** + * + * Use to sequence an effectful step in a do-notation pipeline when that step + * depends on fields already accumulated in the record and its success value + * should be stored under a name. + * + * **Details** + * + * The function receives the current record, runs the returned effect after the + * input effect succeeds, and inserts its success value under `name`. The + * resulting effect combines the error and service requirements of both steps. + * + * **Gotchas** + * + * Binding a name that already exists replaces that field in the resulting + * record. + * + * @see {@link Do} for starting from an empty do-notation record + * @see {@link bindTo} for naming the success value of an existing effect + * @see {@link gen} for generator-based sequencing without accumulating a record + * + * @category sequencing + * @since 2.0.0 + */ +export const bind: { + , B, E2, R2>( + name: N, + f: (a: NoInfer) => Effect + ): ( + self: Effect + ) => Effect & Record>, E | E2, R | R2> + , E, R, B, E2, R2, N extends string>( + self: Effect, + name: N, + f: (a: NoInfer) => Effect + ): Effect & Record>, E | E2, R | R2> +} = internal.bind + +/** + * Provides a way to write effectful code using generator functions, simplifying + * control flow and error handling. + * + * **When to use** + * + * Use when you want to write effectful code that looks and behaves like + * synchronous code, while still handling asynchronous tasks, errors, and complex + * control flow such as loops and conditions. + * + * Generator functions work similarly to `async/await` but keep errors, + * requirements, and interruption in the Effect type. You can `yield*` values + * from effects and return the final result at the end. + * + * **Example** (Sequencing effects with generators) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} + * + * const addServiceCharge = (amount: number) => amount + 1 + * + * const applyDiscount = ( + * total: number, + * discountRate: number + * ): Effect.Effect => + * discountRate === 0 + * ? Effect.fail(new DiscountRateError()) + * : Effect.succeed(total - (total * discountRate) / 100) + * + * const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) + * + * const fetchDiscountRate = Effect.promise(() => Promise.resolve(5)) + * + * export const program = Effect.gen(function*() { + * const transactionAmount = yield* fetchTransactionAmount + * const discountRate = yield* fetchDiscountRate + * const discountedAmount = yield* applyDiscount( + * transactionAmount, + * discountRate + * ) + * const finalAmount = addServiceCharge(discountedAmount) + * return `Final amount to charge: ${finalAmount}` + * }) + * + * await Effect.runPromise(program) // => "Final amount to charge: 96" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const gen: { + , AEff>( + f: () => Generator + ): Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + > + , AEff>( + options: { + readonly self: Self + }, + f: (this: Self) => Generator + ): Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + > +} = internal.gen + +/** + * Type helpers for `Effect.gen` generator return signatures. + * + * @since 2.0.0 + */ +export declare namespace gen { + /** + * Generator return type accepted by `Effect.gen`. + * + * @category constructors + * @since 4.0.0 + */ + export type Return = Generator, A, any> +} + +/** + * Creates an `Effect` that represents a recoverable error. + * + * **When to use** + * + * Use to explicitly signal a recoverable error in an `Effect`. + * + * **Details** + * + * The error keeps propagating unless it is handled. You can handle tagged + * errors with functions like {@link catchTag} or {@link catchTags}. + * + * **Example** (Creating a failed effect) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class OperationFailedError extends Data.TaggedError("OperationFailedError")<{}> {} + * + * // ┌─── Effect + * // ▼ + * 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. + * @category constructors + * @since 2.0.0 + */ +export const fail: (error: E) => Effect = internal.fail + +/** + * Creates an `Effect` that represents a recoverable error using a lazy evaluation. + * + * **When to use** + * + * Use to defer computing a recoverable error value until the effect is run. + * + * **Details** + * + * The error-producing function is evaluated each time the effect is executed. + * + * **Example** (Lazily creating failures) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class ProgramError extends Data.TaggedError("ProgramError")<{ readonly operation: string }> {} + * + * const program = Effect.failSync(() => new ProgramError({ operation: "sync" })) + * + * Effect.runSync(Effect.flip(program)).operation // => "sync" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failSync: (evaluate: LazyArg) => Effect = internal.failSync + +/** + * Creates an `Effect` that represents a failure with a specific `Cause`. + * + * **When to use** + * + * Use when you already have a full `Cause` and need to preserve defects, + * interruptions, annotations, or combined failures in the effect's failure + * channel. + * + * **Details** + * + * This function allows you to create effects that fail with complex error + * structures, including multiple errors, defects, interruptions, and more. + * + * **Example** (Failing with a full Cause) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * + * const program = Effect.failCause( + * Cause.fail("Network error") + * ) + * + * Effect.runSync(Effect.flip(program)) // => "Network error" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCause: (cause: Cause.Cause) => Effect = internal.failCause + +/** + * Creates an `Effect` that represents a failure with a `Cause` computed lazily. + * + * **When to use** + * + * Use to defer computing a full `Cause` until the effect is run. + * + * **Details** + * + * The cause-producing function is evaluated each time the effect is executed. + * + * **Example** (Lazily creating a Cause) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * + * const program = Effect.failCauseSync(() => + * Cause.fail("Error computed at runtime") + * ) + * + * Effect.runSync(Effect.flip(program)) // => "Error computed at runtime" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCauseSync: ( + evaluate: LazyArg> +) => Effect = internal.failCauseSync + +/** + * Creates an effect that terminates a fiber with a specified error. + * + * **When to use** + * + * Use when you need an `Effect` to report an unrecoverable defect instead of a + * typed error. + * + * **Details** + * + * The `die` function is used to signal a defect, which represents a critical + * and unexpected error in the code. When invoked, it produces an effect that + * does not handle the error and instead terminates the fiber. + * + * The error channel of the resulting effect is of type `never`, indicating that + * it cannot recover from this failure. + * + * **Example** (Failing on division by zero) + * + * ```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(defect) + * : Effect.succeed(a / b) + * + * // ┌─── Effect + * // ▼ + * const program = divide(1, 0) + * + * Effect.runSyncExit(program) // => Exit.die(defect) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const die: (defect: unknown) => Effect = internal.die + +const try_: ( + options: { + readonly try: LazyArg + readonly catch: (error: unknown) => E + } | LazyArg +) => Effect = internal.try + +export { + /** + * Creates an `Effect` from a synchronous computation that may throw, mapping + * thrown values into the error channel. + * + * **When to use** + * + * Use when you need to perform synchronous operations that might throw, such + * as parsing JSON, and want thrown exceptions captured as Effect errors. + * + * **Details** + * + * The thunk is evaluated when the effect runs. If it returns normally, the + * returned value becomes the success value. If it throws, the thrown value is + * mapped into the error channel. + * + * Passing the thunk directly maps failures to {@link Cause.UnknownError}. + * Passing `{ try, catch }` uses `catch` to map failures to an error of type + * `E`. + * + * **Gotchas** + * + * If `catch` throws while mapping the error, that thrown value is treated as + * a defect. Return the error value you want in the error channel instead of + * throwing it. + * + * **Example** (Parsing JSON) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const parseJSON = (input: string) => + * Effect.try(() => JSON.parse(input)) + * + * // Success case + * await Effect.runPromise(parseJSON("{\"name\": \"Alice\"}")) // => { name: 'Alice' } + * + * // Failure case maps the thrown value to UnknownError + * const exit = await Effect.runPromiseExit(parseJSON("invalid json")) + * exit._tag // => "Failure" + * ``` + * + * **Example** (Mapping exceptions to a tagged error) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class JsonParsingError extends Data.TaggedError("JsonParsingError")<{ readonly cause: unknown }> {} + * + * const parseJSON = (input: string) => + * Effect.try({ + * try: () => JSON.parse(input), + * catch: (cause) => new JsonParsingError({ cause }) + * }) + * + * 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 + * throw errors. + * @category constructors + * @since 2.0.0 + */ + try_ as try +} + +/** + * Yields control back to the Effect runtime, allowing other fibers to execute. + * + * **Example** (Yielding to other fibers) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * void output.push("Before yield") + * yield* Effect.yieldNow + * void output.push("After yield") + * }) + * + * await Effect.runPromise(program) + * output // => ["Before yield", "After yield"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const yieldNow: Effect = internal.yieldNow + +/** + * Yields control back to the Effect runtime with a specified priority, allowing other fibers to execute. + * + * **Example** (Yielding with priority) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * void output.push("High priority task") + * yield* Effect.yieldNowWith(10) // Higher priority + * void output.push("Continued after yield") + * }) + * + * await Effect.runPromise(program) + * output // => ["High priority task", "Continued after yield"] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const yieldNowWith: (priority?: number) => Effect = internal.yieldNowWith + +/** + * Provides access to the current fiber within an effect computation. + * + * **Example** (Reading the current fiber) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.withFiber((fiber) => Effect.succeed(typeof fiber.id)) + * + * Effect.runSync(program) // => "number" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const withFiber: ( + evaluate: (fiber: Fiber) => Effect +) => Effect = core.withFiber + +// ----------------------------------------------------------------------------- +// Conversions +// ----------------------------------------------------------------------------- + +/** + * Converts a `Result` to an `Effect`. + * + * **Example** (Converting a Result into an Effect) + * + * ```ts import.meta.vitest + * import { Effect, Result } from "effect" + * const output: Array = [] + * + * const success = Result.succeed(42) + * const failure = Result.fail("Something went wrong") + * + * const effect1 = Effect.fromResult(success) + * const effect2 = Effect.fromResult(failure) + * + * void output.push(Effect.runSync(effect1)) + * void output.push(Effect.runSync(Effect.flip(effect2))) + * output // => [42, "Something went wrong"] + * ``` + * + * @category converting + * @since 4.0.0 + */ +export const fromResult: (result: Result.Result) => Effect = internal.fromResult + +/** + * Converts an `Option` into an `Effect`. + * + * **When to use** + * + * Use when absence should become a typed `NoSuchElementError` in the effect error + * channel. + * + * **Details** + * + * `Option.some` becomes a successful effect with the contained value, while + * `Option.none` becomes a failed effect. By default the failure is a + * `NoSuchElementError`, but you can provide an `onNone` callback to customize + * the error value. + * + * **Example** (Converting an Option into an Effect) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * const output: Array = [] + * + * const some = Option.some(42) + * const none = Option.none() + * + * const effect1 = Effect.fromOption(some) + * const effect2 = Effect.fromOption(none) + * const effect3 = Effect.fromOption(none, () => new Error("missing")) + * + * 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: < + 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 + +/** + * Converts an `Option` of an `Effect` into an `Effect` of an `Option`. + * + * **When to use** + * + * Use when an effect should run only when an optional value is present, while + * preserving absence as a successful `None`. + * + * **Details** + * + * - `None` becomes an effect that succeeds with `None` + * - `Some(effect)` runs the inner effect and wraps its success value in `Some` + * - Inner failures are preserved in the resulting effect + * + * **Example** (Transposing an Option of an Effect) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const some = Option.some(Effect.succeed(42)) + * + * // ┌─── Effect, never, never> + * // ▼ + * const program = Effect.transposeOption(some) + * + * Effect.runSync(program) // => Option.some(42) + * ``` + * + * @category converting + * @since 3.13.0 + */ +export const transposeOption: ( + self: Option> +) => Effect, E, R> = internal.transposeOption + +/** + * Converts a nullable value to an `Effect`, failing with a `NoSuchElementError` + * when the value is `null` or `undefined`. + * + * **Example** (Failing on nullish values) + * + * ```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* Effect.sync(() => { output.push(value) }) + * }, + * Effect.catch(() => Effect.sync(() => { output.push("missing") })) + * ) + * + * await Effect.runPromise(program(null)) + * await Effect.runPromise(program("hello")) + * output // => ["missing", "hello"] + * ``` + * + * @category converting + * @since 4.0.0 + */ +export const fromNullishOr: (value: A) => Effect, Cause.NoSuchElementError> = internal.fromNullishOr + +// ----------------------------------------------------------------------------- +// Mapping +// ----------------------------------------------------------------------------- + +/** + * Chains effects to produce new `Effect` instances, useful for combining + * operations that depend on previous results. + * + * **When to use** + * + * Use when you need to chain multiple effects, ensuring that each + * step produces a new `Effect` while flattening any nested effects that may + * occur. + * + * **Details** + * + * `flatMap` lets you sequence effects so that the result of one effect can be + * used in the next step. It is similar to `flatMap` used with arrays but works + * specifically with `Effect` instances, allowing you to avoid deeply nested + * effect structures. + * + * Since effects are immutable, `flatMap` always returns a new effect instead of + * changing the original one. + * + * **Example** (Choosing flatMap syntax variants) + * + * ```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) + * + * 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 import.meta.vitest + * import { Data, Effect, pipe } from "effect" + * + * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} + * + * // Function to apply a discount safely to a transaction amount + * const applyDiscount = ( + * total: number, + * discountRate: number + * ): Effect.Effect => + * discountRate === 0 + * ? Effect.fail(new DiscountRateError()) + * : Effect.succeed(total - (total * discountRate) / 100) + * + * // Simulated asynchronous task to fetch a transaction amount from database + * const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) + * + * // Chaining the fetch and discount application using `flatMap` + * const finalAmount = pipe( + * fetchTransactionAmount, + * Effect.flatMap((amount) => applyDiscount(amount, 5)) + * ) + * + * await Effect.runPromise(finalAmount) // => 95 + * ``` + * + * @see {@link tap} for a version that ignores the result of the effect. + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + ( + f: (a: A) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (a: A) => Effect + ): Effect +} = internal.flatMap + +/** + * Flattens an `Effect` that produces another `Effect` into a single effect. + * + * **Example** (Flattening nested effects) + * + * ```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* Effect.sync(() => { output.push(value) }) + * }) + * + * Effect.runSync(program) + * output // => ["hello"] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatten: (self: Effect, E2, R2>) => Effect = + internal.flatten + +/** + * Runs this effect and then runs another effect, optionally using the first + * effect's success value to choose the next effect. + * + * **When to use** + * + * Use when you need one effect to run after another and the second effect may + * depend on the first effect's success value. + * + * **Details** + * + * When the second argument is an `Effect`, the first success value is discarded + * and the returned effect produces the second effect's value. When the second + * argument is a function, it receives the first success value and must return + * the next `Effect`. + * + * Failures or requirements from either effect are preserved in the returned + * effect. + * + * **Example** (Choosing andThen syntax variants) + * + * ```ts import.meta.vitest + * import { Effect, pipe } from "effect" + * const output: Array = [] + * + * const myEffect = Effect.succeed(1) + * const anotherEffect = Effect.succeed("done") + * + * 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 import.meta.vitest + * import { Data, Effect, pipe } from "effect" + * + * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} + * + * // Function to apply a discount safely to a transaction amount + * const applyDiscount = ( + * total: number, + * discountRate: number + * ): Effect.Effect => + * discountRate === 0 + * ? Effect.fail(new DiscountRateError()) + * : Effect.succeed(total - (total * discountRate) / 100) + * + * // Simulated asynchronous task to fetch a transaction amount from database + * const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) + * + * // Using Effect.map and Effect.flatMap + * const result1 = pipe( + * fetchTransactionAmount, + * Effect.map((amount) => amount * 2), + * Effect.flatMap((amount) => applyDiscount(amount, 5)) + * ) + * + * await Effect.runPromise(result1) // => 190 + * + * // Using Effect.andThen + * const result2 = pipe( + * fetchTransactionAmount, + * Effect.andThen((amount) => Effect.succeed(amount * 2)), + * Effect.andThen((amount) => applyDiscount(amount, 5)) + * ) + * + * await Effect.runPromise(result2) // => 190 + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const andThen: { + ( + f: (a: A) => Effect + ): (self: Effect) => Effect + ( + f: Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (a: A) => Effect + ): Effect + ( + self: Effect, + f: Effect + ): Effect +} = internal.andThen + +/** + * Runs a side effect with the result of an effect without changing the original + * value. + * + * **When to use** + * + * Use when you need to run an effectful observation, such as logging or + * tracking, while passing the original success value to the next step. + * + * **Details** + * + * `tap` works similarly to `flatMap`, but it ignores the result of the function + * passed to it. The value from the previous effect remains available for the + * next part of the chain. Note that if the side effect fails, the entire chain + * will fail too. + * + * **Example** (Logging a step in a pipeline) + * + * ```ts import.meta.vitest + * import { Data, Effect, pipe } from "effect" + * const output: Array = [] + * + * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} + * + * // Function to apply a discount safely to a transaction amount + * const applyDiscount = ( + * total: number, + * discountRate: number + * ): Effect.Effect => + * discountRate === 0 + * ? Effect.fail(new DiscountRateError()) + * : Effect.succeed(total - (total * discountRate) / 100) + * + * // Simulated asynchronous task to fetch a transaction amount from database + * const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) + * + * const finalAmount = pipe( + * fetchTransactionAmount, + * // Log the fetched transaction amount + * Effect.tap((amount) => Effect.sync(() => { output.push(`Apply a discount to: ${amount}`) })), + * // `amount` is still available! + * Effect.flatMap((amount) => applyDiscount(amount, 5)) + * ) + * + * void output.push(await Effect.runPromise(finalAmount)) + * output // => ["Apply a discount to: 100", 95] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const tap: { + ( + f: (a: NoInfer) => Effect + ): (self: Effect) => Effect + ( + f: Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (a: NoInfer) => Effect + ): Effect + ( + self: Effect, + f: Effect + ): Effect +} = internal.tap + +/** + * Converts both success and failure of an `Effect` into a `Result` type. + * + * **When to use** + * + * Use when you want an `Effect`'s typed failures to be handled as `Result` + * data while preserving the original error value. + * + * **Details** + * + * This function converts an effect that may fail into an effect that always + * succeeds, wrapping the outcome in a `Result` type. The result will be + * `Result.Failure` if the effect fails, containing the recoverable error, or + * `Result.Success` if it succeeds, containing the result. + * + * Using this function, you can handle recoverable errors explicitly without + * causing the effect to fail. This is particularly useful in scenarios where + * you want to chain effects and manage both success and failure in the same + * logical flow. + * + * The resulting effect cannot fail directly because all recoverable failures + * are represented inside the `Result` type. + * + * **Gotchas** + * + * `result` only captures typed, recoverable failures. Defects and + * interruptions are not captured inside the `Result` and still fail the + * effect. + * + * **Example** (Capturing success or failure as Result) + * + * ```ts import.meta.vitest + * import { Effect, Result } from "effect" + * + * const success = Effect.succeed(42) + * const failure = Effect.fail("Something went wrong") + * + * const program1 = Effect.result(success) + * const program2 = Effect.result(failure) + * + * Effect.runSync(program1) // => Result.succeed(42) + * + * 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 error handling + * @since 4.0.0 + */ +export const result: (self: Effect) => Effect, never, R> = internal.result + +/** + * Converts success to `Option.some` and failure to `Option.none`. + * + * **When to use** + * + * Use when you only care whether an effect succeeds and want recoverable + * failures represented as `Option.none`. + * + * **Details** + * + * Success values become `Option.some`, recoverable failures become + * `Option.none`, and defects still fail the effect. + * + * **Gotchas** + * + * `option` only captures typed, recoverable failures as `Option.none`. + * Defects and interruptions are not captured inside the `Option` and still + * fail the effect. + * + * `option` also discards typed failure values. Use `result` if the failure + * value matters. + * + * **Example** (Capturing success or failure as Option) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.all([ + * Effect.option(Effect.succeed(1)), + * Effect.option(Effect.fail("missing")) + * ]) + * + * Effect.runSync(program) // => [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 error handling + * @since 2.0.0 + */ +export const option: (self: Effect) => Effect, never, R> = internal.option + +/** + * Transforms an effect to encapsulate both failure and success using the `Exit` + * data type. + * + * **When to use** + * + * Use when you need to inspect the full outcome, including typed failures, defects, + * and interruptions. + * + * **Details** + * + * `exit` wraps an effect's success or failure inside an `Exit` type, allowing + * you to handle both cases explicitly. + * + * The resulting effect cannot fail because the failure is encapsulated within + * the `Exit.Failure` type. The error type is set to `never`, indicating that + * the effect is structured to never fail directly. + * + * **Example** (Capturing completion as Exit) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * + * const success = Effect.succeed(42) + * const failure = Effect.fail("Something went wrong") + * + * const program1 = Effect.exit(success) + * const program2 = Effect.exit(failure) + * + * Effect.runSync(program1) // => Exit.succeed(42) + * + * 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 error handling + * @since 2.0.0 + */ +export const exit: ( + self: Effect +) => Effect, never, R> = internal.exit + +/** + * Transforms the value inside an effect by applying a function to it. + * + * **When to use** + * + * Use to transform an effect's success value with a function that returns a + * plain value, producing a new effect without changing the original effect's + * typed error or context requirements. + * + * **Details** + * + * `map` takes a function and applies it to the value contained within an + * effect, creating a new effect with the transformed value. + * + * It's important to note that effects are immutable, meaning that the original + * effect is not modified. Instead, a new effect is returned with the updated + * value. + * + * **Example** (Choosing map syntax variants) + * + * ```ts import.meta.vitest + * import { Effect, pipe } from "effect" + * const output: Array = [] + * + * const myEffect = Effect.succeed(1) + * const transformation = (n: number) => n + 1 + * + * 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 import.meta.vitest + * import { Effect, pipe } from "effect" + * + * const addServiceCharge = (amount: number) => amount + 1 + * + * const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) + * + * const finalAmount = pipe( + * fetchTransactionAmount, + * Effect.map(addServiceCharge) + * ) + * + * await Effect.runPromise(finalAmount) // => 101 + * ``` + * + * @see {@link mapError} for a version that operates on the error channel. + * @see {@link mapBoth} for a version that operates on both channels. + * @see {@link flatMap} or {@link andThen} for a version that can return a new effect. + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (a: A) => B): (self: Effect) => Effect + (self: Effect, f: (a: A) => B): Effect +} = internal.map + +/** + * Replaces the value inside an effect with a constant value. + * + * **When to use** + * + * Use to replace a successful value with a constant while preserving failures + * and requirements. + * + * **Details** + * + * `as` allows you to ignore the original value inside an effect and + * replace it with a new constant value. + * + * **Example** (Replacing a success value) + * + * ```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.runSync(program) // => "new value" + * ``` + * + * @see {@link map} for deriving the replacement value from the success value + * @see {@link asVoid} for replacing the success value with `void` + * + * @category mapping + * @since 2.0.0 + */ +export const as: { + (value: B): (self: Effect) => Effect + (self: Effect, value: B): Effect +} = internal.as + +/** + * Maps the success value of an `Effect` to `Some`, preserving failures. + * + * **Example** (Wrapping success in Option.some) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.asSome(Effect.succeed(42)) + * + * Effect.runSync(program) // => Option.some(42) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const asSome: (self: Effect) => Effect, E, R> = internal.asSome + +/** + * Maps the success value of an `Effect` to `void`, preserving failures. + * + * **Example** (Discarding success values) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.asVoid(Effect.succeed(42)) + * + * Effect.runSync(program) // => undefined + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const asVoid: (self: Effect) => Effect = internal.asVoid + +/** + * Swaps an effect's success and failure channels. + * + * **When to use** + * + * Use to swap an `Effect`'s success and failure channels. + * + * **Details** + * + * For an `Effect`, the returned effect has type `Effect`. + * + * **Example** (Swapping success and failure channels) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // ┌─── Effect + * // ▼ + * const program = Effect.fail("Oh uh!").pipe(Effect.as(2)) + * + * // ┌─── Effect + * // ▼ + * const flipped = Effect.flip(program) + * Effect.runSync(flipped) // => "Oh uh!" + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const flip: (self: Effect) => Effect = internal.flip + +// ----------------------------------------------------------------------------- +// Zipping +// ----------------------------------------------------------------------------- + +/** + * Combines two effects into a single effect, producing a tuple with the results of both effects. + * + * **When to use** + * + * Use to combine exactly two effects into a tuple. + * + * **Details** + * + * The `zip` function executes the first effect (left) and then the second effect (right). + * Once both effects succeed, their results are combined into a tuple. + * + * Concurrency: + * + * By default, `zip` processes the effects sequentially. To execute the effects concurrently, + * use the `{ concurrent: true }` option. + * + * **Example** (Combining two effects sequentially) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") + * + * // Combine the two effects together + * // + * // ┌─── Effect<[number, string], never, never> + * // ▼ + * const program = Effect.zip(task1, task2) + * + * Effect.runSync(program) // => [1, 'hello'] + * ``` + * + * **Example** (Combining two effects concurrently) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * 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 }) + * + * await Effect.runPromise(program) // => [1, 'hello'] + * ``` + * + * @see {@link zipWith} for a version that combines the results with a custom function. + * @see {@link all} for collecting a larger structure of effects. + * + * @category zipping + * @since 2.0.0 + */ +export const zip: { + ( + that: Effect, + options?: { readonly concurrent?: boolean | undefined } | undefined + ): (self: Effect) => Effect<[A, A2], E2 | E, R2 | R> + ( + self: Effect, + that: Effect, + options?: { readonly concurrent?: boolean | undefined } + ): Effect<[A, A2], E | E2, R | R2> +} = internal.zip + +/** + * Combines two effects sequentially and applies a function to their results to + * produce a single value. + * + * **When to use** + * + * Use when you need to run two effects sequentially and combine their results + * with a function instead of keeping the results as a tuple. + * + * **Details** + * + * Concurrency: + * + * By default, the effects are run sequentially. To execute them concurrently, + * use the `{ concurrent: true }` option. + * + * **Example** (Combining two success values with a function) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") + * + * const task3 = Effect.zipWith( + * task1, + * task2, + * // Combines results into a single value + * (number, string) => number + string.length + * ) + * + * Effect.runSync(task3) // => 6 + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWith: { + ( + that: Effect, + f: (a: A, b: A2) => B, + options?: { readonly concurrent?: boolean | undefined } + ): (self: Effect) => Effect + ( + self: Effect, + that: Effect, + f: (a: A, b: A2) => B, + options?: { readonly concurrent?: boolean | undefined } + ): Effect +} = internal.zipWith + +// ----------------------------------------------------------------------------- +// Error handling +// ----------------------------------------------------------------------------- + +const catch_: { + ( + f: (e: E) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (e: E) => Effect + ): Effect +} = internal.catch_ + +export { + /** + * Handles all errors in an effect by providing a fallback effect. + * + * **When to use** + * + * Use when every recoverable error from an effect should be handled by the same + * fallback function while unrecoverable defects remain defects. + * + * **Details** + * + * The `catch` function catches any errors that may occur during the + * execution of an effect and allows you to handle them by specifying a fallback + * effect. This ensures that the program continues without failing by recovering + * from errors using the provided fallback logic. + * + * **Gotchas** + * + * `catch` only handles recoverable errors. It will not recover from + * unrecoverable defects. + * + * @see {@link catchCause} for a version that can recover from both recoverable and unrecoverable errors. + * + * @category error handling + * @since 4.0.0 + */ + catch_ as catch +} + +/** + * Catches and handles specific errors by their `_tag` field, which is used as a + * discriminator. + * + * **When to use** + * + * Use when you need to recover from one specific tagged error in an effect + * error channel. + * + * **Details** + * + * The error type must have a readonly `_tag` field. `catchTag` matches that + * field and only handles errors with the requested tag. + * + * **Example** (Handling a tagged error) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * class NetworkError { + * readonly _tag = "NetworkError" + * constructor(readonly message: string) {} + * } + * + * class ValidationError { + * readonly _tag = "ValidationError" + * constructor(readonly message: string) {} + * } + * + * 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 + * @see {@link catchIf} for recovering from errors that match a predicate + * + * @category error handling + * @since 2.0.0 + */ +export const catchTag: { + < + const K extends Tags | Arr.NonEmptyReadonlyArray>, + E, + A1, + E1, + R1, + A2 = unassigned, + E2 = never, + R2 = never + >( + k: K, + f: (e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K>) => Effect, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Effect) + | undefined + ): ( + self: Effect + ) => Effect< + A | A1 | Exclude, + | E1 + | E2 + | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), + R | R1 | R2 + > + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1, + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Effect, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Effect, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Effect) + | undefined + ): Effect< + A | A1 | Exclude, + | E1 + | E2 + | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), + R | R1 | R2 + > +} = internal.catchTag + +/** + * Handles multiple errors in a single block of code using their `_tag` field. + * + * **When to use** + * + * Use when one recovery step should handle several tagged error types by + * matching their readonly `_tag` fields. + * + * **Details** + * + * Pass a handler table whose keys are tags, plus an optional fallback for + * unmatched errors. + * + * The error type must have a readonly `_tag` field to use `catchTags`. This + * field is used to identify and match errors. + * + * **Example** (Handling multiple tagged errors) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * // Define tagged error types + * class ValidationError extends Data.TaggedError("ValidationError")<{ + * message: string + * }> {} + * + * class NetworkError extends Data.TaggedError("NetworkError")<{ + * statusCode: number + * }> {} + * + * // An effect that might fail with multiple error types + * const program: Effect.Effect = + * Effect.fail(new NetworkError({ statusCode: 503 })) + * + * // Handle multiple error types at once + * const handled = Effect.catchTags(program, { + * ValidationError: (error) => + * Effect.succeed(`Validation failed: ${error.message}`), + * NetworkError: (error) => Effect.succeed(`Network error: ${error.statusCode}`) + * }) + * + * Effect.runSync(handled) // => "Network error: 503" + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const catchTags: { + < + E, + Cases extends + & { [K in Extract["_tag"]]+?: ((error: Extract) => Effect) } + & (unknown extends E ? {} : { [K in Exclude["_tag"]>]: never }), + A2 = unassigned, + E2 = never, + R2 = never + >( + cases: Cases, + orElse?: ((e: Exclude) => Effect) | undefined + ): ( + self: Effect + ) => Effect< + | A + | Exclude + | { + [K in keyof Cases]: Cases[K] extends (...args: Array) => Effect ? A : never + }[keyof Cases], + | E2 + | (A2 extends unassigned ? Exclude : never) + | { + [K in keyof Cases]: Cases[K] extends (...args: Array) => Effect ? E : never + }[keyof Cases], + | R + | R2 + | { + [K in keyof Cases]: Cases[K] extends (...args: Array) => Effect ? R : never + }[keyof Cases] + > + < + R, + E, + A, + Cases extends + & { [K in Extract["_tag"]]+?: ((error: Extract) => Effect) } + & (unknown extends E ? {} : { [K in Exclude["_tag"]>]: never }), + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Effect, + cases: Cases, + orElse?: ((e: Exclude) => Effect) | undefined + ): Effect< + | A + | Exclude + | { + [K in keyof Cases]: Cases[K] extends (...args: Array) => Effect ? A : never + }[keyof Cases], + | E2 + | (A2 extends unassigned ? Exclude : never) + | { + [K in keyof Cases]: Cases[K] extends (...args: Array) => Effect ? E : never + }[keyof Cases], + | R + | R2 + | { + [K in keyof Cases]: Cases[K] extends (...args: Array) => Effect ? R : never + }[keyof Cases] + > +} = internal.catchTags + +/** + * Catches a specific reason within a tagged error. + * + * **When to use** + * + * Use to handle one nested reason inside an `Effect`'s tagged error while + * preserving the parent error shape for unmatched reasons. + * + * **Details** + * + * Use this to handle nested error causes without removing the parent error + * from the error channel. The handler receives the unwrapped reason. + * + * **Example** (Handling an error reason) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class RateLimitError extends Data.TaggedError("RateLimitError")<{ + * retryAfter: number + * }> {} + * + * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ + * limit: number + * }> {} + * + * class AiError extends Data.TaggedError("AiError")<{ + * reason: RateLimitError | QuotaExceededError + * }> {} + * + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) + * ) + * + * // Handle rate limits specifically + * const handled = program.pipe( + * Effect.catchReason("AiError", "RateLimitError", (reason) => + * Effect.succeed(`Retry after ${reason.retryAfter}s`) + * ) + * ) + * + * Effect.runSync(handled) // => "Retry after 30s" + * ``` + * + * @see {@link catchReasons} for handling several nested reason tags + * + * @category error handling + * @since 4.0.0 + */ +export const catchReason: { + < + K extends Tags, + E, + RK extends ReasonTags, K>>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + errorTag: K, + reasonTag: RK, + f: ( + reason: ExtractReason, K>, RK>, + error: NarrowReason, K>, RK> + ) => Effect, + orElse?: + | (( + reasons: ExcludeReason, K>, RK>, + error: OmitReason, K>, RK> + ) => Effect) + | undefined + ): ( + self: Effect + ) => Effect< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > + < + A, + E, + R, + K extends Tags, + RK extends ReasonTags>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + self: Effect, + errorTag: K, + reasonTag: RK, + f: (reason: ExtractReason, RK>, error: NarrowReason, RK>) => Effect, + orElse?: + | ((reasons: ExcludeReason, RK>, error: OmitReason, RK>) => Effect) + | undefined + ): Effect< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > +} = internal.catchReason + +/** + * Catches multiple reasons within a tagged error using an object of handlers. + * + * **Example** (Handling multiple error reasons) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class RateLimitError extends Data.TaggedError("RateLimitError")<{ + * retryAfter: number + * }> {} + * + * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ + * limit: number + * }> {} + * + * class AiError extends Data.TaggedError("AiError")<{ + * reason: RateLimitError | QuotaExceededError + * }> {} + * + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new QuotaExceededError({ limit: 100 }) }) + * ) + * + * const handled = program.pipe( + * Effect.catchReasons("AiError", { + * RateLimitError: (reason) => + * Effect.succeed(`Retry after ${reason.retryAfter}s`), + * QuotaExceededError: (reason) => + * Effect.succeed(`Quota exceeded: ${reason.limit}`) + * }) + * ) + * + * Effect.runSync(handled) // => "Quota exceeded: 100" + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const catchReasons: { + < + K extends Tags, + E, + Cases extends { + [RK in ReasonTags, K>>]+?: ( + reason: ExtractReason, K>, RK>, + error: NarrowReason, K>, RK> + ) => Effect + }, + A2 = unassigned, + E2 = never, + R2 = never + >( + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: ExcludeReason, K>, Extract>, + error: OmitReason, K>, Extract> + ) => Effect) + | undefined + ): ( + self: Effect + ) => Effect< + | A + | Exclude + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect ? A : never + }[keyof Cases], + | ExcludeTag + | E2 + | (A2 extends unassigned ? ExtractTag : never) + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect ? E : never + }[keyof Cases], + | R + | R2 + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect ? R : never + }[keyof Cases] + > + < + A, + E, + R, + K extends Tags, + Cases extends { + [RK in ReasonTags>]+?: ( + reason: ExtractReason, RK>, + error: NarrowReason, RK> + ) => Effect + }, + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Effect, + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: ExcludeReason, K>, Extract>, + error: OmitReason, K>, Extract> + ) => Effect) + | undefined + ): Effect< + | A + | Exclude + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect ? A : never + }[keyof Cases], + | ExcludeTag + | E2 + | (A2 extends unassigned ? ExtractTag : never) + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect ? E : never + }[keyof Cases], + | R + | R2 + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect ? R : never + }[keyof Cases] + > +} = internal.catchReasons + +/** + * Type helper that keeps only error tags whose tagged error contains a tagged `reason` field. + * + * **When to use** + * + * Use to constrain custom helpers or overloads to parent error tags whose error + * contains a tagged reason. + * + * **Details** + * + * The mapped type keeps each parent error tag whose extracted tagged error has + * at least one reason tag, and removes tags that do not carry tagged reasons. + * + * @see {@link unwrapReason} for promoting nested reason errors into the error channel + * @see {@link catchReason} for handling one nested reason tag + * @see {@link catchReasons} for handling several nested reason tags + * + * @category error handling + * @since 4.0.0 + */ +export type TagsWithReason = { + [T in Tags]: ReasonTags> extends never ? never : T +}[Tags] + +/** + * Promotes nested reason errors into the Effect error channel, replacing + * the parent error. + * + * **Example** (Extracting the reason from a tagged error) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class RateLimitError extends Data.TaggedError("RateLimitError")<{ + * retryAfter: number + * }> {} + * + * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ + * limit: number + * }> {} + * + * class AiError extends Data.TaggedError("AiError")<{ + * reason: RateLimitError | QuotaExceededError + * }> {} + * + * 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 + * @since 4.0.0 + */ +export const unwrapReason: { + < + K extends TagsWithReason, + E + >( + errorTag: K + ): (self: Effect) => Effect | ReasonOf>, R> + < + A, + E, + R, + K extends TagsWithReason + >( + self: Effect, + errorTag: K + ): Effect | ReasonOf>, R> +} = internal.unwrapReason + +/** + * Handles both recoverable and unrecoverable errors by providing a recovery + * effect. + * + * **When to use** + * + * Use when you need to recover from an `Effect` by inspecting the full `Cause`, + * including recoverable failures, defects, and interruptions, instead of only + * the typed error value. + * + * **Details** + * + * When to Recover from Defects: + * + * Defects are unexpected errors that typically shouldn't be recovered from, as + * they often indicate serious issues. However, in some cases, such as + * dynamically loaded plugins, controlled recovery might be needed. + * + * **Example** (Recovering from full failure causes) + * + * ```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") + * + * // Recover from any cause (including defects) + * const recovered = Effect.catchCause(program, (cause) => { + * if (Cause.hasDies(cause)) { + * 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 + * @since 4.0.0 + */ +export const catchCause: { + ( + f: (cause: Cause.Cause) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (cause: Cause.Cause) => Effect + ): Effect +} = internal.catchCause + +/** + * Recovers from defects using a provided recovery function. + * + * **When to use** + * + * Use when you need to report or translate defects at integration boundaries. + * + * **Details** + * + * `catchDefect` handles unexpected defects, such as thrown exceptions or + * values passed to `die`, without catching typed failures or interruptions. + * + * When to Recover from Defects: + * + * Defects are unexpected errors that typically should not be recovered from, as + * they often indicate serious issues. In some cases, such as dynamically loaded + * plugins, controlled recovery may be needed. + * + * **Example** (Recovering from defects) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * // An effect that might throw an unexpected error (defect) + * const program = Effect.sync(() => { + * throw new Error("Unexpected error") + * }) + * + * // Recover from defects only + * const recovered = Effect.catchDefect(program, (defect) => { + * 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 + * @since 4.0.0 + */ +export const catchDefect: { + ( + f: (defect: unknown) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (defect: unknown) => Effect + ): Effect +} = internal.catchDefect + +/** + * Recovers from specific errors using a `Predicate` or `Refinement`. + * + * **When to use** + * + * Use when you need to recover from errors that match a condition. + * + * **Details** + * + * Use a `Refinement` for type narrowing or a `Predicate` for simple boolean + * matching. Non-matching errors re-fail with the original cause. Defects and + * interrupts are not caught. + * + * **Example** (Recovering when a predicate matches) + * + * ```ts import.meta.vitest + * import { Data, Effect, Filter } from "effect" + * + * class NotFound extends Data.TaggedError("NotFound")<{ id: string }> {} + * + * const program = Effect.fail(new NotFound({ id: "user-1" })) + * + * // With a refinement + * const recovered = program.pipe( + * Effect.catchIf( + * (error): error is NotFound => error._tag === "NotFound", + * (error) => Effect.succeed(`missing:${error.id}`) + * ) + * ) + * + * // With a Filter + * const recovered2 = program.pipe( + * Effect.catchFilter( + * Filter.tagged("NotFound"), + * (error) => Effect.succeed(`missing:${error.id}`) + * ) + * ) + * + * Effect.runSync(Effect.all([recovered, recovered2])) // => ['missing:user-1', 'missing:user-1'] + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const catchIf: { + ( + refinement: Predicate.Refinement, EB>, + f: (e: EB) => Effect, + orElse?: ((e: Exclude) => Effect) | undefined + ): ( + self: Effect + ) => Effect, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3> + ( + predicate: Predicate.Predicate>, + f: (e: NoInfer) => Effect, + orElse?: ((e: NoInfer) => Effect) | undefined + ): ( + self: Effect + ) => Effect, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> + ( + self: Effect, + refinement: Predicate.Refinement, + f: (e: EB) => Effect, + orElse?: ((e: Exclude) => Effect) | undefined + ): Effect, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3> + ( + self: Effect, + predicate: Predicate.Predicate, + f: (e: E) => Effect, + orElse?: ((e: E) => Effect) | undefined + ): Effect, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> +} = internal.catchIf + +/** + * Recovers from specific errors using a `Filter`. + * + * **When to use** + * + * Use to recover from typed `Effect` errors with a reusable `Filter` when + * matching can also narrow or transform the error before choosing the recovery + * effect. + * + * **Details** + * + * The filter runs on typed failures extracted from the `Cause`. Successful + * filter results are passed to `f`; failed filter results are passed to + * `orElse` when provided. Without `orElse`, the original failure cause is + * preserved. + * + * @see {@link catchIf} for predicate-based recovery from typed errors + * @see {@link catchTag} for recovering from a single tagged error + * @see {@link catchTags} for recovering from several tagged errors + * @see {@link catchCauseFilter} for filtering full causes instead of typed errors + * + * @category error handling + * @since 4.0.0 + */ +export const catchFilter: { + ( + filter: Filter.Filter, EB, X>, + f: (e: EB) => Effect, + orElse?: ((e: X) => Effect) | undefined + ): ( + self: Effect + ) => Effect, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> + ( + self: Effect, + filter: Filter.Filter, EB, X>, + f: (e: EB) => Effect, + orElse?: ((e: X) => Effect) | undefined + ): Effect, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> +} = internal.catchFilter + +/** + * Catches `NoSuchElementError` failures and converts them to `Option.none`. + * + * **When to use** + * + * Use when you expect missing-value failures and want them to become an + * optional success while all other failures keep failing. + * + * **Details** + * + * Success values become `Option.some`, `NoSuchElementError` becomes + * `Option.none`, and all other errors are preserved. + * + * **Example** (Recovering from missing Option values) + * + * ```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) + * + * 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` + * @see {@link fromNullishOr} for converting nullish values into `NoSuchElementError` + * @see {@link option} for converting any failure into `Option.none` + * + * @category error handling + * @since 4.0.0 + */ +export const catchNoSuchElement: ( + self: Effect +) => Effect, Exclude, R> = internal.catchNoSuchElement + +/** + * Recovers from specific failures based on a predicate. + * + * **When to use** + * + * Use to recover an `Effect` from full causes selected by a predicate. + * + * **Details** + * + * This function allows you to conditionally catch and recover from failures + * that match a specific predicate. This is useful when you want to handle + * only certain types of errors while letting others propagate. + * + * **Example** (Recovering from selected causes) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * const output: Array = [] + * + * const httpRequest = Effect.fail("Network Error") + * + * // Only catch network-related failures + * const program = Effect.catchCauseIf( + * httpRequest, + * Cause.hasFails, + * (cause) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push(`Caught network error: ${Cause.squash(cause)}`) }) + * return "Fallback response" + * }) + * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Caught network error: Network Error", "Fallback response"] + * ``` + * + * @see {@link catchCause} for recovering from every cause + * @see {@link catchCauseFilter} for selecting full causes with a `Filter` + * @see {@link catchIf} for predicate-based recovery from typed errors + * + * @category error handling + * @since 4.0.0 + */ +export const catchCauseIf: { + ( + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect + ): Effect +} = internal.catchCauseIf + +/** + * Recovers from specific failures based on a `Filter`. + * + * **When to use** + * + * Use when you need to recover an `Effect` only from causes selected by a + * `Filter`, while giving the recovery both the selected value and the original + * `Cause`. + * + * **Details** + * + * The filter is applied to the full `Cause`. When it succeeds, the handler + * receives the selected value and the original cause. When it fails, the effect + * re-fails with the residual cause returned by the filter. + * + * @see {@link catchCauseIf} for predicate-based cause selection + * @see {@link catchFilter} for filtering typed error values instead of full causes + * @see {@link catchCause} for recovering from every cause without filtering + * + * @category error handling + * @since 4.0.0 + */ +export const catchCauseFilter: { + >( + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect + ): (self: Effect) => Effect | E2, R | R2> + >( + self: Effect, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect + ): Effect | E2, R | R2> +} = internal.catchCauseFilter + +/** + * Transforms the failure value of an effect without changing its success value. + * + * **When to use** + * + * Use to translate an `Effect`'s typed failures while leaving successful values + * unchanged. + * + * **Details** + * + * Only the failure channel is transformed. The success channel and requirements + * are preserved. + * + * **Example** (Transforming the error channel) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} + * + * // ┌─── Effect + * // ▼ + * const simulatedTask = Effect.fail("Oh no!").pipe(Effect.as(1)) + * + * // ┌─── Effect + * // ▼ + * const mapped = Effect.mapError( + * 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. + * @see {@link mapBoth} for a version that operates on both channels. + * + * @category error handling + * @since 2.0.0 + */ +export const mapError: { + (f: (e: E) => E2): (self: Effect) => Effect + (self: Effect, f: (e: E) => E2): Effect +} = internal.mapError + +/** + * Applies transformations to both the success and error channels of an effect. + * + * **When to use** + * + * Use to transform both success and failure channels of an `Effect` without + * changing whether it succeeds or fails. + * + * **Details** + * + * This function takes two map functions as arguments: one for the error channel + * and one for the success channel. You can use it when you want to modify both + * the error and the success values without altering the overall success or + * failure status of the effect. + * + * **Example** (Transforming success and failure channels) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} + * + * // ┌─── Effect + * // ▼ + * const simulatedTask = Effect.fail("Oh no!").pipe(Effect.as(1)) + * + * // ┌─── Effect + * // ▼ + * const modified = Effect.mapBoth(simulatedTask, { + * 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. + * @see {@link mapError} for a version that operates on the error channel. + * + * @category mapping + * @since 2.0.0 + */ +export const mapBoth: { + ( + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): (self: Effect) => Effect + ( + self: Effect, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Effect +} = internal.mapBoth + +/** + * Converts typed failures from the error channel into defects, removing the + * error type from the returned effect. + * + * **When to use** + * + * Use when you need to turn an `Effect` typed failure that represents an + * unrecoverable bug or invalid state into a defect. + * + * **Example** (Converting typed failures into defects) + * + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" + * + * class DivideByZeroError extends Data.TaggedError("DivideByZeroError")<{}> {} + * + * const divide = (a: number, b: number) => + * b === 0 + * ? Effect.fail(new DivideByZeroError()) + * : Effect.succeed(a / b) + * + * // ┌─── Effect + * // ▼ + * const program = Effect.orDie(divide(1, 0)) + * + * Effect.runSyncExit(program) // => Exit.die(new DivideByZeroError()) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const orDie: (self: Effect) => Effect = internal.orDie + +/** + * Runs an effectful operation when the source effect fails, while preserving + * the original failure when the operation succeeds. + * + * **Details** + * + * Use this for logging, metrics, or other failure-side observations. If the + * operation passed to `tapError` fails, that error is also represented in the + * returned effect's error channel. + * + * **Example** (Running effects on failure) + * + * ```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") + * + * // Use tapError to log the error message when the task fails + * const tapping = Effect.tapError( + * task, + * (error) => Effect.sync(() => { output.push(`expected error: ${error}`) }) + * ) + * + * void output.push(Effect.runSyncExit(tapping)) + * output // => ["expected error: NetworkError", Exit.fail("NetworkError")] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const tapError: { + ( + f: (e: NoInfer) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (e: E) => Effect + ): Effect +} = internal.tapError + +/** + * Runs an effectful handler when a failure's `_tag` matches. + * + * **Details** + * + * Use this with tagged-union errors to perform side effects for one tag or a + * list of tags. When the handler succeeds, the original failure is preserved; + * if the handler fails, its error is also included in the returned effect. + * + * **Example** (Running effects for tagged failures) + * + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" + * const output: Array = [] + * + * class NetworkError extends Data.TaggedError("NetworkError")<{ + * statusCode: number + * }> {} + * + * class ValidationError extends Data.TaggedError("ValidationError")<{ + * field: string + * }> {} + * + * const task: Effect.Effect = + * Effect.fail(new NetworkError({ statusCode: 504 })) + * + * const program = Effect.tapErrorTag(task, "NetworkError", (error) => + * Effect.sync(() => { output.push(`expected error: ${error.statusCode}`) }) + * ) + * + * void output.push(Effect.runSyncExit(program)) + * output // => ["expected error: 504", Exit.fail(new NetworkError({ statusCode: 504 }))] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const tapErrorTag: { + | Arr.NonEmptyReadonlyArray>, E, A1, E1, R1>( + k: K, + f: (e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K>) => Effect + ): (self: Effect) => Effect + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1 + >( + self: Effect, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Effect + ): Effect +} = internal.tapErrorTag + +/** + * Runs an effectful operation with the full `Cause` when the source effect + * fails. + * + * **When to use** + * + * Use when failure observation needs typed failures, defects, and interruptions + * rather than only the typed error value. + * + * **Details** + * + * Use this to log or inspect typed failures, defects, and interruptions. When + * the operation succeeds, the original cause is preserved. If the operation + * fails, its error is also represented in the returned effect. + * + * **Example** (Observing full failure causes) + * + * ```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) => Effect.sync(() => { output.push(`Logging cause: ${Cause.squash(cause)}`) }) + * ) + * + * void output.push(Effect.runSyncExit(program)) + * output // => ["Logging cause: Something went wrong", Exit.fail("Something went wrong")] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const tapCause: { + ( + f: (cause: Cause.Cause>) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (cause: Cause.Cause) => Effect + ): Effect +} = internal.tapCause + +/** + * Executes a side effect conditionally when a failed effect's cause matches a predicate. + * + * **Details** + * + * This function allows you to tap into the cause of an effect's failure only when + * the cause matches a specific predicate. This is useful for conditional logging, + * monitoring, or other side effects based on the type of failure. + * + * **Example** (Observing selected failure causes) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] + * + * const task = Effect.fail("Network timeout") + * + * // Only log causes that contain failures (not interrupts or defects) + * const program = Effect.tapCauseIf( + * task, + * Cause.hasFails, + * (cause) => Effect.sync(() => { output.push(`Logging failure cause: ${Cause.squash(cause)}`) }) + * ) + * + * void output.push(Effect.runSyncExit(program)) + * output // => ["Logging failure cause: Network timeout", Exit.fail("Network timeout")] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const tapCauseIf: { + ( + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect + ): Effect +} = internal.tapCauseIf + +/** + * Executes a side effect conditionally when a failed effect's cause passes a filter. + * + * **When to use** + * + * Use when you need to observe only failure causes selected by a `Filter`, + * while giving the side effect both the selected value and the original + * `Cause`. + * + * **Details** + * + * A successful filter result runs the side effect with the selected value and + * original cause. A failed filter result skips the side effect and preserves the + * original cause. + * + * @see {@link tapCauseIf} for selecting causes with a boolean predicate + * @see {@link tapCause} for observing every failure cause + * @see {@link catchCauseFilter} for recovering from selected causes instead of only observing them + * + * @category sequencing + * @since 4.0.0 + */ +export const tapCauseFilter: { + >( + filter: Filter.Filter, EB, X>, + f: (a: EB, cause: Cause.Cause) => Effect + ): (self: Effect) => Effect + >( + self: Effect, + filter: Filter.Filter, EB, X>, + f: (a: EB, cause: Cause.Cause) => Effect + ): Effect +} = internal.tapCauseFilter + +/** + * Runs an effectful operation when the source effect dies with a defect. + * + * **Details** + * + * Use this for diagnostics such as logging unexpected thrown exceptions or + * values passed to `die`. Recoverable failures are not handled. When the + * operation succeeds, the original defect is preserved; if the operation fails, + * its error is also represented in the returned effect. + * + * **Example** (Observing defects) + * + * ```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( + * "Something went wrong" + * ) + * + * // Log the defect using tapDefect + * const tapping2 = Effect.tapDefect( + * task2, + * (defect) => Effect.sync(() => { output.push(`defect: ${defect}`) }) + * ) + * + * 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 + (self: Effect, f: (defect: unknown) => Effect): Effect +} = internal.tapDefect + +/** + * Retries an effect until it succeeds, discarding failures. + * + * **Details** + * + * Yields between attempts so other fibers can run. + * + * **Example** (Retrying until success) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * let attempts = 0 + * + * const flaky = Effect.gen(function*() { + * attempts++ + * yield* Effect.sync(() => { output.push(`Attempt ${attempts}`) }) + * if (attempts < 3) { + * return yield* Effect.fail("Not ready") + * } + * return "Ready" + * }) + * + * const program = Effect.eventually(flaky) + * + * void output.push(await Effect.runPromise(program)) + * output // => ["Attempt 1", "Attempt 2", "Attempt 3", "Ready"] + * ``` + * + * @category repetition + * @since 2.0.0 + */ +export const eventually: (self: Effect) => Effect = internal.eventually + +// ----------------------------------------------------------------------------- +// Error Handling +// ----------------------------------------------------------------------------- + +/** + * Type helpers for retrying effects. + * + * @since 2.0.0 + */ +export declare namespace Retry { + /** + * Computes the result type of `Effect.retry` from the original effect and retry options. + * + * @category error handling + * @since 2.0.0 + */ + export type Return> = Effect< + A, + | (O extends { schedule: Schedule } ? E + : O extends { times: number } ? E + : O extends { until: Predicate.Refinement } ? E2 + : O extends { while: Predicate.Refinement } ? Exclude + : E) + | (O extends { schedule: Schedule } ? E + : never) + | (O extends { while: (...args: Array) => Effect } ? E + : never) + | (O extends { until: (...args: Array) => Effect } ? E + : never), + | R + | (O extends { schedule: Schedule } ? R + : never) + | (O extends { while: (...args: Array) => Effect } ? R + : never) + | (O extends { until: (...args: Array) => Effect } ? R + : never) + > extends infer Z ? Z + : never + + /** + * Options that control whether and how a failing effect is retried. + * + * @category error handling + * @since 2.0.0 + */ + export interface Options { + while?: ((error: E) => boolean | Effect) | undefined + until?: ((error: E) => boolean | Effect) | undefined + times?: number | undefined + schedule?: Schedule | undefined + } +} + +/** + * Retries typed failures from an effect according to a retry policy. + * + * **When to use** + * + * Use when you need to rerun an effect after transient typed failures, such as + * network issues or temporary resource unavailability. + * + * **Details** + * + * The policy can be a `Schedule`, a schedule builder, or a `Retry.Options` + * object using `schedule`, `times`, `while`, or `until`. If a retry eventually + * succeeds, the returned effect succeeds with that value. If the policy stops + * while the effect is still failing, the last failure is propagated. + * + * **Gotchas** + * + * The source effect is always evaluated once before any retry policy is + * applied. For example, `Schedule.recurs(3)` allows up to three retries after + * the initial attempt. + * + * Defects and interruptions are not retried. + * + * **Example** (Retrying with a schedule) + * + * ```ts import.meta.vitest + * import { Data, Effect, Schedule } from "effect" + * + * class AttemptError extends Data.TaggedError("AttemptError")<{ readonly attempt: number }> {} + * + * let attempt = 0 + * const task = Effect.callback((resume) => { + * attempt++ + * if (attempt <= 2) { + * resume(Effect.fail(new AttemptError({ attempt }))) + * } else { + * resume(Effect.succeed("Success!")) + * } + * }) + * + * const policy = Schedule.recurs(5) + * const program = Effect.retry(task, policy) + * + * await Effect.runPromise(program) // => "Success!" + * ``` + * + * @see {@link retryOrElse} for a version that allows you to run a fallback. + * @see {@link repeat} if your retry condition is based on successful outcomes rather than errors. + * @category error handling + * @since 2.0.0 + */ +export const retry: { + >(options: O): (self: Effect) => Retry.Return + ( + policy: Schedule, Error, Env> + ): (self: Effect) => Effect + ( + builder: ( + $: (_: Schedule, SE, R>) => Schedule + ) => Schedule, Error, Env> + ): (self: Effect) => Effect + >(self: Effect, options: O): Retry.Return + ( + self: Effect, + policy: Schedule, Error, Env> + ): Effect + ( + self: Effect, + builder: ( + $: (_: Schedule, SE, R>) => Schedule + ) => Schedule, Error, Env> + ): Effect +} = internalSchedule.retry + +/** + * Retries a failing effect and runs a fallback effect if retries are exhausted. + * + * **When to use** + * + * Use when you want to handle failures gracefully by specifying an alternative + * action after repeated failures. + * + * **Details** + * + * The `Effect.retryOrElse` function attempts to retry a failing effect multiple + * times according to a defined {@link Schedule} policy. + * + * If the retries are exhausted and the effect still fails, it runs a fallback + * effect instead. + * + * **Example** (Falling back after retries are exhausted) + * + * ```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* Effect.sync(() => { output.push(`Network attempt ${attempt}`) }) + * if (attempt < 3) { + * return yield* Effect.fail(new NetworkTimeoutError()) + * } + * return "Network data" + * }) + * + * // Retry up to 2 times, then fall back to cached data + * const program = Effect.retryOrElse( + * networkRequest, + * Schedule.recurs(2), + * (error, retryCount) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push(`All ${retryCount} retries failed, using cache`) }) + * return "Cached 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. + * @category error handling + * @since 2.0.0 + */ +export const retryOrElse: { + ( + policy: Schedule, E1, R1>, + orElse: (e: NoInfer, out: A1) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + policy: Schedule, E1, R1>, + orElse: (e: NoInfer, out: A1) => Effect + ): Effect +} = internalSchedule.retryOrElse + +/** + * Exposes an effect's full failure cause in the error channel as `Cause`. + * + * **Details** + * + * Use `sandbox` when downstream error handling needs to distinguish typed + * failures, defects, and interruptions. Use `unsandbox` to restore the original + * typed error channel after cause-level handling. + * + * **Example** (Exposing failures as causes) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * + * const task = Effect.fail("Something went wrong") + * + * // Sandbox exposes the full cause as the error type + * const program = Effect.gen(function*() { + * const result = yield* Effect.flip(Effect.sandbox(task)) + * return `Caught cause: ${Cause.squash(result)}` + * }) + * + * Effect.runSync(program) // => "Caught cause: Something went wrong" + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const sandbox: ( + self: Effect +) => Effect, R> = internal.sandbox + +/** + * Discards both the success and failure values of an effect. + * + * **When to use** + * + * Use when an effect should run for its side effects while both success and + * failure values are discarded. + * + * **Details** + * + * Use the `log` option to emit the full {@link Cause} when the effect fails, + * and `message` to prepend a custom log message. + * + * **Example** (Discarding success and failure values) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // ┌─── Effect + * // ▼ + * const task = Effect.fail("Uh oh!").pipe(Effect.as(5)) + * + * // ┌─── Effect + * // ▼ + * const program = task.pipe(Effect.ignore) + * Effect.runSync(program) // => undefined + * ``` + * + * **Example** (Logging failures while ignoring results) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const task = Effect.fail("Uh oh!") + * + * const program = task.pipe(Effect.ignore) + * Effect.runSync(program) // => undefined + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const ignore: < + Arg extends Effect | { + readonly log?: boolean | Severity | undefined + readonly message?: string | undefined + } | undefined = { + readonly log?: boolean | Severity | undefined + readonly message?: string | undefined + } +>( + effectOrOptions?: Arg, + options?: { + readonly log?: boolean | Severity | undefined + readonly message?: string | undefined + } | undefined +) => [Arg] extends [Effect] ? Effect + : (self: Effect) => Effect = internal.ignore + +/** + * Ignores the effect's failure cause, including defects and interruptions. + * + * **When to use** + * + * Use when a best-effort effect should never fail, even from defects or + * interruption, and optional cause logging is enough. + * + * **Details** + * + * Use the `log` option to emit the full {@link Cause} when the effect fails, + * and `message` to prepend a custom log message. + * + * **Example** (Ignoring failures and logging causes) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const task = Effect.fail("boom") + * + * const program = task.pipe(Effect.ignoreCause) + * Effect.runSync(program) // => undefined + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const ignoreCause: < + Arg extends Effect | { + readonly log?: boolean | Severity | undefined + readonly message?: string | undefined + } | undefined = { + readonly log?: boolean | Severity | undefined + readonly message?: string | undefined + } +>( + effectOrOptions?: Arg, + options?: { + readonly log?: boolean | Severity | undefined + readonly message?: string | undefined + } | undefined +) => [Arg] extends [Effect] ? Effect + : (self: Effect) => Effect = internal.ignoreCause + +/** + * Applies an `ExecutionPlan` to an effect, retrying with step-provided resources + * until it succeeds or the plan is exhausted. + * + * **Details** + * + * Each attempt updates `ExecutionPlan.CurrentMetadata` (attempt and step index), + * 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 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" }), attempts: 2 }, + * { provide: Layer.succeed(Endpoint, { url: "good" }) } + * ) + * + * const program = Effect.withExecutionPlan(fetchUrl, plan) + * Effect.runSync(program) // => "good" + * ``` + * + * **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.ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }>, + options?: { + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect) | undefined + } + ): ( + effect: Effect + ) => Effect | PlanR | RX> + ( + effect: Effect, + plan: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }>, + options?: { + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect) | undefined + } + ): Effect | PlanR | RX> +} = internalExecutionPlan.withExecutionPlan + +/** + * Runs an effect and reports any errors to the configured `ErrorReporter`s. + * + * **Details** + * + * If the `defectsOnly` option is set to `true`, only defects (unrecoverable + * errors) will be reported, while regular failures will be ignored. + * + * @category error handling + * @since 4.0.0 + */ +export const withErrorReporting: < + Arg extends Effect | { readonly defectsOnly?: boolean | undefined } | undefined = { + readonly defectsOnly?: boolean | undefined + } +>( + effectOrOptions: Arg, + options?: { readonly defectsOnly?: boolean | undefined } | undefined +) => [Arg] extends [Effect] ? Arg : (self: Effect) => Effect = + internal.withErrorReporting + +// ----------------------------------------------------------------------------- +// Fallback +// ----------------------------------------------------------------------------- + +/** + * Recovers from a typed failure by producing a fallback success value. + * + * **Details** + * + * If the source effect succeeds, its value is preserved. If it fails in the + * error channel, `orElseSucceed` evaluates the fallback and succeeds with that + * value, removing the typed error from the returned effect. + * + * Defects and interruptions are not recovered by this operator. + * + * **Example** (Replacing failures with a value) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * + * const validate = (age: number): Effect.Effect => { + * if (age < 0) { + * return Effect.fail("NegativeAgeError") + * } else if (age < 18) { + * return Effect.fail("IllegalAgeError") + * } else { + * return Effect.succeed(age) + * } + * } + * + * const program = Effect.orElseSucceed(validate(-1), () => 18) + * + * Effect.runSyncExit(program) // => Exit.succeed(18) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const orElseSucceed: { + ( + evaluate: LazyArg + ): (self: Effect) => Effect + ( + self: Effect, + evaluate: LazyArg + ): Effect +} = internal.orElseSucceed + +/** + * Runs a sequence of effects and returns the result of the first successful + * one. + * + * **When to use** + * + * Use when you have prioritized fallback `Effect`s, such as attempting + * multiple APIs, reading configuration from several sources, or trying + * alternative resource locations in order. + * + * **Details** + * + * This function executes the provided effects in sequence, stopping at the + * first success. If an effect succeeds, its result is returned immediately and + * no further effects in the sequence are executed. + * + * If all effects fail, the returned effect fails with the error from the last + * effect. If the collection is empty, the returned effect defects with an + * `Error` whose message is `"Received an empty collection of effects"`. + * + * **Example** (Trying alternatives until one succeeds) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const primary = Effect.fail("primary unavailable") + * const secondary = Effect.succeed("secondary result") + * const tertiary = Effect.sync(() => { + * throw new Error("not evaluated") + * }) + * + * const program = Effect.firstSuccessOf([ + * primary, + * secondary, + * tertiary + * ]) + * + * Effect.runSync(program) // => "secondary result" + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const firstSuccessOf: >( + effects: Iterable +) => Effect, Error, Services> = internal.firstSuccessOf + +// ----------------------------------------------------------------------------- +// Delays & timeouts +// ----------------------------------------------------------------------------- + +/** + * Adds a time limit to an effect, triggering a timeout if the effect exceeds + * the duration. + * + * **When to use** + * + * Use when you need a timeout of an `Effect` to be represented as a typed + * failure. + * + * **Details** + * + * 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 + * `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** + * + * If the timeout wins, the source effect is interrupted. + * + * **Example** (Failing when work takes too long) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * 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. + * @see {@link timeoutOrElse} for a version that allows specifying both success and timeout handlers. + * + * @category delays & timeouts + * @since 2.0.0 + */ +export const timeout: { + ( + duration: Duration.Input + ): (self: Effect) => Effect + ( + self: Effect, + duration: Duration.Input + ): Effect +} = internal.timeout + +/** + * Runs an effect with a time limit and represents only the timeout case as + * `Option.none`. + * + * **When to use** + * + * Use when a timeout of an `Effect` should be handled as `Option.none`. + * + * **Details** + * + * If the source effect succeeds before the timeout, the returned effect + * succeeds with `Option.some(value)`. If the timeout wins, the source effect is + * interrupted and the returned effect succeeds with `Option.none`. If the + * source effect fails before the timeout, that failure is preserved. + * + * **Example** (Returning None on timeout) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const timedOutEffect = Effect.never.pipe(Effect.timeoutOption(0)) + * await Effect.runPromise(timedOutEffect) // => Option.none() + * ``` + * + * @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 + * @since 3.1.0 + */ +export const timeoutOption: { + ( + duration: Duration.Input + ): (self: Effect) => Effect, E, R> + ( + self: Effect, + duration: Duration.Input + ): Effect, E, R> +} = 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. + * + * **Example** (Falling back on timeout) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.timeoutOrElse(Effect.never, { + * duration: 0, + * orElse: () => Effect.sync(() => { output.push("Query timed out, using cached data") }).pipe( + * Effect.as("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 `TimeoutError`. + * @see {@link timeoutOption} for returning `Option.none` on timeout. + * + * @category delays & timeouts + * @since 4.0.0 + */ +export const timeoutOrElse: { + (options: { + readonly duration: Duration.Input + readonly orElse: LazyArg> + }): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly duration: Duration.Input + readonly orElse: LazyArg> + } + ): Effect +} = internal.timeoutOrElse + +/** + * Returns an effect that is delayed from this effect by the specified + * `Duration`. + * + * **Example** (Delaying an effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.delay(Effect.sync(() => { output.push("Delayed message") }), 0) + * + * await Effect.runPromise(program) + * output // => ["Delayed message"] + * ``` + * + * @category delays & timeouts + * @since 2.0.0 + */ +export const delay: { + ( + duration: Duration.Input + ): (self: Effect) => Effect + ( + self: Effect, + duration: Duration.Input + ): Effect +} = internal.delay + +/** + * Returns an effect that suspends the current fiber for the specified duration + * without blocking a JavaScript thread. + * + * **Example** (Pausing without blocking) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Start") }) + * yield* Effect.sleep(0) + * yield* Effect.sync(() => { output.push("End") }) + * }) + * + * await Effect.runPromise(program) + * output // => ["Start", "End"] + * ``` + * + * @category delays & timeouts + * @since 2.0.0 + */ +export const sleep: (duration: Duration.Input) => Effect = internal.sleep + +/** + * Returns the runtime duration of an effect together with its result. + * + * **Details** + * + * The original success, failure, or interruption is preserved; only the success + * value is paired with the duration. + * + * **Example** (Measuring execution time) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const [, value] = yield* Effect.timed(Effect.succeed("ok")) + * return value + * }) + * + * Effect.runSync(program) // => "ok" + * ``` + * + * @category delays & timeouts + * @since 2.0.0 + */ +export const timed: (self: Effect) => Effect<[duration: Duration.Duration, result: A], E, R> = + internal.timed + +// ----------------------------------------------------------------------------- +// Racing +// ----------------------------------------------------------------------------- + +/** + * Runs multiple effects concurrently and returns the first successful result. + * + * **When to use** + * + * Use when early failures should be ignored until a success occurs + * or all effects fail. + * + * **Details** + * + * Early failures do not finish the race; `raceAll` keeps waiting until one + * effect succeeds or every effect has failed. When one effect succeeds, the + * remaining effects are interrupted. If every effect fails, the returned effect + * fails with a cause containing the collected failure reasons. + * + * **Example** (Racing many effects) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * 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. + * @category racing + * @since 2.0.0 + */ +export const raceAll: >( + all: Iterable, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber + readonly index: number + readonly parentFiber: Fiber + }) => void + } +) => Effect, Error, Services> = internal.raceAll + +/** + * Runs multiple effects concurrently and completes with the first effect to + * finish, whether it succeeds or fails. + * + * **Details** + * + * After the first effect completes, all remaining effects are interrupted. Use + * `raceAll` when early failures should be ignored until a success occurs or + * all effects fail. + * + * **Example** (Taking the first settled result) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const raced = Effect.raceAllFirst([ + * Effect.fail("First failed"), + * Effect.never + * ]) + * await Effect.runPromise(Effect.flip(raced)) // => "First failed" + * ``` + * + * @category racing + * @since 4.0.0 + */ +export const raceAllFirst: >( + all: Iterable, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber + readonly index: number + readonly parentFiber: Fiber + }) => void + } +) => Effect, Error, Services> = internal.raceAllFirst + +/** + * Races two effects and returns the first successful result. + * + * **Details** + * + * If one effect succeeds, the other is interrupted and `onWinner` can observe the + * winning fiber. If both fail, the race fails. + * + * **Example** (Racing two effects) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * 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* Effect.sync(() => { output.push(`winner: ${result}`) }) + * }) + * + * await Effect.runPromise(program) + * output // => ["winner: slow-success"] + * ``` + * + * @category racing + * @since 2.0.0 + */ +export const race: { + ( + that: Effect, + options?: { + readonly onWinner?: ( + options: { readonly fiber: Fiber; readonly index: number; readonly parentFiber: Fiber } + ) => void + } + ): (self: Effect) => Effect + ( + self: Effect, + that: Effect, + options?: { + readonly onWinner?: ( + options: { readonly fiber: Fiber; readonly index: number; readonly parentFiber: Fiber } + ) => void + } + ): Effect +} = internal.race + +/** + * Races two effects and returns the result of the first one to complete, whether + * it succeeds or fails. + * + * **When to use** + * + * Use when any completion, including failure, should decide the race and + * interrupt the losing effect. + * + * **Details** + * + * The losing effect is interrupted, and `onWinner` can observe the winning fiber. + * + * **Example** (Observing the winning fiber) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * 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* Effect.sync(() => { output.push(message) }) + * }) + * + * await Effect.runPromise(program) + * output // => ["failed: fast-fail"] + * ``` + * + * @category racing + * @since 2.0.0 + */ +export const raceFirst: { + ( + that: Effect, + options?: { + readonly onWinner?: ( + options: { readonly fiber: Fiber; readonly index: number; readonly parentFiber: Fiber } + ) => void + } + ): (self: Effect) => Effect + ( + self: Effect, + that: Effect, + options?: { + readonly onWinner?: ( + options: { readonly fiber: Fiber; readonly index: number; readonly parentFiber: Fiber } + ) => void + } + ): Effect +} = internal.raceFirst + +// ----------------------------------------------------------------------------- +// Filtering +// ----------------------------------------------------------------------------- + +/** + * Filters elements of an iterable using a predicate, refinement, or effectful + * predicate. + * + * **Example** (Filtering success values) + * + * ```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) + * + * // Effectful predicate + * const checked = Effect.filter([1, 2, 3], (n) => Effect.succeed(n > 1)) + * + * void output.push(Effect.runSync(evens)) + * void output.push(Effect.runSync(checked)) + * output // => [[2, 4], [2, 3]] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + ( + refinement: Predicate.Refinement, B> + ): (elements: Iterable) => Effect> + ( + predicate: Predicate.Predicate> + ): (elements: Iterable) => Effect> + ( + predicate: (a: NoInfer, i: number) => Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): (iterable: Iterable) => Effect, E, R> + ( + elements: Iterable, + refinement: Predicate.Refinement + ): Effect> + ( + elements: Iterable, + predicate: Predicate.Predicate + ): Effect> + ( + iterable: Iterable, + predicate: (a: NoInfer, i: number) => Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect, E, R> +} = internal.filter + +/** + * Filters and maps elements of an iterable with a `Filter`. + * + * **When to use** + * + * Use when you need to filter an iterable with a `Filter` inside an `Effect`, + * collecting each filter success value. + * + * **Details** + * + * `Result.succeed` values are collected in the returned array, and + * `Result.fail` values are skipped. + * + * @see {@link filter} for keeping original elements with a boolean predicate, refinement, or effectful predicate + * @see {@link filterMapEffect} for using an effectful `Filter` + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + ( + filter: Filter.Filter, B, X> + ): (elements: Iterable) => Effect> + ( + elements: Iterable, + filter: Filter.Filter, B, X> + ): Effect> +} = internal.filterMap + +/** + * Filters and maps elements of an iterable effectfully with a `FilterEffect`. + * + * **When to use** + * + * Use when you need to filter each iterable element effectfully and transform + * accepted elements into successful output values. + * + * **Details** + * + * `Result.succeed` values are collected in the returned array, and + * `Result.fail` values are skipped. + * + * **Gotchas** + * + * With concurrent execution, successful values are collected in completion + * order, not input order. + * + * @see {@link filterMap} for using a synchronous `Filter` + * @see {@link filter} for keeping original elements with a predicate + * + * @category filtering + * @since 4.0.0 + */ +export const filterMapEffect: { + ( + filter: Filter.FilterEffect, B, X, E, R>, + options?: { readonly concurrency?: Concurrency | undefined } + ): (elements: Iterable) => Effect, E, R> + ( + elements: Iterable, + filter: Filter.FilterEffect, B, X, E, R>, + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect, E, R> +} = internal.filterMapEffect + +/** + * Filters an effect, providing an alternative effect if the predicate fails. + * + * **When to use** + * + * Use when a successful value that fails a predicate should continue with an + * effectful fallback instead of failing the effect. + * + * **Details** + * + * This function applies a predicate to the result of an effect. If the + * predicate evaluates to `false`, it executes the `orElse` effect instead. The + * `orElse` effect can produce an alternative value or perform additional + * computations. + * + * **Example** (Filtering with a fallback effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // An effect that produces a number + * const program = Effect.succeed(5) + * + * // Filter for even numbers, provide alternative for odd numbers + * const filtered = Effect.filterOrElse( + * program, + * (n) => n % 2 === 0, + * (n) => Effect.succeed(`Number ${n} is odd`) + * ) + * + * Effect.runSync(filtered) // => "Number 5 is odd" + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterOrElse: { + ( + refinement: Predicate.Refinement, B>, + orElse: (a: EqualsWith, Exclude, B>>) => Effect + ): (self: Effect) => Effect + ( + predicate: Predicate.Predicate>, + orElse: (a: NoInfer) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + refinement: Predicate.Refinement, + orElse: (a: EqualsWith>) => Effect + ): Effect + ( + self: Effect, + predicate: Predicate.Predicate>, + orElse: (a: NoInfer) => Effect + ): Effect +} = internal.filterOrElse + +/** + * Filters an effect with a `Filter`, providing an alternative effect on failure. + * + * **When to use** + * + * Use when a successful effect value should be accepted and transformed by a + * `Filter`, while rejected values should continue with an alternative effect + * built from the filter failure. + * + * **Details** + * + * `Result.succeed` becomes the returned success value, and `Result.fail` is + * passed to `orElse`. + * + * @see {@link filterOrElse} for using a predicate and fallback effect + * @see {@link filterMapOrFail} for failing the effect when the filter fails + * + * @category filtering + * @since 4.0.0 + */ +export const filterMapOrElse: { + ( + filter: Filter.Filter, B, X>, + orElse: (x: X) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + filter: Filter.Filter, B, X>, + orElse: (x: X) => Effect + ): Effect +} = internal.filterMapOrElse + +/** + * Filters an effect, failing with a custom error if the predicate fails. + * + * **Details** + * + * This function applies a predicate to the result of an effect. If the + * predicate evaluates to `false`, the effect fails with either a custom + * error (if `orFailWith` is provided) or a `NoSuchElementError`. + * + * **Example** (Filtering with a custom failure) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // An effect that produces a number + * const program = Effect.succeed(5) + * + * // Filter for even numbers, fail for odd numbers + * const filtered = Effect.filterOrFail( + * program, + * (n) => n % 2 === 0, + * (n) => `Expected even number, got ${n}` + * ) + * + * Effect.runSync(Effect.flip(filtered)) // => "Expected even number, got 5" + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterOrFail: { + ( + refinement: Predicate.Refinement, B>, + orFailWith: (a: NoInfer) => E2 + ): (self: Effect) => Effect + ( + predicate: Predicate.Predicate>, + orFailWith: (a: NoInfer) => E2 + ): (self: Effect) => Effect + ( + refinement: Predicate.Refinement, B> + ): (self: Effect) => Effect + ( + predicate: Predicate.Predicate> + ): (self: Effect) => Effect + ( + self: Effect, + refinement: Predicate.Refinement, B>, + orFailWith: (a: NoInfer) => E2 + ): Effect + ( + self: Effect, + predicate: Predicate.Predicate>, + orFailWith: (a: NoInfer) => E2 + ): Effect + ( + self: Effect, + refinement: Predicate.Refinement, B> + ): Effect + ( + self: Effect, + predicate: Predicate.Predicate> + ): Effect +} = internal.filterOrFail + +/** + * Filters and maps an effect with a `Filter`, failing when the filter fails. + * + * **When to use** + * + * Use when validating and transforming one effect success with a synchronous + * `Filter`, while rejected values should fail the effect. + * + * **Details** + * + * `Result.succeed` becomes the returned success value. `Result.fail` is mapped + * with `orFailWith` when provided, or fails with `NoSuchElementError`. + * + * @see {@link filterMapOrElse} for continuing with a fallback effect when the filter fails + * @see {@link filterOrFail} for validating with a predicate instead of a `Filter` + * @see {@link filterMap} for filtering and mapping iterable elements + * + * @category filtering + * @since 4.0.0 + */ +export const filterMapOrFail: { + ( + filter: Filter.Filter, B, X>, + orFailWith: (x: X) => E2 + ): (self: Effect) => Effect + ( + filter: Filter.Filter, B, X> + ): (self: Effect) => Effect + ( + self: Effect, + filter: Filter.Filter, + orFailWith: (x: X) => E2 + ): Effect + ( + self: Effect, + filter: Filter.Filter + ): Effect +} = internal.filterMapOrFail + +// ----------------------------------------------------------------------------- +// Conditional Operators +// ----------------------------------------------------------------------------- + +/** + * Runs an effect conditionally based on the result of an effectful boolean + * condition. + * + * **When to use** + * + * Use when you need an effectful check to decide whether another effect should + * run while representing the skipped case explicitly. + * + * **Details** + * + * The condition effect is evaluated first. If it succeeds with `true`, the + * source effect is run and its success value is wrapped in `Option.some`. If it + * succeeds with `false`, the source effect is skipped and the result is + * `Option.none`. If the condition effect fails, that failure is preserved. + * + * **Example** (Conditionally running an effect) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * const output: Array = [] + * + * const shouldLog = true + * + * const program = Effect.when( + * Effect.sync(() => { output.push("Condition is true!") }), + * Effect.succeed(shouldLog) + * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Condition is true!", Option.some(undefined)] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const when: { + ( + condition: Effect + ): (self: Effect) => Effect, E | E2, R | R2> + ( + self: Effect, + condition: Effect + ): Effect, E | E2, R | R2> +} = internal.when + +// ----------------------------------------------------------------------------- +// Pattern matching +// ----------------------------------------------------------------------------- + +/** + * Handles both success and failure cases of an effect without performing side + * effects. + * + * **When to use** + * + * Use when you need to fold an `Effect` into a value by handling success and + * failure differently without triggering side effects. + * + * **Details** + * + * `match` lets you define custom handlers for both success and failure + * scenarios. You provide separate functions to handle each case, allowing you + * to process the result if the effect succeeds, or handle the error if the + * effect fails. + * + * **Example** (Matching success and failure values) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class ExampleError extends Data.TaggedError("ExampleError")<{ readonly message: string }> {} + * + * const success: Effect.Effect = Effect.succeed(42) + * + * const program1 = Effect.match(success, { + * onFailure: (error) => `failure: ${error.message}`, + * onSuccess: (value) => `success: ${value}` + * }) + * + * // Run and log the result of the successful effect + * Effect.runSync(program1) // => "success: 42" + * + * const failure: Effect.Effect = Effect.fail( + * new ExampleError({ message: "Uh oh!" }) + * ) + * + * const program2 = Effect.match(failure, { + * onFailure: (error) => `failure: ${error.message}`, + * onSuccess: (value) => `success: ${value}` + * }) + * + * // Run and log the result of the failed effect + * Effect.runSync(program2) // => "failure: Uh oh!" + * ``` + * + * @see {@link matchEffect} if you need to perform side effects in the handlers. + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + (options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + }): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect +} = internal.match + +/** + * Handles both success and failure cases of an effect without performing side + * effects, with eager evaluation for resolved effects. + * + * **When to use** + * + * Use when you need to handle both success and failure cases of an + * already-resolved `Effect` with optimized handling. + * + * **Details** + * + * `matchEager` works like `match` but provides better performance for resolved + * effects (Success or Failure). When the effect is already resolved, it applies + * the handlers immediately without fiber scheduling. For unresolved effects, + * it falls back to the regular `match` behavior. + * + * **Example** (Pattern matching eagerly when possible) + * + * ```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}` + * }) + * void output.push(result) + * }) + * + * Effect.runSync(program) + * output // => ["Success: 42"] + * ``` + * + * @see {@link match} for the non-eager version. + * @see {@link matchEffect} if you need to perform side effects in the handlers. + * @category pattern matching + * @since 4.0.0 + */ +export const matchEager: { + (options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + }): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect +} = internal.matchEager + +/** + * Handles failures by matching the cause of failure. + * + * **When to use** + * + * Use when you need to fold an `Effect` while the failure handler inspects the + * full `Cause`. + * + * **Details** + * + * The `matchCause` function allows you to handle failures with access to the + * full cause of the failure within a fiber. + * + * **Example** (Matching on success or failure causes) + * + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * + * const task = Effect.fail("Something went wrong") + * + * const program = Effect.matchCause(task, { + * onFailure: (cause) => `Failed: ${Cause.squash(cause)}`, + * onSuccess: (value) => `Success: ${value}` + * }) + * + * Effect.runSync(program) // => "Failed: Something went wrong" + * ``` + * + * @see {@link matchCauseEffect} if you need to perform side effects in the + * handlers. + * @see {@link match} if you don't need to handle the cause of the failure. + * @category pattern matching + * @since 2.0.0 + */ +export const matchCause: { + (options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (a: A) => A3 + }): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (a: A) => A3 + } + ): Effect +} = internal.matchCause + +/** + * Handles failures by matching the cause of failure with eager evaluation. + * + * **When to use** + * + * Use when you expect an `Effect` to already be resolved and want to match the + * `Cause` without regular effect pipeline overhead. + * + * **Details** + * + * `matchCauseEager` works like `matchCause` but provides better performance for resolved + * effects by immediately applying the matching function instead of deferring it + * through the effect pipeline. + * + * **Example** (Eagerly matching already completed effects) + * + * ```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 + * @since 4.0.0 + */ +export const matchCauseEager: { + (options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (value: A) => A3 + }): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect +} = internal.matchCauseEager + +/** + * Handles success or failure eagerly with effectful handlers when the effect is already resolved. + * + * **When to use** + * + * Use when you need effectful success and cause-aware failure handlers for + * `Effect` inputs that may already be resolved. + * + * **Details** + * + * If the effect is an `Exit`, the matching handler runs immediately; otherwise it behaves like + * {@link matchCauseEffect}. + * + * @see {@link matchCauseEffect} for the non-eager effectful variant + * @see {@link matchCauseEager} for eager cause matching with pure handlers + * @see {@link matchEffect} for effectful matching on typed failures instead of full causes + * + * @category pattern matching + * @since 4.0.0 + */ +export const matchCauseEffectEager: { + ( + options: { + readonly onFailure: (cause: Cause.Cause) => Effect + readonly onSuccess: (a: A) => Effect + } + ): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => Effect + readonly onSuccess: (a: A) => Effect + } + ): Effect +} = internal.matchCauseEffectEager + +/** + * Handles failures with access to the cause and allows performing side effects. + * + * **When to use** + * + * Use when you need to fold an `Effect` with effectful success handlers and + * `Cause`-aware failure handlers. + * + * **Details** + * + * The `matchCauseEffect` function works similarly to {@link matchCause}, but it + * also allows you to perform additional side effects based on the failure + * cause. This function provides access to the complete cause of the failure, + * making it possible to differentiate between various failure types, and allows + * you to respond accordingly while performing side effects (like logging or + * other operations). + * + * **Example** (Effectfully matching on causes) + * + * ```ts import.meta.vitest + * import { Cause, Data, Effect, Result } from "effect" + * const output: Array = [] + * + * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} + * + * const task = Effect.fail(new TaskError({ message: "Task failed" })) + * + * const program = Effect.matchCauseEffect(task, { + * onFailure: (cause) => + * Effect.gen(function*() { + * if (Cause.hasFails(cause)) { + * const error = Cause.findError(cause) + * if (Result.isSuccess(error)) { + * yield* Effect.sync(() => { output.push(`Handling error: ${error.success.message}`) }) + * } + * return "recovered from error" + * } else { + * yield* Effect.sync(() => { output.push("Handling interruption or defect") }) + * return "recovered from interruption/defect" + * } + * }), + * onSuccess: (value) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push(`Success: ${value}`) }) + * return `processed ${value}` + * }) + * }) + * + * 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. + * @see {@link matchEffect} if you don't need to handle the cause of the failure. + * + * @category pattern matching + * @since 2.0.0 + */ +export const matchCauseEffect: { + (options: { + readonly onFailure: (cause: Cause.Cause) => Effect + readonly onSuccess: (a: A) => Effect + }): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => Effect + readonly onSuccess: (a: A) => Effect + } + ): Effect +} = internal.matchCauseEffect + +/** + * Handles both success and failure by running effectful handlers. + * + * **When to use** + * + * Use when you need to handle an `Effect`'s failure or success with handlers + * that return effects. + * + * **Details** + * + * Use `matchEffect` when either branch needs to return an `Effect`, such as + * performing logging, recovery, notification, or other effectful work. The + * returned effect succeeds or fails according to the handler that is run. + * + * **Example** (Matching success and failure with effectful handlers) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class ExampleError extends Data.TaggedError("ExampleError")<{ readonly message: string }> {} + * + * const success: Effect.Effect = Effect.succeed(42) + * const failure: Effect.Effect = Effect.fail( + * new ExampleError({ message: "Uh oh!" }) + * ) + * + * const program1 = Effect.matchEffect(success, { + * onFailure: (error) => + * Effect.succeed(`failure: ${error.message}`), + * onSuccess: (value) => + * Effect.succeed(`success: ${value}`) + * }) + * + * Effect.runSync(program1) // => "success: 42" + * + * const program2 = Effect.matchEffect(failure, { + * onFailure: (error) => + * Effect.succeed(`failure: ${error.message}`), + * onSuccess: (value) => + * Effect.succeed(`success: ${value}`) + * }) + * + * Effect.runSync(program2) // => "failure: Uh oh!" + * ``` + * + * @see {@link match} if you don't need side effects and only want to handle the + * result or failure. + * @category pattern matching + * @since 2.0.0 + */ +export const matchEffect: { + (options: { + readonly onFailure: (e: E) => Effect + readonly onSuccess: (a: A) => Effect + }): (self: Effect) => Effect + ( + self: Effect, + options: { + readonly onFailure: (e: E) => Effect + readonly onSuccess: (a: A) => Effect + } + ): Effect +} = internal.matchEffect + +// ----------------------------------------------------------------------------- +// Condition checking +// ----------------------------------------------------------------------------- + +/** + * Determines whether an effect fails. + * + * **Details** + * + * Defects are not converted; if the effect dies, the resulting effect dies too. + * + * **Example** (Checking whether an effect fails) + * + * ```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* Effect.sync(() => { output.push(failed) }) + * }) + * + * Effect.runSync(program) + * output // => [true] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isFailure: (self: Effect) => Effect = internal.isFailure + +/** + * Returns whether an effect completes successfully. + * + * **Details** + * + * Returns `false` for failures in the error channel, but defects still fail the + * effect. + * + * **Example** (Checking whether an effect succeeds) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * const ok = yield* Effect.isSuccess(Effect.succeed("done")) + * const failed = yield* Effect.isSuccess(Effect.fail("Uh oh")) + * yield* Effect.sync(() => { output.push(`ok: ${ok}`) }) + * yield* Effect.sync(() => { output.push(`failed: ${failed}`) }) + * }) + * + * Effect.runSync(program) + * output // => ["ok: true", "failed: false"] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isSuccess: (self: Effect) => Effect = internal.isSuccess + +// ----------------------------------------------------------------------------- +// Environment +// ----------------------------------------------------------------------------- + +/** + * Returns the complete context. + * + * **When to use** + * + * Use to read the complete `Context` available to the current effect. + * + * **Details** + * + * This function allows you to access all services that are currently available + * in the effect's environment. This can be useful for debugging, introspection, + * or when you need to pass the entire context to another function. + * + * **Example** (Reading the full context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Option } from "effect" + * const output: Array = [] + * + * const Logger = Context.Service<{ + * log: (msg: string) => void + * }>("Logger") + * const Database = Context.Service<{ + * query: (sql: string) => string + * }>("Database") + * + * const program = Effect.gen(function*() { + * const allServices = yield* Effect.context() + * + * // Check if specific services are available + * const loggerOption = Context.getOption(allServices, Logger) + * const databaseOption = Context.getOption(allServices, Database) + * + * yield* Effect.sync(() => { output.push(`Logger available: ${Option.isSome(loggerOption)}`) }) + * yield* Effect.sync(() => { output.push(`Database available: ${Option.isSome(databaseOption)}`) }) + * }) + * + * 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 accessors + * @since 2.0.0 + */ +export const context: () => Effect, never, R> = internal.context + +/** + * Transforms the current context using the provided function. + * + * **When to use** + * + * Use to derive an effect from the complete `Context`. + * + * **Details** + * + * This function allows you to access the complete context and perform + * computations based on all available services. This is useful when you need + * to conditionally execute logic based on what services are available. + * + * **Example** (Deriving values from the context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Option } from "effect" + * const output: Array = [] + * + * const Logger = Context.Service<{ + * log: (msg: string) => void + * }>("Logger") + * const Cache = Context.Service<{ + * get: (key: string) => string | null + * }>("Cache") + * + * 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* Effect.sync(() => { output.push("Using cached data") }) + * return cache.get("user:123") || "default" + * }) + * } else { + * return Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("No cache available, using fallback") }) + * return "fallback data" + * }) + * } + * }) + * + * 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 accessors + * @since 2.0.0 + */ +export const contextWith: ( + f: (context: Context.Context) => Effect +) => Effect = internal.contextWith + +/** + * Provides dependencies to an effect using layers or a context. Use `options.local` + * to build the layer every time; by default, layers are shared between provide + * calls. + * + * **Example** (Providing dependencies with a layer) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * interface Database { + * readonly query: (sql: string) => Effect.Effect + * } + * + * const Database = Context.Service("Database") + * + * const DatabaseLayer = Layer.succeed(Database)({ + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`)) + * }) + * + * const program = Effect.gen(function*() { + * const db = yield* Database + * return yield* db.query("SELECT * FROM users") + * }) + * + * const provided = Effect.provide(program, DatabaseLayer) + * + * await Effect.runPromise(provided) // => "Result for: SELECT * FROM users" + * ``` + * + * @category providing services + * @since 2.0.0 + */ +export const provide: { + ]>( + layers: Layers, + options?: { + readonly local?: boolean | undefined + } | undefined + ): ( + self: Effect + ) => Effect< + A, + E | Layer.Error, + Layer.Services | Exclude> + > + ( + layer: Layer.Layer, + options?: { + readonly local?: boolean | undefined + } | undefined + ): ( + self: Effect + ) => Effect> + ( + context: Context.Context + ): (self: Effect) => Effect> + ]>( + self: Effect, + layers: Layers, + options?: { + readonly local?: boolean | undefined + } | undefined + ): Effect< + A, + E | Layer.Error, + Layer.Services | Exclude> + > + ( + self: Effect, + layer: Layer.Layer, + options?: { + readonly local?: boolean | undefined + } | undefined + ): Effect> + ( + self: Effect, + context: Context.Context + ): Effect> +} = internalLayer.provide + +/** + * Provides a context to an effect, fulfilling its service requirements. + * + * **Details** + * + * This function provides multiple services at once by supplying a context + * that contains all the required services. It removes the provided services + * from the effect's requirements, making them available to the effect. + * + * **Example** (Providing a complete context) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] + * + * // Define service keys + * const Logger = Context.Service<{ + * log: (msg: string) => void + * }>("Logger") + * const Database = Context.Service<{ + * query: (sql: string) => string + * }>("Database") + * + * // Create a context with multiple services + * const context = Context.make(Logger, { log: (message) => { output.push(message) } }) + * .pipe(Context.add(Database, { query: () => "result" })) + * + * // An effect that requires both services + * const program = Effect.gen(function*() { + * const logger = yield* Effect.service(Logger) + * const db = yield* Effect.service(Database) + * logger.log("Querying database") + * return db.query("SELECT * FROM users") + * }) + * + * const provided = Effect.provideContext(program, context) + * void output.push(Effect.runSync(provided)) + * output // => ["Querying database", "result"] + * ``` + * + * @category providing services + * @since 4.0.0 + */ +export const provideContext: { + ( + context: Context.Context + ): (self: Effect) => Effect> + ( + self: Effect, + context: Context.Context + ): 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 import.meta.vitest + * import { Context, Effect } from "effect" + * + * interface Database { + * readonly query: (sql: string) => Effect.Effect + * } + * + * const Database = Context.Service("Database") + * + * const program = Effect.gen(function*() { + * 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 accessors + * @since 4.0.0 + */ +export const service: (service: Context.Key) => Effect = internal.service + +/** + * Optionally accesses a service from the environment. + * + * **When to use** + * + * Use to read an optional dependency from the current context without making + * that dependency part of the effect's required environment. + * + * **Details** + * + * This function attempts to access a service from the environment. If the + * service is available, it returns `Some(service)`. If the service is not + * available, it returns `None`. Unlike `service`, this function does not + * require the service to be present in the environment. + * + * **Example** (Accessing an optional service) + * + * ```ts import.meta.vitest + * import { Context, Effect, Option } from "effect" + * const output: Array = [] + * + * // Define a service key + * const Logger = Context.Service<{ + * log: (msg: string) => void + * }>("Logger") + * + * // Use serviceOption to optionally access the logger + * const program = Effect.gen(function*() { + * const maybeLogger = yield* Effect.serviceOption(Logger) + * + * if (Option.isSome(maybeLogger)) { + * maybeLogger.value.log("Service is available") + * } else { + * void output.push("Service not available") + * } + * }) + * + * Effect.runSync(program) + * output // => ["Service not available"] + * ``` + * + * @category accessors + * @since 2.0.0 + */ +export const serviceOption: (key: Context.Key) => Effect> = internal.serviceOption + +/** + * Provides part of the required context while leaving the rest unchanged. + * + * **Details** + * + * This function allows you to transform the context required by an effect, + * providing part of the context and leaving the rest to be fulfilled later. + * + * **Example** (Updating the context before running) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * + * // Define services + * const Logger = Context.Service<{ + * log: (msg: string) => void + * }>("Logger") + * const Config = Context.Service<{ + * name: string + * }>("Config") + * + * const program = Effect.service(Config).pipe( + * Effect.map((config) => `Hello ${config.name}!`) + * ) + * + * // Transform services by providing Config while keeping Logger requirement + * const configured = program.pipe( + * 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: () => {} + * }) + * Effect.runSync(result) // => "Hello World!" + * ``` + * + * @category providing services + * @since 4.0.0 + */ +export const updateContext: { + ( + f: (context: Context.Context) => Context.Context> + ): (self: Effect) => Effect + ( + self: Effect, + f: (context: Context.Context) => Context.Context> + ): Effect +} = internal.updateContext + +/** + * Runs an effect with a service implementation transformed by the provided + * function. + * + * **Details** + * + * The service must be available in the effect's context; `updateService` + * replaces it for the wrapped effect with the value returned by the updater. + * + * **Example** (Replacing a service for one 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* Effect.sync(() => { output.push(`Updated count: ${updatedCounter.count}`) }) + * return updatedCounter.count + * }).pipe( + * Effect.updateService(Counter, (counter) => ({ count: counter.count + 1 })) + * ) + * + * // Provide initial service and run + * const result = Effect.provideService(program, Counter, { count: 0 }) + * void output.push(Effect.runSync(result)) + * output // => ["Updated count: 1", 1] + * ``` + * + * @category providing services + * @since 2.0.0 + */ +export const updateService: { + ( + service: Context.Key, + f: (value: A) => NoInfer + ): (self: Effect) => Effect + ( + self: Effect, + service: Context.Key, + 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. + * + * **When to use** + * + * Use to satisfy one service requirement with an already-built implementation. + * + * **Details** + * + * The service requirement identified by the `Context.Key` is removed from the + * effect requirements after the implementation is provided. + * + * **Example** (Providing a service value) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] + * + * // Define a service for configuration + * const Config = Context.Service<{ + * apiUrl: string + * timeout: number + * }>("Config") + * + * const fetchData = Effect.gen(function*() { + * const config = yield* Effect.service(Config) + * yield* Effect.sync(() => { output.push(`Fetching from: ${config.apiUrl}`) }) + * yield* Effect.sync(() => { output.push(`Timeout: ${config.timeout}ms`) }) + * return "data" + * }) + * + * // Provide the service implementation + * const program = Effect.provideService(fetchData, Config, { + * apiUrl: "https://api.example.com", + * timeout: 5000 + * }) + * + * 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 providing services + * @since 2.0.0 + */ +export const provideService: { + ( + service: Context.Key + ): { + (implementation: S): (self: Effect) => Effect> + (self: Effect, implementation: S): Effect> + } + ( + service: Context.Key, + implementation: S + ): (self: Effect) => Effect> + ( + self: Effect, + service: Context.Key, + implementation: S + ): Effect> +} = internal.provideService + +/** + * Provides one service to an effect using an effectful acquisition. + * + * **When to use** + * + * Use when the service implementation must be created by an effect and its + * acquisition failure should remain in the returned effect. + * + * **Details** + * + * `provideServiceEffect` runs the acquisition effect to produce the service + * implementation, removes that service from the wrapped effect's requirements, + * and leaves any other requirements to be provided later. Acquisition failures + * are included in the returned effect's error channel. + * + * **Example** (Providing a service with an effect) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] + * + * // Define a database connection service + * interface DatabaseConnection { + * readonly query: (sql: string) => Effect.Effect + * } + * const Database = Context.Service("Database") + * + * // Effect that creates a database connection + * const createConnection = Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Establishing database connection...") }) + * yield* Effect.sync(() => { output.push("Database connected!") }) + * return { + * query: (sql: string) => Effect.succeed(`Result for: ${sql}`) + * } + * }) + * + * const program = Effect.gen(function*() { + * const db = yield* Effect.service(Database) + * return yield* db.query("SELECT * FROM users") + * }) + * + * // Provide the service through an effect + * const withDatabase = Effect.provideServiceEffect( + * program, + * Database, + * createConnection + * ) + * + * void output.push(await Effect.runPromise(withDatabase)) + * output // => ["Establishing database connection...", "Database connected!", "Result for: SELECT * FROM users"] + * ``` + * + * @category providing services + * @since 2.0.0 + */ +export const provideServiceEffect: { + ( + service: Context.Key, + acquire: Effect, E2, R2> + ): (self: Effect) => Effect | R2> + ( + self: Effect, + service: Context.Key, + acquire: Effect, E2, R2> + ): Effect | R2> +} = internal.provideServiceEffect + +// ----------------------------------------------------------------------------- +// Resource management & finalization +// ----------------------------------------------------------------------------- + +/** + * Returns the current scope for resource management. + * + * **Example** (Accessing the current scope) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * const currentScope = yield* Effect.scope + * yield* Effect.sync(() => { output.push("Got scope for resource management") }) + * + * // Use the scope to manually manage resources if needed + * const resource = yield* Effect.acquireRelease( + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) + * ) + * + * return resource + * }) + * + * void output.push(Effect.runSync(Effect.scoped(program))) + * output // => ["Got scope for resource management", "Acquiring resource", "Releasing resource", "resource"] + * ``` + * + * @category resource management + * @since 2.0.0 + */ +export const scope: Effect = internal.scope + +/** + * Runs an effect with a scope that closes when the effect completes. + * + * **When to use** + * + * Use to acquire scoped resources for the duration of a single workflow. + * + * **Details** + * + * Finalizers for resources acquired inside the workflow run as soon as the + * workflow completes, whether by success, failure, or interruption. + * + * **Example** (Running a scoped acquisition) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const resource = Effect.acquireRelease( + * 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* Effect.sync(() => { output.push(`Using ${res}`) }) + * return res + * }) + * ) + * + * Effect.runSync(program) + * output // => ["Acquiring resource", "Using resource", "Releasing resource"] + * ``` + * + * @category resource management + * @since 2.0.0 + */ +export const scoped: ( + self: Effect +) => Effect> = internal.scoped + +/** + * Creates a scoped effect by providing access to the scope. + * + * **When to use** + * + * Use when resource acquisition needs direct access to the scope being created, + * for example to register finalizers manually. + * + * **Example** (Working with an explicit scope) + * + * ```ts import.meta.vitest + * import { Effect, Scope } from "effect" + * const output: Array = [] + * + * const program = Effect.scopedWith((scope) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Inside scoped context") }) + * + * // Manually add a finalizer to the scope + * yield* Scope.addFinalizer(scope, Effect.sync(() => { output.push("Manual finalizer") })) + * + * // Create a scoped resource + * const resource = yield* Effect.scoped( + * Effect.acquireRelease( + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) + * ) + * ) + * + * return resource + * }) + * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Inside scoped context", "Acquiring resource", "Releasing resource", "Manual finalizer", "resource"] + * ``` + * + * @category resource management + * @since 3.11.0 + */ +export const scopedWith: ( + f: (scope: Scope) => Effect +) => Effect = internal.scopedWith + +/** + * Constructs a scoped resource from an acquisition effect and a release + * finalizer. + * + * **When to use** + * + * Use to acquire a scoped resource with an explicit release finalizer. + * + * **Details** + * + * If acquisition succeeds, the release finalizer is added to the current scope + * and is guaranteed to run when that scope closes. The finalizer receives the + * `Exit` value used to close the scope. + * + * By default, acquisition is protected by an uninterruptible region. Pass + * `{ interruptible: true }` to allow the acquisition effect to be interrupted. + * + * **Example** (Acquiring and releasing a resource) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] + * + * // Simulate a resource that needs cleanup + * interface FileHandle { + * readonly path: string + * readonly content: string + * } + * + * // Acquire a file handle + * const acquire = Effect.gen(function*() { + * 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) => + * 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) + * + * // Use the resource within a scope + * const program = Effect.scoped( + * Effect.gen(function*() { + * const handle = yield* resource + * 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 + * @see {@link acquireUseRelease} for bracketing acquire, use, and release in one effect + * + * @category resource management + * @since 2.0.0 + */ +export const acquireRelease: ( + acquire: Effect, + release: (a: A, exit: Exit.Exit) => Effect, + options?: { readonly interruptible?: boolean } +) => Effect = internal.acquireRelease + +/** + * Acquires a scoped resource that implements JavaScript disposal protocols. + * + * **When to use** + * + * Use when you work with JavaScript `Disposable` or `AsyncDisposable` resources + * that should be closed with the surrounding scope. + * + * **Details** + * + * The resource is automatically disposed when the surrounding + * {@link Scope} is closed, using {@link Symbol.dispose} for + * synchronous disposables or {@link Symbol.asyncDispose} for asynchronous + * disposables. + * + * This is similar to {@link acquireRelease}, but uses the standard + * JavaScript disposal protocol instead of requiring an explicit release + * function. It works with JavaScript `Disposable` and `AsyncDisposable` + * resources. + * + * **Example** (Acquiring a disposable resource) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * class Resource implements Disposable { + * [Symbol.dispose]() { + * void output.push("disposed") + * } + * } + * + * 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 + * + * @category resource management + * @since 4.0.0 + */ +export const acquireDisposable: ( + acquire: Effect +) => Effect = internal.acquireDisposable + +/** + * Runs resource acquisition, usage, and release as one bracketed effect. + * + * **When to use** + * + * Use to bracket acquire, use, and release logic in one effect. + * + * **Details** + * + * `acquireUseRelease` does the following: + * + * 1. Ensures that the `Effect` value that acquires the resource will not be + * interrupted. Note that acquisition may still fail due to internal + * reasons (such as an uncaught exception). + * 2. Ensures that the `release` `Effect` value will not be interrupted, + * and will be executed as long as the acquisition `Effect` value + * successfully acquires the resource. + * + * During the time period between the acquisition and release of the resource, + * the `use` `Effect` value will be executed. + * + * If the `release` `Effect` value fails, then the entire `Effect` value will + * fail, even if the `use` `Effect` value succeeds. If this fail-fast behavior + * is not desired, errors produced by the `release` `Effect` value can be caught + * and ignored. + * + * **Example** (Acquiring resources with cleanup) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] + * + * interface Database { + * readonly connection: string + * readonly query: (sql: string) => Effect.Effect + * } + * + * const program = Effect.acquireUseRelease( + * // Acquire - connect to database + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Connecting to database...") }) + * return { + * connection: "db://localhost:5432", + * query: (sql: string) => Effect.succeed(`Result for: ${sql}`) + * } + * }), + * // Use - perform database operations + * (db) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push(`Connected to ${db.connection}`) }) + * const result = yield* db.query("SELECT * FROM users") + * yield* Effect.sync(() => { output.push(`Query result: ${result}`) }) + * return result + * }), + * // Release - close database connection + * (db, exit) => + * Effect.gen(function*() { + * if (Exit.isSuccess(exit)) { + * yield* Effect.sync(() => { output.push(`Closing connection to ${db.connection} (success)`) }) + * } else { + * yield* Effect.sync(() => { output.push(`Closing connection to ${db.connection} (failure)`) }) + * } + * }) + * ) + * + * 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 + * + * @category resource management + * @since 2.0.0 + */ +export const acquireUseRelease: ( + acquire: Effect, + use: (a: Resource) => Effect, + release: (a: Resource, exit: Exit.Exit) => Effect +) => Effect = internal.acquireUseRelease + +/** + * Adds a finalizer to the current scope. + * + * **When to use** + * + * Use to register low-level cleanup in the current scope. + * + * **Details** + * + * The finalizer runs when the surrounding scope is closed and receives the + * `Exit` value used to close the scope. + * + * **Example** (Registering scope finalizers) + * + * ```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) => + * Effect.sync(() => { output.push( + * Exit.isSuccess(exit) + * ? "Cleanup: Operation completed successfully" + * : "Cleanup: Operation failed, cleaning up resources" + * ) }) + * ) + * + * yield* Effect.sync(() => { output.push("Performing main operation...") }) + * + * // This could succeed or fail + * return "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 + * @see {@link ensuring} for attaching a finalizer to one effect + * + * @category resource management + * @since 2.0.0 + */ +export const addFinalizer: ( + finalizer: (exit: Exit.Exit) => Effect +) => Effect = internal.addFinalizer + +/** + * Returns an effect that, if this effect _starts_ execution, then the + * specified `finalizer` is guaranteed to be executed, whether this effect + * succeeds, fails, or is interrupted. + * + * **Details** + * + * For use cases that need access to the effect's result, see `onExit`. + * + * Finalizers offer very powerful guarantees, but they are low-level, and + * should generally not be used for releasing resources. For higher-level + * logic built on `ensuring`, see the `acquireRelease` family of methods. + * + * **Example** (Always running cleanup) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const task = Effect.gen(function*() { + * 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, + * Effect.sync(() => { output.push("Cleanup: This always runs!") }) + * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Task started", "Task completed", "Cleanup: This always runs!", 42] + * ``` + * + * @category resource management + * @since 2.0.0 + */ +export const ensuring: { + ( + finalizer: Effect + ): (self: Effect) => Effect + ( + self: Effect, + finalizer: Effect + ): Effect +} = internal.ensuring + +/** + * Runs the specified effect if this effect fails, providing the error to the + * effect if it exists. The provided effect will not be interrupted. + * + * **Example** (Running cleanup on failure) + * + * ```ts import.meta.vitest + * import { Cause, Data, Effect, Exit } from "effect" + * const output: Array = [] + * + * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} + * + * const error = new TaskError({ message: "Something went wrong" }) + * const task = Effect.fail(error) + * + * const program = Effect.onError( + * task, + * (cause) => Effect.sync(() => { output.push(`Cleanup on error: ${Cause.squash(cause)}`) }) + * ) + * + * void output.push(Effect.runSyncExit(program)) + * output // => ["Cleanup on error: TaskError: Something went wrong", Exit.fail(error)] + * ``` + * + * @category resource management + * @since 2.0.0 + */ +export const onError: { + ( + cleanup: (cause: Cause.Cause) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + cleanup: (cause: Cause.Cause) => Effect + ): Effect +} = internal.onError + +/** + * Runs the finalizer only when this effect fails and the `Cause` matches the + * provided predicate. + * + * **Example** (Running cleanup for selected failures) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] + * + * const task = Effect.fail("boom") + * + * const program = Effect.onErrorIf( + * task, + * Cause.hasFails, + * (cause) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push(`Cause: ${Cause.squash(cause)}`) }) + * }) + * ) + * + * void output.push(Effect.runSyncExit(program)) + * output // => ["Cause: boom", Exit.fail("boom")] + * ``` + * + * @category resource management + * @since 4.0.0 + */ +export const onErrorIf: { + ( + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect + ): Effect +} = internal.onErrorIf + +/** + * Runs the finalizer only when this effect fails and the cause matches the provided `Filter`. + * + * **When to use** + * + * Use when cleanup or diagnostics should run only for failures whose full + * `Cause` is accepted or transformed by a `Filter`, and the finalizer needs the + * filter's pass value plus the original cause. + * + * @see {@link onError} for cleanup on every failure + * @see {@link onErrorIf} for selecting failures with a boolean predicate + * @see {@link onExitFilter} for selecting from every exit instead of only failures + * + * @category resource management + * @since 4.0.0 + */ +export const onErrorFilter: { + ( + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect + ): Effect +} = internal.onErrorFilter + +/** + * Runs an optional finalizer with the effect's `Exit` value when the effect + * completes. + * + * **When to use** + * + * Use when you are building a low-level `Effect` operator that must inspect the + * source effect's `Exit`, may skip finalization by returning `undefined`, or + * must choose whether finalization is forced into an uninterruptible region. + * + * **Details** + * + * This low-level operator preserves the source effect's result unless the + * 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 + * + * @category resource management + * @since 4.0.0 + */ +export const onExitPrimitive: ( + self: Effect, + f: (exit: Exit.Exit) => Effect | undefined, + interruptible?: boolean +) => Effect = internal.onExitPrimitive + +/** + * Ensures that a cleanup function runs whether this effect succeeds, fails, or + * is interrupted. + * + * If both the effect and the cleanup function fail, the two causes are merged. + * + * **Example** (Observing every exit) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] + * + * const task = Effect.succeed(42) + * + * const program = Effect.onExit(task, (exit) => + * Effect.sync(() => { output.push( + * Exit.isSuccess(exit) + * ? `Task succeeded with: ${exit.value}` + * : `Task failed: ${Exit.isFailure(exit) ? exit.cause : "interrupted"}` + * ) })) + * + * void output.push(Effect.runSync(program)) + * output // => ["Task succeeded with: 42", 42] + * ``` + * + * @category resource management + * @since 2.0.0 + */ +export const onExit: { + ( + f: (exit: Exit.Exit) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (exit: Exit.Exit) => Effect + ): Effect +} = internal.onExit + +/** + * Runs the cleanup effect only when the `Exit` satisfies the provided + * predicate. + * + * **Example** (Observing selected exits) + * + * ```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) + * ? Effect.sync(() => { output.push(`Succeeded with: ${exit.value}`) }) + * : Effect.void + * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Succeeded with: 42", 42] + * ``` + * + * @category resource management + * @since 4.0.0 + */ +export const onExitIf: { + ( + predicate: Predicate.Predicate, NoInfer>>, + f: (exit: Exit.Exit, NoInfer>) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + predicate: Predicate.Predicate, NoInfer>>, + f: (exit: Exit.Exit, NoInfer>) => Effect + ): Effect +} = internal.onExitIf + +/** + * Runs the cleanup effect only when the `Exit` matches the provided `Filter`. + * + * **When to use** + * + * Use when cleanup should run only for `Exit` values selected by a `Filter`, + * and the cleanup needs the extracted pass value together with the original + * `Exit`. + * + * **Details** + * + * `Result.fail` skips cleanup, and `Result.succeed` runs cleanup with the + * selected value and the original `Exit`. + * + * @see {@link onExit} for cleanup on every exit + * @see {@link onExitIf} for selecting exits with a boolean predicate + * @see {@link onErrorFilter} for selecting only failure causes + * + * @category resource management + * @since 4.0.0 + */ +export const onExitFilter: { + ( + filter: Filter.Filter, NoInfer>, B, X>, + f: (b: B, exit: Exit.Exit, NoInfer>) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + filter: Filter.Filter, NoInfer>, B, X>, + f: (b: B, exit: Exit.Exit, NoInfer>) => Effect + ): Effect +} = internal.onExitFilter + +// ----------------------------------------------------------------------------- +// Caching +// ----------------------------------------------------------------------------- + +/** + * Returns an effect that lazily computes a result and caches it for subsequent + * evaluations. + * + * **When to use** + * + * Use when you need an expensive or time-consuming operation to be evaluated + * once and reused by later callers. + * + * **Details** + * + * This function wraps an effect and ensures that its result is computed only + * once. Once the result is computed, it is cached, meaning that subsequent + * evaluations of the same effect will return the cached result without + * re-executing the logic. + * + * **Example** (Memoizing an effect until invalidated) + * + * ```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.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` + * }) + * + * const program = Effect.gen(function*() { + * 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(record)) + * yield* cached.pipe(Effect.andThen(record)) + * }) + * + * 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 + * time-to-live duration for the cached value. + * @see {@link cachedInvalidateWithTTL} for a similar function that includes an + * additional effect for manually invalidating the cached value. + * @category caching + * @since 2.0.0 + */ +export const cached: (self: Effect) => Effect> = internal.cached + +/** + * Returns an effect that caches its result for a specified `Duration`, + * known as "timeToLive" (TTL). + * + * **When to use** + * + * Use when you need a costly effect result to be reused for a bounded duration + * before being recomputed. + * + * **Details** + * + * This function is used to cache the result of an effect for a specified amount + * of time. This means that the first time the effect is evaluated, its result + * is computed and stored. + * + * If the effect is evaluated again within the specified `timeToLive`, the + * cached result will be used, avoiding recomputation. + * + * After the specified duration has passed, the cache expires, and the effect + * will be recomputed upon the next evaluation. + * + * **Example** (Memoizing an effect with TTL) + * + * ```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.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` + * }) + * + * const program = Effect.gen(function*() { + * 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.runSync(program) + * output // => ["expensive task...", "result 1", "result 1", "result 1"] + * ``` + * + * @see {@link cached} for a similar function that caches the result + * indefinitely. + * @see {@link cachedInvalidateWithTTL} for a similar function that includes an + * additional effect for manually invalidating the cached value. + * @category caching + * @since 2.0.0 + */ +export const cachedWithTTL: { + (timeToLive: Duration.Input): (self: Effect) => Effect> + (self: Effect, timeToLive: Duration.Input): Effect> +} = internal.cachedWithTTL + +/** + * Creates a cached effect result for a specified duration and allows manual + * invalidation before expiration. + * + * **When to use** + * + * Use when an effect result should be cached for a bounded time and callers + * also need a manual invalidation effect to force recomputation before + * expiration. + * + * **Details** + * + * This function behaves similarly to {@link cachedWithTTL} by caching the + * result of an effect for a specified period of time. However, it introduces an + * additional feature: it provides an effect that allows you to manually + * invalidate the cached result before it naturally expires. + * + * This gives you more control over the cache, allowing you to refresh the + * result when needed, even if the original cache has not yet expired. + * + * Once the cache is invalidated, the next time the effect is evaluated, the + * result will be recomputed, and the cache will be refreshed. + * + * **Example** (Memoizing with TTL and invalidation) + * + * ```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.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` + * }) + * + * const program = Effect.gen(function*() { + * const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL( + * expensiveTask, + * "1 hour" + * ) + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) + * yield* invalidate + * yield* cached.pipe(Effect.andThen(record)) + * }) + * + * 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 + * indefinitely. + * @see {@link cachedWithTTL} for a similar function that caches the result for + * a specified duration but does not include an effect for manual invalidation. + * @category caching + * @since 2.0.0 + */ +export const cachedInvalidateWithTTL: { + (timeToLive: Duration.Input): (self: Effect) => Effect<[Effect, Effect]> + (self: Effect, timeToLive: Duration.Input): Effect<[Effect, Effect]> +} = internal.cachedInvalidateWithTTL + +// ----------------------------------------------------------------------------- +// Interruption +// ----------------------------------------------------------------------------- + +/** + * Returns an effect that is immediately interrupted. + * + * **Example** (Creating an interrupted effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * return yield* Effect.interrupt + * yield* Effect.succeed("This won't execute and is unreachable") + * }) + * + * Effect.runSyncExit(program)._tag // => "Failure" + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const interrupt: Effect = internal.interrupt + +/** + * Returns a new effect that allows the effect to be interruptible. + * + * **Example** (Allowing interruption) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.interruptible(Effect.never).pipe( + * Effect.timeoutOption(0) + * ) + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const interruptible: ( + self: Effect +) => Effect = internal.interruptible + +/** + * Runs the specified finalizer effect if this effect is interrupted. + * + * **Example** (Running cleanup on interruption) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * const output: Array = [] + * + * const task = Effect.forever(Effect.succeed("working...")) + * + * const program = Effect.onInterrupt( + * task, + * () => Effect.sync(() => { output.push("Task was interrupted, cleaning up...") }) + * ) + * + * const fiber = Effect.runFork(program) + * await Effect.runPromise(Fiber.interrupt(fiber)) + * output // => ["Task was interrupted, cleaning up..."] + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const onInterrupt: { + ( + finalizer: (interruptors: ReadonlySet) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + finalizer: (interruptors: ReadonlySet) => Effect + ): Effect +} = internal.onInterrupt + +/** + * Returns a new effect that disables interruption for the given effect. + * + * **Example** (Preventing interruption) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const criticalTask = Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Starting critical section...") }) + * yield* Effect.sync(() => { output.push("Critical section completed") }) + * }) + * + * const program = Effect.uninterruptible(criticalTask) + * + * Effect.runSync(program) + * output // => ["Starting critical section...", "Critical section completed"] + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const uninterruptible: ( + self: Effect +) => Effect = internal.uninterruptible + +/** + * Disables interruption and provides a restore function to restore the + * interruptible state within the effect. + * + * **Example** (Restoring interruption in protected regions) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.uninterruptibleMask((restore) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Uninterruptible phase...") }) + * // Restore interruptibility for this part + * yield* restore( + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Interruptible phase...") }) + * }) + * ) + * + * yield* Effect.sync(() => { output.push("Back to uninterruptible") }) + * }) + * ) + * + * Effect.runSync(program) + * output // => ["Uninterruptible phase...", "Interruptible phase...", "Back to uninterruptible"] + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const uninterruptibleMask: ( + f: ( + restore: (effect: Effect) => Effect + ) => Effect +) => Effect = internal.uninterruptibleMask + +/** + * Runs an effect in an interruptible region while providing `restore` for + * locally restoring the previous interruptibility. + * + * **Example** (Controlling interruptibility locally) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.interruptibleMask((restore) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Interruptible phase...") }) + * // Make this part uninterruptible + * yield* restore( + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Uninterruptible phase...") }) + * }) + * ) + * + * yield* Effect.sync(() => { output.push("Back to interruptible") }) + * }) + * ) + * + * Effect.runSync(program) + * output // => ["Interruptible phase...", "Uninterruptible phase...", "Back to interruptible"] + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const interruptibleMask: ( + f: ( + restore: (effect: Effect) => Effect + ) => Effect +) => Effect = internal.interruptibleMask + +/** + * Creates an AbortSignal that is managed by the provided scope. + * + * **When to use** + * + * Use to obtain a scope-managed `AbortSignal` for APIs that accept cancellation + * through a signal. + * + * **Details** + * + * Each acquisition creates a fresh `AbortController`. Closing the owning scope + * runs a finalizer that aborts the controller and the effect succeeds with the + * controller's signal. + * + * **Gotchas** + * + * The signal is aborted when its owning scope closes, so avoid keeping it for + * work that outlives that scope. + * + * @see {@link scoped} for binding resource lifetime to a scope + * + * @category interruption + * @since 4.0.0 + */ +export const abortSignal: Effect = internal.abortSignal + +// ----------------------------------------------------------------------------- +// Repetition & Recursion +// ----------------------------------------------------------------------------- + +/** + * Type helpers for repeating effects. + * + * @since 2.0.0 + */ +export declare namespace Repeat { + /** + * Computes the result type of `Effect.repeat` from the original effect and repeat options. + * + * @category repetition + * @since 2.0.0 + */ + export type Return> = Effect< + O extends { until: Predicate.Refinement } ? B + : O extends { while: Predicate.Refinement } ? Exclude + : A, + | E + | (O extends { schedule: Schedule } ? E + : never) + | (O extends { while: (...args: Array) => Effect } ? E + : never) + | (O extends { until: (...args: Array) => Effect } ? E + : never), + | R + | (O extends { schedule: Schedule } ? R + : never) + | (O extends { + while: (...args: Array) => Effect + } ? R + : never) + | (O extends { + until: (...args: Array) => Effect + } ? R + : never) + > extends infer Z ? Z + : never + + /** + * Options that control whether and how an effect is repeated. + * + * @category repetition + * @since 2.0.0 + */ + export interface Options { + while?: ((_: A) => boolean | Effect) | undefined + until?: ((_: A) => boolean | Effect) | undefined + times?: number | undefined + schedule?: Schedule | undefined + } +} + +/** + * Repeats this effect forever (until the first error). + * + * **Example** (Repeating forever) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const program = Effect.forever(Effect.never).pipe(Effect.timeoutOption(0)) + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category repetition + * @since 2.0.0 + */ +export const forever: < + Arg extends Effect | { + readonly disableYield?: boolean | undefined + } | undefined = { + readonly disableYield?: boolean | undefined + } +>( + effectOrOptions?: Arg, + options?: { + readonly disableYield?: boolean | undefined + } | undefined +) => [Arg] extends [Effect] ? Effect + : (self: Effect) => Effect = internal.forever + +/** + * Repeats an effect based on a specified schedule or until the first failure. + * + * **When to use** + * + * Use to rerun an effect after successful executions. + * + * **Details** + * + * This function executes an effect repeatedly according to the given schedule. + * Each repetition occurs after the initial execution of the effect, meaning + * that the schedule determines the number of additional repetitions. For + * example, using `Schedule.once` will result in the effect being executed twice + * (once initially and once as part of the repetition). + * + * If the effect succeeds, it is repeated according to the schedule. If it + * fails, the repetition stops immediately, and the failure is returned. + * + * The schedule can also specify delays between repetitions, making it useful + * for tasks like retrying operations with backoff, periodic execution, or + * performing a series of dependent actions. + * + * You can combine schedules for more advanced repetition logic, such as adding + * delays, limiting recursions, or dynamically adjusting based on the outcome of + * each execution. + * + * **Gotchas** + * + * The source effect is always evaluated once before the schedule is stepped. + * The schedule controls additional repetitions, not the initial execution. + * + * **Example** (Repeating successful effects with a schedule) + * + * ```ts import.meta.vitest + * // Success Example + * import { Effect, Schedule } from "effect" + * const output: Array = [] + * + * const action = Effect.sync(() => { output.push("success") }) + * const policy = Schedule.recurs(2) + * const program = Effect.repeat(action, policy) + * + * void output.push(Effect.runSync(program)) + * output // => ["success", "success", "success", 2] + * ``` + * + * **Example** (Stopping repetition on failure) + * + * ```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) { + * void output.push("failure") + * resume(Effect.fail("Uh oh!")) + * } else { + * count++ + * void output.push("success") + * resume(Effect.succeed("yay!")) + * } + * }) + * + * const policy = Schedule.recurs(2) + * const program = Effect.repeat(action, policy) + * + * void output.push((await Effect.runPromiseExit(program))._tag) + * output // => ["success", "success", "failure", "Failure"] + * ``` + * + * @see {@link retry} for failure-based repetition + * @see {@link repeatOrElse} for fallback handling when repetition fails + * + * @category repetition + * @since 2.0.0 + */ +export const repeat: { + , A>(options: O): (self: Effect) => Repeat.Return + ( + schedule: Schedule, Error, Env> + ): (self: Effect) => Effect + ( + builder: ( + $: (_: Schedule, E, R>) => Schedule + ) => Schedule, Error, Env> + ): (self: Effect) => Effect + >(self: Effect, options: O): Repeat.Return + ( + self: Effect, + schedule: Schedule, Error, Env> + ): Effect + ( + self: Effect, + builder: ( + $: (_: Schedule, E, R>) => Schedule + ) => Schedule, Error, Env> + ): Effect +} = internalSchedule.repeat + +/** + * Repeats an effect according to a schedule and runs a fallback effect if + * repetition fails before the schedule completes. + * + * **When to use** + * + * Use when successful repetitions should follow a schedule, but failures from + * the repeated effect or schedule need an effectful fallback. + * + * **Details** + * + * If the repeated effect or schedule step fails, `orElse` receives the failure + * and the latest schedule metadata when at least one schedule step has run; + * otherwise it receives `None`. If the schedule completes normally, the + * returned effect succeeds with the schedule's output. + * + * **Example** (Recovering after repetition stops) + * + * ```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* Effect.sync(() => { output.push(`Attempt ${attempt} failed`) }) + * return yield* Effect.fail(`Error ${attempt}`) + * } + * yield* Effect.sync(() => { output.push(`Attempt ${attempt} succeeded`) }) + * return "success" + * }) + * + * const program = Effect.repeatOrElse( + * task, + * Schedule.recurs(3), + * (error, attempts) => + * Effect.sync(() => { output.push( + * `Final failure: ${error}, after ${ + * Option.getOrElse(attempts, () => 0) + * } attempts` + * ) }).pipe(Effect.map(() => 0)) + * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Attempt 1 failed", "Final failure: Error 1, after 0 attempts", 0] + * ``` + * + * @category repetition + * @since 2.0.0 + */ +export const repeatOrElse: { + ( + schedule: Schedule, + orElse: (error: E | E2, option: Option) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + schedule: Schedule, + orElse: (error: E | E2, option: Option) => Effect + ): Effect +} = internalSchedule.repeatOrElse + +/** + * Returns an array of `n` identical effects. + * + * **When to use** + * + * Use when you need an array of identical effect values without running them + * yet. + * + * **Details** + * + * This only creates the array of effects. It does not run or collect them. + * + * @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 repetition + * @since 2.0.0 + */ +export const replicate: { + (n: number): (self: Effect) => Array> + (self: Effect, n: number): Array> +} = internal.replicate + +/** + * Performs this effect `n` times and collects results with `Effect.all` semantics. + * + * **When to use** + * + * Use when you want to run the repeated effects immediately, with optional + * concurrency control or result discarding. + * + * **Details** + * + * Use `concurrency` to control parallelism and `discard: true` to ignore results. + * + * **Example** (Replicating an 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* Effect.sync(() => { output.push(results) }) + * }) + * + * Effect.runSync(program) + * output // => [[1, 1, 1]] + * ``` + * + * @category repetition + * @since 2.0.0 + */ +export const replicateEffect: { + ( + n: number, + options?: { readonly concurrency?: Concurrency | undefined; readonly discard?: false | undefined } + ): (self: Effect) => Effect, E, R> + ( + n: number, + options: { readonly concurrency?: Concurrency | undefined; readonly discard: true } + ): (self: Effect) => Effect + ( + self: Effect, + n: number, + options?: { readonly concurrency?: Concurrency | undefined; readonly discard?: false | undefined } + ): Effect, E, R> + ( + self: Effect, + n: number, + options: { readonly concurrency?: Concurrency | undefined; readonly discard: true } + ): Effect +} = internal.replicateEffect + +/** + * Runs an effect repeatedly according to a schedule and returns the schedule's + * final output. + * + * **When to use** + * + * Use to rerun a successful effect according to a `Schedule` when the schedule + * does not need a custom initial input. + * + * **Details** + * + * The schedule is first stepped with `undefined`. After each successful + * execution, the effect's success value is fed to the schedule to decide + * whether to run again. The returned effect fails if the effect or schedule + * fails, and otherwise succeeds with the schedule output when the schedule + * completes. + * + * **Example** (Scheduling repeated execution) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * const output: Array = [] + * + * const task = Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("Task executing...") }) + * return 1 + * }) + * + * const program = Effect.schedule(task, Schedule.recurs(2)) + * + * void output.push(Effect.runSync(program)) + * output // => ["Task executing...", "Task executing...", 2] + * ``` + * + * @see {@link scheduleFrom} for a variant that allows the schedule's decision + * to depend on the result of this effect. + * + * @category repetition + * @since 2.0.0 + */ +export const schedule: { + ( + schedule: Schedule + ): (self: Effect) => Effect + ( + self: Effect, + schedule: Schedule + ): Effect +} = dual(2, ( + self: Effect, + schedule: Schedule +): Effect => scheduleFrom(self, undefined, schedule)) + +/** + * Runs an effect repeatedly according to a schedule that is initialized with a + * specific schedule input. + * + * **Details** + * + * `initial` is passed to the schedule before the first execution, not to the + * effect itself. After each successful execution, the effect's success value is + * fed back into the schedule to decide whether to continue. The returned effect + * succeeds with the schedule output when the schedule completes and fails if + * the effect or schedule fails. + * + * **Example** (Scheduling from an initial value) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * const output: Array = [] + * + * const task = (input: number) => + * Effect.gen(function*() { + * yield* Effect.sync(() => { output.push(`Processing: ${input}`) }) + * return input + 1 + * }) + * + * // Start with 0, repeat 3 times + * const program = Effect.scheduleFrom( + * task(0), + * 0, + * Schedule.recurs(2) + * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Processing: 0", "Processing: 0", 2] + * ``` + * + * @category repetition + * @since 2.0.0 + */ +export const scheduleFrom: { + ( + initial: Input, + schedule: Schedule + ): (self: Effect) => Effect + ( + self: Effect, + initial: Input, + schedule: Schedule + ): Effect +} = internalSchedule.scheduleFrom + +// ----------------------------------------------------------------------------- +// Tracing +// ----------------------------------------------------------------------------- + +/** + * Returns the current tracer from the context. + * + * **Example** (Accessing the current tracer) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const currentTracer = yield* Effect.tracer + * return typeof currentTracer.span + * }) + * + * Effect.runSync(program) // => "function" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const tracer: Effect = internal.tracer + +/** + * Provides a tracer to an effect. + * + * **Example** (Providing a tracer) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const tracer = yield* Effect.tracer + * return yield* Effect.withTracer(Effect.succeed("completed"), tracer) + * }) + * + * Effect.runSync(program) // => "completed" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const withTracer: { + (value: Tracer): (effect: Effect) => Effect + (effect: Effect, value: Tracer): Effect +} = internal.withTracer + +/** + * Enables or disables tracing for spans created by the given effect. + * + * **Details** + * + * When `enabled` is `false`, spans created inside the effect are not registered + * with the current tracer and do not propagate as normal trace parents. + * + * **Example** (Enabling or disabling tracing) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * 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 + * @since 2.0.0 + */ +export const withTracerEnabled: { + (enabled: boolean): (effect: Effect) => Effect + (effect: Effect, enabled: boolean): Effect +} = internal.withTracerEnabled + +/** + * Enables or disables tracer timing for the given Effect. + * + * **Example** (Enabling or disabling tracing timing) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * 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 + * @since 2.0.0 + */ +export const withTracerTiming: { + (enabled: boolean): (effect: Effect) => Effect + (effect: Effect, enabled: boolean): Effect +} = internal.withTracerTiming + +/** + * Adds an annotation to each span in this effect. + * + * **Example** (Annotating all spans) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.succeed("result") + * + * // Add single annotation + * const annotated1 = Effect.annotateSpans(program, "user", "john") + * + * // Add multiple annotations + * const annotated2 = Effect.annotateSpans(program, { + * operation: "data-processing", + * version: "1.0.0", + * environment: "production" + * }) + * + * Effect.runSync(Effect.all([annotated1, annotated2])) // => ['result', 'result'] + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const annotateSpans: { + ( + key: string, + value: unknown + ): (effect: Effect) => Effect + ( + values: Record + ): (effect: Effect) => Effect + ( + effect: Effect, + key: string, + value: unknown + ): Effect + ( + effect: Effect, + values: Record + ): Effect +} = internal.annotateSpans + +/** + * Adds an annotation to the current span if available. + * + * **Example** (Annotating the current span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * yield* Effect.annotateCurrentSpan("userId", "123") + * yield* Effect.annotateCurrentSpan({ + * operation: "user-lookup" + * }) + * return "success" + * }) + * + * const traced = Effect.withSpan(program, "user-operation") + * Effect.runSync(traced) // => "success" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const annotateCurrentSpan: { + (key: string, value: unknown): Effect + (values: Record): Effect +} = internal.annotateCurrentSpan + +/** + * Returns the currently active local tracing span. + * + * **Details** + * + * The effect fails with `NoSuchElementError` when there is no active local + * `Span`. + * + * **Example** (Reading the current span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const span = yield* Effect.currentSpan + * return span.name + * }) + * + * const traced = Effect.withSpan(program, "my-span") + * Effect.runSync(traced) // => "my-span" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const currentSpan: Effect = internal.currentSpan + +/** + * Returns the current parent span from the effect context. + * + * **Details** + * + * The effect succeeds with either a local span or external span when one is + * present, and fails with `NoSuchElementError` when no parent span is + * available. + * + * **Example** (Reading the parent span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const childOperation = Effect.gen(function*() { + * const parentSpan = yield* Effect.currentParentSpan + * return parentSpan._tag + * }) + * + * const program = Effect.withSpan(childOperation, "child-span") + * + * const traced = Effect.withSpan(program, "parent-span") + * Effect.runSync(traced) // => "Span" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const currentParentSpan: Effect = internal.currentParentSpan + +/** + * Returns the tracing span annotations currently carried in the effect context. + * + * **Details** + * + * These annotations are applied to spans created inside the context, such as + * spans created by `withSpan`, `useSpan`, or `makeSpan`. + * + * **Example** (Providing span annotations) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const annotations = yield* Effect.spanAnnotations + * return annotations + * }).pipe(Effect.annotateSpans({ userId: "123", operation: "data-processing" })) + * + * Effect.runSync(program) // => { userId: '123', operation: 'data-processing' } + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const spanAnnotations: Effect>> = internal.spanAnnotations + +/** + * Returns the tracing span links currently carried in the effect context. + * + * **Details** + * + * These links are attached to spans created inside the context. Span links + * connect related spans without making one span the parent of another. + * + * **Example** (Providing span links) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * // Get the current span links + * const links = yield* Effect.spanLinks + * return links + * }) + * + * Effect.runSync(program).length // => 0 + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const spanLinks: Effect> = internal.spanLinks + +/** + * Adds a link with the provided span to all spans in this effect. + * + * **Details** + * + * This is useful for connecting spans that are related but not in a direct + * parent-child relationship. For example, you might want to link spans from + * parallel operations or connect spans across different traces. + * + * **Example** (Linking one span to another span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.withSpan(Effect.gen(function*() { + * const parentSpan = yield* Effect.currentSpan + * return yield* Effect.spanLinks.pipe( + * Effect.linkSpans(parentSpan, { relationship: "follows" }) + * ) + * }), "parent-operation") + * + * Effect.runSync(program).length // => 1 + * ``` + * + * **Example** (Linking multiple spans at once) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const span1 = yield* Effect.makeSpan("span-1") + * const span2 = yield* Effect.makeSpan("span-2") + * + * return yield* Effect.spanLinks.pipe( + * Effect.linkSpans([span1, span2], { + * type: "dependency", + * source: "multiple-operations" + * }) + * ) + * }) + * + * Effect.runSync(program).length // => 2 + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const linkSpans: { + ( + span: AnySpan | ReadonlyArray, + attributes?: Record + ): (self: Effect) => Effect + ( + self: Effect, + span: AnySpan | ReadonlyArray, + attributes?: Record + ): Effect +} = internal.linkSpans + +/** + * Creates a new tracing span and returns it without managing its lifetime. + * + * **Details** + * + * The span is not added to the current span stack and is not ended + * automatically. Use `withSpan`, `useSpan`, or `makeSpanScoped` when the span + * should be installed as context or closed automatically. + * + * **Example** (Creating a span manually) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const span = yield* Effect.makeSpan("my-operation") + * return span.name + * }) + * + * Effect.runSync(program) // => "my-operation" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const makeSpan: (name: string, options?: SpanOptionsNoTrace) => Effect = internal.makeSpan + +/** + * Create a new span for tracing, and automatically close it when the Scope + * finalizes. + * + * **Details** + * + * The span is not added to the current span stack, so no child spans will be + * created for it. + * + * **Example** (Creating a scoped standalone span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const span = yield* Effect.makeSpanScoped("scoped-operation") + * return span.name + * // Span automatically closes when scope ends + * }) + * ) + * + * Effect.runSync(program) // => "scoped-operation" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const makeSpanScoped: ( + name: string, + options?: SpanOptionsNoTrace | undefined +) => Effect = internal.makeSpanScoped + +/** + * Create a new span for tracing, and automatically close it when the effect + * completes. + * + * **Details** + * + * The span is not added to the current span stack, so no child spans will be + * created for it. + * + * **Example** (Running an effect with a standalone span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.useSpan( + * "user-operation", + * (span) => Effect.succeed(`${span.name}: success`) + * ) + * Effect.runSync(program) // => "user-operation: success" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const useSpan: { + (name: string, evaluate: (span: Span) => Effect): Effect + (name: string, options: SpanOptionsNoTrace, evaluate: (span: Span) => Effect): Effect +} = internal.useSpan + +/** + * Wraps the effect with a child span for tracing. + * + * **Example** (Wrapping an effect in a child span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const task = Effect.succeed("result") + * + * const traced = Effect.withSpan(task, "my-task", { + * attributes: { version: "1.0" } + * }) + * Effect.runSync(traced) // => "result" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const withSpan: { + >( + name: string, + options?: + | SpanOptionsNoTrace + | ((...args: NoInfer) => SpanOptionsNoTrace) + | undefined, + traceOptions?: TraceOptions | undefined + ): (self: Effect, ...args: Args) => Effect> + ( + self: Effect, + name: string, + options?: SpanOptions | undefined + ): Effect> +} = internal.withSpan + +/** + * Wraps the effect with a scoped child span for tracing. + * + * **Details** + * + * The span is ended when the Scope is finalized. + * + * **Example** (Creating a scoped child span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const task = Effect.succeed("working") + * yield* Effect.withSpanScoped(task, "scoped-task") + * return "completed" + * }) + * ) + * Effect.runSync(program) // => "completed" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const withSpanScoped: { + ( + name: string, + options?: SpanOptions + ): ( + self: Effect + ) => Effect | Scope> + ( + self: Effect, + name: string, + options?: SpanOptions + ): Effect | Scope> +} = internal.withSpanScoped + +/** + * Adds the provided span to the current span stack. + * + * **Example** (Setting a parent span) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.gen(function*() { + * const span = yield* Effect.makeSpan("parent-span") + * const childTask = Effect.succeed("child operation") + * yield* Effect.withParentSpan(childTask, span) + * return "completed" + * }) + * Effect.runSync(program) // => "completed" + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const withParentSpan: { + (value: AnySpan, options?: TraceOptions): (self: Effect) => Effect> + (self: Effect, value: AnySpan, options?: TraceOptions): Effect> +} = internal.withParentSpan + +// ----------------------------------------------------------------------------- +// Batching +// ----------------------------------------------------------------------------- + +/** + * Executes a request using the provided resolver. + * + * **When to use** + * + * Use when you need resolver-driven batching for a typed `Request`. + * + * **Example** (Executing a request through a resolver) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * const output: Array = [] + * + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: number + * } + * const GetUser = Request.tagged("GetUser") + * + * const resolver = RequestResolver.make( + * Effect.fnUntraced(function*(entries) { + * for (const entry of entries) { + * yield* Request.complete(entry, Exit.succeed(`user-${entry.request.id}`)) + * } + * }) + * ) + * + * const program = Effect.gen(function*() { + * const name = yield* Effect.request(GetUser({ id: 1 }), resolver) + * 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 running + * @since 2.0.0 + */ +export const request: { + ( + resolver: RequestResolver | Effect, EX, RX> + ): (self: A) => Effect, Request.Error | EX, Request.Services | RX> + ( + self: A, + resolver: RequestResolver | Effect, EX, RX> + ): Effect, Request.Error | EX, Request.Services | RX> +} = internalRequest.request + +/** + * Registers a request with a resolver and delivers the exit value via `onExit`. + * + * **When to use** + * + * Use when you already have a `Context` and need to enqueue a request outside + * an `Effect` while receiving completion through `onExit`. + * + * **Details** + * + * It returns a canceler that removes the pending request entry. + * + * @see {@link request} for the `Effect`-returning API used for normal request execution + * + * @category unsafe + * @since 4.0.0 + */ +export const requestUnsafe: ( + self: A, + options: { + readonly resolver: RequestResolver + readonly onExit: (exit: Exit.Exit, Request.Error>) => void + readonly context: Context.Context + } +) => () => void = internalRequest.requestUnsafe + +// ----------------------------------------------------------------------------- +// Supervision & Fiber's +// ----------------------------------------------------------------------------- + +/** + * Returns an effect that forks this effect into its own separate fiber, + * returning the fiber immediately, without waiting for it to begin executing + * the effect. + * + * **Details** + * + * You can use the `forkChild` method whenever you want to execute an effect in a + * new fiber, concurrently and without "blocking" the fiber executing other + * effects. Using fibers can be tricky, so instead of using this method + * directly, consider other higher-level methods, such as `raceWith`, + * `zipPar`, and so forth. + * + * The fiber returned by this method has methods to interrupt the fiber and to + * wait for it to finish executing the effect. See `Fiber` for more + * information. + * + * Whenever you use this method to launch a new fiber, the new fiber is + * attached to the parent fiber's scope. This means when the parent fiber + * terminates, the child fiber will be terminated as well, ensuring that no + * fibers leak. This behavior is called "auto supervision", and if this + * behavior is not desired, you may use the `forkDetach` or `forkIn` methods. + * + * **Example** (Forking a child fiber) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const task = Effect.succeed("result") + * + * const program = Effect.gen(function*() { + * const fiber = yield* task.pipe(Effect.forkChild) + * const result = yield* Fiber.join(fiber) + * return result + * }) + * + * await Effect.runPromise(program) // => "result" + * ``` + * + * @category forking + * @since 4.0.0 + */ +export const forkChild: < + Arg extends Effect | { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined = { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } +>( + effectOrOptions?: Arg, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined +) => [Arg] extends [Effect] ? Effect, never, _R> + : (self: Effect) => Effect, never, R> = internal.forkChild + +/** + * Forks the effect in the specified scope. The fiber will be interrupted + * when the scope is closed. + * + * **Example** (Forking into a supplied scope) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const task = Effect.never + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const scope = yield* Effect.scope + * const fiber = yield* Effect.forkIn(task, scope) + * // Fiber will be interrupted when scope closes + * return "done" + * }) + * ) + * + * await Effect.runPromise(program) // => "done" + * ``` + * + * @category forking + * @since 2.0.0 + */ +export const forkIn: { + ( + scope: Scope, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + ): (self: Effect) => Effect, never, R> + ( + self: Effect, + scope: Scope, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + ): Effect, never, R> +} = internal.forkIn + +/** + * Forks the fiber in a `Scope`, interrupting it when the scope is closed. + * + * **Example** (Forking into the current scope) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const backgroundTask = Effect.never + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * yield* backgroundTask.pipe(Effect.forkScoped) + * + * // Fiber will be interrupted when scope closes + * return "scope completed" + * }) + * ) + * + * await Effect.runPromise(program) // => "scope completed" + * ``` + * + * @category forking + * @since 2.0.0 + */ +export const forkScoped: < + Arg extends Effect | { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined = { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } +>( + effectOrOptions?: Arg, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined +) => [Arg] extends [Effect] ? Effect, never, _R | Scope> + : (self: Effect) => Effect, never, R | Scope> = internal.forkScoped + +/** + * Forks the effect into a new fiber attached to the global scope. Because the + * new fiber is attached to the global scope, when the fiber executing the + * returned effect terminates, the forked fiber will continue running. + * + * **Example** (Forking a detached fiber) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const daemonTask = Effect.succeed("daemon result") + * + * const program = Effect.gen(function*() { + * const fiber = yield* daemonTask.pipe(Effect.forkDetach) + * return yield* Fiber.join(fiber) + * }) + * + * await Effect.runPromise(program) // => "daemon result" + * ``` + * + * @category forking + * @since 4.0.0 + */ +export const forkDetach: < + Arg extends Effect | { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined = { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } +>( + effectOrOptions?: Arg, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined +) => [Arg] extends [Effect] ? Effect, never, _R> + : (self: Effect) => Effect, never, R> = internal.forkDetach + +/** + * Waits for all child fibers forked by this effect to complete before this + * effect completes. + * + * **When to use** + * + * Use to let an effect start child work concurrently while still delaying its + * own completion until that child work is done. + * + * **Gotchas** + * + * Child fibers that already exist before the wrapped effect starts are not + * awaited. + * + * @see {@link forkChild} for forking child fibers that are awaited by this operator + * @see {@link forkDetach} for forking fibers outside the child scope + * @see {@link forkIn} for forking into an explicit scope + * @see {@link forkScoped} for forking fibers tied to the current scope + * + * @category sequencing + * @since 2.0.0 + */ +export const awaitAllChildren: (self: Effect) => Effect = internal.awaitAllChildren + +/** + * Accesses the fiber currently executing the effect. + * + * **Example** (Accessing the current fiber) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * const fiber = yield* Effect.fiber + * yield* Effect.sync(() => { output.push(typeof fiber.id) }) + * }) + * + * Effect.runSync(program) + * output // => ["number"] + * ``` + * + * @category accessors + * @since 4.0.0 + */ +export const fiber: Effect> = internal.fiber + +/** + * Accesses the current fiber id executing the effect. + * + * **Example** (Accessing the current fiber id) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.fiberId.pipe(Effect.map((id) => typeof id)) + * Effect.runSync(program) // => "number" + * ``` + * + * @category accessors + * @since 2.0.0 + */ +export const fiberId: Effect = internal.fiberId + +// ----------------------------------------------------------------------------- +// Running Effects +// ----------------------------------------------------------------------------- + +/** + * Configuration options for running Effect programs, providing control over + * interruption and scheduling behavior. + * + * **When to use** + * + * Use to pass cancellation, scheduler, interruptibility, and fiber-start hooks + * when running an `Effect` at a program boundary. + * + * **Details** + * + * `signal` interrupts the fiber, `scheduler` provides the scheduler service, + * `uninterruptible` starts the fiber uninterruptibly, and `onFiberStart` + * receives the created fiber. + * + * @see {@link runFork} for starting a fiber with these options + * @see {@link runCallback} for callback-based running with these options + * @see {@link runPromise} for promise-based running with these options + * @see {@link runPromiseExit} for promise-based running that returns an `Exit` + * + * @category running + * @since 4.0.0 + */ +export interface RunOptions { + readonly signal?: AbortSignal | undefined + readonly scheduler?: Scheduler | undefined + readonly uninterruptible?: boolean | undefined + readonly onFiberStart?: ((fiber: Fiber) => void) | undefined +} + +/** + * Runs an effect in the background, returning a fiber that can + * be observed or interrupted. + * + * **When to use** + * + * Use when you need to start an effect in the background and receive a fiber. + * + * **Example** (Running an effect in the background) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * const output: Array = [] + * + * // ┌─── Effect + * // ▼ + * const program = Effect.sync(() => { output.push("running...") }).pipe(Effect.as("done")) + * + * // ┌─── RuntimeFiber + * // ▼ + * const fiber = Effect.runFork(program) + * + * void output.push(await Effect.runPromise(Fiber.join(fiber))) + * output // => ["running...", "done"] + * ``` + * + * @category running + * @since 2.0.0 + */ +export const runFork: (effect: Effect, options?: RunOptions | undefined) => Fiber = + internal.runFork + +/** + * Runs an effect in the background with the provided services. + * + * **When to use** + * + * Use when an effect still requires services, you already have a `Context`, and + * you want a background fiber. + * + * **Example** (Running with services in the background) + * + * ```ts import.meta.vitest + * import { Context, Effect, Fiber } from "effect" + * const output: Array = [] + * + * interface Logger { + * log: (message: string) => void + * } + * + * const Logger = Context.Service("Logger") + * + * const services = Context.make(Logger, { + * log: (message) => void output.push(message) + * }) + * + * const program = Effect.gen(function*() { + * const logger = yield* Logger + * logger.log("Hello from service!") + * return "done" + * }) + * + * const fiber = Effect.runForkWith(services)(program) + * void output.push(await Effect.runPromise(Fiber.join(fiber))) + * output // => ["Hello from service!", "done"] + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runForkWith: ( + context: Context.Context +) => (effect: Effect, options?: RunOptions | undefined) => Fiber = internal.runForkWith + +/** + * Forks an effect with the provided services, registers `onExit` as a fiber observer, and returns an interruptor. + * + * **When to use** + * + * Use when embedding an effect into callback-style code with explicit services + * and a synchronous interruptor. + * + * **Details** + * + * The returned interruptor calls `fiber.interruptUnsafe`, optionally with an interruptor id. + * + * **Example** (Running with services and a callback) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] + * + * interface Logger { + * log: (message: string) => Effect.Effect + * } + * + * const Logger = Context.Service("Logger") + * + * const services = Context.make(Logger, { + * log: (message) => Effect.sync(() => { output.push(message) }) + * }) + * + * const program = Effect.gen(function*() { + * const logger = yield* Logger + * yield* logger.log("Started") + * return "done" + * }) + * + * await new Promise((resolve) => { + * Effect.runCallbackWith(services)(program, { + * onExit: (exit) => { + * void output.push(exit._tag) + * resolve() + * } + * }) + * }) + * output // => ["Started", "Success"] + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runCallbackWith: ( + context: Context.Context +) => ( + effect: Effect, + options?: (RunOptions & { readonly onExit: (exit: Exit.Exit) => void }) | undefined +) => (interruptor?: number | undefined) => void = internal.runCallbackWith + +/** + * Runs an effect asynchronously, registering `onExit` as a fiber observer and + * returning an interruptor. + * + * **Details** + * + * The interruptor calls `fiber.interruptUnsafe` with the optional interruptor + * id. + * + * **Example** (Running with a callback) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.sync(() => { output.push("working") }) + * return "done" + * }) + * + * 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"] + * ``` + * + * @category running + * @since 2.0.0 + */ +export const runCallback: ( + effect: Effect, + options?: (RunOptions & { readonly onExit: (exit: Exit.Exit) => void }) | undefined +) => (interruptor?: number | undefined) => void = internal.runCallback + +/** + * Executes an effect and returns the result as a `Promise`. + * + * **When to use** + * + * Use when you need to execute an effect and work with the + * result using `Promise` syntax, typically for compatibility with other + * promise-based code. + * + * If the effect succeeds, the promise will resolve with the result. If the + * effect fails, the promise will reject with an error. + * + * **Example** (Running a successful effect as a Promise) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * await Effect.runPromise(Effect.succeed(1)) // => 1 + * ``` + * + * **Example** (Running effects as promises) + * + * ```ts import.meta.vitest + * //Example: Handling a Failing Effect as a Rejected Promise + * import { Effect } from "effect" + * const output: Array = [] + * + * 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. + * @category running + * @since 2.0.0 + */ +export const runPromise: ( + effect: Effect, + options?: RunOptions | undefined +) => Promise = internal.runPromise + +/** + * Executes an effect as a Promise with the provided services. + * + * **When to use** + * + * Use when you already have a `Context` and need Promise interop that rejects on + * effect failure. + * + * **Example** (Running with services as a promise) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * + * interface Config { + * apiUrl: string + * } + * + * const Config = Context.Service("Config") + * + * const context = Context.make(Config, { + * apiUrl: "https://api.example.com" + * }) + * + * const program = Effect.gen(function*() { + * const config = yield* Config + * return `Connecting to ${config.apiUrl}` + * }) + * + * await Effect.runPromiseWith(context)(program) // => "Connecting to https://api.example.com" + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runPromiseWith: ( + context: Context.Context +) => (effect: Effect, options?: RunOptions | undefined) => Promise = internal.runPromiseWith + +/** + * Runs an effect and returns a `Promise` that resolves to an `Exit`, which + * represents the outcome (success or failure) of the effect. + * + * **When to use** + * + * Use when you need to determine if an effect succeeded + * or failed, including any defects, and you want to work with a `Promise`. + * + * **Details** + * + * The `Exit` type represents the result of the effect. Successful effects are + * wrapped in `Success`, and failed effects are wrapped in `Failure` with a + * `Cause`. + * + * **Example** (Observing promise results as Exit) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * + * // Execute a successful effect and get the Exit result as a Promise + * await Effect.runPromiseExit(Effect.succeed(1)) // => Exit.succeed(1) + * + * // Execute a failing effect and get the Exit result as a Promise + * await Effect.runPromiseExit(Effect.fail("my error")) // => Exit.fail("my error") + * ``` + * + * @see {@link runPromise} for a version that rejects on failure. + * + * @category running + * @since 2.0.0 + */ +export const runPromiseExit: ( + effect: Effect, + options?: RunOptions | undefined +) => Promise> = internal.runPromiseExit + +/** + * Runs an effect and returns a Promise of Exit with provided services. + * + * **When to use** + * + * Use when you already have a `Context` and need Promise interop that preserves + * success and failure as an `Exit`. + * + * **Example** (Running with services as an Exit promise) + * + * ```ts import.meta.vitest + * import { Context, Effect, Exit } from "effect" + * const output: Array = [] + * + * interface Database { + * query: (sql: string) => string + * } + * + * const Database = Context.Service("Database") + * + * const services = Context.make(Database, { + * query: (sql) => `Result for: ${sql}` + * }) + * + * const program = Effect.gen(function*() { + * const db = yield* Database + * return db.query("SELECT * FROM users") + * }) + * + * 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 + * @since 4.0.0 + */ +export const runPromiseExitWith: ( + context: Context.Context +) => (effect: Effect, options?: RunOptions | undefined) => Promise> = + internal.runPromiseExitWith + +/** + * Executes an effect synchronously and returns its success value. + * + * **When to use** + * + * Use when you need to execute an effect that is guaranteed to complete + * synchronously. + * + * **Details** + * + * If the effect fails, dies, is interrupted, or performs asynchronous work, + * `runSync` throws a `FiberFailure` instead of returning a value. Use + * `runSyncExit` when you want the failure captured as an `Exit`. + * + * **Example** (Running a synchronous effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.sync(() => { + * void output.push("Hello, World!") + * return 1 + * }) + * + * const result = Effect.runSync(program) + * void output.push(result) + * output // => ["Hello, World!", 1] + * ``` + * + * **Example** (Throwing for failed or async effects) + * + * ```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) { + * void output.push("failed effect") + * } + * try { + * // Attempt to run an effect that involves async work + * Effect.runSync(Effect.promise(() => Promise.resolve(1))) + * } catch (e) { + * void output.push("async effect") + * } + * output // => ["failed effect", "async effect"] + * ``` + * + * @see {@link runSyncExit} for a version that returns an `Exit` type instead of + * throwing an error. + * @category running + * @since 2.0.0 + */ +export const runSync: (effect: Effect) => A = internal.runSync + +/** + * Executes an effect synchronously with provided services. + * + * **When to use** + * + * Use when you already have a `Context`, the effect is known to complete + * synchronously, and failures should throw. + * + * **Example** (Running synchronously with services) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * + * interface MathService { + * add: (a: number, b: number) => number + * } + * + * const MathService = Context.Service("MathService") + * + * const context = Context.make(MathService, { + * add: (a, b) => a + b + * }) + * + * const program = Effect.gen(function*() { + * const math = yield* MathService + * return math.add(2, 3) + * }) + * + * const result = Effect.runSyncWith(context)(program) + * result // => 5 + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runSyncWith: ( + context: Context.Context +) => (effect: Effect) => A = internal.runSyncWith + +/** + * Runs an effect synchronously and captures the outcome safely as an `Exit` type, which + * represents the outcome (success or failure) of the effect. + * + * **When to use** + * + * Use to find out whether an effect succeeded or failed, + * including any defects, without dealing with asynchronous operations. + * + * **Details** + * + * The `Exit` type represents the result of the effect. Successful effects are + * wrapped in `Success`, and failed effects are wrapped in `Failure` with a + * `Cause`. + * + * If the effect contains asynchronous operations, `runSyncExit` will + * return an `Failure` with a `Die` cause, indicating that the effect cannot be + * resolved synchronously. + * + * **Example** (Observing synchronous results as Exit) + * + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * + * Effect.runSyncExit(Effect.succeed(1)) // => Exit.succeed(1) + * + * Effect.runSyncExit(Effect.fail("my error")) // => Exit.fail("my error") + * ``` + * + * **Example** (Capturing async work as a Die cause) + * + * ```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) + * ) + * + * isAsyncDie // => true + * ``` + * + * @see {@link runSync} for a version that throws on failure. + * + * @category running + * @since 2.0.0 + */ +export const runSyncExit: (effect: Effect) => Exit.Exit = internal.runSyncExit + +/** + * Runs an effect synchronously with provided services, returning an Exit result safely. + * + * **When to use** + * + * Use when you already have a `Context` and need a synchronous `Exit` instead of + * throwing on failure. + * + * **Example** (Running synchronously with services as Exit) + * + * ```ts import.meta.vitest + * import { Context, Effect, Exit } from "effect" + * const output: Array = [] + * + * // Define a logger service + * const Logger = Context.Service<{ + * log: (msg: string) => void + * }>("Logger") + * + * const program = Effect.gen(function*() { + * const logger = yield* Effect.service(Logger) + * logger.log("Computing result...") + * return 42 + * }) + * + * // Prepare context + * const context = Context.make(Logger, { + * log: (msg) => void output.push(`[LOG] ${msg}`) + * }) + * + * const exit = Effect.runSyncExitWith(context)(program) + * + * if (Exit.isSuccess(exit)) { + * void output.push(`Success: ${exit.value}`) + * } else { + * void output.push(`Failure: ${exit.cause}`) + * } + * output // => ["[LOG] Computing result...", "Success: 42"] + * ``` + * + * @category running + * @since 4.0.0 + */ +export const runSyncExitWith: ( + context: Context.Context +) => (effect: Effect) => Exit.Exit = internal.runSyncExitWith + +// ----------------------------------------------------------------------------- +// Function +// ----------------------------------------------------------------------------- + +/** + * Type helpers for functions built with `Effect.fn` and `Effect.fnUntraced`. + * + * **Details** + * + * Use these to describe generator-based signatures and traced or untraced variants. + * + * @since 3.11.0 + */ +export declare namespace fn { + /** + * Generator return type accepted by {@link fn} and {@link fnUntraced}. + * + * **When to use** + * + * Use when you need to annotate the return type of a generator body while + * keeping the produced function's `Effect` return type inferred. + * + * **Example** (Annotating an Effect function) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fnUntraced(function*( + * value: string + * ): Effect.fn.Return { + * return yield* Effect.succeed(value.length) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => 5 + * ``` + * + * **Example** (Annotating a parametric Effect function) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fnUntraced(function*( + * value: A + * ): Effect.fn.Return { + * return yield* Effect.succeed(value) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => "hello" + * ``` + * + * @category utility types + * @since 3.19.0 + */ + export type Return = Generator, A, any> + + /** + * Type of the untraced function builder used by `Effect.fnUntraced`. + * + * @category utility types + * @since 3.11.0 + */ + export type Untraced = { + , AEff, Args extends Array>( + body: (this: unassigned, ...args: Args) => Generator + ): (...args: Args) => Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + > + , AEff, Args extends Array>( + body: (this: Self, ...args: Args) => Generator + ): (this: Self, ...args: Args) => Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + > + + , AEff, Args extends Array, A>( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A + ): (...args: Args) => A + , AEff, Args extends Array, A>( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A + ): (this: Self, ...args: Args) => A + , AEff, Args extends Array, A, B>( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B + ): (...args: Args) => B + , AEff, Args extends Array, A, B>( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B + ): (this: Self, ...args: Args) => B + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C + ): (...args: Args) => C + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C + ): (this: Self, ...args: Args) => C + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D + >( + body: (...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D + ): (...args: Args) => D + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D + ): (this: Self, ...args: Args) => D + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E + ): (...args: Args) => E + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E + ): (this: Self, ...args: Args) => E + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F + ): (...args: Args) => F + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F + ): (this: Self, ...args: Args) => F + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G + ): (...args: Args) => G + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G + ): (this: Self, ...args: Args) => G + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H + ): (...args: Args) => H + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H + ): (this: Self, ...args: Args) => H + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I + ): (...args: Args) => I + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I + ): (this: Self, ...args: Args) => I + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J + ): (...args: Args) => J + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J + ): (this: Self, ...args: Args) => J + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K + ): (...args: Args) => K + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K + ): (this: Self, ...args: Args) => K + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L + ): (...args: Args) => L + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L + ): (this: Self, ...args: Args) => L + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M + ): (...args: Args) => M + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M + ): (this: Self, ...args: Args) => M + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N + ): (...args: Args) => N + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N + ): (this: Self, ...args: Args) => N + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O + ): (...args: Args) => O + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O + ): (this: Self, ...args: Args) => O + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P + ): (...args: Args) => P + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P + ): (this: Self, ...args: Args) => P + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q + ): (...args: Args) => Q + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q + ): (this: Self, ...args: Args) => Q + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R + ): (...args: Args) => R + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R + ): (this: Self, ...args: Args) => R + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S + ): (...args: Args) => S + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S + ): (this: Self, ...args: Args) => S + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T + >( + body: (this: unassigned, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S, + t: (_: S, ...args: Args) => T + ): (...args: Args) => T + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T + >( + body: (this: Self, ...args: Args) => Generator, + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S, + t: (_: S, ...args: Args) => T + ): (this: Self, ...args: Args) => T + } + + /** + * Type of the traced function builder used by `Effect.fn`. + * + * @category utility types + * @since 4.0.0 + */ + export type Traced = { + , AEff, Args extends Array>( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect) + ): (...args: Args) => Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + > + , AEff, Args extends Array>( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect) + ): (this: Self, ...args: Args) => Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + > + , AEff, Args extends Array>( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect) + ): (...args: Args) => Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + > + + , AEff, Args extends Array, A>( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A + ): (...args: Args) => A + , AEff, Args extends Array, A>( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A + ): (this: Self, ...args: Args) => A + , AEff, Args extends Array, A>( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A + ): (...args: Args) => A + + , AEff, Args extends Array, A, B>( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B + ): (...args: Args) => B + , AEff, Args extends Array, A, B>( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B + ): (this: Self, ...args: Args) => B + , AEff, Args extends Array, A, B>( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B + ): (...args: Args) => B + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C + ): (...args: Args) => C + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C + ): (this: Self, ...args: Args) => C + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C + ): (...args: Args) => C + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D + ): (...args: Args) => D + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D + ): (this: Self, ...args: Args) => D + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D + ): (...args: Args) => D + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E + ): (...args: Args) => E + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E + ): (this: Self, ...args: Args) => E + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E + ): (...args: Args) => E + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F + ): (...args: Args) => F + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F + ): (this: Self, ...args: Args) => F + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F + ): (...args: Args) => F + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G + ): (...args: Args) => G + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G + ): (this: Self, ...args: Args) => G + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G + ): (...args: Args) => G + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H + ): (...args: Args) => H + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H + ): (this: Self, ...args: Args) => H + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H + ): (...args: Args) => H + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I + ): (...args: Args) => I + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I + ): (this: Self, ...args: Args) => I + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I + ): (...args: Args) => I + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J + ): (...args: Args) => J + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J + ): (this: Self, ...args: Args) => J + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J + ): (...args: Args) => J + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K + ): (...args: Args) => K + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K + ): (this: Self, ...args: Args) => K + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K + ): (...args: Args) => K + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L + ): (...args: Args) => L + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L + ): (this: Self, ...args: Args) => L + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L + ): (...args: Args) => L + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M + ): (...args: Args) => M + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M + ): (this: Self, ...args: Args) => M + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M + ): (...args: Args) => M + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N + ): (...args: Args) => N + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N + ): (this: Self, ...args: Args) => N + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N + ): (...args: Args) => N + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O + ): (...args: Args) => O + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O + ): (this: Self, ...args: Args) => O + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O + ): (...args: Args) => O + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P + ): (...args: Args) => P + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P + ): (this: Self, ...args: Args) => P + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P + ): (...args: Args) => P + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q + ): (...args: Args) => Q + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q + ): (this: Self, ...args: Args) => Q + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q + ): (...args: Args) => Q + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R + ): (...args: Args) => R + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R + ): (this: Self, ...args: Args) => R + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R + ): (...args: Args) => R + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S + ): (...args: Args) => S + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S + ): (this: Self, ...args: Args) => S + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S + ): (...args: Args) => S + + < + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T + >( + body: (this: unassigned, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S, + t: (_: S, ...args: Args) => T + ): (...args: Args) => T + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T + >( + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S, + t: (_: S, ...args: Args) => T + ): (this: Self, ...args: Args) => T + + < + Self, + Eff extends Effect, + AEff, + Args extends Array, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T + >( + options: { readonly self: Self }, + body: (this: Self, ...args: Args) => Generator | (Eff & Effect), + a: ( + _: Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect] ? R + : never + >, + ...args: Args + ) => A, + b: (_: A, ...args: Args) => B, + c: (_: B, ...args: Args) => C, + d: (_: C, ...args: Args) => D, + e: (_: D, ...args: Args) => E, + f: (_: E, ...args: Args) => F, + g: (_: F, ...args: Args) => G, + h: (_: G, ...args: Args) => H, + i: (_: H, ...args: Args) => I, + j: (_: I, ...args: Args) => J, + k: (_: J, ...args: Args) => K, + l: (_: K, ...args: Args) => L, + m: (_: L, ...args: Args) => M, + n: (_: M, ...args: Args) => N, + o: (_: N, ...args: Args) => O, + p: (_: O, ...args: Args) => P, + q: (_: P, ...args: Args) => Q, + r: (_: Q, ...args: Args) => R, + s: (_: R, ...args: Args) => S, + t: (_: S, ...args: Args) => T + ): (...args: Args) => T + } +} + +/** + * Creates an Effect-returning function without tracing. + * + * **When to use** + * + * Use when you are defining a reusable Effect function whose implementation + * would otherwise be a normal function returning {@link gen}, especially when + * tracing spans or stack-frame capture are not needed. + * + * **Details** + * + * Compared to a plain function that returns {@link gen}, `Effect.fnUntraced` + * reuses the generator body instead of allocating a fresh generator closure + * around the arguments on every call. It does not record an Effect stack-frame + * boundary and does not create tracing spans. Use {@link fn} when you need + * those stack frames or spans. Additional arguments after the generator body + * act like `pipe` transforms: each transform receives the previous result and + * the original function arguments. Annotate the generator return type with + * `Effect.fn.Return` when the produced `Effect` type needs to be + * stated explicitly. + * + * **Example** (Defining untraced effect functions) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fnUntraced(function*( + * value: string + * ) { + * return yield* Effect.succeed(value.length) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => 5 + * ``` + * + * **Example** (Transforming the returned Effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fnUntraced( + * function*(value: string) { + * return yield* Effect.succeed(value.length) + * }, + * (effect, value) => + * effect.pipe(Effect.map((length) => `${value}: ${length}`)) + * ) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => "hello: 5" + * ``` + * + * **Example** (Annotating an untraced non-parametric function) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fnUntraced(function*( + * value: string + * ): Effect.fn.Return { + * return yield* Effect.succeed(value.length) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => 5 + * ``` + * + * **Example** (Annotating an untraced parametric function) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fnUntraced(function*( + * value: A + * ): Effect.fn.Return { + * return yield* Effect.succeed(value) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => "hello" + * ``` + * + * @category constructors + * @since 3.12.0 + */ +export const fnUntraced: fn.Untraced = internal.fnUntraced + +/** + * Creates a reusable traced function from an Effect body. + * + * **When to use** + * + * Use when you are defining a reusable Effect function whose implementation + * would otherwise be a normal function returning {@link gen}, and you want + * tracing spans or stack-frame capture. + * + * **Details** + * + * Compared to a plain function that returns {@link gen}, `Effect.fn` reuses the + * generator body instead of allocating a fresh generator closure around the + * arguments on every call. Call `Effect.fn(body, ...)` for a generic + * stack-frame boundary without creating a span. Call + * `Effect.fn("operationName", options?)(body, ...)` when that boundary should + * have a readable operation name and the returned `Effect` should create a + * tracing span when run. {@link SpanOptionsNoTrace} configures span metadata + * such as attributes, links, parent or root selection, kind, sampling, and log + * level. Additional arguments after the generator body act like `pipe` + * transforms: each transform receives the previous result and the original + * function arguments. When those transforms return an `Effect`, the returned + * effect includes stack-frame metadata and, for the named form, a tracing span. + * Generator bodies may declare a `this` parameter; pass `{ self }` before the + * body to bind `this` when the function is created. + * + * **Example** (Defining traced effect functions) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fn("calculateLength")(function*(value: string) { + * return yield* Effect.succeed(value.length) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => 5 + * ``` + * + * **Example** (Transforming the returned Effect) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fn("formatLength")( + * function*(value: string) { + * return yield* Effect.succeed(value.length) + * }, + * (effect, value) => + * effect.pipe(Effect.map((length) => `${value}: ${length}`)) + * ) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => "hello: 5" + * ``` + * + * **Example** (Binding this) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * class Counter { + * count = 0 + * + * increment = Effect.fn("Counter.increment")( + * { self: this }, + * function*(this: Counter, by: number) { + * this.count += by + * return yield* Effect.succeed(this.count) + * } + * ) + * } + * + * const counter = new Counter() + * + * // ┌─── Effect.Effect + * // ▼ + * const program = counter.increment(1) + * Effect.runSync(program) // => 1 + * ``` + * + * **Example** (Annotating a traced non-parametric function) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fn("calculateLength")(function*( + * value: string + * ): Effect.fn.Return { + * return yield* Effect.succeed(value.length) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => 5 + * ``` + * + * **Example** (Annotating a traced parametric function) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const f = Effect.fn("succeed")(function*( + * value: A + * ): Effect.fn.Return { + * return yield* Effect.succeed(value) + * }) + * + * // ┌─── Effect.Effect + * // ▼ + * const program = f("hello") + * Effect.runSync(program) // => "hello" + * ``` + * + * @category constructors + * @since 3.11.0 + */ +export const fn: fn.Traced & { + (name: string, options?: SpanOptionsNoTrace): fn.Traced +} = internal.fn + +// ======================================================================== +// Clock +// ======================================================================== + +/** + * Retrieves the `Clock` service from the context and provides it to the + * specified effectful function. + * + * **Example** (Accessing the Clock service) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.clockWith((clock) => + * clock.currentTimeMillis.pipe( + * Effect.map(() => "Clock is available") + * ) + * ) + * + * Effect.runSync(program) // => "Clock is available" + * ``` + * + * @category accessors + * @since 2.0.0 + */ +export const clockWith: ( + f: (clock: Clock) => Effect +) => Effect = internal.clockWith + +// ======================================================================== +// Logging +// ======================================================================== + +/** + * Creates a logger function that logs at the specified level. + * + * **Details** + * + * If no level is provided, the logger uses the fiber's current log level and + * extracts any `Cause` values from the message list. + * + * **Example** (Logging at a dynamic level) + * + * ```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") + * }) + * 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 + * @since 2.0.0 + */ +export const logWithLevel: (level?: Severity) => (...message: ReadonlyArray) => Effect = + internal.logWithLevel + +/** + * Logs one or more messages using the default log level. + * + * **Example** (Logging at the default level) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * const result = 2 + 2 + * yield* Effect.log("Result:", result) + * return result + * }) + * + * 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 + * @since 2.0.0 + */ +export const log: (...message: ReadonlyArray) => Effect = internal.logWithLevel() + +/** + * Logs one or more messages at the FATAL level. + * + * **Example** (Logging fatal messages) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.logFatal("Critical system failure") + * }) + * + * 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 + * @since 2.0.0 + */ +export const logFatal: (...message: ReadonlyArray) => Effect = internal.logWithLevel("Fatal") + +/** + * Logs one or more messages at the WARNING level. + * + * **Example** (Logging warnings) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.logWarning("API rate limit approaching") + * }) + * + * 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 + * @since 2.0.0 + */ +export const logWarning: (...message: ReadonlyArray) => Effect = internal.logWithLevel("Warn") + +/** + * Logs one or more messages at the ERROR level. + * + * **Example** (Logging errors) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.logError("Database connection failed") + * }) + * + * 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 + * @since 2.0.0 + */ +export const logError: (...message: ReadonlyArray) => Effect = internal.logWithLevel("Error") + +/** + * Logs one or more messages at the INFO level. + * + * **Example** (Logging information) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.logInfo("Application starting up") + * }) + * + * 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 + * @since 2.0.0 + */ +export const logInfo: (...message: ReadonlyArray) => Effect = internal.logWithLevel("Info") + +/** + * Logs one or more messages at the DEBUG level. + * + * **Example** (Logging debug messages) + * + * ```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 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 + * @since 2.0.0 + */ +export const logDebug: (...message: ReadonlyArray) => Effect = internal.logWithLevel("Debug") + +/** + * Logs one or more messages at the TRACE level. + * + * **Example** (Logging trace messages) + * + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.logTrace("Entering 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 + * @since 2.0.0 + */ +export const logTrace: (...message: ReadonlyArray) => Effect = internal.logWithLevel("Trace") + +/** + * Adds a logger to the set of loggers which will output logs for this effect. + * + * **Example** (Adding a logger to an effect) + * + * ```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 }) => + * void output.push(`[CUSTOM]: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * ) + * + * const program = Effect.gen(function*() { + * yield* Effect.log("This will go to both default and custom logger") + * return "completed" + * }) + * + * // Add the custom logger to the effect + * const programWithLogger = Effect.withLogger(program, customLogger) + * + * Effect.runSync(Effect.provide(programWithLogger, Logger.layer([]))) + * output // => ["[CUSTOM]: This will go to both default and custom logger"] + * ``` + * + * @category logging + * @since 4.0.0 + */ +export const withLogger = dual< + ( + logger: Logger + ) => (effect: Effect) => Effect, + ( + effect: Effect, + logger: Logger + ) => Effect +>(2, (effect, logger) => + internal.updateService( + effect, + internal.CurrentLoggers, + (loggers) => new Set([...loggers, logger]) + )) + +/** + * Adds an annotation to each log line in this effect. + * + * **Example** (Adding log annotations) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Effect.log("Starting operation") + * }) + * + * // Add annotations to all log messages + * const annotatedProgram = Effect.annotateLogs(program, { + * userId: "user123", + * operation: "data-processing" + * }) + * + * // Also supports single key-value annotations + * const singleAnnotated = Effect.annotateLogs(program, "requestId", "req-456") + * + * 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 + * @since 2.0.0 + */ +export const annotateLogs = dual< + { + ( + key: string, + value: unknown + ): (effect: Effect) => Effect + ( + values: Record + ): (effect: Effect) => Effect + }, + { + ( + effect: Effect, + key: string, + value: unknown + ): Effect + ( + effect: Effect, + values: Record + ): Effect + } +>( + (args) => isEffect(args[0]), + ( + effect: Effect, + ...args: [Record] | [key: string, value: unknown] + ): Effect => + internal.updateService(effect, CurrentLogAnnotations, (annotations) => { + const newAnnotations = args.length === 1 ? { ...annotations, ...args[0] } : { ...annotations } + if (args.length === 1) { + return newAnnotations + } else { + InternalRecord.assignProperty(newAnnotations, args[0], args[1]) + } + return newAnnotations + }) +) + +/** + * Adds log annotations to the current scope. + * + * **When to use** + * + * Use to attach log annotations that last until the current scope closes. + * + * **Details** + * + * This differs from `annotateLogs`, which only annotates a specific effect. + * `annotateLogsScoped` updates annotations for the entire current `Scope` and + * restores the previous annotations when the scope closes. + * + * **Example** (Adding scoped log annotations) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * yield* Effect.log("before") + * yield* Effect.annotateLogsScoped({ requestId: "req-123" }) + * yield* Effect.log("inside scope") + * }) + * ) + * + * 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 + * + * @category logging + * @since 3.1.0 + */ +export const annotateLogsScoped: { + (key: string, value: unknown): Effect + (values: Record): Effect +} = internal.annotateLogsScoped + +/** + * Adds a span to each log line in this effect. + * + * **Example** (Adding a log span) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] + * + * const databaseOperation = Effect.gen(function*() { + * yield* Effect.log("Connecting to database") + * yield* Effect.log("Executing query") + * yield* Effect.log("Processing results") + * return "data" + * }) + * + * const httpRequest = Effect.gen(function*() { + * yield* Effect.log("Making HTTP request") + * const data = yield* Effect.withLogSpan(databaseOperation, "db-operation") + * yield* Effect.log("Sending response") + * return data + * }) + * + * const program = Effect.withLogSpan(httpRequest, "http-handler") + * + * 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 + * @since 2.0.0 + */ +export const withLogSpan = dual< + (label: string) => (effect: Effect) => Effect, + (effect: Effect, label: string) => Effect +>( + 2, + (effect, label) => + internal.flatMap(internal.currentTimeMillis, (now) => + internal.updateService(effect, CurrentLogSpans, (spans) => { + const span: [label: string, timestamp: number] = [label, now] + return [span, ...spans] + })) +) + +// ----------------------------------------------------------------------------- +// Metrics +// ----------------------------------------------------------------------------- + +/** + * Updates the `Metric` every time the `Effect` is executed. + * + * **Details** + * + * 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) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const counter = Metric.counter("effect_executions", { + * description: "Counts effect executions" + * }).pipe(Metric.withConstantInput(1)) + * + * const program = Effect.succeed("Hello").pipe( + * Effect.track(counter) + * ) + * + * Effect.runSync(program) + * Effect.runSync(Metric.value(counter)).count // => 1 + * ``` + * + * **Example** (Mapping exits before updating a metric) + * + * ```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" + * }) + * + * const mapExitToString = (exit: Exit.Exit) => { + * if (Exit.isSuccess(exit)) return "success" + * if (Exit.isFailure(exit)) return "failure" + * return "defect" + * } + * + * const effect = Effect.succeed("result").pipe( + * Effect.track(exitTracker, mapExitToString) + * ) + * Effect.runSync(effect) + * Effect.runSync(Metric.value(exitTracker)).occurrences.get("success") // => 1 + * ``` + * + * @category metrics + * @since 4.0.0 + */ +export const track: { + ( + metric: Metric.Metric, + f: (exit: Exit.Exit) => Input + ): (self: Effect) => Effect + ( + metric: Metric.Metric, NoInfer>, State> + ): (self: Effect) => Effect + ( + self: Effect, + metric: Metric.Metric, + f: (exit: Exit.Exit) => Input + ): Effect + ( + self: Effect, + metric: Metric.Metric, NoInfer>, State> + ): Effect +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect, + metric: Metric.Metric, + f: (exit: Exit.Exit) => Input + ): Effect => + onExit(self, (exit) => { + const input = f === undefined ? exit : internalCall(() => f(exit)) + return Metric.update(metric, input as any) + }) +) + +/** + * Updates the provided `Metric` every time the wrapped `Effect` succeeds with + * a value. + * + * **Details** + * + * Also accepts an optional function which can be used to map the success value + * of the `Effect` into a valid `Input` for the `Metric`. + * + * **Example** (Counting successful results) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const successCounter = Metric.counter("successes").pipe( + * Metric.withConstantInput(1) + * ) + * + * const program = Effect.succeed(42).pipe( + * Effect.trackSuccesses(successCounter) + * ) + * + * Effect.runSync(program) + * Effect.runSync(Metric.value(successCounter)).count // => 1 + * ``` + * + * **Example** (Mapping successes before tracking) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * // Track successful request sizes + * const requestSizeGauge = Metric.gauge("request_size_bytes") + * + * const program = Effect.succeed("Hello World!").pipe( + * Effect.trackSuccesses(requestSizeGauge, (value: string) => value.length) + * ) + * + * Effect.runSync(program) + * Effect.runSync(Metric.value(requestSizeGauge)).value // => 12 + * ``` + * + * @category metrics + * @since 4.0.0 + */ +export const trackSuccesses: { + ( + metric: Metric.Metric, + f: (value: A) => Input + ): (self: Effect) => Effect + ( + metric: Metric.Metric, State> + ): (self: Effect) => Effect + ( + self: Effect, + metric: Metric.Metric, + f: (value: A) => Input + ): Effect + ( + self: Effect, + metric: Metric.Metric, State> + ): Effect +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect, + metric: Metric.Metric, + f: ((value: A) => Input) | undefined + ): Effect => + tap(self, (value) => { + const input = f === undefined ? value : f(value) + return Metric.update(metric, input as any) + }) +) + +/** + * Updates the provided `Metric` every time the wrapped `Effect` fails with an + * **expected** error. + * + * **Details** + * + * Also accepts an optional function which can be used to map the error value + * of the `Effect` into a valid `Input` for the `Metric`. + * + * **Example** (Counting expected failures) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const errorCounter = Metric.counter("errors").pipe( + * Metric.withConstantInput(1) + * ) + * + * const program = Effect.fail("Network timeout").pipe( + * Effect.trackErrors(errorCounter) + * ) + * + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(errorCounter)).count // => 1 + * ``` + * + * **Example** (Mapping errors before tracking) + * + * ```ts import.meta.vitest + * import { Data, Effect, Metric } from "effect" + * + * class ConnectionFailedError extends Data.TaggedError("ConnectionFailedError")<{}> {} + * + * // Track error types using frequency metric + * const errorTypeFrequency = Metric.frequency("error_types") + * + * const program = Effect.fail(new ConnectionFailedError()).pipe( + * Effect.trackErrors(errorTypeFrequency, (error: ConnectionFailedError) => error._tag) + * ) + * + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(errorTypeFrequency)).occurrences.get("ConnectionFailedError") // => 1 + * ``` + * + * @category metrics + * @since 4.0.0 + */ +export const trackErrors: { + ( + metric: Metric.Metric, + f: (error: E) => Input + ): (self: Effect) => Effect + ( + metric: Metric.Metric, State> + ): (self: Effect) => Effect + ( + self: Effect, + metric: Metric.Metric, + f: (error: E) => Input + ): Effect + ( + self: Effect, + metric: Metric.Metric, State> + ): Effect +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect, + metric: Metric.Metric, + f: ((error: E) => Input) | undefined + ): Effect => + tapError(self, (error) => { + const input = f === undefined ? error : internalCall(() => f(error)) + return Metric.update(metric, input as any) + }) +) + +/** + * Updates the provided `Metric` every time the wrapped `Effect` fails with an + * **unexpected** error (i.e. a defect). + * + * **Details** + * + * Also accepts an optional function which can be used to map the defect value + * of the `Effect` into a valid `Input` for the `Metric`. + * + * **Example** (Counting defects) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const defectCounter = Metric.counter("defects").pipe( + * Metric.withConstantInput(1) + * ) + * + * const program = Effect.die("Critical system failure").pipe( + * Effect.trackDefects(defectCounter) + * ) + * + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(defectCounter)).count // => 1 + * ``` + * + * **Example** (Mapping defects before tracking) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * // Track defect types using frequency metric + * const defectTypeFrequency = Metric.frequency("defect_types") + * + * const program = Effect.die(new Error("Null pointer exception")).pipe( + * Effect.trackDefects(defectTypeFrequency, (defect: unknown) => { + * if (defect instanceof Error) return defect.constructor.name + * return typeof defect + * }) + * ) + * + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(defectTypeFrequency)).occurrences.get("Error") // => 1 + * ``` + * + * @category metrics + * @since 4.0.0 + */ +export const trackDefects: { + ( + metric: Metric.Metric, + f: (defect: unknown) => Input + ): (self: Effect) => Effect + ( + metric: Metric.Metric + ): (self: Effect) => Effect + ( + self: Effect, + metric: Metric.Metric, + f: (defect: unknown) => Input + ): Effect + ( + self: Effect, + metric: Metric.Metric + ): Effect +} = dual( + (args) => isEffect(args[0]), + (self, metric, f) => + tapDefect(self, (defect) => { + const input = f === undefined ? defect : internalCall(() => f(defect)) + return Metric.update(metric, input) + }) +) + +/** + * Updates the provided `Metric` with the `Duration` of time (in nanoseconds) + * that the wrapped `Effect` took to complete. + * + * **Details** + * + * Also accepts an optional function which can be used to map the `Duration` + * that the wrapped `Effect` took to complete into a valid `Input` for the + * `Metric`. + * + * **Example** (Recording execution duration) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const executionTimer = Metric.timer("execution_time") + * + * const program = Effect.succeed("done").pipe( + * Effect.trackDuration(executionTimer) + * ) + * + * Effect.runSync(program) + * Effect.runSync(Metric.value(executionTimer)).count // => 1 + * ``` + * + * **Example** (Mapping duration before tracking) + * + * ```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.succeed("done").pipe( + * Effect.trackDuration(durationGauge, () => 1) + * ) + * + * Effect.runSync(program) + * Effect.runSync(Metric.value(durationGauge)).value // => 1 + * ``` + * + * @category metrics + * @since 4.0.0 + */ +export const trackDuration: { + ( + metric: Metric.Metric, + f: (duration: Duration.Duration) => Input + ): (self: Effect) => Effect + ( + metric: Metric.Metric + ): (self: Effect) => Effect + ( + self: Effect, + metric: Metric.Metric, + f: (duration: Duration.Duration) => Input + ): Effect + ( + self: Effect, + metric: Metric.Metric + ): Effect +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect, + metric: Metric.Metric, + f: ((duration: Duration.Duration) => Input) | undefined + ): Effect => + clockWith((clock) => { + const startTime = clock.monotonicTimeNanosUnsafe() + return onExit(self, () => { + const endTime = clock.monotonicTimeNanosUnsafe() + const duration = Duration.subtract( + Duration.fromInputUnsafe(endTime), + Duration.fromInputUnsafe(startTime) + ) + const input = f === undefined ? duration : internalCall(() => f(duration)) + return Metric.update(metric, input as any) + }) + }) +) + +// ----------------------------------------------------------------------------- +// Transactions +// ----------------------------------------------------------------------------- + +/** + * Service that holds the current transaction state. + * + * **Details** + * + * It includes a journal that stores non-committed changes to `TxRef` values and + * a retry flag that records whether the transaction should be retried. + * + * **Example** (Building transactions) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // Transaction class for software transactional memory operations + * const txEffect = Effect.gen(function*() { + * const tx = yield* Effect.Transaction + * // 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 services + * @since 4.0.0 + */ +export class Transaction extends Context.Service< + Transaction, + { + retry: boolean + readonly journal: Map< + TxRef, + { + readonly version: number + value: any + } + > + } +>()("effect/Effect/Transaction") {} + +/** + * Defines a transaction boundary. Transactions are "all or nothing" with respect to changes + * made to transactional values (i.e. TxRef) that occur within the transaction body. + * + * **Details** + * + * If called inside an active transaction, `tx` composes with the current transaction and reuses + * its journal and retry state instead of creating a nested boundary. + * + * Effect transactions are optimistic with retry. A transaction is retried when + * its body explicitly calls `Effect.txRetry` and any accessed transactional + * value changes, or when any accessed transactional value changes because a + * different transaction commits before the current one. + * + * The outermost `tx` call creates the transaction boundary and commits or rolls back the full + * composed transaction. + * + * **Example** (Running a transaction) + * + * ```ts import.meta.vitest + * import { Effect, TxRef } from "effect" + * const output: Array = [] + * + * const program = Effect.gen(function*() { + * const ref1 = yield* TxRef.make(0) + * const ref2 = yield* TxRef.make(0) + * + * // Nested tx calls compose into the same transaction + * yield* Effect.tx(Effect.gen(function*() { + * yield* TxRef.set(ref1, 10) + * yield* Effect.tx(TxRef.set(ref2, 20)) + * const sum = (yield* TxRef.get(ref1)) + (yield* TxRef.get(ref2)) + * void output.push(`Transaction sum: ${sum}`) + * })) + * + * 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 + * @since 4.0.0 + */ +export const tx = ( + effect: Effect +): Effect> => + withFiber((fiber) => { + let state = Context.getOrUndefined(fiber.context, Transaction) + if (state) { + return effect as Effect> + } + // Create transaction state only at the outermost boundary + state = { journal: new Map(), retry: false } + let result: Exit.Exit | undefined + return uninterruptibleMask((restore) => + flatMap( + whileLoop({ + while: () => !result, + body: constant( + restore(effect).pipe( + provideService(Transaction, state), + tapCause(() => { + if (!state.retry) return void_ + return restore(awaitPendingTransaction(state)) + }), + exit + ) + ), + step(exit: Exit.Exit) { + if (state.retry || !isTransactionConsistent(state)) { + return clearTransaction(state) + } + if (Exit.isSuccess(exit)) { + commitTransaction(fiber, state) + } else { + clearTransaction(state) + } + result = exit + } + }), + () => result! + ) + ) + }) + +const isTransactionConsistent = (state: Transaction["Service"]) => { + for (const [ref, { version }] of state.journal) { + if (ref.version !== version) { + return false + } + } + return true +} + +const awaitPendingTransaction = (state: Transaction["Service"]) => + suspend(() => { + const key = {} + const refs = Array.from(state.journal.keys()) + const clearPending = () => { + for (const clear of refs) { + clear.pending.delete(key) + } + } + return callback((resume) => { + const onCall = () => { + clearPending() + resume(void_) + } + for (const ref of refs) { + ref.pending.set(key, onCall) + } + return sync(clearPending) + }) + }) + +function commitTransaction(fiber: Fiber, state: Transaction["Service"]) { + for (const [ref, { value }] of state.journal) { + if (value !== ref.value) { + ref.version = ref.version + 1 + ref.value = value + } + for (const pending of ref.pending.values()) { + fiber.currentDispatcher.scheduleTask(pending, 0) + } + ref.pending.clear() + } +} + +function clearTransaction(state: Transaction["Service"]) { + state.retry = false + state.journal.clear() +} + +/** + * Retries the current transaction by signaling that it must be retried. + * + * **Details** + * + * NOTE: the transaction retries on any change to transactional values (i.e. TxRef) accessed in its body. + * + * **Example** (Retrying transactions) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, TxRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxRef.make(0) + * const update = yield* Deferred.make() + * + * yield* Effect.forkChild( + * Deferred.await(update).pipe(Effect.andThen(Effect.tx(TxRef.set(ref, 1)))) + * ) + * + * return yield* Effect.tx(Effect.gen(function*() { + * const value = yield* TxRef.get(ref) + * if (value === 0) { + * yield* Deferred.succeed(update, undefined) + * return yield* Effect.txRetry + * } + * return value + * })) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category transactions + * @since 4.0.0 + */ +export const txRetry: Effect = flatMap( + Transaction, + (state) => { + state.retry = true + return interrupt + } +) +/** + * Type helpers for converting callback-based functions into `Effect` functions. + * + * @since 4.0.0 + */ +export declare namespace Effectify { + interface Callback { + (err: E, a?: A): void + } + + type ArgsWithCallback, E, A> = [...args: Args, cb: Callback] + + type WithoutNull = unknown extends A ? void : Exclude + + /** + * Converts a callback-based function type into an `Effect`-returning function type. + * + * @category utility types + * @since 4.0.0 + */ + export type Effectify = T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + (...args: ArgsWithCallback): infer _R9 + (...args: ArgsWithCallback): infer _R10 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + (...args: Args4): Effect, E> + (...args: Args5): Effect, E> + (...args: Args6): Effect, E> + (...args: Args7): Effect, E> + (...args: Args8): Effect, E> + (...args: Args9): Effect, E> + (...args: Args10): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + (...args: ArgsWithCallback): infer _R9 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + (...args: Args4): Effect, E> + (...args: Args5): Effect, E> + (...args: Args6): Effect, E> + (...args: Args7): Effect, E> + (...args: Args8): Effect, E> + (...args: Args9): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + (...args: Args4): Effect, E> + (...args: Args5): Effect, E> + (...args: Args6): Effect, E> + (...args: Args7): Effect, E> + (...args: Args8): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + (...args: Args4): Effect, E> + (...args: Args5): Effect, E> + (...args: Args6): Effect, E> + (...args: Args7): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + (...args: Args4): Effect, E> + (...args: Args5): Effect, E> + (...args: Args6): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + (...args: Args4): Effect, E> + (...args: Args5): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + (...args: Args4): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + (...args: Args3): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + } ? { + (...args: Args1): Effect, E> + (...args: Args2): Effect, E> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + } ? { + (...args: Args1): Effect, E> + } + : never + + /** + * Extracts the callback error type from a callback-based function type. + * + * @category utility types + * @since 4.0.0 + */ + export type EffectifyError = T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + (...args: ArgsWithCallback): infer _R9 + (...args: ArgsWithCallback): infer _R10 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + (...args: ArgsWithCallback): infer _R9 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + } ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + } ? NonNullable + : never +} + +/** + * Converts an error-first callback API into a function that returns an + * `Effect`. + * + * **Details** + * + * The original function is called with the supplied arguments plus a final + * callback. A non-null callback error fails the returned effect, while a + * successful callback value becomes the effect success. Use `onError` to map + * callback errors and `onSyncError` to turn synchronous throws into typed + * failures; otherwise synchronous throws become defects. + * + * **Example** (Converting callbacks to effects) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const uppercase = ( + * input: string, + * callback: (error: Error | null, value?: string) => void + * ) => queueMicrotask(() => callback(null, input.toUpperCase())) + * + * const effectfulUppercase = Effect.effectify(uppercase) + * const program = effectfulUppercase("hello") + * + * await Effect.runPromise(program) // => "HELLO" + * ``` + * + * **Example** (Mapping callback errors to typed failures) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * 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 = Effect.flip(effectfulFail("hello")) + * + * const error = await Effect.runPromise(program) + * error.message // => "Failed to process hello: unavailable" + * ``` + * + * @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 + ): Effectify.Effectify + ) => any, E, E2>( + fn: F, + onError: (error: Effectify.EffectifyError, args: Parameters) => E, + onSyncError: (error: unknown, args: Parameters) => E2 + ): Effectify.Effectify +} = + ((fn: Function, onError?: (e: any, args: any) => any, onSyncError?: (e: any, args: any) => any) => + (...args: Array) => + callback((resume) => { + try { + fn(...args, (err: globalThis.Error | null, result: A) => { + if (err) { + resume(fail(onError ? onError(err, args) : err)) + } else { + resume(succeed(result)) + } + }) + } catch (err) { + resume(onSyncError ? fail(onSyncError(err, args)) : die(err)) + } + })) as any + +// ----------------------------------------------------------------------------- +// Type constraints +// ----------------------------------------------------------------------------- + +/** + * Ensures that an effect's success type extends a given type `A`. + * + * **Details** + * + * This helper is checked at compile time and does not change the effect's + * runtime behavior. + * + * **Example** (Constraining the success type) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // Define a constraint that the success type must be a number + * const satisfiesNumber = Effect.satisfiesSuccessType() + * + * // 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")) + * // ^^^^^^^^^^^^^^^^^^^^^^ + * // Type 'string' is not assignable to type 'number' + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const satisfiesSuccessType = () => (effect: Effect): Effect => effect + +/** + * Ensures that an effect's error type extends a given type `E`. + * + * **Details** + * + * This helper is checked at compile time and does not change the effect's + * runtime behavior. + * + * **Example** (Constraining the error type) + * + * ```ts import.meta.vitest + * import { Data, Effect } from "effect" + * + * class ValidationError extends Data.TaggedError("ValidationError")<{}> {} + * + * // Define a constraint that the error type must be a ValidationError + * const satisfiesError = Effect.satisfiesErrorType() + * + * // 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")) + * // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * // Type 'string' is not assignable to type 'ValidationError' + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const satisfiesErrorType = () => (effect: Effect): Effect => effect + +/** + * Ensures that an effect's requirements type extends a given type `R`. + * + * **Details** + * + * This helper is checked at compile time and does not change the effect's + * runtime behavior. + * + * **Example** (Constraining the services type) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // Define a constraint that requires a string as the requirements type + * const satisfiesStringServices = Effect.satisfiesServicesType() + * + * // This works - effect requires string + * const validEffect: Effect.Effect = Effect.succeed(42) + * const constrainedEffect = satisfiesStringServices(validEffect) + * + * // This would cause a TypeScript compilation error if uncommented: + * // const invalidEffect: Effect.Effect = Effect.succeed(42) + * // const constrainedInvalid = satisfiesStringServices(invalidEffect) + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const satisfiesServicesType = () => (effect: Effect): Effect => effect + +/** + * Applies `map` eagerly when an effect is already resolved. + * + * **When to use** + * + * Use when an already-resolved effect should apply a success transformation + * immediately while pending effects still use regular mapping. + * + * **Details** + * + * Success effects apply the mapping function immediately. Failure effects pass + * through unchanged, and pending effects fall back to regular `map` behavior. + * + * **Example** (Mapping already completed effects) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // For resolved effects, the mapping is applied immediately + * const resolved = Effect.succeed(5) + * const mapped = Effect.mapEager(resolved, (n) => n * 2) // Applied eagerly + * + * // For pending effects, behaves like regular map + * 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 mapping + * @since 4.0.0 + */ +export const mapEager: { + (f: (a: A) => B): (self: Effect) => Effect + (self: Effect, f: (a: A) => B): Effect +} = internal.mapEager + +/** + * Applies `mapError` eagerly when an effect is already resolved. + * + * **When to use** + * + * Use when an already-resolved failed effect should apply an error + * transformation immediately while pending effects still use regular error + * mapping. + * + * **Details** + * + * Success effects pass through unchanged because there is no error to + * transform. Failure effects apply the mapping function immediately, and + * pending effects fall back to regular `mapError` behavior. + * + * **Example** (Mapping errors eagerly when possible) + * + * ```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"), 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 error handling + * @since 4.0.0 + */ +export const mapErrorEager: { + (f: (e: E) => E2): (self: Effect) => Effect + (self: Effect, f: (e: E) => E2): Effect +} = internal.mapErrorEager + +/** + * Applies `mapBoth` eagerly when an effect is already resolved. + * + * **When to use** + * + * Use when an already-resolved effect should transform either success or + * failure immediately while pending effects still use regular channel mapping. + * + * **Details** + * + * Success effects apply `onSuccess` immediately, and failure effects apply + * `onFailure` immediately. Pending effects fall back to regular `mapBoth` + * behavior. + * + * **Example** (Mapping both channels eagerly when possible) + * + * ```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) + * const mapped = Effect.mapBothEager(success, { + * onFailure: (err: string) => `Failed: ${err}`, + * onSuccess: (n: number) => n * 2 + * }) // onSuccess applied eagerly + * + * const failure = Effect.fail("error") + * const mappedError = Effect.mapBothEager(failure, { + * 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 mapping + * @since 4.0.0 + */ +export const mapBothEager: { + ( + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): (self: Effect) => Effect + ( + self: Effect, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Effect +} = internal.mapBothEager + +/** + * Applies `flatMap` eagerly when an effect is already resolved. + * + * **When to use** + * + * Use when an already-resolved successful effect should bind immediately to the + * next effect while pending effects still use regular flat mapping. + * + * **Details** + * + * Success effects apply the flatMap function immediately. Failure effects pass + * through unchanged, and pending effects fall back to regular `flatMap` + * behavior. + * + * **Example** (Flat mapping eagerly when possible) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * // For resolved effects, the flatMap is applied immediately + * const resolved = Effect.succeed(5) + * 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), 0) + * const flatMappedPending = Effect.flatMapEager( + * pending, + * (n) => Effect.succeed(n * 2) + * ) // Uses regular flatMap + * + * await Effect.runPromise(Effect.all([flatMapped, flatMappedPending])) // => [10, 10] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const flatMapEager: { + (f: (a: A) => Effect): (self: Effect) => Effect + (self: Effect, f: (a: A) => Effect): Effect +} = internal.flatMapEager + +/** + * Applies `catch` eagerly when an effect is already resolved. + * + * **When to use** + * + * Use when an already-resolved failed effect should recover immediately while + * pending effects still use regular error recovery. + * + * **Details** + * + * Success effects pass through unchanged because there is no error to catch. + * Failure effects apply the catch function immediately, and pending effects + * fall back to regular `catch` behavior. + * + * **Example** (Catching failures eagerly when possible) + * + * ```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") + * const recovered = Effect.catchEager( + * failed, + * (err: string) => Effect.succeed(`recovered from: ${err}`) + * ) // Applied eagerly + * + * // For success effects, returns success as-is + * const success = Effect.succeed(42) + * const unchanged = Effect.catchEager( + * success, + * (err: string) => Effect.succeed(`recovered from: ${err}`) + * ) // Returns success as-is + * + * // For pending effects, behaves like regular catch + * 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 error handling + * @since 4.0.0 + */ +export const catchEager: { + ( + f: (e: NoInfer) => Effect + ): (self: Effect) => Effect + ( + self: Effect, + f: (e: NoInfer) => Effect + ): Effect +} = internal.catchEager + +/** + * Creates untraced function effects with eager evaluation optimization. + * + * **Details** + * + * Executes generator functions eagerly when all yielded effects are synchronous, + * stopping at the first async effect and deferring to normal execution. + * + * **Example** (Defining eager untraced effect functions) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const computation = Effect.fnUntracedEager(function*() { + * yield* Effect.succeed(1) + * yield* Effect.succeed(2) + * return "computed eagerly" + * }) + * + * const effect = computation() // Executed immediately if all effects are sync + * Effect.runSync(effect) // => "computed eagerly" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fnUntracedEager: fn.Untraced = internal.fnUntracedEager diff --git a/.repos/effect/packages/effect/src/Effectable.ts b/.repos/effect/packages/effect/src/Effectable.ts new file mode 100644 index 000000000..f4a114ad8 --- /dev/null +++ b/.repos/effect/packages/effect/src/Effectable.ts @@ -0,0 +1,68 @@ +/** + * Low-level helpers for making custom values behave like Effects. The module + * exposes a prototype builder and an abstract base class that let + * domain-specific values, such as service keys or configuration descriptions, + * be evaluated by Effect and yielded inside `Effect.gen`. + * + * @since 4.0.0 + */ +import type * as Effect from "./Effect.ts" +import type * as Fiber from "./Fiber.ts" +import { evaluate, makePrimitiveProto } from "./internal/core.ts" + +/** + * Create a low-level `Effect` prototype. + * + * **When to use** + * + * Use when you need to create a custom Effect-like value without extending a + * class, by providing a label and an evaluate function that receives the + * current fiber. + * + * **Details** + * + * 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 + * + * @category prototypes + * @since 4.0.0 + */ +export const Prototype = >(options: { + readonly label: string + readonly evaluate: ( + this: A, + fiber: Fiber.Fiber + ) => Effect.Effect, Effect.Error, Effect.Services> +}): Effect.Effect, Effect.Error, Effect.Services> => + makePrimitiveProto({ + op: options.label, + [evaluate]: options.evaluate + }) as any + +const Base: new() => Effect.Effect = (() => { + const Base = function() {} + Base.prototype = Prototype({ + label: "Effectable", + evaluate(_) { + return this + } + }) + return Base as any +})() + +/** + * Provides an abstract class that can be extended to create an `Effect`. + * + * **When to use** + * + * Use as an abstract base class to define custom classes whose instances behave + * as `Effect` values. + * + * @see {@link Prototype} for a lower-level primitive approach to creating custom Effect-like values without a class + * @category constructors + * @since 2.0.0 + */ +export abstract class Class extends Base { + abstract override: Effect.Effect +} diff --git a/.repos/effect/packages/effect/src/Encoding.ts b/.repos/effect/packages/effect/src/Encoding.ts new file mode 100644 index 000000000..5e6a77844 --- /dev/null +++ b/.repos/effect/packages/effect/src/Encoding.ts @@ -0,0 +1,799 @@ +/** + * Encoding and decoding helpers for Base64, Base64Url, and hexadecimal text. + * The functions convert between strings, UTF-8 text, and `Uint8Array` bytes. + * Encode functions return strings directly, while decode functions return + * `Result.Result` so invalid input is reported as an `EncodingError` instead of + * being thrown. + * + * @since 4.0.0 + */ +import * as Data from "./Data.ts" +import { hasProperty } from "./Predicate.ts" +import * as Result from "./Result.ts" + +// ------------------------------------------------------------------------------------- +// EncodingError +// ------------------------------------------------------------------------------------- + +/** + * Type identifier stored on `EncodingError` values and used by + * `isEncodingError`. + * + * **When to use** + * + * Use when implementing low-level `EncodingError`-compatible values that need + * to carry the runtime marker. + * + * **Details** + * + * This marker is part of the runtime representation of `EncodingError`. Prefer + * `isEncodingError` when narrowing unknown values. + * + * @see {@link isEncodingError} for the public guard that checks this marker + * + * @category type IDs + * @since 4.0.0 + */ +export const EncodingErrorTypeId = "~effect/encoding/EncodingError" as const + +/** + * Literal type of the `EncodingErrorTypeId` marker. + * + * **When to use** + * + * Use to type the marker carried by `EncodingError` values. + * + * @category type IDs + * @since 4.0.0 + */ +export type EncodingErrorTypeId = typeof EncodingErrorTypeId + +/** + * Error returned when an encoding or decoding operation cannot process its + * input. + * + * **When to use** + * + * Use when you need to handle or inspect failures from encoding or decoding + * operations. + * + * **Details** + * + * The error records whether the failure happened during encoding or decoding, + * which encoding module reported it, the original input, and a human-readable + * message. + * + * @see {@link isEncodingError} for checking whether a value is an EncodingError + * @category errors + * @since 4.0.0 + */ +export class EncodingError extends Data.TaggedError("EncodingError")<{ + kind: "Decode" | "Encode" + module: string + input: unknown + message: string +}> { + /** + * Marks this value as an encoding or decoding error for runtime guards. + * + * **When to use** + * + * Use to identify `EncodingError` instances through `isEncodingError`. + * + * @since 4.0.0 + */ + readonly [EncodingErrorTypeId]: EncodingErrorTypeId = EncodingErrorTypeId +} + +/** + * Checks whether a value is an `EncodingError`. + * + * **When to use** + * + * Use to narrow an unknown value before handling it as an `EncodingError` from + * encoding or decoding code. + * + * **Details** + * + * Returns `true` when the value carries the `EncodingErrorTypeId` marker and + * narrows the value to `EncodingError`. + * + * @see {@link EncodingError} for the structured error produced by failed + * encoding and decoding operations + * + * @category guards + * @since 4.0.0 + */ +export const isEncodingError = (u: unknown): u is EncodingError => hasProperty(u, EncodingErrorTypeId) + +// ------------------------------------------------------------------------------------- +// Base64 +// ------------------------------------------------------------------------------------- + +/** + * Encodes the given value into a base64 (RFC4648) `string`. + * + * **When to use** + * + * Use to encode text or bytes as a standard padded Base64 string for storage or + * transport. + * + * **Details** + * + * String inputs are encoded as UTF-8 bytes before Base64 encoding. + * `Uint8Array` inputs are encoded directly. The output uses the standard + * RFC4648 alphabet with `=` padding. + * + * **Example** (Encoding Base64 strings and bytes) + * + * ```ts import.meta.vitest + * import { Encoding } from "effect" + * + * // Encode a string + * Encoding.encodeBase64("hello") // => "aGVsbG8=" + * + * // Encode binary data + * const bytes = new Uint8Array([72, 101, 108, 108, 111]) + * Encoding.encodeBase64(bytes) // => "SGVsbG8=" + * ``` + * + * @see {@link decodeBase64} for decoding standard Base64 to bytes + * @see {@link decodeBase64String} for decoding standard Base64 to UTF-8 text + * @see {@link encodeBase64Url} for URL-safe unpadded Base64 output + * + * @category encoding + * @since 2.0.0 + */ +export const encodeBase64: (input: Uint8Array | string) => string = (input) => + typeof input === "string" ? base64EncodeUint8Array(encoder.encode(input)) : base64EncodeUint8Array(input) + +/** + * Decodes a base64 (RFC4648) string into bytes safely. + * + * **When to use** + * + * Use to decode a standard padded Base64 string into bytes without throwing on + * invalid input. + * + * **Details** + * + * Returns `Result.succeed` with a `Uint8Array` when decoding succeeds, or + * `Result.fail` with an `EncodingError` when the input is not valid base64. + * + * **Example** (Decoding Base64 bytes) + * + * ```ts import.meta.vitest + * import { Encoding, Result } from "effect" + * + * Encoding.decodeBase64("SGVsbG8=") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) + * ``` + * + * @category decoding + * @since 2.0.0 + */ +export const decodeBase64 = (str: string): Result.Result => { + const stripped = stripCrlf(str) + const length = stripped.length + if (length % 4 !== 0) { + return Result.fail( + new EncodingError({ + kind: "Decode", + module: "Base64", + input: stripped, + message: `Length must be a multiple of 4, but is ${length}` + }) + ) + } + + const index = stripped.indexOf("=") + if (index !== -1 && ((index < length - 2) || (index === length - 2 && stripped[length - 1] !== "="))) { + return Result.fail( + new EncodingError({ + kind: "Decode", + module: "Base64", + input: stripped, + message: `Found a '=' character, but it is not at the end` + }) + ) + } + + try { + const missingOctets = stripped.endsWith("==") ? 2 : stripped.endsWith("=") ? 1 : 0 + const result = new Uint8Array(3 * (length / 4) - missingOctets) + for (let i = 0, j = 0; i < length; i += 4, j += 3) { + const buffer = getBase64Code(stripped.charCodeAt(i)) << 18 | + getBase64Code(stripped.charCodeAt(i + 1)) << 12 | + getBase64Code(stripped.charCodeAt(i + 2)) << 6 | + getBase64Code(stripped.charCodeAt(i + 3)) + + result[j] = buffer >> 16 + result[j + 1] = (buffer >> 8) & 0xff + result[j + 2] = buffer & 0xff + } + + return Result.succeed(result) + } catch (e) { + return Result.fail( + new EncodingError({ + kind: "Decode", + module: "Base64", + input: stripped, + message: e instanceof Error ? e.message : "Invalid input" + }) + ) + } +} + +/** + * Decodes a base64 (RFC4648) string into a UTF-8 string safely. + * + * **When to use** + * + * Use to decode a standard padded Base64 string into UTF-8 text without + * throwing on invalid input. + * + * **Details** + * + * Returns `Result.succeed` with the decoded text when decoding succeeds, or + * `Result.fail` with an `EncodingError` when the input is not valid base64. + * + * **Example** (Decoding Base64 strings) + * + * ```ts import.meta.vitest + * import { Encoding, Result } from "effect" + * + * Encoding.decodeBase64String("aGVsbG8=") // => Result.succeed("hello") + * ``` + * + * @category decoding + * @since 2.0.0 + */ +export const decodeBase64String = (str: string) => Result.map(decodeBase64(str), (_) => decoder.decode(_)) + +// ------------------------------------------------------------------------------------- +// Base64Url +// ------------------------------------------------------------------------------------- + +/** + * Encodes the given value into a base64 (URL) `string`. + * + * **When to use** + * + * Use to encode text or bytes as an unpadded Base64Url string for contexts that + * require the URL-safe alphabet. + * + * **Details** + * + * String inputs are encoded as UTF-8 bytes before Base64Url encoding. + * `Uint8Array` inputs are encoded directly. The output removes `=` padding and + * replaces `+` with `-` and `/` with `_`. + * + * **Example** (Encoding URL-safe Base64) + * + * ```ts import.meta.vitest + * import { Encoding } from "effect" + * + * // URL-safe base64 encoding (uses - and _ instead of + and /) + * Encoding.encodeBase64Url("hello?") // => "aGVsbG8_" + * + * const bytes = new Uint8Array([72, 101, 108, 108, 111, 63]) + * Encoding.encodeBase64Url(bytes) // => "SGVsbG8_" + * ``` + * + * @see {@link decodeBase64Url} for decoding URL-safe Base64 to bytes + * @see {@link decodeBase64UrlString} for decoding URL-safe Base64 to UTF-8 text + * @see {@link encodeBase64} for standard padded Base64 output + * + * @category encoding + * @since 2.0.0 + */ +export const encodeBase64Url: (input: Uint8Array | string) => string = (input) => + typeof input === "string" ? base64UrlEncodeUint8Array(encoder.encode(input)) : base64UrlEncodeUint8Array(input) + +/** + * Decodes a URL-safe base64 string into bytes safely. + * + * **When to use** + * + * Use to decode padded or unpadded Base64Url text into bytes without throwing + * on invalid input. + * + * **Details** + * + * Returns `Result.succeed` with a `Uint8Array` when decoding succeeds, or + * `Result.fail` with an `EncodingError` when the input is not valid URL-safe + * base64. Both padded and unpadded URL-safe base64 forms are accepted when + * otherwise valid. + * + * **Example** (Decoding URL-safe Base64 bytes) + * + * ```ts import.meta.vitest + * import { Encoding, Result } from "effect" + * + * Encoding.decodeBase64Url("SGVsbG8_") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111, 63])) + * ``` + * + * @category decoding + * @since 2.0.0 + */ +export const decodeBase64Url = (str: string): Result.Result => { + const stripped = stripCrlf(str) + const length = stripped.length + if (length % 4 === 1) { + return Result.fail( + new EncodingError({ + module: "Base64Url", + kind: "Decode", + input: stripped, + message: `Length should be a multiple of 4, but is ${length}` + }) + ) + } + + if (!/^[-_A-Z0-9]*?={0,2}$/i.test(stripped)) { + return Result.fail( + new EncodingError({ + module: "Base64Url", + kind: "Decode", + input: stripped, + message: "Invalid input" + }) + ) + } + + // Some variants allow or require omitting the padding '=' signs + let sanitized = length % 4 === 2 ? `${stripped}==` : length % 4 === 3 ? `${stripped}=` : stripped + sanitized = sanitized.replace(/-/g, "+").replace(/_/g, "/") + + return decodeBase64(sanitized) +} + +/** + * Decodes a URL-safe base64 string into a UTF-8 string safely. + * + * **When to use** + * + * Use to decode padded or unpadded Base64Url text into UTF-8 text without + * throwing on invalid input. + * + * **Details** + * + * Returns `Result.succeed` with the decoded text when decoding succeeds, or + * `Result.fail` with an `EncodingError` when the input is not valid URL-safe + * base64. + * + * **Example** (Decoding URL-safe Base64 strings) + * + * ```ts import.meta.vitest + * import { Encoding, Result } from "effect" + * + * Encoding.decodeBase64UrlString("aGVsbG8_") // => Result.succeed("hello?") + * ``` + * + * @category decoding + * @since 2.0.0 + */ +export const decodeBase64UrlString = (str: string) => Result.map(decodeBase64Url(str), (_) => decoder.decode(_)) + +// ------------------------------------------------------------------------------------- +// Hex +// ------------------------------------------------------------------------------------- + +/** + * Encodes the given value into a hex `string`. + * + * **When to use** + * + * Use to encode text or bytes as lowercase hexadecimal text. + * + * **Example** (Encoding hex strings and bytes) + * + * ```ts import.meta.vitest + * import { Encoding } from "effect" + * + * // Encode a string to hex + * Encoding.encodeHex("hello") // => "68656c6c6f" + * + * // Encode binary data to hex + * const bytes = new Uint8Array([72, 101, 108, 108, 111]) + * Encoding.encodeHex(bytes) // => "48656c6c6f" + * ``` + * + * @category encoding + * @since 2.0.0 + */ +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. + * + * `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 => { + let result = "" + for (let i = length >>> 3; i > 0; i--) { + const word = (Math.random() * 0x100000000) >>> 0 + result += byteToHex[word >>> 24] + byteToHex[(word >>> 16) & 0xff] + byteToHex[(word >>> 8) & 0xff] + + byteToHex[word & 0xff] + } + return result +} + +/** + * Decodes a hexadecimal string into bytes safely. + * + * **When to use** + * + * Use to decode hexadecimal text into bytes without throwing on invalid input. + * + * **Details** + * + * Returns `Result.succeed` with a `Uint8Array` when decoding succeeds, or + * `Result.fail` with an `EncodingError` when the input has an odd length or + * contains invalid hex characters. + * + * **Example** (Decoding hex bytes) + * + * ```ts import.meta.vitest + * import { Encoding, Result } from "effect" + * + * Encoding.decodeHex("48656c6c6f") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) + * ``` + * + * @category decoding + * @since 2.0.0 + */ +export const decodeHex = (str: string): Result.Result => { + const bytes = new TextEncoder().encode(str) + if (bytes.length % 2 !== 0) { + return Result.fail( + new EncodingError({ + module: "Hex", + kind: "Decode", + input: str, + message: `Length must be a multiple of 2, but is ${bytes.length}` + }) + ) + } + + try { + const length = bytes.length / 2 + const result = new Uint8Array(length) + for (let i = 0; i < length; i++) { + const a = fromHexChar(bytes[i * 2]) + const b = fromHexChar(bytes[i * 2 + 1]) + result[i] = (a << 4) | b + } + + return Result.succeed(result) + } catch (e) { + return Result.fail( + new EncodingError({ + module: "Hex", + kind: "Decode", + input: str, + message: e instanceof Error ? e.message : "Invalid input" + }) + ) + } +} + +/** + * Decodes a hexadecimal string into a UTF-8 string safely. + * + * **When to use** + * + * Use to decode hexadecimal text into UTF-8 text without throwing on invalid + * input. + * + * **Details** + * + * Returns `Result.succeed` with the decoded text when decoding succeeds, or + * `Result.fail` with an `EncodingError` when the input is not valid hex. + * + * **Example** (Decoding hex strings) + * + * ```ts import.meta.vitest + * import { Encoding, Result } from "effect" + * + * Encoding.decodeHexString("68656c6c6f") // => Result.succeed("hello") + * ``` + * + * @category decoding + * @since 2.0.0 + */ +export const decodeHexString = (str: string) => Result.map(decodeHex(str), (_) => decoder.decode(_)) + +// ------------------------------------------------------------------------------------- +// internals +// ------------------------------------------------------------------------------------- + +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +const stripCrlf = (str: string) => str.replace(/[\n\r]/g, "") + +// Base64 internals + +const base64EncodeUint8Array = (bytes: Uint8Array) => { + const length = bytes.length + + let result = "" + let i: number + + for (i = 2; i < length; i += 3) { + result += base64abc[bytes[i - 2] >> 2] + result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)] + result += base64abc[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)] + result += base64abc[bytes[i] & 0x3f] + } + + if (i === length + 1) { + result += base64abc[bytes[i - 2] >> 2] + result += base64abc[(bytes[i - 2] & 0x03) << 4] + result += "==" + } + + if (i === length) { + result += base64abc[bytes[i - 2] >> 2] + result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)] + result += base64abc[(bytes[i - 1] & 0x0f) << 2] + result += "=" + } + + return result +} + +function getBase64Code(charCode: number) { + if (charCode >= base64codes.length) { + throw new TypeError(`Invalid character ${String.fromCharCode(charCode)}`) + } + + const code = base64codes[charCode] + if (code === 255) { + throw new TypeError(`Invalid character ${String.fromCharCode(charCode)}`) + } + + return code +} + +const base64abc = [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "+", + "/" +] + +const base64codes = [ + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 62, + 255, + 255, + 255, + 63, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 255, + 255, + 255, + 0, + 255, + 255, + 255, + 0, + 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, + 255, + 255, + 255, + 255, + 255, + 255, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51 +] + +// Base64Url internals + +const base64UrlEncodeUint8Array = (data: Uint8Array) => + base64EncodeUint8Array(data).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") + +// Hex internals + +const byteToHex: Array = [] +for (let i = 0; i < 256; i++) { + byteToHex.push(i.toString(16).padStart(2, "0")) +} + +const hexEncodeUint8Array = (bytes: Uint8Array): string => { + let result = "" + for (let i = 0; i < bytes.length; i++) { + result += byteToHex[bytes[i]] + } + return result +} + +const fromHexChar = (byte: number) => { + if (48 <= byte && byte <= 57) { + return byte - 48 + } + + if (97 <= byte && byte <= 102) { + return byte - 97 + 10 + } + + if (65 <= byte && byte <= 70) { + return byte - 65 + 10 + } + + throw new TypeError("Invalid input") +} diff --git a/.repos/effect/packages/effect/src/Equal.ts b/.repos/effect/packages/effect/src/Equal.ts new file mode 100644 index 000000000..0b45a6ad3 --- /dev/null +++ b/.repos/effect/packages/effect/src/Equal.ts @@ -0,0 +1,568 @@ +/** + * Compares values with Effect's structural equality rules. + * + * `equals` compares primitives, arrays, plain objects, maps, sets, dates, + * regular expressions, and values that implement the `Equal` interface. This + * module also defines the equality symbol, guards, adapters, map and set + * comparison builders, and helpers for marking objects that should compare only + * by reference. + * + * @since 2.0.0 + */ +import type { Equivalence } from "./Equivalence.ts" +import * as Hash from "./Hash.ts" +import { byReferenceInstances, getAllObjectKeys } from "./internal/equal.ts" +import { hasProperty } from "./Predicate.ts" + +/** + * Defines the unique string identifier for the `Equal` interface. + * + * **When to use** + * + * Use when you implement custom equality and need the computed property key for + * the equality method. + * + * **Details** + * + * This is a pure constant with no allocation or side effects. + * + * **Example** (Implementing Equal on a class) + * + * ```ts import.meta.vitest + * import { Equal, Hash } from "effect" + * + * class UserId implements Equal.Equal { + * constructor(readonly id: string) {} + * + * [Equal.symbol](that: Equal.Equal): boolean { + * return that instanceof UserId && this.id === that.id + * } + * + * [Hash.symbol](): number { + * 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 + * @see {@link isEqual} — type guard for `Equal` implementors + * @category symbols + * @since 2.0.0 + */ +export const symbol = "~effect/interfaces/Equal" + +/** + * The interface for types that define their own equality logic. + * + * **When to use** + * + * Use when you need value-based equality for a class (e.g. domain IDs, + * coordinates, money values). + * - When your type will be stored in `HashMap` or `HashSet`. + * - When the default structural comparison is too broad or too narrow for + * your type. + * + * **Details** + * + * Any object that implements both `[Equal.symbol]` (equality) and + * `[Hash.symbol]` (hashing) is recognized by {@link equals} and by hash-based + * collections such as `HashMap` and `HashSet`. + * + * - Extends `Hash.Hash`, so implementors **must** also provide `[Hash.symbol]`. + * - The hash contract: if `a[Equal.symbol](b)` returns `true`, then + * `Hash.hash(a)` must equal `Hash.hash(b)`. + * - {@link equals} delegates to this method when both operands implement it. + * If only one operand implements `Equal`, they are considered unequal. + * + * **Example** (Comparing coordinates by value) + * + * ```ts import.meta.vitest + * import { Equal, Hash } from "effect" + * + * class Coordinate implements Equal.Equal { + * constructor(readonly x: number, readonly y: number) {} + * + * [Equal.symbol](that: Equal.Equal): boolean { + * return that instanceof Coordinate && + * this.x === that.x && + * this.y === that.y + * } + * + * [Hash.symbol](): number { + * return Hash.string(`${this.x},${this.y}`) + * } + * } + * + * 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 + * @see {@link equals} — the main comparison function + * @see {@link isEqual} — type guard for `Equal` implementors + * @category models + * @since 2.0.0 + */ +export interface Equal extends Hash.Hash { + [symbol](that: Equal): boolean +} + +/** + * Checks whether two values are deeply structurally equal. + * + * **When to use** + * + * Use when you need Effect's default structural equality check. + * + * **Details** + * + * Returns a `boolean` and never throws. Primitives are compared by value, and + * `NaN` equals `NaN`. Objects implementing `Equal` delegate to their + * `[Equal.symbol]` method; if only one operand implements `Equal`, the result + * is `false`. + * + * Dates compare by ISO string, RegExps compare by string representation, + * arrays compare element-by-element, Maps and Sets compare entries + * order-independently, and plain objects compare enumerable keys recursively. + * Functions without an `Equal` implementation compare by reference. Circular + * references are handled when both structures are circular at the same depth. + * + * Hash values are checked first as a fast-path rejection. The function also + * supports dual data-last usage: call it with one argument to get a curried + * predicate. + * + * **Gotchas** + * + * - Results are cached per object pair in a WeakMap. **Objects must not be + * mutated after their first comparison.** + * - Map and Set comparisons are O(n²) in size. + * + * **Example** (Comparing values) + * + * ```ts import.meta.vitest + * import { Equal } from "effect" + * + * Equal.equals(1, 1) // => true + * Equal.equals(NaN, NaN) // => true + * Equal.equals("a", "b") // => false + * + * Equal.equals({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true + * Equal.equals([1, [2, 3]], [1, [2, 3]]) // => true + * + * Equal.equals(new Date("2024-01-01"), new Date("2024-01-01")) // => true + * + * const m1 = new Map([["a", 1], ["b", 2]]) + * const m2 = new Map([["b", 2], ["a", 1]]) + * Equal.equals(m1, m2) // => true + * + * const is5 = Equal.equals(5) + * is5(5) // => true + * is5(3) // => false + * ``` + * + * @see {@link Equal} — the interface for custom equality + * @see {@link isEqual} — check whether a value implements `Equal` + * @see {@link asEquivalence} — wrap `equals` as an `Equivalence` + * @category equality + * @since 2.0.0 + */ +export function equals(that: B): (self: A) => boolean +export function equals(self: A, that: B): boolean +export function equals(): any { + if (arguments.length === 1) { + return (self: unknown) => compareBoth(self, arguments[0]) + } + return compareBoth(arguments[0], arguments[1]) +} + +function compareBoth(self: unknown, that: unknown): boolean { + if (self === that) return true + if (self == null || that == null) return false + const selfType = typeof self + if (selfType !== typeof that) { + return false + } + // Special case for NaN: NaN should be considered equal to NaN + if (selfType === "number" && self !== self && that !== that) { + return true + } + if (selfType !== "object" && selfType !== "function") { + return false + } + + if (byReferenceInstances.has(self) || byReferenceInstances.has(that)) { + return false + } + + // For objects and functions, use cached comparison + return withCache(self, that, compareObjects) +} + +/** Helper to run comparison with proper visited tracking */ +function withVisitedTracking( + self: object, + that: object, + fn: () => boolean +): boolean { + const hasLeft = visitedLeft.has(self) + const hasRight = visitedRight.has(that) + // Check for circular references before adding + if (hasLeft && hasRight) { + return true // Both are circular at the same level + } + if (hasLeft || hasRight) { + return false // Only one is circular + } + visitedLeft.add(self) + visitedRight.add(that) + const result = fn() + visitedLeft.delete(self) + visitedRight.delete(that) + return result +} + +const visitedLeft = new WeakSet() +const visitedRight = new WeakSet() + +/** Helper to perform cached object comparison */ +function compareObjects(self: object, that: object): boolean { + if (Hash.hash(self) !== Hash.hash(that)) { + return false + } else if (self instanceof Date) { + if (!(that instanceof Date)) return false + 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() + } + const selfIsEqual = isEqual(self) + const thatIsEqual = isEqual(that) + if (selfIsEqual !== thatIsEqual) return false + const bothEquals = selfIsEqual && thatIsEqual + if (typeof self === "function" && !bothEquals) { + return false + } + return withVisitedTracking(self, that, () => { + if (bothEquals) { + return (self as any)[symbol](that) + } else if (Array.isArray(self)) { + if (!Array.isArray(that) || self.length !== that.length) { + return false + } + return compareArrays(self, that) + } else if (ArrayBuffer.isView(self)) { + 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) { + return false + } + return compareMaps(self, that) + } else if (self instanceof Set) { + if (!(that instanceof Set) || self.size !== that.size) { + return false + } + return compareSets(self, that) + } + return compareRecords(self as any, that as any) + }) +} + +function withCache(self: object, that: object, f: (a: any, b: any) => boolean): boolean { + // Check cache first + let selfMap = equalityCache.get(self) + if (!selfMap) { + selfMap = new WeakMap() + equalityCache.set(self, selfMap) + } else if (selfMap.has(that)) { + return selfMap.get(that)! + } + + // Perform the comparison + const result = f(self, that) + + // Cache the result bidirectionally + selfMap.set(that, result) + + let thatMap = equalityCache.get(that) + if (!thatMap) { + thatMap = new WeakMap() + equalityCache.set(that, thatMap) + } + thatMap.set(self, result) + + return result +} + +const equalityCache = new WeakMap>() + +function compareArrays(self: Array, that: Array): boolean { + for (let i = 0; i < self.length; i++) { + if (!compareBoth(self[i], that[i])) { + return false + } + } + + return true +} + +function compareTypedArrays(self: Uint8Array, that: Uint8Array): boolean { + if (self.length !== that.length) { + return false + } + for (let i = 0; i < self.length; i++) { + if (self[i] !== that[i]) { + return false + } + } + return true +} + +function compareRecords( + self: Record, + that: Record +): boolean { + const selfKeys = getAllObjectKeys(self) + const thatKeys = getAllObjectKeys(that) + + if (selfKeys.size !== thatKeys.size) { + return false + } + + for (const key of selfKeys) { + if (!(thatKeys.has(key)) || !compareBoth(self[key], that[key])) { + return false + } + } + + return true +} + +/** @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 (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 + } + } + if (!found) { + return false + } + } + + return true + } +} + +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 (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 + } + } + if (!found) { + return false + } + } + + return true + } +} + +const compareSets = makeCompareSet(compareBoth) + +/** + * Checks whether a value implements the {@link Equal} interface. + * + * **When to use** + * + * Use when you need generic utility code to distinguish `Equal` implementors + * from plain values before calling `[Equal.symbol]` directly. + * + * **Details** + * + * - Pure function, no side effects. + * - Returns `true` if and only if `u` has a property keyed by + * {@link symbol}. + * - Acts as a TypeScript type guard, narrowing the input to {@link Equal}. + * + * **Example** (Checking Equal values) + * + * ```ts import.meta.vitest + * import { Equal, Hash } from "effect" + * + * class Token implements Equal.Equal { + * constructor(readonly value: string) {} + * [Equal.symbol](that: Equal.Equal): boolean { + * return that instanceof Token && this.value === that.value + * } + * [Hash.symbol](): number { + * return Hash.string(this.value) + * } + * } + * + * Equal.isEqual(new Token("abc")) // => true + * Equal.isEqual({ x: 1 }) // => false + * Equal.isEqual(42) // => false + * ``` + * + * @see {@link Equal} — the interface being checked + * @see {@link symbol} — the property key that signals `Equal` support + * @category guards + * @since 2.0.0 + */ +export const isEqual = (u: unknown): u is Equal => hasProperty(u, symbol) + +/** + * Wraps {@link equals} as an `Equivalence`. + * + * **When to use** + * + * Use when you want to pass `Equal.equals` to APIs that require an + * `Equivalence`. + * + * **Details** + * + * - Returns a function `(a: A, b: A) => boolean` that delegates to + * {@link equals}. + * - Pure; allocates a thin wrapper on each call. + * + * **Example** (Deduplicating with Equal semantics) + * + * ```ts import.meta.vitest + * import { Array, Equal } from "effect" + * + * Array.dedupeWith([1, 2, 2, 3, 1], Equal.asEquivalence()) // => [1, 2, 3] + * ``` + * + * @see {@link equals} — the underlying comparison function + * @category instances + * @since 4.0.0 + */ +export const asEquivalence: () => Equivalence = () => equals + +/** + * Creates a proxy that uses reference equality instead of structural equality. + * + * **When to use** + * + * Use when you need to compare a plain object or array by identity without + * mutating the original value. + * + * **Details** + * + * - Returns a `Proxy` wrapping `obj`. The proxy reads through to the + * original, so property access is unchanged. + * - The proxy is registered in an internal WeakSet; {@link equals} returns + * `false` for any pair where at least one operand is in that set (unless + * they are the same reference). + * - Each call creates a **new** proxy, so `byReference(x) !== byReference(x)`. + * - Does **not** mutate the original object (unlike {@link byReferenceUnsafe}). + * + * **Example** (Opting out of structural equality) + * + * ```ts import.meta.vitest + * import { Equal } from "effect" + * + * const a = { x: 1 } + * const b = { x: 1 } + * + * Equal.equals(a, b) // => true + * + * const aRef = Equal.byReference(a) + * Equal.equals(aRef, b) // => false + * Equal.equals(aRef, aRef) // => true + * aRef.x // => 1 + * ``` + * + * @see {@link byReferenceUnsafe} — same effect without a proxy (mutates the + * original) + * @see {@link equals} — the comparison function affected by this opt-out + * @category equality + * @since 4.0.0 + */ +export const byReference = (obj: T): T => byReferenceUnsafe(new Proxy(obj, {})) + +/** + * Marks an object permanently to use reference equality, without creating a proxy. + * + * **When to use** + * + * Use when you need reference equality without proxy allocation and accept + * permanently marking the original object for reference-only equality. + * + * **Details** + * + * - Adds `obj` to an internal WeakSet. From that point on, {@link equals} + * treats it as reference-only. + * - Returns the **same** object (not a copy or proxy), so + * `byReferenceUnsafe(x) === x`. + * - Does **not** affect the object's prototype, properties, or behavior + * beyond equality checks. + * + * **Gotchas** + * + * The marking is irreversible for the lifetime of the object. + * + * **Example** (Marking an object for reference equality) + * + * ```ts import.meta.vitest + * import { Equal } from "effect" + * + * const obj1 = { a: 1, b: 2 } + * const obj2 = { a: 1, b: 2 } + * + * const marked = Equal.byReferenceUnsafe(obj1) + * + * Equal.equals(obj1, obj2) // => false + * Equal.equals(obj1, obj1) // => true + * marked === obj1 // => true + * ``` + * + * @see {@link byReference} — safer alternative that creates a proxy + * @see {@link equals} — the comparison function affected by this opt-out + * @category unsafe + * @since 4.0.0 + */ +export const byReferenceUnsafe = (obj: T): T => { + byReferenceInstances.add(obj) + return obj +} diff --git a/.repos/effect/packages/effect/src/Equivalence.ts b/.repos/effect/packages/effect/src/Equivalence.ts new file mode 100644 index 000000000..e1fe8ce5d --- /dev/null +++ b/.repos/effect/packages/effect/src/Equivalence.ts @@ -0,0 +1,868 @@ +/** + * Defines reusable equality functions for values of the same type. + * + * An `Equivalence` returns `true` when two `A` values should be treated as + * the same for a particular purpose. This module includes strict equality + * instances for primitive types, constructors for custom comparisons, and + * helpers for tuples, arrays, structs, records, dates, and values compared + * through a derived field. + * + * @since 2.0.0 + */ +import { dual } from "./Function.ts" +import type { TypeLambda } from "./HKT.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Represents an equivalence relation over type `A`. + * + * **When to use** + * + * Use as a type annotation when you accept or return an equivalence function. + * + * **Details** + * + * - Returns `boolean`: `true` if values are equivalent, `false` otherwise + * - Must satisfy reflexive, symmetric, and transitive properties + * + * **Example** (Defining simple number equivalence) + * + * ```ts import.meta.vitest + * import type { Equivalence } from "effect" + * + * const numberEq: Equivalence.Equivalence = (a, b) => a === b + * + * numberEq(1, 1) // => true + * numberEq(1, 2) // => false + * ``` + * + * **Example** (Defining custom object equivalence) + * + * ```ts import.meta.vitest + * import type { Equivalence } from "effect" + * + * interface Point { + * x: number + * y: number + * } + * + * const pointEq: Equivalence.Equivalence = (a, b) => + * a.x === b.x && a.y === b.y + * + * pointEq({ x: 1, y: 2 }, { x: 1, y: 2 }) // => true + * ``` + * + * @see {@link make} + * @see {@link strictEqual} + * @category models + * @since 2.0.0 + */ +export type Equivalence = (self: A, that: A) => boolean + +/** + * Type lambda for `Equivalence`, used for higher-kinded type operations. + * + * **When to use** + * + * Use when you need to abstract over `Equivalence` in higher-kinded type code. + * + * **Details** + * + * - Enables `Equivalence` to work with the Effect type system's HKT infrastructure + * - Used internally for type-level computations and generic abstractions + * + * **Example** (Type-level usage) + * + * ```ts import.meta.vitest + * import type { Equivalence, HKT } from "effect" + * + * // Used internally for type-level computations + * type NumberEquivalence = HKT.Kind< + * Equivalence.EquivalenceTypeLambda, + * never, + * never, + * never, + * number + * > + * // Equivalent to: Equivalence.Equivalence + * ``` + * + * @see {@link Equivalence} + * @see {@link TypeLambda} + * @category utility types + * @since 2.0.0 + */ +export interface EquivalenceTypeLambda extends TypeLambda { + readonly type: Equivalence +} + +/** + * Creates a custom equivalence relation with an optimized reference equality check. + * + * **When to use** + * + * Use when you need an equality rule that the built-in instances and input + * mapping helpers cannot express, and you can provide a law-abiding comparison. + * + * **Details** + * + * The returned equivalence first checks reference equality (`===`) for + * performance. If the values are not the same reference, it falls back to the + * provided equivalence function, which must satisfy reflexive, symmetric, and + * transitive properties. + * + * **Example** (Case-insensitive string equivalence) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const caseInsensitive = Equivalence.make((a, b) => + * a.toLowerCase() === b.toLowerCase() + * ) + * + * caseInsensitive("Hello", "HELLO") // => true + * caseInsensitive("foo", "bar") // => false + * + * // Same reference optimization + * const str = "test" + * caseInsensitive(str, str) // => true + * ``` + * + * **Example** (Comparing numbers with tolerance) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const tolerance = Equivalence.make((a, b) => Math.abs(a - b) < 0.0001) + * + * tolerance(1.0, 1.001) // => false + * tolerance(1.0, 1.00001) // => true + * ``` + * + * @see {@link strictEqual} + * @see {@link mapInput} + * @category constructors + * @since 2.0.0 + */ +export const make = (isEquivalent: (self: A, that: A) => boolean): Equivalence => (self: A, that: A): boolean => + self === that || isEquivalent(self, that) + +const isStrictEquivalent = (x: unknown, y: unknown) => x === y + +/** + * Creates an equivalence relation that uses strict equality (`===`) to compare values. + * + * **When to use** + * + * Use when you need strict equality (`===`) as the comparison. + * + * **Details** + * + * Uses JavaScript's strict equality operator (`===`). Primitives compare by + * value. Objects compare by reference, so only the same object instance is + * equivalent. Use this as a building block for more complex equivalences via + * `mapInput` or `combine`. + * + * **Gotchas** + * + * `NaN !== NaN`, so `NaN` values are never considered equivalent. + * + * **Example** (Comparing primitive types) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const strictEq = Equivalence.strictEqual() + * + * strictEq(1, 1) // => true + * strictEq(1, 2) // => false + * strictEq(NaN, NaN) // => false + * ``` + * + * **Example** (Comparing objects by reference) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const obj = { value: 42 } + * const strictObjEq = Equivalence.strictEqual() + * + * strictObjEq(obj, obj) // => true + * strictObjEq(obj, { value: 42 }) // => false + * ``` + * + * @see {@link make} + * @see `Equal` for structural equality + * @category constructors + * @since 4.0.0 + */ +export const strictEqual: () => Equivalence = () => isStrictEquivalent + +/** + * Equivalence instance for strings using strict equality (`===`). + * + * **When to use** + * + * Use when you need to supply case-sensitive string equality. + * + * **Example** (Comparing strings) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * Equivalence.String("hello", "hello") // => true + * Equivalence.String("hello", "world") // => false + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const String: Equivalence = isStrictEquivalent + +/** + * Equivalence instance for numbers. + * + * **When to use** + * + * Use when you need numeric equality that treats NaN as equal to itself. + * + * **Example** (Comparing numbers) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * Equivalence.Number(1, 1) // => true + * Equivalence.Number(1, 2) // => false + * Equivalence.Number(NaN, NaN) // => true + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const Number: Equivalence = make((self, that) => + globalThis.Number.isNaN(self) && globalThis.Number.isNaN(that) +) + +/** + * Equivalence instance for booleans using strict equality (`===`). + * + * **When to use** + * + * Use when you need to supply boolean equality. + * + * **Example** (Comparing booleans) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * Equivalence.Boolean(true, true) // => true + * Equivalence.Boolean(true, false) // => false + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const Boolean: Equivalence = isStrictEquivalent + +/** + * Equivalence instance for bigints using strict equality (`===`). + * + * **When to use** + * + * Use when you need to supply bigint equality. + * + * **Example** (Comparing bigints) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * Equivalence.BigInt(1n, 1n) // => true + * Equivalence.BigInt(1n, 2n) // => false + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const BigInt: Equivalence = isStrictEquivalent + +/** + * Combines two equivalence relations using logical AND. + * + * **When to use** + * + * Use when you need to combine exactly two equivalences with AND semantics. + * + * **Details** + * + * Returns `true` only if both equivalences return `true`. The comparison + * short-circuits when the first equivalence returns `false`. The result is also + * an equivalence that satisfies reflexive, symmetric, and transitive + * properties. + * + * **Example** (Combining name and age equivalences) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * interface Person { + * name: string + * age: number + * } + * + * const nameEquivalence = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (p: Person) => p.name + * ) + * + * const ageEquivalence = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (p: Person) => p.age + * ) + * + * const personEquivalence = Equivalence.combine(nameEquivalence, ageEquivalence) + * + * const person1 = { name: "Alice", age: 30 } + * const person2 = { name: "Alice", age: 30 } + * const person3 = { name: "Alice", age: 31 } + * + * personEquivalence(person1, person2) // => true + * personEquivalence(person1, person3) // => false + * ``` + * + * @see {@link combineAll} + * @see {@link mapInput} + * @category combining + * @since 2.0.0 + */ +export const combine: { + (that: Equivalence): (self: Equivalence) => Equivalence + (self: Equivalence, that: Equivalence): Equivalence +} = dual(2, (self: Equivalence, that: Equivalence): Equivalence => make((x, y) => self(x, y) && that(x, y))) + +/** + * Combines multiple equivalence relations into a single equivalence using logical AND. + * + * **When to use** + * + * Use when you need to combine many `Equivalence` instances from an iterable. + * + * **Details** + * + * Returns `true` only if all equivalences in the collection return `true`. The + * comparison stops at the first equivalence that returns `false`. Empty + * collections return an equivalence that always returns `true`. The result is + * also an equivalence that satisfies reflexive, symmetric, and transitive + * properties. + * + * **Example** (Combining multiple field equivalences) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * interface Point3D { + * x: number + * y: number + * z: number + * } + * + * const xEq = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (p: Point3D) => p.x + * ) + * const yEq = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (p: Point3D) => p.y + * ) + * const zEq = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (p: Point3D) => p.z + * ) + * + * const point3DEq = Equivalence.combineAll([xEq, yEq, zEq]) + * + * const point1 = { x: 1, y: 2, z: 3 } + * const point2 = { x: 1, y: 2, z: 3 } + * const point3 = { x: 1, y: 2, z: 4 } + * + * point3DEq(point1, point2) // => true + * point3DEq(point1, point3) // => false + * ``` + * + * **Example** (Handling empty collections) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * // Empty collection always returns true + * const alwaysEq = Equivalence.combineAll([]) + * alwaysEq("anything", "else") // => true + * ``` + * + * @see {@link combine} + * @see {@link mapInput} + * @category combining + * @since 2.0.0 + */ +export const combineAll = (collection: Iterable>): Equivalence => + make((x, y) => { + for (const equivalence of collection) { + if (!equivalence(x, y)) { + return false + } + } + return true + }) + +/** + * Transforms an equivalence relation by mapping the input values before comparison. + * + * **When to use** + * + * Use when you need an equivalence for one type by comparing a derived value. + * + * **Details** + * + * - Applies the transformation function to both values before comparing + * - The transformation function should be pure and have no side effects + * - The resulting equivalence compares the transformed values using the provided equivalence + * - The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties + * - Useful for comparing by one property or normalizing values before + * comparison, such as case-insensitive strings + * + * **Example** (Deriving equivalence from an object property) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * interface User { + * id: number + * name: string + * email: string + * } + * + * // Create equivalence based on user ID only + * const userByIdEq = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (user: User) => user.id + * ) + * + * const user1 = { id: 1, name: "Alice", email: "alice@example.com" } + * const user2 = { id: 1, name: "Alice Smith", email: "alice.smith@example.com" } + * const user3 = { id: 2, name: "Bob", email: "bob@example.com" } + * + * userByIdEq(user1, user2) // => true + * userByIdEq(user1, user3) // => false + * ``` + * + * **Example** (Case-insensitive string equivalence) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const caseInsensitiveEq = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (s: string) => s.toLowerCase() + * ) + * + * caseInsensitiveEq("Hello", "HELLO") // => true + * caseInsensitiveEq("Hello", "World") // => false + * ``` + * + * @see {@link combine} + * @see {@link Struct} + * @category mapping + * @since 2.0.0 + */ +export const mapInput: { + (f: (b: B) => A): (self: Equivalence) => Equivalence + (self: Equivalence, f: (b: B) => A): Equivalence +} = dual( + 2, + (self: Equivalence, f: (b: B) => A): Equivalence => make((x, y) => self(f(x), f(y))) +) + +/** + * Creates an equivalence for tuples with heterogeneous element types. + * + * **When to use** + * + * Use when you need an `Equivalence` for fixed-length tuples with per-position + * equivalences. + * + * **Details** + * + * Tuples must have the same length; different lengths are never equivalent. + * Each equivalence is applied to the corresponding element position. The result + * returns `true` only if all elements are equivalent according to their + * respective equivalences, and it also satisfies reflexive, symmetric, and + * transitive properties. + * + * **Example** (Comparing homogeneous tuples) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const stringTupleEq = Equivalence.Tuple([ + * Equivalence.strictEqual(), + * Equivalence.strictEqual(), + * Equivalence.strictEqual() + * ]) + * + * const tuple1 = ["hello", "world", "test"] as const + * const tuple2 = ["hello", "world", "test"] as const + * const tuple3 = ["hello", "world", "different"] as const + * + * stringTupleEq(tuple1, tuple2) // => true + * stringTupleEq(tuple1, tuple3) // => false + * ``` + * + * **Example** (Comparing tuples with custom equivalences) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const caseInsensitive = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (s: string) => s.toLowerCase() + * ) + * + * const customTupleEq = Equivalence.Tuple([ + * caseInsensitive, + * caseInsensitive, + * caseInsensitive + * ]) + * + * customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"]) // => true + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export function Tuple>>( + elements: Elements +): Equivalence<{ readonly [I in keyof Elements]: [Elements[I]] extends [Equivalence] ? A : never }> { + return make((self, that) => { + if (self.length !== that.length) { + return false + } + for (let i = 0; i < self.length; i++) { + if (!elements[i](self[i], that[i])) { + return false + } + } + return true + }) +} + +/** + * @since 4.0.0 + */ +function Array_(item: Equivalence): Equivalence> { + return make((self, that) => { + if (self.length !== that.length) return false + + for (let i = 0; i < self.length; i++) { + if (!item(self[i], that[i])) return false + } + + return true + }) +} +export { + /** + * Creates an equivalence for arrays where all elements are compared using the same equivalence. + * + * **When to use** + * + * Use when you need to compare arrays with one equivalence for every element. + * + * **Details** + * + * - Requires arrays to have the same length; different lengths are never equivalent + * - Compares elements positionally, such as index `0` with index `0` + * - Returns `true` only if all corresponding elements are equivalent + * - Empty arrays are considered equivalent + * - The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties + * + * **Example** (Comparing number arrays) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const numberArrayEq = Equivalence.Array(Equivalence.strictEqual()) + * + * 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 import.meta.vitest + * import { Equivalence } from "effect" + * + * const caseInsensitive = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (s: string) => s.toLowerCase() + * ) + * const stringArrayEq = Equivalence.Array(caseInsensitive) + * + * stringArrayEq(["Hello", "World"], ["HELLO", "WORLD"]) // => true + * stringArrayEq(["Hello"], ["Hi"]) // => false + * stringArrayEq([], []) // => true + * ``` + * + * @see {@link Tuple} + * @see {@link Record} + * @category combinators + * @since 4.0.0 + */ + Array_ as Array +} + +/** + * Creates an equivalence for objects by comparing their properties using provided equivalences. + * + * **When to use** + * + * Use when you need an `Equivalence` for objects with known, fixed property + * names. + * + * **Details** + * + * Compares only the properties specified in the struct definition; other + * properties are ignored. String and symbol keys are supported via + * `Reflect.ownKeys`. The result returns `true` only if all specified properties + * are equivalent according to their equivalences, and it also satisfies + * reflexive, symmetric, and transitive properties. + * + * **Example** (Comparing structs with different equivalences per field) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * interface Person { + * name: string + * age: number + * email: string + * } + * + * const caseInsensitive = Equivalence.mapInput( + * Equivalence.strictEqual(), + * (s: string) => s.toLowerCase() + * ) + * + * const personEq = Equivalence.Struct({ + * name: caseInsensitive, + * age: Equivalence.strictEqual(), + * email: caseInsensitive + * }) + * + * const person1 = { name: "Alice", age: 30, email: "alice@example.com" } + * const person2 = { name: "ALICE", age: 30, email: "ALICE@EXAMPLE.COM" } + * const person3 = { name: "Alice", age: 31, email: "alice@example.com" } + * + * personEq(person1, person2) // => true + * personEq(person1, person3) // => false + * ``` + * + * **Example** (Comparing specific fields) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const nameAgeEq = Equivalence.Struct({ + * name: Equivalence.strictEqual(), + * age: Equivalence.strictEqual() + * }) + * + * // Only compares name and age, ignores other properties + * const obj1 = { name: "Alice", age: 30, extra: "ignored" } + * const obj2 = { name: "Alice", age: 30, extra: "different" } + * nameAgeEq(obj1, obj2) // => true + * ``` + * + * @see {@link Record} + * @see {@link mapInput} + * @see {@link combine} + * @category combinators + * @since 4.0.0 + */ +export function Struct>>( + fields: R +): Equivalence<{ readonly [K in keyof R]: [R[K]] extends [Equivalence] ? A : never }> { + const keys: Array = Reflect.ownKeys(fields) + return make((self, that) => { + for (const key of keys) { + if (!fields[key](self[key], that[key])) return false + } + return true + }) +} + +/** + * Creates an equivalence for objects by comparing all properties using the same equivalence. + * + * **When to use** + * + * Use when you need to compare records with the same equivalence for every + * property value. + * + * **Details** + * + * - Compares all properties present in both objects + * - Requires both objects to have the same set of keys; different keys result in `false` + * - All property values must be equivalent according to the provided equivalence + * - Supports both string and symbol keys via `Reflect.ownKeys` + * - Empty objects are considered equivalent + * - The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties + * + * **Example** (Defining records with string values) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const stringRecordEq = Equivalence.Record(Equivalence.strictEqual()) + * + * const record1 = { a: "hello", b: "world" } + * const record2 = { a: "hello", b: "world" } + * const record3 = { a: "hello", b: "different" } + * const record4 = { a: "hello" } // missing key 'b' + * + * stringRecordEq(record1, record2) // => true + * stringRecordEq(record1, record3) // => false + * stringRecordEq(record1, record4) // => false + * ``` + * + * **Example** (Defining records with number values) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const numberRecordEq = Equivalence.Record(Equivalence.strictEqual()) + * + * const scores1 = { alice: 100, bob: 85 } + * const scores2 = { alice: 100, bob: 85 } + * const scores3 = { alice: 100, bob: 90 } + * + * numberRecordEq(scores1, scores2) // => true + * numberRecordEq(scores1, scores3) // => false + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export function Record(value: Equivalence): Equivalence> { + return make((self, that) => { + const selfKeys = Reflect.ownKeys(self) + const thatKeys = Reflect.ownKeys(that) + + if (selfKeys.length !== thatKeys.length) return false + + for (const key of selfKeys) { + if (!Object.hasOwn(that, key) || !value(self[key], that[key])) { + return false + } + } + + return true + }) +} + +/** + * Creates a `Reducer` for combining `Equivalence` instances, useful for aggregating equivalences in collections. + * + * **When to use** + * + * Use when you need a reducer that combines equivalences. + * + * **Details** + * + * Returns a reducer that combines equivalences using `combine`. The identity + * element for empty collections is an equivalence that always returns `true`. + * The reducer uses `combineAll` for collections of equivalences and can be used + * with fold operations. + * + * **Example** (Creating a Reducer) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const reducer = Equivalence.makeReducer() + * const equivalences = [ + * Equivalence.strictEqual(), + * Equivalence.make((a, b) => Math.abs(a - b) < 1) + * ] + * + * const combined = reducer.combineAll(equivalences) + * // Combined equivalence requires both conditions to be true + * combined(1, 1) // => true + * combined(1, 1.5) // => false + * ``` + * + * @see {@link combine} Combine two equivalences + * @see {@link combineAll} Combine multiple equivalences + * @see {@link Reducer} Reducer type for collection operations + * @category constructors + * @since 4.0.0 + */ +export function makeReducer() { + return Reducer.make>( + combine, + () => true, + combineAll + ) +} + +/** + * Equivalence instance for `Date` objects that compares their `getTime()` values using `Equivalence.Number`. + * + * **When to use** + * + * Use when you need an `Equivalence` for JavaScript date objects by their + * millisecond timestamp. + * + * **Details** + * + * Different `Date` instances that represent the same millisecond timestamp are equivalent. Because `Equivalence.Number` + * treats `NaN` as equal to `NaN`, two invalid `Date` values are also considered equivalent. + * + * **Example** (Comparing Date values) + * + * ```ts import.meta.vitest + * import { Equivalence } from "effect" + * + * const d1 = new Date("2020-01-01T00:00:00.000Z") + * const d2 = new Date("2020-01-01T00:00:00.000Z") + * const d3 = new Date("2021-01-01T00:00:00.000Z") + * const invalidDate1 = new Date("foo") + * const invalidDate2 = new Date("bar") + * + * 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 import.meta.vitest + * import { Equivalence } from "effect" + * + * const d1 = new Date(0) + * const d2 = new Date(0) + * + * d1 === d2 // => false + * Equivalence.Date(d1, d2) // => true + * ``` + * + * @see {@link Number} for the numeric equivalence applied to each `Date#getTime()` result + * @see {@link mapInput} for deriving an equivalence by mapping inputs before comparison + * @see {@link strictEqual} for reference equality when two values must be the same object + * @category instances + * @since 2.0.0 + */ +export const Date: Equivalence = mapInput( + Number, + (d: Date) => d.getTime() +) diff --git a/.repos/effect/packages/effect/src/ErrorReporter.ts b/.repos/effect/packages/effect/src/ErrorReporter.ts new file mode 100644 index 000000000..220c141e0 --- /dev/null +++ b/.repos/effect/packages/effect/src/ErrorReporter.ts @@ -0,0 +1,567 @@ +/** + * Reports Effect failures to external code. + * + * An `ErrorReporter` receives `Cause` values from `Effect.withErrorReporting`, + * manual `report` calls, or built-in reporting boundaries. It forwards each + * non-interruption error to a callback, so applications can send failures to + * logging, monitoring, or error-tracking systems. This module also includes + * layers for installing reporters and symbols for marking errors as ignored or + * attaching severity and attributes. + * + * @since 4.0.0 + */ +import type * as Cause from "./Cause.ts" +import type * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import type * as Fiber from "./Fiber.ts" +import * as effect from "./internal/effect.ts" +import * as references from "./internal/references.ts" +import * as Layer from "./Layer.ts" +import * as LogLevel from "./LogLevel.ts" +import type { Severity } from "./LogLevel.ts" +import type { ReadonlyRecord } from "./Record.ts" +import type * as Scope from "./Scope.ts" + +/** + * String literal type used as the runtime type identifier for + * `ErrorReporter` values. + * + * **When to use** + * + * Use to refer to the runtime type identifier type in low-level integrations. + * + * @category type IDs + * @since 4.0.0 + */ +export type TypeId = "~effect/ErrorReporter" + +/** + * Runtime type identifier attached to `ErrorReporter` values. + * + * **Details** + * + * This marker is part of the runtime representation of `ErrorReporter` + * implementations. Most code should create reporters with `make` and register + * them with `layer`. + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId: TypeId = "~effect/ErrorReporter" + +/** + * An `ErrorReporter` receives reported failures and forwards them to an + * external system such as a logging service or error tracker. + * + * **When to use** + * + * Use as the interface for custom reporters that forward reported Effect + * failures to logging, monitoring, or error-tracking systems. + * + * **Details** + * + * Reporting is triggered by `Effect.withErrorReporting`, + * `ErrorReporter.report`, or built-in boundaries in the HTTP and RPC server + * modules. Use {@link make} to create a reporter; it handles deduplication + * and per-error annotation extraction automatically. + * + * @see {@link make} for creating an `ErrorReporter` from a callback + * @see {@link layer} for registering reporters in the environment + * @see {@link report} for manually reporting a `Cause` + * @see {@link Effect.withErrorReporting} for reporting failures from an effect + * + * @category services + * @since 4.0.0 + */ +export interface ErrorReporter { + readonly [TypeId]: TypeId + report(options: { + readonly cause: Cause.Cause + readonly fiber: Fiber.Fiber + readonly timestamp: bigint + }): void +} + +/** + * Creates an `ErrorReporter` from a callback. + * + * **When to use** + * + * Use to define how reported failures are forwarded to a logging, monitoring, + * or error-tracking backend. + * + * **Details** + * + * The returned reporter automatically deduplicates causes and individual + * errors (the same object is never reported twice), skips interruptions, + * and resolves the `ignore`, `severity`, and `attributes` annotations on + * each error before invoking your callback. + * + * **Example** (Forwarding errors to a callback) + * + * ```ts import.meta.vitest + * import { Effect, ErrorReporter } from "effect" + * + * 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 + * @see {@link report} for manually reporting a `Cause` + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + report: (options: { + readonly cause: Cause.Cause + readonly error: Error + readonly attributes: ReadonlyRecord + readonly severity: Severity + readonly fiber: Fiber.Fiber + readonly timestamp: bigint + }) => void +): ErrorReporter => { + const reported = new WeakSet | object>() + return { + [TypeId]: TypeId, + report(options) { + if (reported.has(options.cause)) return + reported.add(options.cause) + for (let i = 0; i < options.cause.reasons.length; i++) { + const reason = options.cause.reasons[i] + if (reason._tag === "Interrupt") continue + const original = reason._tag === "Fail" ? reason.error : reason.defect + const isObject = typeof original === "object" && original !== null + if (isObject) { + if (reported.has(original)) continue + reported.add(original) + } + if (isIgnored(original)) continue + const pretty = effect.causePrettyError(original as any, reason.annotations) + report({ + ...options, + error: pretty, + severity: isObject ? getSeverity(original) : "Info", + attributes: isObject ? getAttributes(original) : emptyAttributes + }) + } + } + } +} + +/** + * Context reference that holds the set of active error reporters for the + * current fiber. Defaults to an empty set (no reporting). + * + * **When to use** + * + * Use when you need to read or replace the current set of error reporters + * directly. + * + * @category services + * @since 4.0.0 + */ +export const CurrentErrorReporters: Context.Reference> = references.CurrentErrorReporters + +/** + * Creates a `Layer` that registers one or more `ErrorReporter`s. + * + * **When to use** + * + * Use to provide one or more error reporters to effects that perform error + * reporting. + * + * **Details** + * + * Reporters can be plain `ErrorReporter` values or effectful + * `Effect` values that are resolved when the layer is built. By + * default the provided reporters **replace** any previously registered + * reporters. Set `mergeWithExisting: true` to add them alongside existing ones. + * + * **Example** (Providing error reporters) + * + * ```ts import.meta.vitest + * import { Effect, ErrorReporter } from "effect" + * + * const reports: Array = [] + * const firstReporter = ErrorReporter.make(({ error, severity }) => { + * reports.push(`[${severity}] ${error.message}`) + * }) + * const secondReporter = ErrorReporter.make(({ error, severity }) => { + * reports.push(`${severity}: ${error.message}`) + * }) + * + * // Replace all existing reporters + * const ReporterLayer = ErrorReporter.layer([ + * firstReporter, + * secondReporter + * ]) + * + * // Add to existing reporters instead of replacing + * const ReporterMerged = ErrorReporter.layer( + * [secondReporter], + * { mergeWithExisting: true } + * ) + * + * const program = Effect.fail("boom").pipe( + * Effect.withErrorReporting, + * Effect.provide(ReporterLayer), + * Effect.exit + * ) + * + * await Effect.runPromise(program) + * reports // => ["[Info] boom", "Info: boom"] + * ``` + * + * @see {@link make} for creating an `ErrorReporter` from a callback + * @see {@link CurrentErrorReporters} for low-level access to the current reporters + * + * @category layers + * @since 4.0.0 + */ +export const layer = < + const Reporters extends ReadonlyArray> +>( + reporters: Reporters, + options?: { readonly mergeWithExisting?: boolean | undefined } | undefined +): Layer.Layer< + never, + Reporters extends readonly [] ? never : Effect.Error, + Exclude< + Reporters extends readonly [] ? never : Effect.Services, + Scope.Scope + > +> => + Layer.effect( + CurrentErrorReporters, + Effect.withFiber(Effect.fnUntraced(function*(fiber) { + const currentReporters = new Set( + options?.mergeWithExisting === true ? fiber.getRef(references.CurrentErrorReporters) : [] + ) + for (const reporter of reporters) { + currentReporters.add(Effect.isEffect(reporter) ? yield* reporter : reporter) + } + return currentReporters + })) + ) + +/** + * Runs all registered error reporters on the current fiber for a `Cause`. + * + * **When to use** + * + * Use to report a failure for observability without failing the current fiber. + * + * **Example** (Reporting a cause manually) + * + * ```ts import.meta.vitest + * import { Cause, Effect, ErrorReporter } from "effect" + * + * 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 logging + * @since 4.0.0 + */ +export const report = (cause: Cause.Cause): Effect.Effect => + Effect.withFiber((fiber) => { + effect.reportCauseUnsafe(fiber, cause) + return Effect.void + }) + +/** + * Interface that object errors can implement to control reporting behavior. + * + * **When to use** + * + * Use as the annotation contract for object errors that customize how error + * reporting handles them. + * + * **Details** + * + * All three annotation properties are optional: `[ErrorReporter.ignore]` + * prevents reporting when set to `true`, `[ErrorReporter.severity]` overrides + * the default `"Info"` severity, and `[ErrorReporter.attributes]` adds extra + * key/value pairs forwarded to reporters. The global `Error` interface is + * augmented with `Reportable`, so these properties are available on `Error` + * instances at the type level. + * + * @see {@link ignore} for the runtime annotation key that suppresses reports + * @see {@link severity} for the runtime annotation key that overrides severity + * @see {@link attributes} for the runtime annotation key that attaches reporter + * metadata + * + * @category annotations + * @since 4.0.0 + */ +export interface Reportable { + readonly [ignore]?: boolean + readonly [severity]?: Severity + readonly [attributes]?: ReadonlyRecord +} + +declare global { + interface Error extends Reportable {} +} + +/** + * Defines the string property key used to mark an object error as ignored by error + * reporting. + * + * **When to use** + * + * Use to type the property key that suppresses reporting for expected object + * errors. + * + * **Details** + * + * Set this property to `true` on an error class or object error to prevent it + * from being forwarded to reporters. This is useful for expected failures such + * as HTTP 404 responses. + * + * @category annotations + * @since 4.0.0 + */ +export type ignore = "~effect/ErrorReporter/ignore" + +/** + * Defines the runtime property key used to mark an object error as ignored by error + * reporting. + * + * **When to use** + * + * Use to suppress reporting for expected object errors, such as HTTP 404 + * responses. + * + * **Details** + * + * Set `error[ErrorReporter.ignore]` to `true` to prevent the error from being + * forwarded to reporters. This is useful for expected failures such as HTTP 404 + * responses. + * + * **Example** (Marking errors as ignored) + * + * ```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 + * @see {@link Reportable} for the annotation contract recognized on object + * errors + * + * @category annotations + * @since 4.0.0 + */ +export const ignore: ignore = "~effect/ErrorReporter/ignore" + +/** + * Returns `true` if the given value has the `ErrorReporter.ignore` annotation + * set to `true`. + * + * **When to use** + * + * Use to check whether an error value is annotated to be skipped before + * forwarding it to error reporting code. + * + * @see {@link ignore} for the annotation key this predicate reads + * + * @category predicates + * @since 4.0.0 + */ +export const isIgnored = (u: unknown): boolean => + typeof u === "object" && u !== null && ignore in u && u[ignore] === true + +/** + * Defines the string property key used to override the severity level of an object error. + * + * **When to use** + * + * Use to type the property key that overrides the reporting severity for object + * errors. + * + * **Details** + * + * When set to a valid `LogLevel.Severity`, the reporter callback receives this + * value as `severity`. Missing or invalid values fall back to `"Info"`. + * + * @category annotations + * @since 4.0.0 + */ +export type severity = "~effect/ErrorReporter/severity" + +/** + * Defines the runtime property key used to override the severity level of an object error. + * + * **When to use** + * + * Use to annotate object errors with the severity reporter callbacks should + * receive. + * + * **Details** + * + * Set `error[ErrorReporter.severity]` to a valid `LogLevel.Severity` value. + * Missing or invalid values fall back to `"Info"`. + * + * **Example** (Setting error severity annotations) + * + * ```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 + * @see {@link Reportable} for the annotation contract recognized on object + * errors + * + * @category annotations + * @since 4.0.0 + */ +export const severity: severity = "~effect/ErrorReporter/severity" + +/** + * Reads the `ErrorReporter.severity` annotation from an error object, + * falling back to `"Info"` when the annotation is unset or invalid. + * + * **When to use** + * + * Use to inspect the severity that reporter callbacks will receive for an + * object error. + * + * @see {@link severity} for the annotation key used to override severity + * @see {@link Reportable} for the annotation properties recognized on object errors + * + * @category annotations + * @since 4.0.0 + */ +export const getSeverity = (error: object): Severity => { + if (severity in error && LogLevel.values.includes(error[severity] as Severity)) { + return error[severity] as Severity + } + return "Info" +} + +/** + * Defines the string property key used to attach extra key/value metadata to an object + * error report. + * + * **When to use** + * + * Use to type the property key that attaches metadata to object error reports. + * + * **Details** + * + * Reporters receive these attributes alongside the error, making it easy to + * include contextual information such as user IDs, request IDs, or other + * domain-specific debugging data. + * + * @category annotations + * @since 4.0.0 + */ +export type attributes = "~effect/ErrorReporter/attributes" + +/** + * Defines the runtime property key used to attach extra key/value metadata to an object + * error report. + * + * **When to use** + * + * Use to attach domain metadata to object errors so reporter callbacks receive + * it with the reported failure. + * + * **Details** + * + * Set `error[ErrorReporter.attributes]` to a record of metadata that should be + * forwarded to reporters alongside the error. + * + * **Example** (Setting error attributes) + * + * ```ts import.meta.vitest + * import { Data, ErrorReporter } from "effect" + * + * class PaymentError extends Data.TaggedError("PaymentError")<{ + * readonly orderId: string + * }> { + * readonly [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 + * @see {@link severity} for overriding reporter severity + * @see {@link getAttributes} for reading the metadata stored under this key + * @see {@link Reportable} for the annotation contract recognized on object + * errors + * + * @category annotations + * @since 4.0.0 + */ +export const attributes: attributes = "~effect/ErrorReporter/attributes" + +/** + * Reads the `ErrorReporter.attributes` annotation from an error object, + * returning an empty record when unset. + * + * **When to use** + * + * Use to inspect the attributes that reporter callbacks will receive for an + * object error. + * + * **Details** + * + * Returns the value stored under `ErrorReporter.attributes`, or the module's + * shared empty record when the annotation is absent. + * + * **Gotchas** + * + * The annotation value is returned as-is; this helper does not validate or + * clone it. + * + * @see {@link attributes} for the annotation key used to attach metadata + * @see {@link Reportable} for the annotation properties recognized on object errors + * + * @category annotations + * @since 4.0.0 + */ +export const getAttributes = (error: object): ReadonlyRecord => { + return attributes in error ? error[attributes] as any : emptyAttributes +} + +const emptyAttributes: ReadonlyRecord = {} diff --git a/.repos/effect/packages/effect/src/ExecutionPlan.ts b/.repos/effect/packages/effect/src/ExecutionPlan.ts new file mode 100644 index 000000000..236004359 --- /dev/null +++ b/.repos/effect/packages/effect/src/ExecutionPlan.ts @@ -0,0 +1,451 @@ +/** + * Describes ordered fallback steps for running effects or streams. + * + * An `ExecutionPlan` contains one or more steps. Each step provides a `Context` + * or `Layer`, and may also define attempt limits, retry schedules, or + * predicates that decide whether to keep trying. The runtime tries steps in + * order until the workflow succeeds or the plan is exhausted. This module also + * supports merging plans and reading metadata for the active step and attempt. + * + * @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" +import * as Layer from "./Layer.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import type * as Schedule from "./Schedule.ts" + +/** + * String literal type used as the runtime type identifier for `ExecutionPlan` + * values. + * + * @category type IDs + * @since 3.16.0 + */ +export type TypeId = "~effect/ExecutionPlan" + +/** + * Runtime type identifier attached to `ExecutionPlan` values and used by + * `isExecutionPlan`. + * + * @category type IDs + * @since 3.16.0 + */ +export const TypeId: TypeId = "~effect/ExecutionPlan" + +/** + * Returns `true` if a value is an `ExecutionPlan` by checking for the + * `ExecutionPlan.TypeId` marker. + * + * **When to use** + * + * Use when accepting an unknown value and you need to narrow it to an + * `ExecutionPlan` before reading plan fields or passing it to plan-consuming + * APIs. + * + * **Gotchas** + * + * This is a structural marker check; it does not validate the marker value or + * the shape of the plan steps. + * + * @see {@link make} for constructing execution plans that satisfy this guard + * @see {@link TypeId} for the runtime marker checked by this guard + * + * @category guards + * @since 3.16.0 + */ +export const isExecutionPlan = (u: unknown): u is ExecutionPlan => Predicate.hasProperty(u, TypeId) + +/** + * A `ExecutionPlan` can be used with `Effect.withExecutionPlan` or `Stream.withExecutionPlan`, allowing you to provide different resources for each step of execution until the effect succeeds or the plan is exhausted. + * + * **Example** (Defining fallback execution steps) + * + * ```ts import.meta.vitest + * import { Context, ExecutionPlan } from "effect" + * + * const ThePlan = ExecutionPlan.make( + * { + * provide: Context.empty(), + * attempts: 2 + * }, + * { + * provide: Context.empty() + * } + * ) + * + * ThePlan.steps.map((step) => step.attempts ?? 1) // => [2, 1] + * ``` + * + * @category models + * @since 3.16.0 + */ +export interface ExecutionPlan< + Config extends { + provides: any + input: any + error: any + requirements: any + } +> extends Pipeable { + readonly [TypeId]: TypeId + readonly steps: NonEmptyReadonlyArray<{ + readonly provide: + | Context.Context + | Layer.Layer + readonly attempts?: number | undefined + readonly while?: + | ((input: Config["input"]) => Effect.Effect) + | undefined + readonly schedule?: Schedule.Schedule | undefined + }> + + /** + * Returns an equivalent `ExecutionPlan` with the requirements satisfied, using the current context. + */ + readonly captureRequirements: Effect.Effect< + ExecutionPlan<{ + provides: Config["provides"] + input: Config["input"] + error: Config["error"] + requirements: never + }>, + never, + Config["requirements"] + > +} + +/** + * Base type-level configuration carried by an `ExecutionPlan`. + * + * **Details** + * + * `provides` tracks services supplied by plan steps, `input` tracks the error + * input consumed by schedules and `while` predicates, `error` tracks failures + * from plan layers or predicates, and `requirements` tracks services needed to + * build or run the plan. + * + * @category models + * @since 4.0.0 + */ +export type ConfigBase = { + provides: any + input: any + error: any + requirements: any +} + +/** + * Create an `ExecutionPlan`, which can be used with `Effect.withExecutionPlan` or `Stream.withExecutionPlan`, allowing you to provide different resources for each step of execution until the effect succeeds or the plan is exhausted. + * + * **Example** (Creating an execution plan) + * + * ```ts import.meta.vitest + * import { Context, ExecutionPlan } from "effect" + * + * const ThePlan = ExecutionPlan.make( + * { + * provide: Context.empty(), + * attempts: 2 + * }, + * { + * provide: Context.empty() + * } + * ) + * + * ThePlan.steps.length // => 2 + * ``` + * + * @category constructors + * @since 3.16.0 + */ +export const make = >( + ...steps: Steps & { [K in keyof Steps]: make.Step } +): ExecutionPlan<{ + provides: make.StepProvides + input: make.StepInput + error: + | (Steps[number]["provide"] extends Context.Context | Layer.Layer ? E + : never) + | (Steps[number]["while"] extends (input: infer _I) => Effect.Effect ? _E : never) + requirements: + | (Steps[number]["provide"] extends Layer.Layer ? R : never) + | (Steps[number]["while"] extends (input: infer _I) => Effect.Effect ? R : never) + | (Steps[number]["schedule"] extends Schedule.Schedule ? R : never) +}> => + makeProto(steps.map((options, i) => { + if (options.attempts !== undefined && options.attempts < 1) { + throw new Error(`ExecutionPlan.make: step[${i}].attempts must be greater than 0`) + } + return { + schedule: options.schedule, + attempts: options.attempts, + while: options.while + ? (input: any) => + effect.suspend(() => { + const result = options.while!(input) + return typeof result === "boolean" ? effect.succeed(result) : result + }) + : undefined, + provide: options.provide + } + }) as any) + +/** + * Namespace containing type helpers used by `ExecutionPlan.make`. + * + * @since 3.16.0 + */ +export declare namespace make { + /** + * Input shape for a single execution-plan step. + * + * **Details** + * + * Each step provides a `Context` or `Layer` and may limit attempts, add a + * `while` predicate for retry decisions, or attach a `Schedule` for retry + * timing. + * + * @category models + * @since 3.16.0 + */ + export type Step = { + readonly provide: Context.Context | Context.Context | Layer.Any + readonly attempts?: number | undefined + readonly while?: ((input: any) => boolean | Effect.Effect) | undefined + readonly schedule?: Schedule.Schedule | undefined + } + + /** + * Computes the intersection of services provided by a list of execution-plan + * steps. + * + * @category utility types + * @since 3.16.1 + */ + export type StepProvides, Out = unknown> = Steps extends + readonly [infer Step, ...infer Rest] ? StepProvides< + Rest, + & Out + & ( + (Step extends { readonly provide: Context.Context | Layer.Layer } ? P + : unknown) + ) + > : + Out + + /** + * Computes the intersection of services provided by a list of execution plans. + * + * @category utility types + * @since 3.16.1 + */ + export type PlanProvides, Out = unknown> = Plans extends + readonly [infer Plan, ...infer Rest] ? + PlanProvides ? T["provides"] : unknown)> : + Out + + /** + * Computes the input type consumed by the `while` predicates and schedules in + * a list of execution-plan steps. + * + * @category utility types + * @since 3.16.0 + */ + export type StepInput, Out = unknown> = Steps extends + readonly [infer Step, ...infer Rest] ? StepInput< + Rest, + & Out + & ( + & (Step extends { readonly while: (input: infer I) => infer _ } ? I : unknown) + & (Step extends { readonly schedule: Schedule.Schedule } ? I : unknown) + ) + > : + Out + + /** + * Computes the combined input type consumed by a list of execution plans. + * + * @category utility types + * @since 3.16.0 + */ + export type PlanInput, Out = unknown> = Plans extends + readonly [infer Plan, ...infer Rest] ? + PlanInput ? T["input"] : unknown)> : + Out +} + +const Proto: Omit, "steps"> = { + [TypeId]: TypeId, + get captureRequirements() { + const self = this as any as ExecutionPlan + return effect.contextWith((context: Context.Context) => + effect.succeed(makeProto(self.steps.map((step) => ({ + ...step, + provide: Layer.isLayer(step.provide) + ? Layer.provide(step.provide, Layer.succeedContext(context)) + : step.provide + })) as any)) + ) + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +const makeProto = ( + steps: ExecutionPlan<{ + provides: Provides + input: In + error: PlanE + requirements: PlanR + }>["steps"] +) => { + const self = Object.create(Proto) + self.steps = steps + return self +} + +/** + * Combines multiple execution plans by concatenating their steps in order. + * + * **When to use** + * + * Use to combine separately defined fallback plans into one ordered plan before + * applying it to an effect or stream. + * + * **Details** + * + * The resulting plan tries every step from the first plan, then every step from + * the next plan, and so on. + * + * @see {@link make} for building a plan from individual steps instead of combining existing plans + * + * @category combining + * @since 3.16.0 + */ +export const merge = >>( + ...plans: Plans +): ExecutionPlan<{ + provides: make.PlanProvides + input: make.PlanInput + error: Plans[number] extends ExecutionPlan ? T["error"] : never + requirements: Plans[number] extends ExecutionPlan ? T["requirements"] : never +}> => makeProto(plans.flatMap((plan) => plan.steps) as any) + +/** + * Metadata describing the currently running execution-plan attempt. + * + * **Details** + * + * `attempt` is the current 1-based attempt number, and `stepIndex` is the + * 0-based index of the plan step currently being evaluated. + * + * @category metadata + * @since 4.0.0 + */ +export interface Metadata { + readonly attempt: number + readonly stepIndex: number +} + +/** + * Context reference containing metadata for the currently running + * execution-plan attempt. + * + * **When to use** + * + * Use to read the active plan step and attempt while code is running under an + * execution plan. + * + * @category services + * @since 4.0.0 + */ +export const CurrentMetadata = Context.Reference("effect/ExecutionPlan/CurrentMetadata", { + defaultValue: constant({ + attempt: 0, + 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/.repos/effect/packages/effect/src/Exit.ts b/.repos/effect/packages/effect/src/Exit.ts new file mode 100644 index 000000000..889d81d48 --- /dev/null +++ b/.repos/effect/packages/effect/src/Exit.ts @@ -0,0 +1,1035 @@ +/** + * Represents the result of an Effect computation as a plain value. + * + * An `Exit` is either a success with an `A` or a failure with a + * `Cause`. The failure cause preserves typed errors, defects, and + * interruptions after a workflow has finished. Use this module when completed + * Effect results need to be inspected, transformed, filtered, or matched + * synchronously as data. + * + * @since 2.0.0 + */ +import type * as Cause from "./Cause.ts" +import type * as Effect from "./Effect.ts" +import * as core from "./internal/core.ts" +import * as effect from "./internal/effect.ts" +import type { Option } from "./Option.ts" +import type * as Result from "./Result.ts" +import type { NoInfer } from "./Types.ts" + +const TypeId = core.ExitTypeId + +/** + * Represents the result of an Effect computation. + * + * **When to use** + * + * Use when you need to synchronously inspect whether an Effect computation + * succeeded or failed. + * + * **Details** + * + * An `Exit` is either `Success` containing a value of type `A`, or + * `Failure` containing a `Cause` describing why the computation + * failed. + * + * Since `Exit` is also an `Effect`, you can yield it inside `Effect.gen`. + * + * **Example** (Pattern matching on an Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * const success: Exit.Exit = Exit.succeed(42) + * const failure: Exit.Exit = Exit.fail("error") + * + * Exit.match(success, { + * onSuccess: (value) => `Got value: ${value}`, + * onFailure: (cause) => `Got error: ${cause}` + * }) // => "Got value: 42" + * ``` + * + * @see {@link Success} for the success case + * @see {@link Failure} for the failure case + * @see {@link match} for pattern matching + * + * @category models + * @since 2.0.0 + */ +export type Exit = Success | Failure + +/** + * Namespace containing helper types shared by `Exit` values. + * + * **When to use** + * + * Use to reference helper types that describe the shared structure of `Exit` + * values. + * + * @since 2.0.0 + */ +export declare namespace Exit { + /** + * Base interface shared by both Success and Failure. + * + * **When to use** + * + * Use to describe the common protocol implemented by every `Exit` value. + * + * **Details** + * + * Every Exit is also an Effect, so you can yield it in `Effect.gen`. + * + * @category models + * @since 4.0.0 + */ + export interface Proto extends Effect.Effect { + readonly [TypeId]: typeof TypeId + } +} + +/** + * A successful Exit containing a value. + * + * **When to use** + * + * Use when working with the successful branch of an `Exit` after narrowing + * with {@link isSuccess}. Access the value via the `value` property after + * narrowing. + * + * **Example** (Accessing the success value) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * const success = Exit.succeed(42) + * + * if (Exit.isSuccess(success)) { + * success.value // => 42 + * } + * ``` + * + * @see {@link isSuccess} to narrow an Exit to Success + * @see {@link Failure} for the failure counterpart + * + * @category models + * @since 2.0.0 + */ +export interface Success extends Exit.Proto { + readonly _tag: "Success" + readonly value: A +} + +/** + * A failed Exit containing a Cause. + * + * **When to use** + * + * Use when working with the failed branch of an `Exit` after narrowing with + * {@link isFailure}. Access the cause via the `cause` property after + * narrowing. + * + * **Details** + * + * The `Cause` may contain typed errors, defects, or interruptions. + * + * **Example** (Accessing the failure cause) + * + * ```ts import.meta.vitest + * import { Cause, Exit } from "effect" + * + * const failure = Exit.fail("something went wrong") + * + * if (Exit.isFailure(failure)) { + * failure.cause // => Cause.fail("something went wrong") + * } + * ``` + * + * @see {@link isFailure} to narrow an Exit to Failure + * @see {@link Success} for the success counterpart + * + * @category models + * @since 2.0.0 + */ +export interface Failure extends Exit.Proto { + readonly _tag: "Failure" + readonly cause: Cause.Cause +} + +/** + * Checks whether an unknown value is an Exit. + * + * **When to use** + * + * Use to validate unknown values at system boundaries and narrow them to + * `Exit`. + * + * **Details** + * + * Does not inspect the contents of the Exit. Returns `true` for both Success + * and Failure exits. + * + * **Example** (Checking if a value is an Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * 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 + * @see {@link isFailure} to check for a failed Exit + * + * @category guards + * @since 2.0.0 + */ +export const isExit: (u: unknown) => u is Exit = core.isExit + +/** + * Creates a successful Exit containing the given value. + * + * **When to use** + * + * Use when you need an Exit that contains a known success value. + * + * **Details** + * + * Returns a `Success` with the provided value. Does not perform any + * computation. + * + * **Example** (Creating a successful Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.succeed(42) // => Exit.succeed(42) + * ``` + * + * @see {@link fail} to create a failed Exit + * @see {@link void_ void} for a pre-allocated success with no value + * + * @category constructors + * @since 2.0.0 + */ +export const succeed: (a: A) => Exit = core.exitSucceed + +/** + * Creates a failed Exit from a Cause. + * + * **When to use** + * + * Use when you already have a `Cause` and want to wrap it in an Exit + * for advanced error handling where you need full control over the Cause + * structure. + * + * **Details** + * + * Returns a `Failure`. If you only have an error value, use + * {@link fail} instead. + * + * **Example** (Creating a failed Exit from a Cause) + * + * ```ts import.meta.vitest + * import { Cause, Exit } from "effect" + * + * Exit.failCause(Cause.fail("Something went wrong")) // => Exit.fail("Something went wrong") + * ``` + * + * @see {@link fail} to create a Failure from a plain error value + * @see {@link die} to create a Failure from a defect + * + * @category constructors + * @since 2.0.0 + */ +export const failCause: (cause: Cause.Cause) => Exit = core.exitFailCause + +/** + * Creates a failed Exit from a typed error value. + * + * **When to use** + * + * Use when you need to represent an expected typed failure as an `Exit`. + * + * **Details** + * + * The error is wrapped in a `Cause.Fail` internally. + * + * Returns a `Failure`. + * + * **Example** (Creating a failed Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.fail("Something went wrong") // => Exit.fail("Something went wrong") + * ``` + * + * @see {@link succeed} to create a successful Exit + * @see {@link die} to create a Failure from an unexpected defect + * @see {@link failCause} to create a Failure from a full Cause + * + * @category constructors + * @since 2.0.0 + */ +export const fail: (e: E) => Exit = core.exitFail + +/** + * Creates a failed Exit from a defect (unexpected error). + * + * **When to use** + * + * Use when you need unexpected, unrecoverable errors that should not appear in + * the typed error channel. + * + * **Details** + * + * The defect is wrapped in a `Cause.Die` internally. + * + * Returns a `Failure` with `E = never`, since defects do not appear in + * the typed error channel. + * + * **Example** (Creating a defect Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.die("Unexpected error") // => Exit.die("Unexpected error") + * ``` + * + * @see {@link fail} to create a Failure from a typed error + * @see {@link hasDies} to check whether an Exit contains defects + * + * @category constructors + * @since 2.0.0 + */ +export const die: (defect: unknown) => Exit = core.exitDie + +/** + * Creates a failed Exit representing fiber interruption. + * + * **When to use** + * + * Use to signal that a fiber was interrupted. + * + * **Details** + * + * Optionally pass a fiber ID to identify which fiber was interrupted. Returns + * a `Failure` with an `Interrupt` cause. + * + * **Example** (Creating an interruption Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.interrupt(123) // => Exit.interrupt(123) + * ``` + * + * @see {@link hasInterrupts} to check whether an Exit contains interruptions + * + * @category constructors + * @since 2.0.0 + */ +export const interrupt: (fiberId?: number | undefined) => Exit = effect.exitInterrupt + +const void_: Exit = effect.exitVoid +export { + /** + * Provides a pre-allocated successful Exit with a `void` value. + * + * **When to use** + * + * Use when you need a shared successful `Exit` with no meaningful value. + * + * **Details** + * + * Equivalent to `Exit.succeed(undefined)` but shared as a single instance, + * avoiding allocation for a common case. + * + * **Example** (Referencing the void Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.void // => Exit.succeed(undefined) + * ``` + * + * @see {@link succeed} to create a success with a specific value + * @see {@link asVoid} to discard the value of an existing Exit + * + * @category constructors + * @since 2.0.0 + */ + void_ as void +} + +/** + * Checks whether an Exit is a Success. + * + * **When to use** + * + * Use as a type guard to narrow `Exit` to `Success` and access the + * `value` property. + * + * **Example** (Narrowing to success) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * const exit = Exit.succeed(42) + * + * if (Exit.isSuccess(exit)) { + * exit.value // => 42 + * } + * ``` + * + * @see {@link isFailure} for the opposite check + * @see {@link match} for exhaustive pattern matching + * + * @category guards + * @since 2.0.0 + */ +export const isSuccess: (self: Exit) => self is Success = effect.exitIsSuccess + +/** + * Checks whether an Exit is a Failure. + * + * **When to use** + * + * Use as a type guard to narrow `Exit` to `Failure` and access the + * `cause` property. + * + * **Example** (Narrowing to failure) + * + * ```ts import.meta.vitest + * import { Cause, Exit } from "effect" + * + * const exit = Exit.fail("error") + * + * if (Exit.isFailure(exit)) { + * exit.cause // => Cause.fail("error") + * } + * ``` + * + * @see {@link isSuccess} for the opposite check + * @see {@link match} for exhaustive pattern matching + * + * @category guards + * @since 2.0.0 + */ +export const isFailure: (self: Exit) => self is Failure = effect.exitIsFailure + +/** + * Checks whether a failed Exit contains typed errors (Fail reasons). + * + * **When to use** + * + * Use to distinguish typed failures from defects or interruptions. + * + * **Details** + * + * Returns `false` for successful exits. Only checks for `Fail` reasons in the + * Cause. A Cause with only `Die` or `Interrupt` reasons returns `false`. + * + * **Example** (Checking for typed errors) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * 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 + * @see {@link hasInterrupts} to check for interruptions + * + * @category guards + * @since 4.0.0 + */ +export const hasFails: (self: Exit) => self is Failure = effect.exitHasFails + +/** + * Checks whether a failed Exit contains defects (Die reasons). + * + * **When to use** + * + * Use to check whether an `Exit` failure cause contains unexpected errors. + * + * **Details** + * + * Returns `false` for successful exits. Only checks for `Die` reasons in the + * Cause. A Cause with only `Fail` or `Interrupt` reasons returns `false`. + * + * **Example** (Checking for defects) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * 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 + * @see {@link hasInterrupts} to check for interruptions + * + * @category guards + * @since 4.0.0 + */ +export const hasDies: (self: Exit) => self is Failure = effect.exitHasDies + +/** + * Checks whether a failed Exit contains interruptions (Interrupt reasons). + * + * **When to use** + * + * Use to check whether an `Exit` contains fiber interruption. + * + * **Details** + * + * Returns `false` for successful exits. Only checks for `Interrupt` reasons in + * the Cause. A Cause with only `Fail` or `Die` reasons returns `false`. + * + * **Example** (Checking for interruptions) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * 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 + * @see {@link hasDies} to check for defects + * + * @category guards + * @since 4.0.0 + */ +export const hasInterrupts: (self: Exit) => self is Failure = effect.exitHasInterrupts + +/** + * Extracts the Success variant from an Exit as a Result. + * + * **When to use** + * + * Use when composing Exit checks with `Filter` or other `Result`-based + * filtering APIs and you want the full Success wrapper. + * + * **Details** + * + * Returns `Result.succeed(success)` when the Exit is a Success, or + * `Result.fail(failure)` with the original Failure otherwise. + * + * **Gotchas** + * + * This is not an `Option` accessor or an Effect failure. A failed extraction is + * represented as data in the `Result` failure channel. + * + * **Example** (Filtering for success) + * + * ```ts import.meta.vitest + * import { Exit, Result } from "effect" + * + * Exit.filterSuccess(Exit.succeed(42)) // => Result.succeed(Exit.succeed(42)) + * ``` + * + * @see {@link filterFailure} for the inverse + * @see {@link filterValue} to extract the raw value instead of the Success object + * + * @category filtering + * @since 4.0.0 + */ +export const filterSuccess: ( + self: Exit +) => Result.Result, Failure> = effect.exitFilterSuccess + +/** + * Extracts the success value from an Exit as a Result. + * + * **When to use** + * + * Use when composing Exit checks with `Filter` or other `Result`-based + * filtering APIs and you want the raw success value rather than the Success + * wrapper. + * + * **Details** + * + * Returns `Result.succeed(value)` when the Exit is a Success, or + * `Result.fail(failure)` with the original Failure otherwise. + * + * **Gotchas** + * + * This is not an `Option` accessor or an Effect failure. A failed extraction is + * represented as data in the `Result` failure channel. + * + * **Example** (Filtering for the value) + * + * ```ts import.meta.vitest + * import { Exit, Result } from "effect" + * + * Exit.filterValue(Exit.succeed(42)) // => Result.succeed(42) + * ``` + * + * @see {@link filterSuccess} to get the full Success object + * @see {@link getSuccess} to get the value as an Option instead + * + * @category filtering + * @since 4.0.0 + */ +export const filterValue: (self: Exit) => Result.Result> = effect.exitFilterValue + +/** + * Extracts the Failure variant from an Exit as a Result. + * + * **When to use** + * + * Use when composing Exit checks with `Filter` or other `Result`-based + * filtering APIs and you want the full Failure wrapper. + * + * **Details** + * + * Returns `Result.succeed(failure)` when the Exit is a Failure, or + * `Result.fail(success)` with the original Success otherwise. + * + * **Gotchas** + * + * This is not an `Option` accessor or an Effect failure. A failed extraction is + * represented as data in the `Result` failure channel. + * + * **Example** (Filtering for failure) + * + * ```ts import.meta.vitest + * import { Exit, Result } from "effect" + * + * Exit.filterFailure(Exit.fail("err")) // => Result.succeed(Exit.fail("err")) + * ``` + * + * @see {@link filterSuccess} for the inverse + * @see {@link filterCause} to extract the Cause directly + * + * @category filtering + * @since 4.0.0 + */ +export const filterFailure: (self: Exit) => Result.Result, Success> = + effect.exitFilterFailure + +/** + * Extracts the Cause from a failed Exit as a Result. + * + * **When to use** + * + * Use when composing Exit checks with `Filter` or other `Result`-based + * filtering APIs and you want the raw Cause rather than the Failure wrapper. + * + * **Details** + * + * Returns `Result.succeed(cause)` when the Exit is a Failure, or + * `Result.fail(success)` with the original Success otherwise. + * + * **Gotchas** + * + * This is not an `Option` accessor or an Effect failure. A failed extraction is + * represented as data in the `Result` failure channel. + * + * **Example** (Filtering for the cause) + * + * ```ts import.meta.vitest + * import { Cause, Exit, Result } from "effect" + * + * Exit.filterCause(Exit.fail("err")) // => Result.succeed(Cause.fail("err")) + * ``` + * + * @see {@link filterFailure} to get the full Failure object + * @see {@link getCause} to get the Cause as an Option instead + * + * @category filtering + * @since 4.0.0 + */ +export const filterCause: (self: Exit) => Result.Result, Success> = effect.exitFilterCause + +/** + * Extracts the first typed error value from a failed Exit as a Result. + * + * **When to use** + * + * Use when you need the first typed error from an `Exit` as a `Result` for + * `Filter` or other `Result`-based filtering APIs. + * + * **Details** + * + * Returns `Result.succeed(error)` when the Cause contains a Fail reason, or + * `Result.fail(exit)` with the original Exit otherwise. + * + * **Gotchas** + * + * Only finds the first Fail reason. If the Cause has multiple errors, the rest + * are ignored. + * + * **Example** (Finding the first typed error) + * + * ```ts import.meta.vitest + * import { Exit, Result } from "effect" + * + * Exit.findError(Exit.fail("not found")) // => 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 + * @see {@link findDefect} to find defects instead + * + * @category filtering + * @since 4.0.0 + */ +export const findError: (input: Exit) => Result.Result> = effect.exitFindError + +/** + * Extracts the first defect from a failed Exit as a Result. + * + * **When to use** + * + * Use when you need the first defect from an `Exit` as a `Result` for + * `Filter` or other `Result`-based filtering APIs. + * + * **Details** + * + * Returns `Result.succeed(defect)` when the Cause contains a Die reason, or + * `Result.fail(exit)` with the original Exit otherwise. + * + * **Gotchas** + * + * Only finds the first Die reason. If the Cause has multiple defects, the rest + * are ignored. + * + * **Example** (Finding the first defect) + * + * ```ts import.meta.vitest + * import { Exit, Result } from "effect" + * + * 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 + * @see {@link hasDies} to check for defects without extracting them + * + * @category filtering + * @since 4.0.0 + */ +export const findDefect: (input: Exit) => Result.Result> = effect.exitFindDefect + +/** + * Pattern matches on an Exit, handling both success and failure cases. + * + * **When to use** + * + * Use when you need exhaustive handling of both `Exit` success and failure + * outcomes. + * + * **Details** + * + * Calls `onSuccess` with the value if the Exit is a Success, and calls + * `onFailure` with the Cause if the Exit is a Failure. + * + * **Example** (Matching on an Exit) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.match(Exit.succeed(42), { + * onSuccess: (value) => `Got: ${value}`, + * onFailure: () => "Failed" + * }) // => "Got: 42" + * ``` + * + * @see {@link isSuccess} and {@link isFailure} for simple boolean checks + * + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + (options: { + readonly onSuccess: (a: NoInfer) => X1 + readonly onFailure: (cause: Cause.Cause>) => X2 + }): (self: Exit) => X1 | X2 + ( + self: Exit, + options: { + readonly onSuccess: (a: A) => X1 + readonly onFailure: (cause: Cause.Cause) => X2 + } + ): X1 | X2 +} = effect.exitMatch + +/** + * Transforms the success value of an Exit using the given function. + * + * **When to use** + * + * Use to apply a transformation to the value inside a successful Exit + * + * **Details** + * + * Failures pass through unchanged. + * + * Allocates a new Exit if successful. + * + * **Example** (Mapping over a success) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.map(Exit.succeed(21), (x) => x * 2) // => Exit.succeed(42) + * ``` + * + * @see {@link mapError} to transform the error + * @see {@link mapBoth} to transform both success and error + * + * @category combinators + * @since 2.0.0 + */ +export const map: { + (f: (a: A) => B): (self: Exit) => Exit + (self: Exit, f: (a: A) => B): Exit +} = effect.exitMap + +/** + * Transforms the typed error of a failed Exit using the given function. + * + * **When to use** + * + * Use to remap typed errors while preserving the Exit structure + * + * **Details** + * + * Successes pass through unchanged. + * + * Allocates a new Exit if the error is transformed. + * + * **Gotchas** + * + * Only transforms typed errors (Fail reasons). If the Cause contains only + * defects or interruptions, the failure passes through unchanged. + * + * **Example** (Mapping over an error) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.mapError(Exit.fail("bad input"), (error) => error.toUpperCase()) // => Exit.fail("BAD INPUT") + * ``` + * + * @see {@link map} to transform the success value + * @see {@link mapBoth} to transform both success and error + * + * @category combinators + * @since 2.0.0 + */ +export const mapError: { + (f: (a: NoInfer) => E2): (self: Exit) => Exit + (self: Exit, f: (a: NoInfer) => E2): Exit +} = effect.exitMapError + +/** + * Transforms both the success value and typed error of an Exit. + * + * **When to use** + * + * Use when you need to remap both channels in one step. + * + * **Details** + * + * `onSuccess` transforms the value if the Exit is a Success. `onFailure` + * transforms the typed error if the Exit is a Failure with a Fail reason. + * Allocates a new Exit. + * + * **Gotchas** + * + * If the Cause contains only defects or interruptions, the failure passes + * through unchanged. + * + * **Example** (Mapping both channels) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.mapBoth(Exit.succeed(42), { + * onSuccess: (x) => String(x), + * onFailure: (error: string) => error.toUpperCase() + * }) // => Exit.succeed("42") + * ``` + * + * @see {@link map} to transform only the success value + * @see {@link mapError} to transform only the error + * + * @category combinators + * @since 2.0.0 + */ +export const mapBoth: { + ( + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): (self: Exit) => Exit + ( + self: Exit, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Exit +} = effect.exitMapBoth + +/** + * Discards the success value of an Exit, replacing it with `void`. + * + * **When to use** + * + * Use when you need to discard a successful `Exit` value while preserving + * whether the `Exit` succeeded or failed. + * + * **Details** + * + * Failures pass through unchanged. + * + * Allocates a new Exit if successful. + * + * **Example** (Discarding the success value) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * Exit.asVoid(Exit.succeed(42)) // => Exit.succeed(undefined) + * ``` + * + * @see {@link void_ void} for a pre-allocated void success + * @see {@link asVoidAll} to combine multiple exits into a single void Exit + * + * @category combinators + * @since 2.0.0 + */ +export const asVoid: (self: Exit) => Exit = effect.exitAsVoid + +/** + * Combines multiple Exit values into a single `Exit`. + * + * **When to use** + * + * Use to validate that all exits in a collection succeeded + * + * **Details** + * + * If all exits are successful, this returns a void success. If any exit is a + * failure, this returns a single failure with all error causes combined. + * + * Iterates over the entire collection. Collects all failure causes, not just + * the first. + * + * **Example** (Combining exits) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * + * 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 + * + * @category combinators + * @since 4.0.0 + */ +export const asVoidAll: >>( + exits: I +) => Exit> ? _E : never> = effect.exitAsVoidAll + +/** + * Returns the success value of an Exit as an Option. + * + * **When to use** + * + * Use when you need the success value from an `Exit` as an `Option` instead of + * pattern matching. + * + * **Details** + * + * Returns `Option.some(value)` for a Success and `Option.none()` for a Failure. + * + * **Example** (Getting the success value) + * + * ```ts import.meta.vitest + * import { Exit, Option } from "effect" + * + * 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 getters + * @since 4.0.0 + */ +export const getSuccess: (self: Exit) => Option = effect.exitGetSuccess + +/** + * Returns the Cause of a failed Exit as an Option. + * + * **When to use** + * + * Use when you need the failure `Cause` from an `Exit` as an `Option` instead + * of pattern matching. + * + * **Details** + * + * Returns `Option.some(cause)` for a Failure and `Option.none()` for a Success. + * + * **Example** (Getting the failure cause) + * + * ```ts import.meta.vitest + * import { Cause, Exit, Option } from "effect" + * + * 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 getters + * @since 4.0.0 + */ +export const getCause: (self: Exit) => Option> = effect.exitGetCause + +/** + * Returns the first typed error from a failed Exit as an Option. + * + * **When to use** + * + * Use when you need the first typed error from an `Exit` as an `Option`, + * ignoring successes and non-typed failures. + * + * **Details** + * + * Returns `Option.some(error)` if the Cause contains a Fail reason. Successes, + * defect-only failures, and interrupt-only failures return `Option.none()`. + * + * **Gotchas** + * + * Only finds the first Fail reason. If the Cause has multiple typed errors, + * the rest are ignored. + * + * **Example** (Getting the first error) + * + * ```ts import.meta.vitest + * import { Exit, Option } from "effect" + * + * 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 getters + * @since 4.0.0 + */ +export const findErrorOption: (self: Exit) => Option = effect.exitFindErrorOption diff --git a/.repos/effect/packages/effect/src/Fiber.ts b/.repos/effect/packages/effect/src/Fiber.ts new file mode 100644 index 000000000..4e511ee27 --- /dev/null +++ b/.repos/effect/packages/effect/src/Fiber.ts @@ -0,0 +1,595 @@ +/** + * Operations for handles returned by forking effects. A `Fiber` is a + * lightweight execution of an `Effect` that may still be running or may already + * have completed. This module lets callers await or join fiber results, + * interrupt one or many fibers, check unknown values, access the current fiber, + * and attach manually managed fibers to a `Scope` for cleanup. + * + * @since 2.0.0 + */ +import type * as Arr from "./Array.ts" +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 type { LogLevel } from "./LogLevel.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 { Covariant } from "./Types.ts" + +const TypeId = "~effect/Fiber" + +/** + * A runtime fiber is a lightweight thread that executes Effects. Fibers are + * the unit of concurrency in Effect. They provide a way to run multiple + * Effects concurrently while maintaining structured concurrency and + * cancellation safety. + * + * **When to use** + * + * Use to observe, join, interrupt, or coordinate work that has already been + * forked. + * + * **Details** + * + * A fiber exposes both safe Effect-based operations, such as {@link await_ await}, + * {@link join}, and {@link interrupt}, and low-level runtime fields used by + * the scheduler and runtime internals. + * + * **Gotchas** + * + * Prefer the exported functions in this module over calling `interruptUnsafe` + * or `pollUnsafe` directly. The unsafe methods are immediate runtime hooks and + * do not provide the same Effect-based sequencing guarantees. + * + * **Example** (Awaiting a forked fiber) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * // Fork an effect to run in a new fiber + * const fiber = yield* Effect.forkChild(Effect.succeed(42)) + * + * // Wait for the fiber to complete and get its result + * const result = yield* Fiber.await(fiber) + * return result + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => Exit.succeed(42) + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Fiber extends Pipeable { + readonly [TypeId]: Fiber.Variance + + readonly id: number + readonly currentOpCount: number + readonly getRef: (ref: Context.Reference) => A + readonly context: Context.Context + setContext(context: Context.Context): void + readonly currentScheduler: Scheduler + 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, + annotations?: Context.Context | undefined + ) => void + readonly pollUnsafe: () => Exit | undefined +} + +/** + * The Fiber namespace contains utility types and functions for working with fibers. + * It provides type-level utilities for fiber operations and variance encoding. + * + * **When to use** + * + * Use to reference type-level helpers associated with `Fiber`. + * + * **Details** + * + * The namespace currently exposes type-level support used by the `Fiber` + * interface. Runtime operations are exported as module-level functions. + * + * **Example** (Working with fiber types) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a fiber + * const fiber = yield* Effect.forkChild(Effect.succeed(42)) + * + * // Use namespace types for variance + * const typedFiber: Fiber.Fiber = fiber + * + * // Join the fiber + * return yield* Fiber.join(fiber) + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 + * ``` + * + * @since 2.0.0 + */ +export declare namespace Fiber { + /** + * Variance encoding for the Fiber type, specifying covariance in both the + * success type `A` and the error type `E`. + * + * **When to use** + * + * Use to carry the success and error type parameters for `Fiber` in Effect's + * type machinery. + * + * **Example** (Upcasting fibers safely) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * // Variance allows safe subtyping + * 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 + * @since 2.0.0 + */ + export interface Variance { + readonly _A: Covariant + readonly _E: Covariant + } +} + +const await_: (self: Fiber) => Effect> = effect.fiberAwait +export { + /** + * Waits for a fiber to complete and returns its exit value. + * + * **When to use** + * + * Use when you need to inspect whether the fiber succeeded, + * failed, died, or was interrupted without propagating the failure. + * + * **Details** + * + * The returned Effect always succeeds with an `Exit` describing the fiber's + * outcome. + * + * **Gotchas** + * + * This does not flatten the fiber result into the current Effect. Use + * {@link join} when you want fiber failures to fail the current Effect. + * + * **Example** (Awaiting a fiber exit) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * const fiber = yield* Effect.forkChild(Effect.succeed(42)) + * return yield* Fiber.await(fiber) + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => Exit.succeed(42) + * ``` + * + * @category combinators + * @since 2.0.0 + */ + await_ as await +} +/** + * Waits for all fibers in the provided iterable to complete and returns + * an array of their exit values. + * + * **When to use** + * + * Use when you need every fiber outcome as data, including failures and + * interruptions. + * + * **Details** + * + * The returned array is ordered like the input iterable. + * + * **Gotchas** + * + * Failures are captured as `Exit.Failure` values. Use {@link joinAll} when you + * want the first failed fiber to fail the returned Effect. + * + * **Example** (Awaiting multiple fiber exits) + * + * ```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)) + * return yield* Fiber.awaitAll([fiber1, fiber2]) + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Exit.succeed(1), Exit.succeed(2)] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const awaitAll: >( + self: Iterable +) => Effect< + Array< + Exit< + A extends Fiber ? _A : never, + A extends Fiber ? _E : never + > + > +> = effect.fiberAwaitAll + +/** + * Joins a fiber, blocking until it completes. If the fiber succeeds, + * returns its value. If it fails, the error is propagated. + * + * **When to use** + * + * Use when you need a forked fiber's failure to fail the current Effect because + * that fiber is part of the current workflow. + * + * **Gotchas** + * + * Joining a failed fiber propagates the fiber's Cause. Use {@link await_ await} when + * you need to inspect the `Exit` instead of failing. + * + * **Example** (Joining a fiber) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * const fiber = yield* Effect.forkChild(Effect.succeed(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 + * + * @category combinators + * @since 2.0.0 + */ +export const join: (self: Fiber) => Effect = effect.fiberJoin + +/** + * Waits for all fibers to succeed and returns their values in input order. + * + * **When to use** + * + * Use when you need every fiber to succeed and want the successful values + * rather than the `Exit` values. + * + * **Details** + * + * If any fiber fails, the returned `Effect` fails with that fiber's cause and + * stops waiting for additional results. This does not interrupt the remaining + * fibers. + * + * **Gotchas** + * + * A failure stops waiting, but it does not interrupt any other fibers. Use + * {@link interruptAll} separately when remaining fibers should be stopped. + * + * @see {@link awaitAll} for collecting every fiber outcome as an Exit + * + * @category combinators + * @since 2.0.0 + */ +export const joinAll: >>( + self: A +) => Effect< + Arr.ReadonlyArray.With< + A, + A extends Iterable> ? _A : never + >, + A extends Iterable> ? _E : never +> = effect.fiberJoinAll + +/** + * Interrupts a fiber, causing it to stop executing and clean up any + * acquired resources. + * + * **When to use** + * + * Use when you need to cancel a forked fiber and wait for its cleanup to + * complete. + * + * **Details** + * + * The returned Effect completes only after the interrupted fiber has completed. + * + * **Gotchas** + * + * Interruption is cooperative. A fiber can continue running while it is inside + * uninterruptible work or finalizers. + * + * **Example** (Interrupting a fiber) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * const fiber = yield* Effect.forkChild( + * Effect.delay("1 second")(Effect.succeed(42)) + * ) + * yield* Fiber.interrupt(fiber) + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @see {@link interruptAs} for specifying the interrupting fiber ID + * @see {@link await_ await} for observing the interrupted fiber's Exit + * + * @category interruption + * @since 2.0.0 + */ +export const interrupt: (self: Fiber) => Effect = effect.fiberInterrupt + +/** + * Interrupts a fiber with a specific fiber ID as the interruptor. This allows + * tracking which fiber initiated the interruption. + * + * **When to use** + * + * Use when runtime diagnostics or tracing should attribute the interruption to + * a specific fiber ID. + * + * **Details** + * + * The returned Effect completes only after the interrupted fiber has completed. + * + * **Gotchas** + * + * The supplied ID affects the recorded interruptor. It does not make + * interruption synchronous or force uninterruptible regions to stop early. + * + * **Example** (Interrupting a fiber as another fiber) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * const targetFiber = yield* Effect.forkChild( + * Effect.delay("5 seconds")(Effect.succeed("task completed")) + * ) + * + * // Interrupt the fiber, specifying fiber ID 123 as the interruptor + * yield* Fiber.interruptAs(targetFiber, 123) + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @see {@link interrupt} for using the current fiber as the interruptor + * + * @category interruption + * @since 2.0.0 + */ +export const interruptAs: { + ( + fiberId: number | undefined, + annotations?: Context.Context | undefined + ): (self: Fiber) => Effect + ( + self: Fiber, + fiberId: number | undefined, + annotations?: Context.Context | undefined + ): Effect +} = effect.fiberInterruptAs + +/** + * Interrupts all fibers in the provided iterable, causing them to stop executing + * and clean up any acquired resources. + * + * **When to use** + * + * Use when you need to cancel several forked fibers and wait for their cleanup + * to complete. + * + * **Details** + * + * The current fiber is recorded as the interruptor. The returned Effect + * completes only after all interrupted fibers have completed. + * + * **Gotchas** + * + * Interruption is cooperative for each fiber. The returned Effect can wait for + * uninterruptible work and finalizers in any interrupted fiber. + * + * **Example** (Interrupting multiple fibers) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * 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]) + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @see {@link interruptAllAs} for specifying the interrupting fiber ID + * + * @category interruption + * @since 2.0.0 + */ +export const interruptAll: >>( + fibers: A +) => Effect = effect.fiberInterruptAll + +/** + * Interrupts all fibers in the provided iterable using the specified fiber ID as the + * interrupting fiber. This allows you to control which fiber is considered the source + * of the interruption, which can be useful for debugging and tracing. + * + * **When to use** + * + * Use to interrupt several fibers while recording a specific fiber ID as the + * interruptor. + * + * **Details** + * + * The returned Effect completes only after all interrupted fibers have + * completed. + * + * **Gotchas** + * + * The supplied ID affects the recorded interruptor. It does not make + * interruption synchronous or force uninterruptible regions to stop early. + * + * **Example** (Interrupting multiple fibers as another fiber) + * + * ```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")) + * + * const worker1 = yield* Effect.forkChild(Effect.never) + * const worker2 = yield* Effect.forkChild(Effect.never) + * + * yield* Fiber.interruptAllAs([worker1, worker2], controllerFiber.id) + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @see {@link interruptAll} for using the current fiber as the interruptor + * + * @category interruption + * @since 2.0.0 + */ +export const interruptAllAs: { + (fiberId: number): >>(fibers: A) => Effect + >>(fibers: A, fiberId: number): Effect +} = effect.fiberInterruptAllAs + +/** + * Checks whether a value is a Fiber. This is a type guard that can be used to + * determine if an unknown value is a Fiber instance. + * + * **When to use** + * + * Use when checking values at boundaries where an unknown value may be a + * runtime fiber. + * + * **Details** + * + * The check looks for the internal Fiber type ID marker and does not inspect + * the fiber's current state. + * + * **Example** (Checking for fibers) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a fiber + * const fiber = yield* Effect.forkChild(Effect.succeed(42)) + * + * // Test if values are fibers + * 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 + * @since 2.0.0 + */ +export const isFiber = ( + u: unknown +): u is Fiber => hasProperty(u, effect.FiberTypeId) + +/** + * Returns the current fiber if called from within a fiber context, + * otherwise returns `undefined`. + * + * **When to use** + * + * Use when you need low-level runtime integrations that need access to the currently + * executing fiber. + * + * **Gotchas** + * + * This is a synchronous accessor, not an Effect. It returns `undefined` outside + * an active fiber runtime context. + * + * **Example** (Getting the current fiber) + * + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * + * const program = Effect.gen(function*() { + * const current = Fiber.getCurrent() + * return current !== undefined + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => true + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const getCurrent: () => Fiber | undefined = effect.getCurrentFiber + +/** + * Adds a fiber to a `Scope` and returns the same fiber. + * + * **When to use** + * + * Use when a manually managed fiber should be interrupted when a Scope closes. + * + * **Details** + * + * When the scope is closed, the fiber is interrupted. If the scope is already + * closed, the fiber is interrupted immediately. + * + * **Gotchas** + * + * This does not wait for the fiber to complete. It only registers the + * interruption finalizer and returns the same fiber. + * + * @see {@link interrupt} for interrupting and waiting for completion + * + * @category resource management + * @since 4.0.0 + */ +export const runIn: { + (scope: Scope): (self: Fiber) => Fiber + (self: Fiber, scope: Scope): Fiber +} = effect.fiberRunIn diff --git a/.repos/effect/packages/effect/src/FiberHandle.ts b/.repos/effect/packages/effect/src/FiberHandle.ts new file mode 100644 index 000000000..7a004bfa1 --- /dev/null +++ b/.repos/effect/packages/effect/src/FiberHandle.ts @@ -0,0 +1,851 @@ +/** + * Manages at most one fiber inside a scope. + * + * A `FiberHandle` can hold one `Fiber`. Installing a new fiber + * interrupts the previous one unless the operation is configured with + * `onlyIfMissing`, and closing the owning scope interrupts the current fiber. + * This module includes constructors for handles and scoped runtimes, helpers + * for setting, reading, clearing, and running fibers, and operations for joining + * the current fiber or waiting until the handle is empty. + * + * @since 2.0.0 + */ +import * as Cause from "./Cause.ts" +import type { Context } from "./Context.ts" +import * as Deferred from "./Deferred.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import * as Fiber from "./Fiber.ts" +import * as Filter from "./Filter.ts" +import { dual } from "./Function.ts" +import type * as Inspectable from "./Inspectable.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import type { Scheduler } from "./Scheduler.ts" +import type * as Scope from "./Scope.ts" + +const TypeId = "~effect/FiberHandle" + +/** + * Scoped handle that manages at most one fiber, interrupts the current fiber + * when the handle's scope closes, and removes managed fibers from the handle + * when they complete. + * + * **Example** (Managing a single fiber) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberHandle } from "effect" + * + * 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")) + * return yield* Fiber.join(fiber) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface FiberHandle extends Pipeable, Inspectable.Inspectable { + readonly [TypeId]: typeof TypeId + readonly deferred: Deferred.Deferred + state: { + readonly _tag: "Open" + fiber: Fiber.Fiber | undefined + } | { + readonly _tag: "Closed" + } +} + +/** + * Returns `true` if a value is a `FiberHandle` by checking for the + * `FiberHandle` runtime marker. + * + * **Example** (Checking fiber handles) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * + * return [FiberHandle.isFiberHandle(handle), FiberHandle.isFiberHandle("not a handle")] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isFiberHandle = (u: unknown): u is FiberHandle => Predicate.hasProperty(u, TypeId) + +const Proto = { + [TypeId]: TypeId, + ...PipeInspectableProto, + toJSON(this: FiberHandle) { + return { + _id: "FiberHandle", + state: this.state + } + } +} + +const makeUnsafe = (): FiberHandle => { + const self = Object.create(Proto) + self.state = { _tag: "Open", fiber: undefined } + self.deferred = Deferred.makeUnsafe() + return self +} + +/** + * Creates a scoped `FiberHandle` that can store a single fiber. + * + * **Details** + * + * When the associated `Scope` is closed, the contained fiber will be + * interrupted. You can add a fiber to the handle using `FiberHandle.run`, and + * the fiber will be automatically removed from the `FiberHandle` when it + * completes. + * + * **Example** (Creating a scoped fiber handle) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * + * // run some effects + * yield* FiberHandle.run(handle, Effect.never) + * // this will interrupt the previous fiber + * yield* FiberHandle.run(handle, Effect.never) + * + * 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 + * @since 2.0.0 + */ +export const make = (): Effect.Effect, never, Scope.Scope> => + Effect.acquireRelease( + Effect.sync(() => makeUnsafe()), + (handle) => { + const state = handle.state + if (state._tag === "Closed") return Effect.void + handle.state = { _tag: "Closed" } + return state.fiber ? + Deferred.into( + Effect.asVoid(Fiber.interruptAs(state.fiber, internalFiberId)), + handle.deferred + ) : + Deferred.done(handle.deferred, Exit.void) + } + ) + +/** + * Creates a scoped run function that forks effects into a new `FiberHandle`. + * + * **Details** + * + * Each call returns the forked fiber, stores it in the handle, and interrupts + * the previous fiber unless `onlyIfMissing` is set. The managed fiber is + * interrupted when the handle's scope closes. + * + * **Example** (Running effects with a fiber handle) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Fiber, FiberHandle } from "effect" + * + * const program = Effect.gen(function*() { + * const run = yield* FiberHandle.makeRuntime() + * + * // Run effects and get fibers back + * 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 + * @since 2.0.0 + */ +export const makeRuntime = (): Effect.Effect< + ( + effect: Effect.Effect, + options?: + | { + readonly signal?: AbortSignal | undefined + readonly scheduler?: Scheduler | undefined + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ) => Fiber.Fiber, + never, + Scope.Scope | R +> => + Effect.flatMap( + make(), + (self) => runtime(self)() + ) + +/** + * Creates a scoped run function that forks effects into a new `FiberHandle` + * and returns a `Promise` for each effect result. + * + * **When to use** + * + * Use when integrating a scoped `FiberHandle` runner with Promise-based APIs + * and Promise rejection from squashed failures is the desired boundary. + * + * **Details** + * + * Each call stores the fiber in the handle and interrupts the previous fiber + * unless `onlyIfMissing` is set. The returned Promise resolves with the + * effect's success value or rejects with the squashed failure cause. + * + * **Example** (Running effects as promises) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle } from "effect" + * + * const program = Effect.gen(function*() { + * const run = yield* FiberHandle.makeRuntimePromise() + * + * // Run effects and get promises back + * const promise = run(Effect.succeed("hello")) + * return yield* Effect.promise(() => promise) + * }).pipe(Effect.scoped) + * + * const actual = await Effect.runPromise(program) + * actual // => "hello" + * ``` + * + * @category constructors + * @since 3.13.0 + */ +export const makeRuntimePromise = (): Effect.Effect< + ( + effect: Effect.Effect, + options?: { + readonly signal?: AbortSignal | undefined + readonly scheduler?: Scheduler | undefined + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } | undefined + ) => Promise, + never, + Scope.Scope | R +> => + Effect.flatMap( + make(), + (self) => runtimePromise(self)() + ) + +const internalFiberId = -1 +const isInternalInterruption = Filter.toPredicate(Filter.compose( + Cause.filterInterruptors, + Filter.has(internalFiberId) +)) + +/** + * Sets the fiber in a FiberHandle. When the fiber completes, it will be removed from the FiberHandle. + * If a fiber is already running, it will be interrupted unless `options.onlyIfMissing` is set. + * + * **When to use** + * + * Use when an existing forked fiber must be installed synchronously into a + * handle and immediate interruption of replaced or closed fibers is acceptable. + * + * **Example** (Setting a fiber unsafely) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberHandle } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * const fiber = Effect.runFork(Effect.succeed("hello")) + * + * // Set the fiber directly (unsafe) + * FiberHandle.setUnsafe(handle, fiber) + * + * // The fiber is now managed by the handle + * return yield* Fiber.join(fiber) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const setUnsafe: { + ( + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + ): (self: FiberHandle) => void + ( + self: FiberHandle, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + ): void +} = dual((args) => isFiberHandle(args[0]), ( + self: FiberHandle, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } +): void => { + if (self.state._tag === "Closed") { + fiber.interruptUnsafe(internalFiberId) + return + } else if (self.state.fiber !== undefined) { + if (options?.onlyIfMissing === true) { + fiber.interruptUnsafe(internalFiberId) + return + } else if (self.state.fiber === fiber) { + return + } + self.state.fiber.interruptUnsafe(internalFiberId) + self.state.fiber = undefined + } + + self.state.fiber = fiber + fiber.addObserver((exit) => { + if (self.state._tag === "Open" && fiber === self.state.fiber) { + self.state.fiber = undefined + } + if ( + Exit.isFailure(exit) && + ( + options?.propagateInterruption === true ? + !isInternalInterruption(exit.cause) : + !Cause.hasInterruptsOnly(exit.cause) + ) + ) { + Deferred.doneUnsafe(self.deferred, exit as any) + } + }) +}) + +/** + * Sets the fiber in the `FiberHandle`. + * + * **Details** + * + * When the fiber completes, it will be removed from the `FiberHandle`. If a + * fiber already exists in the `FiberHandle`, it will be interrupted unless + * `options.onlyIfMissing` is set. + * + * **Example** (Setting a fiber safely) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberHandle } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * const fiber = Effect.runFork(Effect.succeed("hello")) + * + * // Set the fiber safely + * yield* FiberHandle.set(handle, fiber) + * + * // The fiber is now managed by the handle + * return yield* Fiber.join(fiber) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const set: { + ( + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean + readonly propagateInterruption?: boolean | undefined + } + ): (self: FiberHandle) => Effect.Effect + ( + self: FiberHandle, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean + readonly propagateInterruption?: boolean | undefined + } + ): Effect.Effect +} = dual((args) => isFiberHandle(args[0]), ( + self: FiberHandle, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean + readonly propagateInterruption?: boolean | undefined + } +): Effect.Effect => + Effect.sync(() => + setUnsafe(self, fiber, { + onlyIfMissing: options?.onlyIfMissing, + propagateInterruption: options?.propagateInterruption + }) + )) + +/** + * Retrieves the fiber from the FiberHandle synchronously. + * + * **When to use** + * + * Use when synchronous inspection of the current fiber is needed and an + * `Option` result is enough outside the Effect workflow. + * + * **Example** (Reading the current fiber unsafely) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * + * // No fiber initially + * const emptyFiber = FiberHandle.getUnsafe(handle) + * + * // Add a fiber + * yield* FiberHandle.run(handle, Effect.never) + * const fiber = FiberHandle.getUnsafe(handle) + * return [emptyFiber, Option.map(fiber, () => true)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [Option.none(), Option.some(true)] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export function getUnsafe(self: FiberHandle): Option.Option> { + return self.state._tag === "Closed" ? Option.none() : Option.fromUndefinedOr(self.state.fiber) +} + +/** + * Retrieves the fiber from the FiberHandle effectfully. + * + * **Example** (Reading the current fiber) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * + * // Add a fiber + * yield* FiberHandle.run(handle, Effect.never) + * + * // Get the current fiber if present + * const fiber = yield* FiberHandle.get(handle) + * return Option.map(fiber, () => true) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.some(true) + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export function get(self: FiberHandle): Effect.Effect>> { + return Effect.suspend(() => Effect.succeed(getUnsafe(self))) +} + +/** + * Interrupts the fiber currently stored in the `FiberHandle`, if any, and + * leaves the handle empty. + * + * **Example** (Clearing a fiber handle) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * + * // Add a fiber + * yield* FiberHandle.run(handle, Effect.never) + * + * // Clear the handle, interrupting the fiber + * yield* FiberHandle.clear(handle) + * + * // The handle is now empty + * return FiberHandle.getUnsafe(handle) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.none() + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const clear = (self: FiberHandle): Effect.Effect => + Effect.uninterruptibleMask((restore) => { + if (self.state._tag === "Closed" || self.state.fiber === undefined) { + return Effect.void + } + const fiber = self.state.fiber + return Effect.andThen( + restore(Fiber.interruptAs(fiber, internalFiberId)), + Effect.sync(() => { + if (self.state._tag === "Open" && self.state.fiber === fiber) { + self.state.fiber = undefined + } + }) + ) + }) + +const constInterruptedFiber = (function() { + let fiber: Fiber.Fiber | undefined = undefined + return () => { + if (fiber === undefined) { + fiber = Effect.runFork(Effect.interrupt) + } + return fiber + } +})() + +/** + * Forks an Effect and stores the resulting fiber in the `FiberHandle`. + * + * **Details** + * + * The handle manages only one fiber: running a new effect interrupts the + * previous fiber unless `onlyIfMissing` is set. When the managed fiber + * completes, it is removed from the handle. + * + * **Example** (Running an effect in a fiber handle) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberHandle } from "effect" + * + * 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.join(fiber) + * + * // Running another effect will interrupt the previous one + * const fiber2 = yield* FiberHandle.run(handle, Effect.succeed("world")) + * const result2 = yield* Fiber.join(fiber2) + * return [result, result2] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const run: { + ( + self: FiberHandle, + options?: { + readonly onlyIfMissing?: boolean + readonly propagateInterruption?: boolean | undefined + readonly startImmediately?: boolean | undefined + } + ): ( + effect: Effect.Effect + ) => Effect.Effect, never, R> + ( + self: FiberHandle, + effect: Effect.Effect, + options?: { + readonly onlyIfMissing?: boolean + readonly propagateInterruption?: boolean | undefined + readonly startImmediately?: boolean | undefined + } + ): Effect.Effect, never, R> +} = function() { + const self = arguments[0] as FiberHandle + if (Effect.isEffect(arguments[1])) { + return runImpl(self, arguments[1], arguments[2]) as any + } + const options = arguments[1] + return (effect: Effect.Effect) => runImpl(self, effect, options) +} + +const runImpl = ( + self: FiberHandle, + effect: Effect.Effect, + options?: { + readonly onlyIfMissing?: boolean | undefined + } +): Effect.Effect, never, R> => + Effect.withFiber((parent) => { + if (self.state._tag === "Closed") { + return Effect.interrupt + } else if (self.state.fiber !== undefined && options?.onlyIfMissing === true) { + return Effect.sync(constInterruptedFiber) + } + const fiber = Effect.runForkWith(parent.context as Context)(effect) + setUnsafe(self, fiber, options) + return Effect.succeed(fiber) + }) + +/** + * Captures the current runtime and returns a function for forking effects into + * an existing `FiberHandle`. + * + * **Details** + * + * Each call returns the forked fiber, stores it in the handle, and interrupts + * the previous fiber unless `onlyIfMissing` is set. + * + * **Example** (Capturing a runtime for fiber handles) + * + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberHandle } from "effect" + * + * class Users extends Context.Service> + * }>()("Users") {} + * + * 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 + * const fiberA = run(Effect.andThen(Users, (_) => _.getAll)) + * + * // this will interrupt the previous fiber + * 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 + * @since 2.0.0 + */ +export const runtime: ( + self: FiberHandle +) => () => Effect.Effect< + ( + effect: Effect.Effect, + options?: + | { + readonly signal?: AbortSignal | undefined + readonly scheduler?: Scheduler | undefined + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ) => Fiber.Fiber, + never, + R +> = (self: FiberHandle) => () => + Effect.map( + Effect.context(), + (services) => { + const runFork = Effect.runForkWith(services) + return ( + effect: Effect.Effect, + options?: + | { + readonly signal?: AbortSignal | undefined + readonly scheduler?: Scheduler | undefined + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ) => { + if (self.state._tag === "Closed") { + return constInterruptedFiber() + } else if (self.state.fiber !== undefined && options?.onlyIfMissing === true) { + return constInterruptedFiber() + } + const fiber = runFork(effect, options) + setUnsafe(self, fiber, options) + return fiber + } + } + ) + +/** + * Captures the current runtime and returns a function for running effects in + * an existing `FiberHandle` as Promises. + * + * **Details** + * + * Each call stores the forked fiber in the handle and interrupts the previous + * fiber unless `onlyIfMissing` is set. The Promise resolves with the effect's + * success value or rejects with the squashed failure cause. + * + * **Example** (Capturing a runtime for promises) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle } from "effect" + * + * 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")) + * return yield* Effect.promise(() => promise) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" + * ``` + * + * @category combinators + * @since 3.13.0 + */ +export const runtimePromise = (self: FiberHandle): () => Effect.Effect< + ( + effect: Effect.Effect, + options?: + | { + readonly signal?: AbortSignal | undefined + readonly scheduler?: Scheduler | undefined + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ) => Promise, + never, + R +> => +() => + Effect.map( + runtime(self)(), + (runFork) => + ( + effect: Effect.Effect, + options?: + | { + readonly signal?: AbortSignal | undefined + readonly scheduler?: Scheduler | undefined + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ): Promise => + new Promise((resolve, reject) => + runFork(effect, options).addObserver((exit) => { + if (Exit.isSuccess(exit)) { + resolve(exit.value) + } else { + reject(Cause.squash(exit.cause)) + } + }) + ) + ) + +/** + * Waits for the `FiberHandle` to fail or close. + * + * **Details** + * + * The returned Effect fails with the first managed fiber failure that is not + * ignored by the handle's interruption rules. Normal successful completion of + * a managed fiber only removes it from the handle; use `awaitEmpty` to wait + * for the current fiber to finish. + * + * **Example** (Propagating fiber failures) + * + * ```ts import.meta.vitest + * import { Effect, Exit, FiberHandle } from "effect" + * + * 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 + * @since 2.0.0 + */ +export const join = (self: FiberHandle): Effect.Effect => + Deferred.await(self.deferred as Deferred.Deferred) + +/** + * Waits for the fiber in the FiberHandle to complete. + * + * **Example** (Waiting for a fiber to complete) + * + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" + * + * const program = Effect.gen(function*() { + * const handle = yield* FiberHandle.make() + * + * yield* FiberHandle.run(handle, Effect.yieldNow) + * + * // Wait for the fiber to complete + * yield* FiberHandle.awaitEmpty(handle) + * + * return yield* FiberHandle.get(handle) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.none() + * ``` + * + * @category combinators + * @since 3.13.0 + */ +export const awaitEmpty = (self: FiberHandle): Effect.Effect => + Effect.suspend(() => { + if (self.state._tag === "Closed" || self.state.fiber === undefined) { + return Effect.void + } + return Fiber.await(self.state.fiber) + }) diff --git a/.repos/effect/packages/effect/src/FiberMap.ts b/.repos/effect/packages/effect/src/FiberMap.ts new file mode 100644 index 000000000..828632ac5 --- /dev/null +++ b/.repos/effect/packages/effect/src/FiberMap.ts @@ -0,0 +1,1057 @@ +/** + * Manages fibers by key inside a scope. + * + * A `FiberMap` owns a map of running fibers, interrupts them when its + * scope closes, and automatically removes each entry when the corresponding + * fiber completes. Use it when a program needs to start, replace, join, or + * interrupt background work by a stable key while keeping all fibers tied to + * one scope. + * + * @since 2.0.0 + */ +import * as Cause from "./Cause.ts" +import type { Context } from "./Context.ts" +import * as Deferred from "./Deferred.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import * as Fiber from "./Fiber.ts" +import * as Filter from "./Filter.ts" +import { constVoid, dual } from "./Function.ts" +import type * as Inspectable from "./Inspectable.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as Iterable from "./Iterable.ts" +import * as MutableHashMap from "./MutableHashMap.ts" +import * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import type * as Scope from "./Scope.ts" + +const TypeId = "~effect/FiberMap" + +/** + * A FiberMap is a collection of fibers, indexed by a key. When the associated + * Scope is closed, all fibers in the map will be interrupted. Fibers are + * automatically removed from the map when they complete. + * + * **Example** (Managing fibers in a map) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * // Create a FiberMap with string keys + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * // Add some fibers to the map + * yield* FiberMap.run(map, "task1", Effect.never) + * yield* FiberMap.run(map, "task2", Effect.never) + * + * // Get the size of the map + * return yield* FiberMap.size(map) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 2 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface FiberMap + extends Pipeable, Inspectable.Inspectable, Iterable<[K, Fiber.Fiber]> +{ + readonly [TypeId]: typeof TypeId + readonly deferred: Deferred.Deferred + state: { + readonly _tag: "Open" + readonly backing: MutableHashMap.MutableHashMap> + } | { + readonly _tag: "Closed" + } +} + +/** + * Returns `true` if a value is a `FiberMap`. + * + * **Details** + * + * This is a type guard that checks for the `FiberMap` runtime marker. + * + * **Example** (Checking if a value is a FiberMap) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * return [FiberMap.isFiberMap(map), FiberMap.isFiberMap({}), FiberMap.isFiberMap(null)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false, false] + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isFiberMap = (u: unknown): u is FiberMap => Predicate.hasProperty(u, TypeId) + +const Proto = { + [TypeId]: TypeId, + [Symbol.iterator](this: FiberMap) { + if (this.state._tag === "Closed") { + return Iterable.empty() + } + return this.state.backing[Symbol.iterator]() + }, + ...PipeInspectableProto, + toJSON(this: FiberMap) { + return { + _id: "FiberMap", + state: this.state + } + } +} + +const makeUnsafe = ( + backing: MutableHashMap.MutableHashMap>, + deferred: Deferred.Deferred +): FiberMap => { + const self = Object.create(Proto) + self.state = { _tag: "Open", backing } + self.deferred = deferred + return self +} + +/** + * Creates a scoped `FiberMap` for storing fibers by key. + * + * **Details** + * + * When the associated Scope is closed, all fibers in the map will be + * interrupted. You can add fibers to the map using `FiberMap.set` or + * `FiberMap.run`, and the fibers will be automatically removed from the + * `FiberMap` when they complete. + * + * **Example** (Creating a scoped FiberMap) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * 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.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 + * @since 2.0.0 + */ +export const make = (): Effect.Effect, never, Scope.Scope> => + Effect.acquireRelease( + Effect.sync(() => + makeUnsafe( + MutableHashMap.empty(), + Deferred.makeUnsafe() + ) + ), + (map) => + Effect.suspend(() => { + const state = map.state + if (state._tag === "Closed") return Effect.void + map.state = { _tag: "Closed" } + return Fiber.interruptAll(MutableHashMap.values(state.backing)).pipe( + Deferred.into(map.deferred) + ) + }) + ) + +/** + * Creates a scoped run function that forks effects into a new `FiberMap`. + * + * **Details** + * + * Each call stores the forked fiber under the supplied key and returns that + * fiber. If the key already has a fiber, the previous fiber is interrupted + * unless `onlyIfMissing` is set. All managed fibers are interrupted when the + * map's scope closes. + * + * **Example** (Creating a scoped runtime) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const run = yield* FiberMap.makeRuntime() + * + * // Run effects and get back fibers + * const fiber1 = run("task1", Effect.succeed("Hello")) + * const fiber2 = run("task2", Effect.succeed("World")) + * + * // Join the fibers to get their successful values + * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const makeRuntime = (): Effect.Effect< + ( + key: K, + effect: Effect.Effect, + options?: + | Effect.RunOptions & { + readonly onlyIfMissing?: boolean | undefined + } + | undefined + ) => Fiber.Fiber, + never, + Scope.Scope | R +> => + Effect.flatMap( + make(), + (self) => runtime(self)() + ) + +/** + * Creates a scoped run function that forks effects into a new `FiberMap` and + * returns a `Promise` for each effect result. + * + * **When to use** + * + * Use when keyed fibers must be managed in a scoped map while exposing their + * results through Promise-based APIs. + * + * **Details** + * + * Each call stores the fiber under the supplied key, interrupting any previous + * fiber for that key unless `onlyIfMissing` is set. The returned Promise + * resolves with the effect's success value or rejects with the squashed failure + * cause. + * + * **Example** (Creating a promise runtime) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const run = yield* FiberMap.makeRuntimePromise() + * + * // Run effects and get back promises + * const promise1 = run("task1", Effect.succeed("Hello")) + * const promise2 = run("task2", Effect.succeed("World")) + * + * // Convert to Effect and await + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World"] + * ``` + * + * @category constructors + * @since 3.13.0 + */ +export const makeRuntimePromise = (): Effect.Effect< + ( + key: K, + effect: Effect.Effect, + options?: + | Effect.RunOptions & { + readonly onlyIfMissing?: boolean | undefined + } + | undefined + ) => Promise, + never, + Scope.Scope | R +> => + Effect.flatMap( + make(), + (self) => runtimePromise(self)() + ) + +const internalFiberId = -1 +const isInternalInterruption = Filter.toPredicate(Filter.compose( + Cause.filterInterruptors, + Filter.has(internalFiberId) +)) + +/** + * Adds a fiber to the `FiberMap` under a key using a synchronous, unsafe + * mutation. + * + * **When to use** + * + * Use when an existing forked fiber must be installed under a key immediately + * and synchronous interruption of the replaced fiber is acceptable. + * + * **Details** + * + * 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. + * + * **Example** (Adding a fiber unsafely) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * const deferred = yield* Deferred.make() + * + * // Create a fiber and add it to the map + * const fiber = yield* Effect.forkChild(Deferred.await(deferred)) + * FiberMap.setUnsafe(map, "greeting", fiber) + * + * yield* Deferred.succeed(deferred, "Hello") + * + * // Join the fiber to get its successful value + * return yield* Fiber.join(fiber) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "Hello" + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const setUnsafe: { + ( + key: K, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } | undefined + ): (self: FiberMap) => void + ( + self: FiberMap, + key: K, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } | undefined + ): void +} = dual((args) => isFiberMap(args[0]), ( + self: FiberMap, + key: K, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } | undefined +): void => { + if (self.state._tag === "Closed") { + fiber.interruptUnsafe(internalFiberId) + return + } + + const previous = MutableHashMap.get(self.state.backing, key) + if (previous._tag === "Some") { + if (options?.onlyIfMissing === true) { + fiber.interruptUnsafe(internalFiberId) + return + } else if (previous.value === fiber) { + return + } + previous.value.interruptUnsafe(internalFiberId) + } + + MutableHashMap.set(self.state.backing, key, fiber) + fiber.addObserver((exit) => { + if (self.state._tag === "Closed") { + return + } + const current = MutableHashMap.get(self.state.backing, key) + if (Option.isSome(current) && fiber === current.value) { + MutableHashMap.remove(self.state.backing, key) + } + if ( + Exit.isFailure(exit) && + ( + options?.propagateInterruption === true ? + !isInternalInterruption(exit.cause) : + !Cause.hasInterruptsOnly(exit.cause) + ) + ) { + Deferred.doneUnsafe(self.deferred, exit as any) + } + }) +}) + +/** + * Adds a fiber to the `FiberMap` under a key. + * + * **Details** + * + * 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. + * + * This is the Effect-wrapped version of `setUnsafe`. + * + * **Example** (Adding a fiber) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * const deferred = yield* Deferred.make() + * + * // Create a fiber and add it to the map using Effect + * const fiber = yield* Effect.forkChild(Deferred.await(deferred)) + * yield* FiberMap.set(map, "greeting", fiber) + * + * yield* Deferred.succeed(deferred, "Hello") + * + * // Join the fiber to get its successful value + * return yield* Fiber.join(fiber) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "Hello" + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const set: { + ( + key: K, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } | undefined + ): (self: FiberMap) => Effect.Effect + ( + self: FiberMap, + key: K, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } | undefined + ): Effect.Effect +} = dual((args) => isFiberMap(args[0]), ( + self: FiberMap, + key: K, + fiber: Fiber.Fiber, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } | undefined +): Effect.Effect => Effect.sync(() => setUnsafe(self, key, fiber, options))) + +/** + * Retrieves a fiber from the FiberMap synchronously. + * + * **When to use** + * + * Use when synchronous keyed lookup of a fiber in a `FiberMap` is needed and an + * `Option` result is enough outside the Effect workflow. + * + * **Example** (Retrieving a fiber unsafely) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap, Option } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * const deferred = yield* Deferred.make() + * + * // Add a fiber to the map + * const fiber = yield* Effect.forkChild(Deferred.await(deferred)) + * FiberMap.setUnsafe(map, "greeting", fiber) + * + * // Retrieve the fiber + * const retrieved = FiberMap.getUnsafe(map, "greeting") + * 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 + * @since 4.0.0 + */ +export const getUnsafe: { + (key: K): (self: FiberMap) => Option.Option> + (self: FiberMap, key: K): Option.Option> +} = dual( + 2, + (self: FiberMap, key: K): Option.Option> => { + return self.state._tag === "Closed" ? Option.none() : MutableHashMap.get(self.state.backing, key) + } +) + +/** + * Retrieves a fiber from the FiberMap effectfully. + * + * **Details** + * + * Returns an `Option` wrapped in `Effect`. + * + * **Example** (Retrieving a fiber) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap, Option } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * const deferred = yield* Deferred.make() + * + * // Add a fiber to the map + * const fiber = yield* Effect.forkChild(Deferred.await(deferred)) + * yield* FiberMap.set(map, "greeting", fiber) + * + * // Retrieve the fiber with error handling + * const retrieved = yield* FiberMap.get(map, "greeting") + * 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 + * @since 2.0.0 + */ +export const get: { + (key: K): (self: FiberMap) => Effect.Effect>> + (self: FiberMap, key: K): Effect.Effect>> +} = dual( + 2, + (self: FiberMap, key: K): Effect.Effect>> => + Effect.suspend(() => Effect.succeed(getUnsafe(self, key))) +) + +/** + * Checks whether a key exists in the FiberMap. + * + * **Example** (Checking if a key exists unsafely) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * // Add a fiber to the map + * yield* FiberMap.run(map, "task1", Effect.never) + * + * // Check if keys exist + * return [FiberMap.hasUnsafe(map, "task1"), FiberMap.hasUnsafe(map, "task2")] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const hasUnsafe: { + (key: K): (self: FiberMap) => boolean + (self: FiberMap, key: K): boolean +} = dual( + 2, + (self: FiberMap, key: K): boolean => + self.state._tag === "Closed" ? false : MutableHashMap.has(self.state.backing, key) +) + +/** + * Checks whether a key exists in the FiberMap. + * This is the Effect-wrapped version of `hasUnsafe`. + * + * **Example** (Checking if a key exists) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * // Add a fiber to the map + * yield* FiberMap.run(map, "task1", Effect.never) + * + * // Check if keys exist using Effect + * return [yield* FiberMap.has(map, "task1"), yield* FiberMap.has(map, "task2")] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const has: { + (key: K): (self: FiberMap) => Effect.Effect + (self: FiberMap, key: K): Effect.Effect +} = dual( + 2, + (self: FiberMap, key: K): Effect.Effect => Effect.sync(() => hasUnsafe(self, key)) +) + +/** + * Removes a fiber from the FiberMap, interrupting it if it exists. + * + * **Example** (Removing a fiber) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * // Add some fibers to the map + * yield* FiberMap.run(map, "task1", Effect.never) + * yield* FiberMap.run(map, "task2", Effect.never) + * + * const sizeBefore = yield* FiberMap.size(map) + * + * // Remove a specific fiber (this will interrupt it) + * yield* FiberMap.remove(map, "task1") + * + * return [sizeBefore, yield* FiberMap.size(map)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [2, 1] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const remove: { + (key: K): (self: FiberMap) => Effect.Effect + (self: FiberMap, key: K): Effect.Effect +} = dual< + ( + key: K + ) => (self: FiberMap) => Effect.Effect, + ( + self: FiberMap, + key: K + ) => Effect.Effect +>(2, (self, key) => + Effect.suspend(() => { + if (self.state._tag === "Closed") { + return Effect.void + } + const fiber = MutableHashMap.get(self.state.backing, key) + if (fiber._tag === "None") { + return Effect.void + } + return Fiber.interruptAs(fiber.value, internalFiberId) + })) + +/** + * Removes all fibers from the FiberMap, interrupting them. + * + * **Example** (Clearing all fibers) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * // Add some fibers to the map + * yield* FiberMap.run(map, "task1", Effect.never) + * yield* FiberMap.run(map, "task2", Effect.never) + * yield* FiberMap.run(map, "task3", Effect.never) + * + * const sizeBefore = yield* FiberMap.size(map) + * + * // Clear all fibers (this will interrupt all of them) + * yield* FiberMap.clear(map) + * + * return [sizeBefore, yield* FiberMap.size(map)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [3, 0] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const clear = (self: FiberMap): Effect.Effect => + Effect.suspend(() => { + if (self.state._tag === "Closed") { + return Effect.void + } + return Fiber.interruptAllAs(MutableHashMap.values(self.state.backing), internalFiberId) + }) + +const constInterruptedFiber = (function() { + let fiber: Fiber.Fiber | undefined = undefined + return () => { + if (fiber === undefined) { + fiber = Effect.runFork(Effect.interrupt) + } + return fiber + } +})() + +/** + * Forks an Effect and stores the resulting fiber in the `FiberMap` under a key. + * + * **Details** + * + * When the fiber completes, it is removed from the map. If the key already has + * a fiber, the previous fiber is interrupted unless `onlyIfMissing` is set. + * + * **Example** (Forking effects into a map) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * // Run effects and add the fibers to the map + * const fiber1 = yield* FiberMap.run(map, "task1", Effect.succeed("Hello")) + * const fiber2 = yield* FiberMap.run(map, "task2", Effect.succeed("World")) + * + * // Join the fibers to get their successful values + * const result1 = yield* Fiber.join(fiber1) + * const result2 = yield* Fiber.join(fiber2) + * return [result1, result2, yield* FiberMap.size(map)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World", 0] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const run: { + ( + self: FiberMap, + key: K, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + readonly startImmediately?: boolean | undefined + } | undefined + ): ( + effect: Effect.Effect + ) => Effect.Effect, never, R> + ( + self: FiberMap, + key: K, + effect: Effect.Effect, + options?: { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + readonly startImmediately?: boolean | undefined + } | undefined + ): Effect.Effect, never, R> +} = function() { + const self = arguments[0] + if (Effect.isEffect(arguments[2])) { + return runImpl(self, arguments[1], arguments[2], arguments[3]) as any + } + const key = arguments[1] + const options = arguments[2] + return (effect: Effect.Effect) => runImpl(self, key, effect, options) +} + +const runImpl = ( + self: FiberMap, + key: K, + effect: Effect.Effect, + options?: { + readonly onlyIfMissing?: boolean + readonly propagateInterruption?: boolean | undefined + } +) => + Effect.withFiber((parent) => { + if (self.state._tag === "Closed") { + return Effect.interrupt + } else if (options?.onlyIfMissing === true && hasUnsafe(self, key)) { + return Effect.sync(constInterruptedFiber) + } + const fiber = Effect.runForkWith(parent.context as Context)(effect) + setUnsafe(self, key, fiber, options) + return Effect.succeed(fiber) + }) + +/** + * Captures the current runtime and returns a function for forking effects into + * an existing `FiberMap`. + * + * **Details** + * + * Each call stores the forked fiber under the supplied key. If that key already + * has a fiber, the previous fiber is interrupted unless `onlyIfMissing` is set. + * + * **Example** (Capturing a runtime) + * + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberMap } from "effect" + * + * class Users extends Context.Service> + * }>()("Users") {} + * + * 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 + * 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 + * @since 2.0.0 + */ +export const runtime: ( + self: FiberMap +) => () => Effect.Effect< + ( + key: K, + effect: Effect.Effect, + options?: + | Effect.RunOptions & { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ) => Fiber.Fiber, + never, + R +> = (self: FiberMap) => () => + Effect.map( + Effect.context(), + (services) => { + const runFork = Effect.runForkWith(services) + return ( + key: K, + effect: Effect.Effect, + options?: + | Effect.RunOptions & { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ) => { + if (self.state._tag === "Closed") { + return constInterruptedFiber() + } else if (options?.onlyIfMissing === true && hasUnsafe(self, key)) { + return constInterruptedFiber() + } + const fiber = runFork(effect, options) + setUnsafe(self, key, fiber, options) + return fiber + } + } + ) + +/** + * Captures the current runtime and returns a function for running effects in + * an existing `FiberMap` as Promises. + * + * **Details** + * + * Each call stores the forked fiber under the supplied key, interrupting any + * previous fiber for that key unless `onlyIfMissing` is set. The Promise + * resolves with the effect's success value or rejects with the squashed failure + * cause. + * + * **Example** (Running effects as promises) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * const runPromise = yield* FiberMap.runtimePromise(map)() + * + * // Create promises that will be backed by fibers in the map + * const promise1 = runPromise("task1", Effect.succeed("Hello")) + * const promise2 = runPromise("task2", Effect.succeed("World")) + * + * // Convert promises back to Effects and await + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World"] + * ``` + * + * @category combinators + * @since 3.13.0 + */ +export const runtimePromise = (self: FiberMap): () => Effect.Effect< + ( + key: K, + effect: Effect.Effect, + options?: + | Effect.RunOptions & { + readonly onlyIfMissing?: boolean | undefined + readonly propagateInterruption?: boolean | undefined + } + | undefined + ) => Promise, + never, + R +> => +() => + Effect.map( + runtime(self)(), + (runFork) => + ( + key: K, + effect: Effect.Effect, + options?: + | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined } + | undefined + ): Promise => + new Promise((resolve, reject) => + runFork(key, effect, options).addObserver((exit) => { + if (Exit.isSuccess(exit)) { + resolve(exit.value) + } else { + reject(Cause.squash(exit.cause)) + } + }) + ) + ) + +/** + * Gets the number of fibers currently in the FiberMap. + * + * **Example** (Checking the map size) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * const sizeBefore = yield* FiberMap.size(map) + * + * // Add some fibers + * yield* FiberMap.run(map, "task1", Effect.never) + * yield* FiberMap.run(map, "task2", Effect.never) + * + * return [sizeBefore, yield* FiberMap.size(map)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [0, 2] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const size = (self: FiberMap): Effect.Effect => + Effect.sync(() => self.state._tag === "Closed" ? 0 : MutableHashMap.size(self.state.backing)) + +/** + * Waits for the `FiberMap` to fail or close. + * + * **Details** + * + * The returned Effect fails with the first managed fiber failure that is not + * ignored by the map's interruption rules. Normal successful completion + * removes fibers from the map; use `awaitEmpty` to wait until the map has no + * fibers. + * + * **Example** (Joining failing fibers) + * + * ```ts import.meta.vitest + * import { Effect, Exit, FiberMap } from "effect" + * + * 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 + * @since 2.0.0 + */ +export const join = (self: FiberMap): Effect.Effect => + Deferred.await(self.deferred as Deferred.Deferred) + +/** + * Waits for the FiberMap to be empty. + * This will wait for all currently running fibers to complete. + * + * **Example** (Waiting for an empty map) + * + * ```ts import.meta.vitest + * import { Effect, FiberMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* FiberMap.make() + * + * 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) + * + * return yield* FiberMap.size(map) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 0 + * ``` + * + * @category combinators + * @since 3.13.0 + */ +export const awaitEmpty = (self: FiberMap): Effect.Effect => + Effect.whileLoop({ + while: () => self.state._tag === "Open" && MutableHashMap.size(self.state.backing) > 0, + body: () => Fiber.await(Iterable.headUnsafe(self)[1]), + step: constVoid + }) diff --git a/.repos/effect/packages/effect/src/FiberSet.ts b/.repos/effect/packages/effect/src/FiberSet.ts new file mode 100644 index 000000000..f99040716 --- /dev/null +++ b/.repos/effect/packages/effect/src/FiberSet.ts @@ -0,0 +1,743 @@ +/** + * Manages many fibers together inside one scope. + * + * A `FiberSet` tracks running fibers, removes each fiber when it + * completes, and interrupts all still-running fibers when the owning scope + * closes. This module includes scoped runtime constructors plus helpers for + * adding, clearing, running, counting, joining, and waiting for managed fibers. + * + * @since 2.0.0 + */ +import * as Cause from "./Cause.ts" +import type { Context } from "./Context.ts" +import * as Deferred from "./Deferred.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import * as Fiber from "./Fiber.ts" +import * as Filter from "./Filter.ts" +import { constVoid, dual } from "./Function.ts" +import type * as Inspectable from "./Inspectable.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as Iterable from "./Iterable.ts" +import type { Pipeable } from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import type * as Scope from "./Scope.ts" + +const TypeId = "~effect/FiberSet" + +/** + * A FiberSet is a collection of fibers that can be managed together. + * When the associated Scope is closed, all fibers in the set will be interrupted. + * + * **Example** (Managing fibers in a set) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set = yield* FiberSet.make() + * + * // Add fibers to the set + * yield* FiberSet.run(set, Effect.succeed("hello")) + * yield* FiberSet.run(set, Effect.succeed("world")) + * + * // 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 + * @since 2.0.0 + */ +export interface FiberSet + extends Pipeable, Inspectable.Inspectable, Iterable> +{ + readonly [TypeId]: typeof TypeId + readonly deferred: Deferred.Deferred + state: { + readonly _tag: "Open" + readonly backing: Set> + } | { + readonly _tag: "Closed" + } +} + +/** + * Checks whether a value is a FiberSet. + * + * **Example** (Checking if a value is a FiberSet) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set = yield* FiberSet.make() + * + * return [FiberSet.isFiberSet(set), FiberSet.isFiberSet({})] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isFiberSet = (u: unknown): u is FiberSet => Predicate.hasProperty(u, TypeId) + +const Proto = { + [TypeId]: TypeId, + [Symbol.iterator](this: FiberSet) { + if (this.state._tag === "Closed") { + return Iterable.empty() + } + return this.state.backing[Symbol.iterator]() + }, + ...PipeInspectableProto, + toJSON(this: FiberSet) { + return { + _id: "FiberSet", + state: this.state + } + } +} + +const makeUnsafe = ( + backing: Set>, + deferred: Deferred.Deferred +): FiberSet => { + const self = Object.create(Proto) + self.state = { _tag: "Open", backing } + self.deferred = deferred + return self +} + +/** + * Creates a scoped `FiberSet` for storing fibers. + * + * **Details** + * + * When the associated Scope is closed, all fibers in the set will be + * interrupted. You can add fibers to the set using `FiberSet.add` or + * `FiberSet.run`, and the fibers will be automatically removed from the + * FiberSet when they complete. + * + * **Example** (Creating a scoped FiberSet) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * 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.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 + * @since 2.0.0 + */ +export const make = (): Effect.Effect, never, Scope.Scope> => + Effect.acquireRelease( + Effect.sync(() => makeUnsafe(new Set(), Deferred.makeUnsafe())), + (set) => + Effect.suspend(() => { + const state = set.state + if (state._tag === "Closed") return Effect.void + set.state = { _tag: "Closed" } + const fibers = state.backing + return Fiber.interruptAll(fibers).pipe( + Deferred.into(set.deferred) + ) + }) + ) + +/** + * Creates a scoped run function that forks effects into a new `FiberSet`. + * + * **Details** + * + * Each call returns the forked fiber and adds it to the set. Managed fibers are + * removed when they complete and are interrupted when the set's scope closes. + * + * **Example** (Creating a scoped runtime) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const runFork = yield* FiberSet.makeRuntime() + * + * // Fork effects using the runtime + * const fiber1 = runFork(Effect.succeed("hello")) + * const fiber2 = runFork(Effect.succeed("world")) + * + * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const makeRuntime = (): Effect.Effect< + (( + effect: Effect.Effect, + options?: (Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }) | undefined + ) => Fiber.Fiber), + never, + Scope.Scope | R +> => + Effect.flatMap( + make(), + (self) => runtime(self)() + ) + +/** + * Creates a scoped run function that forks effects into a new `FiberSet` and + * returns a `Promise` for each effect result. + * + * **When to use** + * + * Use when many scoped fibers should be tracked as a set while exposing each + * result through Promise-based APIs. + * + * **Details** + * + * Managed fibers are removed when they complete and are interrupted when the + * set's scope closes. Each Promise resolves with the effect's success value or + * rejects with the squashed failure cause. + * + * **Example** (Creating a promise runtime) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const runPromise = yield* FiberSet.makeRuntimePromise() + * + * // Run effects as promises + * const promise1 = runPromise(Effect.succeed("hello")) + * const promise2 = runPromise(Effect.succeed("world")) + * + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] + * ``` + * + * @category constructors + * @since 3.13.0 + */ +export const makeRuntimePromise = (): Effect.Effect< + (( + effect: Effect.Effect, + options?: (Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }) | undefined + ) => Promise), + never, + R | Scope.Scope +> => + Effect.flatMap( + make(), + (self) => runtimePromise(self)() + ) + +const internalFiberId = -1 +const isInternalInterruption = Filter.toPredicate(Filter.compose( + Cause.filterInterruptors, + Filter.has(internalFiberId) +)) + +/** + * Adds an existing fiber to the `FiberSet` using a synchronous, unsafe + * mutation. + * + * **When to use** + * + * Use when an already forked fiber must be registered immediately and + * synchronous interruption on a closed set is acceptable. + * + * **Details** + * + * When the fiber completes, it is removed from the set. If the set is already + * closed, the supplied fiber is interrupted immediately. Non-interruption + * failures are recorded for `FiberSet.join`. + * + * **Example** (Adding a fiber unsafely) + * + * ```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.never) + * + * // Unsafe add - doesn't return an Effect + * FiberSet.addUnsafe(set, fiber) + * + * // The fiber is now managed by the set + * return yield* FiberSet.size(set) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 1 + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const addUnsafe: { + ( + fiber: Fiber.Fiber, + options?: { + readonly propagateInterruption?: boolean | undefined + } | undefined + ): (self: FiberSet) => void + ( + self: FiberSet, + fiber: Fiber.Fiber, + options?: { + readonly propagateInterruption?: boolean | undefined + } | undefined + ): void +} = dual((args) => isFiberSet(args[0]), ( + self: FiberSet, + fiber: Fiber.Fiber, + options?: { + readonly propagateInterruption?: boolean | undefined + } | undefined +): void => { + if (self.state._tag === "Closed") { + fiber.interruptUnsafe(internalFiberId) + return + } else if (self.state.backing.has(fiber)) { + return + } + self.state.backing.add(fiber) + fiber.addObserver((exit) => { + if (self.state._tag === "Closed") { + return + } + self.state.backing.delete(fiber) + if ( + Exit.isFailure(exit) && + ( + options?.propagateInterruption === true ? + !isInternalInterruption(exit.cause) : + !Cause.hasInterruptsOnly(exit.cause) + ) + ) { + Deferred.doneUnsafe(self.deferred, exit as any) + } + }) +}) + +/** + * Adds a fiber to the FiberSet. When the fiber completes, it will be removed. + * + * **Example** (Adding a fiber) + * + * ```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.never) + * + * // Add the fiber to the set + * yield* FiberSet.add(set, fiber) + * + * // The fiber is now managed by the set + * return yield* FiberSet.size(set) + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 1 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const add: { + ( + fiber: Fiber.Fiber, + options?: { + readonly propagateInterruption?: boolean | undefined + } | undefined + ): (self: FiberSet) => Effect.Effect + ( + self: FiberSet, + fiber: Fiber.Fiber, + options?: { + readonly propagateInterruption?: boolean | undefined + } | undefined + ): Effect.Effect +} = dual( + (args) => isFiberSet(args[0]), + ( + self: FiberSet, + fiber: Fiber.Fiber, + options?: { + readonly propagateInterruption?: boolean | undefined + } | undefined + ): Effect.Effect => Effect.sync(() => addUnsafe(self, fiber, options)) +) + +/** + * Interrupts all fibers in the `FiberSet` and clears the set. + * + * **Example** (Clearing all fibers) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set = yield* FiberSet.make() + * + * // Add some fibers + * yield* FiberSet.run(set, Effect.never) + * yield* FiberSet.run(set, Effect.never) + * + * const sizeBefore = yield* FiberSet.size(set) + * + * // Clear all fibers + * yield* FiberSet.clear(set) + * + * return [sizeBefore, yield* FiberSet.size(set)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [2, 0] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const clear = (self: FiberSet): Effect.Effect => + Effect.suspend(() => { + if (self.state._tag === "Closed") { + return Effect.void + } + return Fiber.interruptAllAs(self.state.backing, internalFiberId) + }) + +const constInterruptedFiber = (function() { + let fiber: Fiber.Fiber | undefined = undefined + return () => { + if (fiber === undefined) { + fiber = Effect.runFork(Effect.interrupt) + } + return fiber + } +})() + +/** + * Forks an Effect and add the forked fiber to the FiberSet. + * When the fiber completes, it will be removed from the FiberSet. + * + * **Example** (Forking effects into a set) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set = yield* FiberSet.make() + * + * // Fork and add to set + * const fiber1 = yield* FiberSet.run(set, Effect.succeed("hello")) + * const fiber2 = yield* FiberSet.run(set, Effect.succeed("world")) + * + * // Get results + * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const run: { + ( + self: FiberSet, + options?: { + readonly propagateInterruption?: boolean | undefined + readonly startImmediately?: boolean | undefined + } | undefined + ): ( + effect: Effect.Effect + ) => Effect.Effect, never, R> + ( + self: FiberSet, + effect: Effect.Effect, + options?: { + readonly propagateInterruption?: boolean | undefined + readonly startImmediately?: boolean | undefined + } | undefined + ): Effect.Effect, never, R> +} = function() { + const self = arguments[0] as FiberSet + if (!Effect.isEffect(arguments[1])) { + const options = arguments[1] + return (effect: Effect.Effect) => runImpl(self, effect, options) + } + return runImpl(self, arguments[1], arguments[2]) as any +} + +const runImpl = ( + self: FiberSet, + effect: Effect.Effect, + options?: { + readonly propagateInterruption?: boolean | undefined + } +): Effect.Effect, never, R> => + Effect.withFiber((parent) => { + if (self.state._tag === "Closed") { + return Effect.sync(constInterruptedFiber) + } + const fiber = Effect.runForkWith(parent.context as Context)(effect) + addUnsafe(self, fiber, options) + return Effect.succeed(fiber) + }) + +/** + * Captures a `Runtime` and uses it to fork effects into the `FiberSet`. + * + * **Example** (Capturing a runtime) + * + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberSet } from "effect" + * + * class Users extends Context.Service> + * }>()("Users") {} + * + * 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 + * 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 + * @since 2.0.0 + */ +export const runtime: ( + self: FiberSet +) => () => Effect.Effect< + ( + effect: Effect.Effect, + options?: + | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined } + | undefined + ) => Fiber.Fiber, + never, + R +> = (self: FiberSet) => () => + Effect.map( + Effect.context(), + (services) => { + const runFork = Effect.runForkWith(services) + return ( + effect: Effect.Effect, + options?: + | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined } + | undefined + ) => { + if (self.state._tag === "Closed") { + return constInterruptedFiber() + } + const fiber = runFork(effect, options) + addUnsafe(self, fiber, options) + return fiber + } + } + ) + +/** + * Captures a `Runtime` and returns a Promise-based runner that forks effects + * into the `FiberSet`. + * + * **When to use** + * + * Use when you need to bridge effects to `Promise` values while still tracking + * their fibers in a `FiberSet`. + * + * **Details** + * + * The returned run function returns a `Promise` for each effect result. + * + * **Example** (Running effects as promises) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set = yield* FiberSet.make() + * const runPromise = yield* FiberSet.runtimePromise(set)() + * + * // Run effects as promises + * const promise1 = runPromise(Effect.succeed("hello")) + * const promise2 = runPromise(Effect.succeed("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` + * + * @category combinators + * @since 3.13.0 + */ +export const runtimePromise = (self: FiberSet): () => Effect.Effect< + ( + effect: Effect.Effect, + options?: + | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined } + | undefined + ) => Promise, + never, + R +> => +() => + Effect.map( + runtime(self)(), + (runFork) => + ( + effect: Effect.Effect, + options?: + | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined } + | undefined + ): Promise => + new Promise((resolve, reject) => + runFork(effect, options).addObserver((exit) => { + if (Exit.isSuccess(exit)) { + resolve(exit.value) + } else { + reject(Cause.squash(exit.cause)) + } + }) + ) + ) + +/** + * Gets the number of fibers currently in the FiberSet. + * + * **Example** (Checking the set size) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set = yield* FiberSet.make() + * + * const sizeBefore = yield* FiberSet.size(set) + * + * // Add some fibers + * yield* FiberSet.run(set, Effect.never) + * yield* FiberSet.run(set, Effect.never) + * + * return [sizeBefore, yield* FiberSet.size(set)] + * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [0, 2] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const size = (self: FiberSet): Effect.Effect => + Effect.sync(() => self.state._tag === "Closed" ? 0 : self.state.backing.size) + +/** + * Joins all fibers in the FiberSet. If any fiber in the set terminates with a failure, + * the returned Effect will terminate with the first failure that occurred. + * + * **Example** (Joining failing fibers) + * + * ```ts import.meta.vitest + * import { Effect, Exit, FiberSet } from "effect" + * + * 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 + * @since 2.0.0 + */ +export const join = (self: FiberSet): Effect.Effect => + Deferred.await(self.deferred as Deferred.Deferred) + +/** + * Waits until the fiber set is empty. + * + * **Example** (Waiting for an empty set) + * + * ```ts import.meta.vitest + * import { Effect, FiberSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set = yield* FiberSet.make() + * + * yield* FiberSet.run(set, Effect.yieldNow) + * yield* FiberSet.run(set, Effect.yieldNow) + * + * // 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 combinators + * @since 3.13.0 + */ +export const awaitEmpty = (self: FiberSet): Effect.Effect => + Effect.whileLoop({ + while: () => self.state._tag === "Open" && self.state.backing.size > 0, + body: () => Fiber.await(Iterable.headUnsafe(self)), + step: constVoid + }) diff --git a/.repos/effect/packages/effect/src/FileSystem.ts b/.repos/effect/packages/effect/src/FileSystem.ts new file mode 100644 index 000000000..5cca2154d --- /dev/null +++ b/.repos/effect/packages/effect/src/FileSystem.ts @@ -0,0 +1,1299 @@ +/** + * Defines the portable file system service for Effect programs. + * + * `FileSystem` is the boundary between Effect code and the host file system. + * 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. + * + * @since 4.0.0 + */ +import * as Arr from "./Array.ts" +import type * as Brand from "./Brand.ts" +import * as Cause from "./Cause.ts" +import * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import { pipe } from "./Function.ts" +import * as Layer from "./Layer.ts" +import * as Option from "./Option.ts" +import { badArgument, type PlatformError, systemError } from "./PlatformError.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Pull from "./Pull.ts" +import type { Scope } from "./Scope.ts" +import * as Sink from "./Sink.ts" +import * as Stream from "./Stream.ts" + +const TypeId = "~effect/platform/FileSystem" + +/** + * Core interface for file system operations in Effect. + * + * **Details** + * + * The FileSystem interface provides a comprehensive set of file and directory operations + * that work cross-platform. All operations return Effect values that can be composed, + * transformed, and executed safely with proper error handling. + * + * **Example** (Accessing file system operations) + * + * ```ts import.meta.vitest + * import { Effect, FileSystem } from "effect" + * + * const fileSystem = FileSystem.makeNoop({ + * exists: () => Effect.succeed(true), + * makeDirectory: () => Effect.void, + * stat: () => Effect.succeed({ size: FileSystem.Size(22) } as FileSystem.File.Info), + * readFileString: () => Effect.succeed("{\"env\": \"development\"}") + * }) + * + * const program = Effect.gen(function*() { + * const fs = yield* FileSystem.FileSystem + * + * // Basic file operations + * const exists = yield* fs.exists("./config.json") + * if (!exists) { + * yield* fs.writeFileString("./config.json", "{\"env\": \"development\"}") + * } + * + * // Directory operations + * yield* fs.makeDirectory("./logs", { recursive: true }) + * + * // File information + * const stats = yield* fs.stat("./config.json") + * // Read the file contents + * const content = yield* fs.readFileString("./config.json") + * return { size: stats.size, content } + * }) + * + * const result = Effect.runSync(Effect.provideService(program, FileSystem.FileSystem, fileSystem)) + * result.size // => 22n + * result.content // => "{\"env\": \"development\"}" + * ``` + * + * @category services + * @since 4.0.0 + */ +export interface FileSystem { + readonly [TypeId]: typeof TypeId + + /** + * Checks whether a file can be accessed. + * You can optionally specify the level of access to check for. + */ + readonly access: ( + path: string, + options?: { + readonly ok?: boolean | undefined + readonly readable?: boolean | undefined + readonly writable?: boolean | undefined + } + ) => Effect.Effect + /** + * Copy a file or directory from `fromPath` to `toPath`. + * + * **Details** + * + * Equivalent to `cp -r`. + */ + readonly copy: ( + fromPath: string, + toPath: string, + options?: { + readonly overwrite?: boolean | undefined + readonly preserveTimestamps?: boolean | undefined + } + ) => Effect.Effect + /** + * Copy a file from `fromPath` to `toPath`. + */ + readonly copyFile: ( + fromPath: string, + toPath: string + ) => Effect.Effect + /** + * Change the permissions of a file. + */ + readonly chmod: ( + path: string, + mode: number + ) => Effect.Effect + /** + * Change the owner and group of a file. + */ + readonly chown: ( + path: string, + 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. + */ + readonly exists: ( + path: string + ) => Effect.Effect + /** + * Create a hard link from `fromPath` to `toPath`. + */ + readonly link: ( + fromPath: string, + toPath: string + ) => Effect.Effect + /** + * Create a directory at `path`. You can optionally specify the mode and + * whether to recursively create nested directories. + */ + readonly makeDirectory: ( + path: string, + options?: { + readonly recursive?: boolean | undefined + readonly mode?: number | undefined + } + ) => Effect.Effect + /** + * Create a temporary directory. + * + * **Details** + * + * By default the directory will be created inside the system's default + * temporary directory, but you can specify a different location by setting + * the `directory` option. + * + * You can also specify a prefix for the directory name by setting the + * `prefix` option. + */ + readonly makeTempDirectory: (options?: { + readonly directory?: string | undefined + readonly prefix?: string | undefined + }) => Effect.Effect + /** + * Create a temporary directory inside a scope. + * + * **Details** + * + * Functionally equivalent to `makeTempDirectory`, but the directory will be + * automatically deleted when the scope is closed. + */ + readonly makeTempDirectoryScoped: (options?: { + readonly directory?: string | undefined + readonly prefix?: string | undefined + }) => Effect.Effect + /** + * Create a temporary file. + * The directory creation is functionally equivalent to `makeTempDirectory`. + * The file name will be a randomly generated string. + */ + readonly makeTempFile: (options?: { + readonly directory?: string | undefined + readonly prefix?: string | undefined + readonly suffix?: string | undefined + }) => Effect.Effect + /** + * Create a temporary file inside a scope. + * + * **Details** + * + * Functionally equivalent to `makeTempFile`, but the file will be + * automatically deleted when the scope is closed. + */ + readonly makeTempFileScoped: (options?: { + readonly directory?: string | undefined + readonly prefix?: string | undefined + readonly suffix?: string | undefined + }) => Effect.Effect + /** + * Open a file at `path` with the specified `options`. + * + * **Details** + * + * The file handle will be automatically closed when the scope is closed. + */ + readonly open: ( + path: string, + options?: { + readonly flag?: OpenFlag | undefined + readonly mode?: number | undefined + } + ) => Effect.Effect + /** + * List the contents of a directory. + * + * **Details** + * + * You can recursively list the contents of nested directories by setting the + * `recursive` option. + */ + readonly readDirectory: ( + path: string, + options?: { + readonly recursive?: boolean | undefined + } + ) => Effect.Effect, PlatformError> + /** + * Read the contents of a file. + */ + readonly readFile: ( + path: string + ) => Effect.Effect + /** + * Read the contents of a file. + */ + readonly readFileString: ( + path: string, + encoding?: string + ) => Effect.Effect + /** + * Read the destination of a symbolic link. + */ + readonly readLink: ( + path: string + ) => Effect.Effect + /** + * Resolve a path to its canonicalized absolute pathname. + */ + readonly realPath: ( + path: string + ) => Effect.Effect + /** + * Remove a file or directory. + */ + readonly remove: ( + path: string, + options?: { + /** + * When `true`, you can recursively remove nested directories. + */ + readonly recursive?: boolean | undefined + /** + * When `true`, exceptions will be ignored if `path` does not exist. + */ + readonly force?: boolean | undefined + } + ) => Effect.Effect + /** + * Rename a file or directory. + */ + readonly rename: ( + oldPath: string, + newPath: string + ) => Effect.Effect + /** + * Create a writable `Sink` for the specified `path`. + */ + readonly sink: ( + path: string, + options?: { + readonly flag?: OpenFlag | undefined + readonly mode?: number | undefined + } + ) => Sink.Sink + /** + * Get information about a file at `path`. + */ + readonly stat: ( + path: string + ) => Effect.Effect + /** + * Create a readable `Stream` for the specified `path`. + * + * **Details** + * + * Changing the `bufferSize` option will change the internal buffer size of + * the stream. It defaults to `4`. + * + * The `chunkSize` option will change the size of the chunks emitted by the + * stream. It defaults to 64kb. + * + * Changing `offset` and `bytesToRead` will change the offset and the number + * of bytes to read from the file. + */ + readonly stream: ( + path: string, + options?: { + readonly bytesToRead?: SizeInput | undefined + readonly chunkSize?: SizeInput | undefined + readonly offset?: SizeInput | undefined + } + ) => Stream.Stream + /** + * Create a symbolic link from `fromPath` to `toPath`. + */ + readonly symlink: ( + fromPath: string, + toPath: string + ) => Effect.Effect + /** + * Truncate a file to a specified length. If the `length` is not specified, + * the file will be truncated to length `0`. + */ + readonly truncate: ( + path: string, + length?: SizeInput + ) => Effect.Effect + /** + * Change the file system timestamps of the file at `path`. + */ + readonly utimes: ( + path: string, + atime: Date | number, + mtime: Date | number + ) => Effect.Effect + /** + * 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, options?: WatchOptions) => Stream.Stream + /** + * Write data to a file at `path`. + */ + readonly writeFile: ( + path: string, + data: Uint8Array, + options?: { + readonly flag?: OpenFlag | undefined + readonly mode?: number | undefined + } + ) => Effect.Effect + /** + * Write a string to a file at `path`. + */ + readonly writeFileString: ( + path: string, + data: string, + options?: { + readonly flag?: OpenFlag | undefined + readonly mode?: number | undefined + } + ) => 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.meta.vitest + * import { FileSystem } from "effect" + * + * FileSystem.Size(1024) // => 1024n + * FileSystem.Size(BigInt("9007199254740992")) // => 9007199254740992n + * ``` + * + * @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.meta.vitest + * import { FileSystem } from "effect" + * + * const inputs: ReadonlyArray = [ + * 1024, + * 2048n, + * FileSystem.Size(4096) + * ] + * inputs.map(FileSystem.Size) // => [1024n, 2048n, 4096n] + * ``` + * + * @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.meta.vitest + * import { FileSystem } from "effect" + * + * // From number + * const size1 = FileSystem.Size(1024) + * typeof size1 // => "bigint" + * + * // From bigint + * const size2 = FileSystem.Size(BigInt(2048)) + * + * // From existing Size (identity) + * const size3 = FileSystem.Size(size1) + * const sizes = [size2, size3] // => [2048n, 1024n] + * ``` + * + * @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.meta.vitest + * import { FileSystem } from "effect" + * + * FileSystem.KiB(64) // => 65536n + * FileSystem.KiB(100) // => 102400n + * ``` + * + * @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.meta.vitest + * import { FileSystem } from "effect" + * + * FileSystem.MiB(10) // => 10485760n + * FileSystem.MiB(100) // => 104857600n + * ``` + * + * @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.meta.vitest + * import { FileSystem } from "effect" + * + * FileSystem.GiB(1) // => 1073741824n + * ``` + * + * @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.meta.vitest + * import { FileSystem } from "effect" + * + * FileSystem.TiB(1) // => 1099511627776n + * ``` + * + * @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.meta.vitest + * import { FileSystem } from "effect" + * + * FileSystem.PiB(2) // => 2251799813685248n + * ``` + * + * @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. + * + * **Details** + * + * These flags correspond to standard POSIX file open modes and control the file access + * permissions and behavior when opening files. + * + * - `"r"` - Read-only. File must exist. + * - `"r+"` - Read/write. File must exist. + * - `"w"` - Write-only. Truncates file to zero length or creates new file. + * - `"wx"` - Like 'w' but fails if file exists. + * - `"w+"` - Read/write. Truncates file to zero length or creates new file. + * - `"wx+"` - Like 'w+' but fails if file exists. + * - `"a"` - Write-only. Appends to file or creates new file. + * - `"ax"` - Like 'a' but fails if file exists. + * - `"a+"` - Read/write. Appends to file or creates new file. + * - `"ax+"` - Like 'a+' but fails if file exists. + * + * **Example** (Opening files with flags) + * + * ```ts import.meta.vitest + * import type { FileSystem } from "effect" + * + * const flags: ReadonlyArray = ["r", "w", "a", "r+"] + * flags // => ["r", "w", "a", "r+"] + * ``` + * + * @category models + * @since 4.0.0 + */ +export type OpenFlag = + | "r" + | "r+" + | "w" + | "wx" + | "w+" + | "wx+" + | "a" + | "ax" + | "a+" + | "ax+" + +/** + * Service tag for platform file-system operations. + * + * **When to use** + * + * Use to access or provide operations for files, directories, permissions, + * streams, and sinks through the Effect context. + * + * **Details** + * + * This key is used to provide and access the FileSystem service in the Effect context. + * + * **Example** (Accessing and providing FileSystem) + * + * ```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") + * return exists ? yield* fs.readFileString("./data.txt") : undefined + * }) + * + * 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") + +/** + * Creates a FileSystem implementation from a partial implementation. + * + * **When to use** + * + * Use to build a concrete `FileSystem` service from platform-specific core + * operations while deriving the convenience methods that can be implemented + * from them. + * + * **Details** + * + * This function takes a partial FileSystem implementation and automatically provides + * default implementations for `exists`, `readFileString`, `stream`, `sink`, and + * `writeFileString` methods based on the provided core methods. + * + * @see {@link makeNoop} for a testing stub that accepts method overrides without requiring a complete implementation + * @see {@link layerNoop} for providing a no-op `FileSystem` as a `Layer` in tests + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + impl: Omit +): FileSystem => + FileSystem.of({ + ...impl, + [TypeId]: TypeId, + exists: (path) => + pipe( + impl.access(path), + Effect.as(true), + Effect.catchTag( + "PlatformError", + (e) => e.reason._tag === "NotFound" ? Effect.succeed(false) : Effect.fail(e) + ) + ), + readFileString: (path, encoding) => + Effect.flatMap(impl.readFile(path), (_) => + Effect.try({ + try: () => new TextDecoder(encoding).decode(_), + catch: (cause) => + badArgument({ + module: "FileSystem", + method: "readFileString", + description: "invalid encoding", + cause + }) + })), + stream: Effect.fnUntraced(function*(path, options) { + const file = yield* impl.open(path, { flag: "r" }) + if (options?.offset) { + yield* file.seek(options.offset, "start") + } + const bytesToRead = options?.bytesToRead !== undefined ? Size(options.bytesToRead) : undefined + let totalBytesRead = BigInt(0) + const chunkSize = Size(options?.chunkSize ?? 64 * 1024) + const readChunk = file.readAlloc(chunkSize) + return Stream.fromPull(Effect.succeed( + Effect.flatMap( + Effect.suspend((): Pull.Pull, PlatformError> => { + if (bytesToRead !== undefined && bytesToRead <= totalBytesRead) { + return Cause.done() + } + return bytesToRead !== undefined && (bytesToRead - totalBytesRead) < chunkSize + ? file.readAlloc(bytesToRead - totalBytesRead) + : readChunk + }), + Option.match({ + onNone: () => Cause.done(), + onSome: (buf) => { + totalBytesRead += BigInt(buf.length) + return Effect.succeed(Arr.of(buf)) + } + }) + ) + )) + }, Stream.unwrap), + sink: (path, options) => + pipe( + impl.open(path, { flag: "w", ...options }), + Effect.map((file) => Sink.forEach((_: Uint8Array) => file.writeAll(_))), + Sink.unwrap + ), + writeFileString: (path, data, options) => + Effect.flatMap( + Effect.try({ + try: () => new TextEncoder().encode(data), + catch: (cause) => + badArgument({ + module: "FileSystem", + method: "writeFileString", + description: "could not encode string", + cause + }) + }), + (_) => impl.writeFile(path, _, options) + ) + }) + +const notFound = (method: string, path: string) => + systemError({ + module: "FileSystem", + method, + _tag: "NotFound", + description: "No such file or directory", + pathOrDescriptor: path + }) + +/** + * Creates a stub `FileSystem` implementation for tests. + * + * **Details** + * + * By default, `exists` returns `false`, `remove` succeeds, many file operations + * fail with `PlatformError` `NotFound`, and temporary-directory/file operations + * die as not implemented. Pass method overrides to provide the behavior needed + * by a specific test without touching the real file system. + * + * **Example** (Creating a no-op FileSystem) + * + * ```ts import.meta.vitest + * import { Effect, FileSystem, PlatformError } from "effect" + * + * // Create a test filesystem that only allows reading specific files + * const testFs = FileSystem.makeNoop({ + * readFileString: (path) => { + * if (path === "test-config.json") { + * return Effect.succeed("{\"test\": true}") + * } + * return Effect.fail( + * PlatformError.systemError({ + * _tag: "NotFound", + * module: "FileSystem", + * method: "readFileString", + * description: "File not found", + * pathOrDescriptor: path + * }) + * ) + * }, + * exists: (path) => Effect.succeed(path === "test-config.json") + * }) + * + * // Use in tests + * const program = Effect.gen(function*() { + * const content = yield* testFs.readFileString("test-config.json") + * return content + * }) + * + * // Test with the no-op filesystem + * const testProgram = Effect.provideService( + * program, + * FileSystem.FileSystem, + * testFs + * ) + * Effect.runSync(testProgram) // => "{\"test\": true}" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeNoop = (fileSystem: Partial): FileSystem => + FileSystem.of({ + [TypeId]: TypeId, + access(path) { + return Effect.fail(notFound("access", path)) + }, + chmod(path) { + return Effect.fail(notFound("chmod", path)) + }, + chown(path) { + return Effect.fail(notFound("chown", path)) + }, + copy(path) { + return Effect.fail(notFound("copy", path)) + }, + copyFile(path) { + return Effect.fail(notFound("copyFile", path)) + }, + glob(pattern) { + return Effect.fail(notFound("glob", pattern)) + }, + exists() { + return Effect.succeed(false) + }, + link(path) { + return Effect.fail(notFound("link", path)) + }, + makeDirectory() { + return Effect.die("not implemented") + }, + makeTempDirectory() { + return Effect.die("not implemented") + }, + makeTempDirectoryScoped() { + return Effect.die("not implemented") + }, + makeTempFile() { + return Effect.die("not implemented") + }, + makeTempFileScoped() { + return Effect.die("not implemented") + }, + open(path) { + return Effect.fail(notFound("open", path)) + }, + readDirectory(path) { + return Effect.fail(notFound("readDirectory", path)) + }, + readFile(path) { + return Effect.fail(notFound("readFile", path)) + }, + readFileString(path) { + return Effect.fail(notFound("readFileString", path)) + }, + readLink(path) { + return Effect.fail(notFound("readLink", path)) + }, + realPath(path) { + return Effect.fail(notFound("realPath", path)) + }, + remove() { + return Effect.void + }, + rename(oldPath) { + return Effect.fail(notFound("rename", oldPath)) + }, + sink(path) { + return Sink.fail(notFound("sink", path)) + }, + stat(path) { + return Effect.fail(notFound("stat", path)) + }, + stream(path) { + return Stream.fail(notFound("stream", path)) + }, + symlink(fromPath) { + return Effect.fail(notFound("symlink", fromPath)) + }, + truncate(path) { + return Effect.fail(notFound("truncate", path)) + }, + utimes(path) { + return Effect.fail(notFound("utimes", path)) + }, + watch(path) { + return Stream.fail(notFound("watch", path)) + }, + writeFile(path) { + return Effect.fail(notFound("writeFile", path)) + }, + writeFileString(path) { + return Effect.fail(notFound("writeFileString", path)) + }, + ...fileSystem + }) + +/** + * Creates a Layer that provides a no-op FileSystem implementation for testing. + * + * **Details** + * + * This is a convenience function that wraps `makeNoop` in a Layer, making it easy + * to provide the test filesystem to your Effect programs. + * + * **Example** (Providing a no-op FileSystem layer) + * + * ```ts import.meta.vitest + * import { Effect, FileSystem } from "effect" + * + * // Create a test layer with specific behaviors + * const testLayer = FileSystem.layerNoop({ + * readFileString: (path) => Effect.succeed("mocked content"), + * exists: () => Effect.succeed(true) + * }) + * + * const program = Effect.gen(function*() { + * const fs = yield* FileSystem.FileSystem + * const content = yield* fs.readFileString("any-file.txt") + * return content + * }) + * + * // Provide the test layer + * const testProgram = Effect.provide(program, testLayer) + * Effect.runSync(testProgram) // => "mocked content" + * ``` + * + * @category layers + * @since 4.0.0 + */ +export const layerNoop = (fileSystem: Partial): Layer.Layer => + Layer.succeed(FileSystem)(makeNoop(fileSystem)) + +/** + * Runtime type identifier attached to `FileSystem.File` handles and used by + * `isFile` to recognize them. + * + * **Details** + * + * This marker is part of the runtime representation of file handles. Prefer + * `isFile` when narrowing unknown values. + * + * @see {@link File} for the open file handle shape that carries this marker + * @see {@link isFile} for the public guard that checks this marker + * + * @category type IDs + * @since 4.0.0 + */ +export const FileTypeId = "~effect/platform/FileSystem/File" + +/** + * Returns `true` if a value is a `File` handle by checking for the + * `FileTypeId` marker. + * + * **When to use** + * + * Use when accepting an unknown value and you need to narrow it to a `File` + * before calling file-handle operations. + * + * **Details** + * + * This is a structural marker check. It does not validate the marker value or + * the shape of the file handle. + * + * @see {@link File} for the file-handle interface narrowed by this guard + * @see {@link FileTypeId} for the runtime marker checked by this guard + * + * @category guards + * @since 4.0.0 + */ +export const isFile = (u: unknown): u is File => hasProperty(u, FileTypeId) + +/** + * Interface representing an open file handle. + * + * **Details** + * + * Provides low-level file operations including reading, writing, seeking, + * and retrieving file information. File handles are automatically managed + * within scoped operations to ensure proper cleanup. + * + * **Example** (Working with file handles) + * + * ```ts import.meta.vitest + * import { Effect, FileSystem, Option } from "effect" + * + * const file: FileSystem.File = { + * [FileSystem.FileTypeId]: FileSystem.FileTypeId, + * stat: Effect.succeed({ size: FileSystem.Size(5) } as FileSystem.File.Info), + * seek: () => Effect.succeed(FileSystem.Size(0)), + * sync: Effect.void, + * read: (buffer) => Effect.sync(() => { + * buffer.set([1, 2, 3, 4, 5]) + * return FileSystem.Size(5) + * }), + * readAlloc: () => Effect.succeed(Option.none()), + * truncate: () => Effect.void, + * write: (buffer) => Effect.succeed(FileSystem.Size(buffer.length)), + * writeAll: () => Effect.void + * } + * + * const program = Effect.gen(function*() { + * 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) } + * }) + * + * Effect.runSync(program) // => { size: 5n, bytesRead: 5n, buffer: [1, 2, 3, 4, 5] } + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface File { + readonly [FileTypeId]: typeof FileTypeId + readonly stat: Effect.Effect + readonly seek: (offset: SizeInput, 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 writeAll: (buffer: Uint8Array) => Effect.Effect +} + +/** + * Namespace containing types associated with open file handles, including file + * descriptors, entry kinds, and stat information. + * + * @since 4.0.0 + */ +export declare namespace File { + /** + * Enumeration of possible file system entry types. + * + * **Details** + * + * Represents the different types of entries that can exist in a file system, + * from regular files to special device files and symbolic links. + * + * @category models + * @since 4.0.0 + */ + export type Type = + | "File" + | "Directory" + | "SymbolicLink" + | "BlockDevice" + | "CharacterDevice" + | "FIFO" + | "Socket" + | "Unknown" + + /** + * Comprehensive file information structure. + * + * **Details** + * + * Contains metadata about a file or directory including type, timestamps, + * permissions, and size information. This structure is returned by file + * stat operations. + * + * **Example** (Inspecting file information) + * + * ```ts import.meta.vitest + * import { FileSystem, Option } from "effect" + * + * 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: FileSystem.Size(5), + * blksize: Option.none(), + * blocks: Option.none() + * } + * + * info.type // => "File" + * info.size // => 5n + * info.mode.toString(8) // => "644" + * + * const modified = Option.match(info.mtime, { + * onNone: () => "unavailable", + * onSome: (mtime) => mtime.toISOString() + * }) + * modified // => "unavailable" + * info.type === "File" // => true + * ``` + * + * @category models + * @since 4.0.0 + */ + export interface Info { + readonly type: Type + readonly mtime: Option.Option + readonly atime: Option.Option + readonly birthtime: Option.Option + readonly dev: number + readonly ino: Option.Option + readonly mode: number + readonly nlink: Option.Option + readonly uid: Option.Option + readonly gid: Option.Option + readonly rdev: Option.Option + readonly size: Size + readonly blksize: Option.Option + readonly blocks: Option.Option + } +} + +/** + * Specifies the reference point for seeking within an open file. + * + * **When to use** + * + * Use with `File` handles when positioning the cursor before a read or write + * and the offset must be interpreted from either the start of the file or the + * current cursor. + * + * **Details** + * + * - `"start"` seeks from the beginning of the file. + * - `"current"` seeks from the current cursor position. + * + * @see {@link File} for the open file handle API whose `seek` method consumes this mode + * + * @category models + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use when consuming file system watch streams and pattern matching on `_tag` + * to handle created, updated, or removed paths. + * + * **Details** + * + * The union covers create, update, and remove events. Each event carries the + * reported `path`. + * + * @see {@link FileSystem} for the service interface whose `watch` operation emits these events + * + * @category models + * @since 4.0.0 + */ +export type WatchEvent = WatchEvent.Create | WatchEvent.Update | WatchEvent.Remove + +/** + * Namespace containing the concrete event shapes emitted by `FileSystem.watch`. + * + * @since 4.0.0 + */ +export declare namespace WatchEvent { + /** + * Event representing the creation of a new file or directory. + * + * **Details** + * + * This event is triggered when a new file or directory is created + * in the watched location. + * + * @category models + * @since 4.0.0 + */ + export interface Create { + readonly _tag: "Create" + readonly path: string + } + + /** + * Event representing the modification of an existing file or directory. + * + * **Details** + * + * This event is triggered when an existing file or directory is + * modified in the watched location. + * + * @category models + * @since 4.0.0 + */ + export interface Update { + readonly _tag: "Update" + readonly path: string + } + + /** + * Event representing the deletion of a file or directory. + * + * **Details** + * + * This event is triggered when a file or directory is deleted + * from the watched location. + * + * @category models + * @since 4.0.0 + */ + export interface Remove { + readonly _tag: "Remove" + readonly path: string + } +} + +/** + * Service key for file system watch backend implementations. + * + * **Details** + * + * This service provides the low-level file watching capabilities that can be + * implemented differently on various platforms (e.g., inotify on Linux, + * FSEvents on macOS, etc.). + * + * **Example** (Providing a custom watch backend) + * + * ```ts import.meta.vitest + * import { Effect, FileSystem, Option, Stream } from "effect" + * + * // Custom watch backend implementation + * const customWatchBackend = { + * register: (path: string, stat: FileSystem.File.Info) => { + * // Implementation would depend on platform + * return Option.some(Stream.empty) // Placeholder implementation + * } + * } + * + * const program = Effect.gen(function*() { + * const backend = yield* FileSystem.WatchBackend + * return Option.isSome( + * backend.register("./directory", { type: "Directory" } as FileSystem.File.Info) + * ) + * }) + * + * const withCustomBackend = Effect.provideService( + * program, + * FileSystem.WatchBackend, + * customWatchBackend + * ) + * Effect.runSync(withCustomBackend) // => true + * ``` + * + * @category services + * @since 4.0.0 + */ +export class WatchBackend extends Context.Service Option.Option> +}>()("effect/platform/FileSystem/WatchBackend") {} diff --git a/.repos/effect/packages/effect/src/Filter.ts b/.repos/effect/packages/effect/src/Filter.ts new file mode 100644 index 000000000..631d3e03d --- /dev/null +++ b/.repos/effect/packages/effect/src/Filter.ts @@ -0,0 +1,841 @@ +/** + * Defines composable checks that can also transform values. + * + * A `Filter` receives an input and returns a `Result`. + * Success means the value passed the filter, and failure means the value was + * filtered out. Filters may also narrow or transform the passing value. This + * module includes constructors from predicates, options, and effects, built-in + * filters for common JavaScript values and tags, helpers for combining filters, + * and conversions to predicates, options, and results. + * + * @since 4.0.0 + */ +import type { Effect } from "./Effect.ts" +import * as Equal from "./Equal.ts" +import { dual } from "./Function.ts" +import * as Option from "./Option.ts" +import * as Predicate from "./Predicate.ts" +import * as Result from "./Result.ts" +import type { EqualsWith, ExcludeTag, ExtractReason, ExtractTag, ReasonTags, Tags } from "./Types.ts" + +/** + * Represents a filter function that can transform inputs to outputs or filter them out. + * + * **Details** + * + * A filter takes an input value and either returns a boxed pass value or the + * special `fail` type to indicate the value should be filtered out. + * + * **Example** (Defining a positive number filter) + * + * ```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) + * + * positiveFilter(5) // => Result.succeed(5) + * positiveFilter(-3) // => Result.fail(-3) + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface Filter { + (input: Input): Result.Result +} + +/** + * Represents an effectful filter function that can produce Effects. + * + * **Details** + * + * Similar to a regular `Filter`, but the filtering operation itself can be + * effectful, allowing for asynchronous operations, error handling, and + * dependency injection. + * + * **Example** (Defining an effectful user filter) + * + * ```ts import.meta.vitest + * import { Effect, Filter, Result } from "effect" + * + * // An effectful filter that validates user data + * type User = { id: string; isActive: boolean } + * type ValidationError = { message: string } + * + * const validateUser: Filter.FilterEffect< + * string, + * User, + * User, + * ValidationError, + * never + * > = (id) => + * Effect.gen(function*() { + * 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 + * @since 4.0.0 + */ +export interface FilterEffect< + in Input, + out Pass, + out Fail, + out E = never, + out R = never +> { + (input: Input): Effect, E, R> +} + +// ------------------------------------------------------------------------------------- +// Constructors +// ------------------------------------------------------------------------------------- + +/** + * Creates a Filter from a function that returns either a `pass` or `fail` value. + * + * **Details** + * + * This is the primary constructor for creating custom filters. The function + * should return either `Result.succeed(value)` or `Result.fail(value)`. + * + * **Example** (Creating custom filters) + * + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" + * + * // Create a filter for positive numbers + * const positiveFilter = Filter.make((n: number) => n > 0 ? Result.succeed(n) : Result.fail(n)) + * + * // Create a filter that transforms strings to uppercase + * 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 + * @since 4.0.0 + */ +export const make = ( + f: (input: Input) => Result.Result +): Filter => f as any + +/** + * Creates an effectful Filter from a function that returns an Effect. + * + * **Details** + * + * This constructor is used when the filtering operation needs to perform + * effectful computations, such as async operations, error handling, or accessing + * services from the environment. + * + * **Example** (Creating effectful filters) + * + * ```ts import.meta.vitest + * import { Effect, Filter, Result } from "effect" + * + * // Create an effectful filter that validates async + * const asyncValidate = Filter.makeEffect((id: string) => + * Effect.gen(function*() { + * const isValid = yield* Effect.succeed(id.length > 0) + * return isValid ? Result.succeed(id) : Result.fail(id) + * }) + * ) + * + * await Effect.runPromise(asyncValidate("id")) // => Result.succeed("id") + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeEffect = ( + f: (input: Input) => Effect, E, R> +): FilterEffect => f as any + +/** + * Transforms the failure value produced by a `Filter`, leaving successful + * results unchanged. + * + * @category mapping + * @since 4.0.0 + */ +export const mapFail: { + (f: (fail: Fail) => Fail2): (self: Filter) => Filter + ( + self: Filter, + f: (fail: Fail) => Fail2 + ): Filter +} = dual(2, ( + self: Filter, + f: (value: Fail) => Fail2 +): Filter => +(input: Input): Result.Result => Result.mapError(self(input), f)) + +const try_ = (f: (input: Input) => Output): Filter => (input) => { + try { + return Result.succeed(f(input)) + } catch { + return Result.fail(input) + } +} + +export { + /** + * Creates a Filter that tries to apply a function and returns `fail` on + * error. + * + * @category constructors + * @since 4.0.0 + */ + try_ as try +} + +/** + * Creates a Filter from a predicate or refinement function. + * + * **Details** + * + * This is a convenient way to create filters from boolean-returning functions. + * When the predicate returns true, the input value is passed through unchanged. + * When it returns false, the `fail` type is returned. + * + * **Example** (Creating filters from predicates) + * + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" + * + * // Create filter from predicate + * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) + * const nonEmptyStrings = Filter.fromPredicate((s: string) => s.length > 0) + * + * // Type refinement + * 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 + * @since 4.0.0 + */ +export const fromPredicate: { + (refinement: Predicate.Refinement): Filter>> + (predicate: Predicate.Predicate): Filter +} = (predicate: Predicate.Predicate | Predicate.Refinement): Filter => (input: A) => + predicate(input) ? Result.succeed(input as B) : Result.fail(input) + +/** + * Creates a `Filter` from a function that returns an `Option`; `Some(value)` + * passes with `value`, and `None` fails with the original input. + * + * @category constructors + * @since 4.0.0 + */ +export const fromPredicateOption = (predicate: (a: A) => Option.Option): Filter => (input) => { + const o = predicate(input) + return o._tag === "None" ? Result.fail(input) : Result.succeed(o.value) +} + +/** + * Converts a Filter into a predicate function. + * + * **When to use** + * + * Use to reuse a `Filter` with APIs that accept only boolean predicates when + * the pass and fail payloads are not needed. + * + * @see {@link toOption} for keeping passed values and discarding failure values + * @see {@link toResult} for preserving both pass and failure values + * + * @category converting + * @since 4.0.0 + */ +export const toPredicate = ( + self: Filter +): Predicate.Predicate => +(input: A) => !Result.isFailure(self(input)) + +/** + * A predefined filter that only passes through string values. + * + * **Example** (Filtering strings) + * + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" + * + * Filter.string("hello") // => Result.succeed("hello") + * Filter.string(42) // => Result.fail(42) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const string: Filter = fromPredicate(Predicate.isString) + +/** + * Creates a `Filter` that passes only values strictly equal to the specified + * value using JavaScript `===` comparison. + * + * **When to use** + * + * Use when you need a `Filter` that accepts only the exact primitive value or + * object reference using JavaScript strict equality in a `Filter` / `Result` + * pipeline. + * + * **Gotchas** + * + * `NaN` never passes, even when the expected value is `NaN`, and objects pass + * only when they are the same reference. + * + * @see {@link equals} for structural equality when distinct values with equal + * contents should pass + * + * @category constructors + * @since 4.0.0 + */ +export const equalsStrict = + (value: A): Filter>> => (u) => + (u as unknown) === value ? Result.succeed(value) : Result.fail(u as any) + +/** + * Creates a `Filter` that passes inputs whose `has(key)` method returns + * `true` for the specified key. + * + * **When to use** + * + * Use to keep inputs that expose a `has` method, such as `Set` or `Map`, when + * they contain a required key. + * + * @see {@link fromPredicate} for custom predicate filters or inputs without a + * `has` method + * @see {@link Predicate.hasProperty} for guarding property presence instead of + * calling an input's `has` method + * + * @category constructors + * @since 4.0.0 + */ +export const has = + (key: K) => boolean }>(input: Input): Result.Result => + input.has(key) ? Result.succeed(input) : Result.fail(input) + +/** + * Creates a filter that only passes instances of the given constructor. + * + * **When to use** + * + * Use to narrow unknown input to values created by a specific JavaScript + * constructor while keeping the result in the `Filter` / `Result` pipeline. + * + * **Details** + * + * The filter succeeds when the input satisfies `instanceof constructor`. + * Otherwise it fails with the original input. + * + * **Gotchas** + * + * This uses JavaScript `instanceof` semantics, including prototype-chain and + * realm behavior. + * + * @see {@link fromPredicate} for custom predicate-based narrowing + * + * @category constructors + * @since 4.0.0 + */ +export const instanceOf = + any>(constructor: K) => + (u: Input): Result.Result, Exclude>> => + u instanceof constructor ? Result.succeed(u as InstanceType) : Result.fail(u) as any + +/** + * A predefined filter that only passes through number values. + * + * **Example** (Filtering numbers) + * + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" + * + * Filter.number(42) // => Result.succeed(42) + * Filter.number("42") // => Result.fail("42") + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const number: Filter = fromPredicate(Predicate.isNumber) + +/** + * A predefined filter that only passes through boolean values. + * + * **When to use** + * + * Use when accepting an unknown input only if it is already a boolean and you + * want a `Filter` result rather than a plain predicate result. + * + * **Details** + * + * Implemented with `fromPredicate(Predicate.isBoolean)`, so `true` and `false` + * succeed and non-booleans fail with the original input. + * + * @see {@link Predicate.isBoolean} for the underlying guard + * @see {@link fromPredicate} for custom predicate-based filters + * + * @category constructors + * @since 4.0.0 + */ +export const boolean: Filter = fromPredicate(Predicate.isBoolean) + +/** + * A predefined filter that only passes through `bigint` primitive values. + * + * **When to use** + * + * Use to keep primitive big integer values from unknown input while staying in + * the composable `Filter` / `Result` pipeline. + * + * **Details** + * + * Implemented with `fromPredicate(Predicate.isBigInt)`, so values where + * `typeof input === "bigint"` succeed and all other inputs fail with the + * original input. + * + * **Gotchas** + * + * This filter does not coerce numbers or strings; `1n` passes while `1` fails. + * + * @see {@link number} for JavaScript `number` values + * @see {@link Predicate.isBigInt} for the underlying guard + * + * @category constructors + * @since 4.0.0 + */ +export const bigint: Filter = fromPredicate(Predicate.isBigInt) + +/** + * A predefined filter that only passes through Symbol values. + * + * @category constructors + * @since 4.0.0 + */ +export const symbol: Filter = fromPredicate(Predicate.isSymbol) + +/** + * A predefined filter that only passes through Date objects. + * + * **When to use** + * + * Use when you need to narrow unknown input to JavaScript `Date` instances with + * a reusable `Filter`. + * + * **Details** + * + * Implemented with `fromPredicate(Predicate.isDate)`, so passing values return + * `Result.succeed(input)` and failing values return `Result.fail(input)`. + * + * **Gotchas** + * + * The check uses `instanceof Date`, so invalid `Date` objects still pass; the + * filter does not validate the timestamp. + * + * @see {@link Predicate.isDate} for the underlying guard + * @see {@link instanceOf} for constructor-based filtering + * @see {@link fromPredicate} for custom date checks + * + * @category constructors + * @since 4.0.0 + */ +export const date: Filter = fromPredicate(Predicate.isDate) + +/** + * Creates a filter that checks if an input is tagged with a specific tag. + * + * **When to use** + * + * Use to keep only the matching member of a `_tag`-discriminated union while + * staying in a composable `Filter` / `Result` pipeline. + * + * **Details** + * + * The filter succeeds when `Predicate.isTagged(input, tag)` returns `true`. + * Otherwise it fails with the original input. + * + * **Gotchas** + * + * This only checks `_tag`; it does not validate the rest of the variant fields. + * + * @see {@link Predicate.isTagged} for the underlying boolean guard when a + * `Filter` result is not needed + * @see {@link reason} for extracting a nested reason variant from tagged errors + * + * @category constructors + * @since 4.0.0 + */ +export const tagged: { + (): >(tag: Tag) => Filter, ExcludeTag> + >( + tag: Tag + ): Filter, ExcludeTag> + ( + tag: Tag + ): (input: Input) => Result.Result, ExcludeTag> +} = function() { + return arguments.length === 0 ? taggedImpl : taggedImpl(arguments[0] as any) +} as any + +const taggedImpl = + (tag: Tag) => + (input: Input): Result.Result, ExcludeTag> => + Predicate.isTagged(input, tag) ? Result.succeed(input as any) : Result.fail(input as ExcludeTag) + +/** + * Creates a filter that extracts a reason from a tagged error. + * + * @category constructors + * @since 4.0.0 + */ +export const reason: { + (): , const ReasonTag extends ReasonTags>>( + tag: Tag, + reasonTag: ReasonTag + ) => Filter, ReasonTag>, Input> + , const ReasonTag extends ReasonTags>>( + tag: Tag, + reasonTag: ReasonTag + ): Filter, ReasonTag>, Input> + ( + tag: Tag, + reasonTag: ReasonTag + ): (input: Input) => Result.Result, ReasonTag>, Input> +} = function() { + return arguments.length === 0 ? reasonImpl : reasonImpl(arguments[0] as any, arguments[1] as any) +} as any + +const reasonImpl = + (tag: Tag, reasonTag: ReasonTag) => + (input: Input): Result.Result, ExcludeTag> => { + if ( + Predicate.isTagged(input, tag) && Predicate.hasProperty(input, "reason") && + Predicate.isTagged(input.reason, reasonTag) + ) { + return Result.succeed(input.reason as any) + } + return Result.fail(input as any) + } + +/** + * Creates a filter that only passes values equal to the specified value using structural equality. + * + * **When to use** + * + * Use to accept inputs that are structurally equal to a known expected value + * while staying in a composable `Filter` / `Result` pipeline. + * + * **Details** + * + * Delegates to `Equal.equals`. On success it returns `Result.succeed(value)`; + * on failure it returns `Result.fail(input)`. + * + * @see {@link equalsStrict} for JavaScript `===` matching instead of structural + * equality + * @see {@link Equal.equals} for the underlying structural equality semantics + * + * @category constructors + * @since 4.0.0 + */ +export const equals = + (value: A): Filter>> => (u) => + Equal.equals(u, value) ? Result.succeed(value) : Result.fail(u as any) + +/** + * Combines two filters with logical OR semantics. + * + * @category combinators + * @since 4.0.0 + */ +export const or: { + ( + that: Filter + ): (self: Filter) => Filter + ( + self: Filter, + that: Filter + ): Filter +} = dual(2, ( + self: Filter, + that: Filter +): Filter => +(input) => { + const selfResult = self(input) + return Result.isSuccess(selfResult) ? selfResult as Result.Result : that(input) +}) + +/** + * Combines two filters and applies a function to their results. + * + * **When to use** + * + * Use to combine two filters with a custom function to merge their outputs. + * + * **Details** + * + * Both filters must succeed (not return `fail`) for the combination to succeed. + * If both filters pass, their outputs are combined using the provided function. + * + * @see {@link zip} for combining two filters into a tuple + * + * @category combinators + * @since 4.0.0 + */ +export const zipWith: { + ( + right: Filter, + f: (left: PassL, right: PassR) => A + ): (left: Filter) => Filter + ( + left: Filter, + right: Filter, + f: (left: PassL, right: PassR) => A + ): Filter +} = dual(3, ( + left: Filter, + right: Filter, + f: (left: PassL, right: PassR) => A +): Filter => +(input) => { + const leftResult = left(input) + if (Result.isFailure(leftResult)) return leftResult as Result.Result + const rightResult = right(input) + if (Result.isFailure(rightResult)) return rightResult as Result.Result + return Result.succeed(f(leftResult.success, rightResult.success)) +}) + +/** + * Combines two filters into a tuple of their results. + * + * **Details** + * + * Both filters must succeed for the combination to succeed. If both pass, their + * outputs are combined into a tuple. + * + * **Example** (Zipping filters) + * + * ```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 + * @since 4.0.0 + */ +export const zip: { + ( + right: Filter + ): ( + left: Filter + ) => Filter + ( + left: Filter, + right: Filter + ): Filter +} = dual(2, ( + left: Filter, + right: Filter +): Filter => + zipWith(left, right, (leftResult, rightResult) => [leftResult, rightResult])) + +/** + * Combines two filters but only returns the result of the left filter. + * + * **Example** (Keeping the left filter result) + * + * ```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 + * @since 4.0.0 + */ +export const andLeft: { + ( + right: Filter + ): ( + left: Filter + ) => Filter + ( + left: Filter, + right: Filter + ): Filter +} = dual(2, ( + left: Filter, + right: Filter +): Filter => zipWith(left, right, (leftResult) => leftResult)) + +/** + * Combines two filters but only returns the result of the right filter. + * + * **Example** (Keeping the right filter result) + * + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" + * + * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) + * const doubleNumbers = Filter.make((n: number) => + * n > 0 ? Result.succeed(n * 2) : Result.fail(n) + * ) + * + * const positiveDoubled = Filter.andRight(positiveNumbers, doubleNumbers) + * positiveDoubled(2) // => Result.succeed(4) + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const andRight: { + ( + right: Filter + ): ( + left: Filter + ) => Filter + ( + left: Filter, + right: Filter + ): Filter +} = dual(2, ( + left: Filter, + right: Filter +): Filter => zipWith(left, right, (_, rightResult) => rightResult)) + +/** + * Composes two filters sequentially, feeding the output of the first into the second. + * + * **Example** (Composing filters) + * + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" + * + * const stringFilter = Filter.string + * const nonEmptyUpper = Filter.make((s: string) => + * s.length > 0 ? Result.succeed(s.toUpperCase()) : Result.fail(s) + * ) + * + * const stringToUpper = Filter.compose(stringFilter, nonEmptyUpper) + * stringToUpper("hello") // => Result.succeed("HELLO") + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const compose: { + ( + right: Filter + ): (left: Filter) => Filter + ( + left: Filter, + right: Filter + ): Filter +} = dual(2, ( + left: Filter, + right: Filter +): Filter => +(input) => { + const leftOut = left(input) + if (Result.isFailure(leftOut)) return leftOut as Result.Result + return right(leftOut.success) +}) + +/** + * Composes two filters sequentially, passing the successful output of the first + * filter to the second. + * + * **Details** + * + * If either filter fails, the returned filter fails with the original input + * instead of the intermediate failure value. + * + * @category combinators + * @since 4.0.0 + */ +export const composePassthrough: { + ( + right: Filter + ): (left: Filter) => Filter + ( + left: Filter, + right: Filter + ): Filter +} = dual(2, ( + left: Filter, + right: Filter +): Filter => +(input) => { + const leftOut = left(input) + if (Result.isFailure(leftOut)) return Result.fail(input) + const rightOut = right(leftOut.success) + if (Result.isFailure(rightOut)) return Result.fail(input) + return rightOut as Result.Result +}) + +/** + * Converts a `Filter` into a function that returns `Some` for passed values + * and `None` for filtered-out values. + * + * **When to use** + * + * Use when adapting a `Filter` to `Option`-based code where passed values + * become `Some` and filtered-out inputs become `None`. + * + * @see {@link toResult} for keeping the filter failure value + * @see {@link toPredicate} for plain boolean pass/fail checks + * + * @category converting + * @since 4.0.0 + */ +export const toOption = ( + self: Filter +): (input: A) => Option.Option => +(input: A) => { + const result = self(input) + return Result.isFailure(result) ? Option.none() : Option.some(result.success) +} + +/** + * Converts a `Filter` into a function that returns the underlying + * `Result.Result` for each input. + * + * **When to use** + * + * Use to adapt a `Filter` to APIs that expect a plain function returning + * `Result`, while preserving both the pass value and the failure value. + * + * @see {@link toOption} for keeping only passed values + * @see {@link toPredicate} for plain boolean pass/fail checks + * + * @category converting + * @since 4.0.0 + */ +export const toResult = ( + self: Filter +): (input: A) => Result.Result => +(input: A) => { + const result = self(input) + return Result.isFailure(result) ? Result.fail(result.failure) : Result.succeed(result.success) +} diff --git a/.repos/effect/packages/effect/src/Formatter.ts b/.repos/effect/packages/effect/src/Formatter.ts new file mode 100644 index 000000000..db202ef93 --- /dev/null +++ b/.repos/effect/packages/effect/src/Formatter.ts @@ -0,0 +1,312 @@ +/** + * Formats JavaScript values into readable strings. + * + * `format` is intended for logs, diagnostics, and error messages. It handles + * primitives, objects, arrays, dates, regular expressions, maps, sets, class + * instances, errors, circular references, and redactable values. `formatJson` + * wraps JSON formatting with redaction and circular-reference handling, and the + * module also includes helpers for property keys, paths, and dates. + * + * @since 4.0.0 + */ +import * as Predicate from "./Predicate.ts" +import { getRedacted, redact, symbolRedactable } from "./Redactable.ts" + +/** + * A callable interface representing a function that converts a `Value` into a `Format`, which defaults to `string`. + * + * **When to use** + * + * Use when you want to type a formatting or rendering function generically, or when you are building a pipeline that accepts pluggable formatters. + * + * **Details** + * + * This is a pure callable type and carries no runtime implementation. It is contravariant in `Value` and covariant in `Format`. + * + * **Example** (Defining a custom formatter) + * + * ```ts import.meta.vitest + * import type { Formatter } from "effect" + * + * const upper: Formatter.Formatter = (s) => s.toUpperCase() + * + * upper("hello") // => "HELLO" + * ``` + * + * @see {@link format} + * @see {@link formatJson} + * @category models + * @since 4.0.0 + */ +export interface Formatter { + (value: Value): Format +} + +/** + * Converts any JavaScript value into a human-readable string. + * + * **When to use** + * + * Use when you need to format arbitrary JavaScript values for debugging, + * logging, or error messages. + * + * **Details** + * + * - Output is **not** valid JSON; use {@link formatJson} when you need + * parseable JSON. + * - Handles `BigInt`, `Symbol`, `Set`, `Map`, `Date`, `RegExp`, and class + * instances that `JSON.stringify` cannot represent. + * - Circular references are shown as `"[Circular]"` instead of throwing. + * - Primitives: stringified naturally (`null`, `undefined`, `123`, `true`). + * Strings are JSON-quoted. + * - Objects with a custom `toString` (not `Object.prototype.toString`): + * `toString()` is called unless `ignoreToString` is `true`. + * - Errors with a `cause`: formatted as `" (cause: )"`. + * - Iterables (`Set`, `Map`, etc.): formatted as + * `ClassName([...elements])`. + * - Class instances: wrapped as `ClassName({...})`. + * - `Redactable` values are automatically redacted. + * - Arrays/objects with 0–1 entries are inline; larger ones are + * pretty-printed when `space` is set. + * - `space` — indentation unit (number of spaces, or a string like + * `"\t"`). Defaults to `0` (compact). + * - `ignoreToString` — skip calling `toString()`. Defaults to `false`. + * + * **Example** (Formatting compact output) + * + * ```ts import.meta.vitest + * import { Formatter } from "effect" + * + * Formatter.format({ a: 1, b: [2, 3] }) // => "{\"a\":1,\"b\":[2,3]}" + * ``` + * + * **Example** (Pretty-printed output) + * + * ```ts import.meta.vitest + * import { Formatter } from "effect" + * + * 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 import.meta.vitest + * import { Formatter } from "effect" + * + * const obj: any = { name: "loop" } + * obj.self = obj + * Formatter.format(obj) // => "{\"name\":\"loop\",\"self\":[Circular]}" + * ``` + * + * @see {@link formatJson} + * @see {@link Formatter} + * @category formatting + * @since 2.0.0 + */ +export function format(input: unknown, options?: { + readonly space?: number | string | undefined + readonly ignoreToString?: boolean | undefined +}): string { + const space = options?.space ?? 0 + const ancestors = new WeakSet() + const gap = !space ? "" : (typeof space === "number" ? " ".repeat(space) : space) + const ind = (d: number) => gap.repeat(d) + + const wrap = (v: unknown, body: string): string => { + const ctor = (v as any)?.constructor + return ctor && ctor !== Object.prototype.constructor && ctor.name ? `${ctor.name}(${body})` : body + } + + const ownKeys = (o: object): Array => { + try { + return Reflect.ownKeys(o) + } catch { + return ["[ownKeys threw]"] + } + } + + function recur(v: unknown, d = 0): string { + if (typeof v === "string") return JSON.stringify(v) + + if ( + typeof v === "number" || + v == null || + typeof v === "boolean" || + typeof v === "symbol" + ) return String(v) + + if (typeof v === "bigint") return String(v) + "n" + + if (typeof v === "object" || typeof v === "function") { + if (ancestors.has(v)) return CIRCULAR + ancestors.add(v) + + 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 ? `${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((v as any)[k], d)}`).join(",")}}` + output = wrap(v, body) + } else { + const body = `{\n${ + keys.map((k) => `${ind(d + 1)}${formatPropertyKey(k)}: ${recur((v as any)[k], d + 1)}`).join(",\n") + }\n${ind(d)}}` + output = wrap(v, body) + } + } + ancestors.delete(v) + return output + } + + return String(v) + } + + return recur(input, 0) +} + +const CIRCULAR = "[Circular]" + +/** + * @internal + */ +export function formatPropertyKey(name: PropertyKey): string { + return typeof name === "string" ? JSON.stringify(name) : String(name) +} + +/** + * Formats an array of property keys as a bracket-notation path string. + * + * @internal + */ +export function formatPath(path: ReadonlyArray): string { + return path.map((key) => `[${formatPropertyKey(key)}]`).join("") +} + +/** + * Formats a `Date` as an ISO 8601 string, returning `"Invalid Date"` for + * invalid dates instead of throwing. + * + * @internal + */ +export function formatDate(date: Date): string { + try { + return date.toISOString() + } catch { + return "Invalid Date" + } +} + +function safeToString(input: any): string { + try { + const s = input.toString() + return typeof s === "string" ? s : String(s) + } catch { + return "[toString threw]" + } +} + +/** + * Stringifies a value to JSON safely, silently dropping circular references. + * + * **When to use** + * + * Use when you need valid JSON output, unlike `format`, and the input may + * contain circular references that should be silently omitted rather than + * throwing a `TypeError`. + * + * **Details** + * + * 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. `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 import.meta.vitest + * import { Formatter } from "effect" + * + * Formatter.formatJson({ name: "Alice", age: 30 }) // => "{\"name\":\"Alice\",\"age\":30}" + * ``` + * + * **Example** (Handling circular references) + * + * ```ts import.meta.vitest + * import { Formatter } from "effect" + * + * const obj: any = { name: "test" } + * obj.self = obj + * Formatter.formatJson(obj) // => "{\"name\":\"test\"}" + * ``` + * + * **Example** (Pretty-printed JSON) + * + * ```ts import.meta.vitest + * import { Formatter } from "effect" + * + * const output = Formatter.formatJson({ name: "Alice", age: 30 }, { space: 2 }) + * output // => "{\n \"name\": \"Alice\",\n \"age\": 30\n}" + * ``` + * + * @see {@link format} + * @see {@link Formatter} + * @category serialization + * @since 4.0.0 + */ +export function formatJson(input: unknown, options?: { + readonly space?: number | string | undefined +}): string { + const ancestors: Array = [] + return JSON.stringify( + input, + 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 + } + while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) { + ancestors.pop() + } + if (ancestors.includes(redacted)) { + return undefined // circular reference + } + ancestors.push(redacted) + return redacted + }, + options?.space + ) ?? "null" +} diff --git a/.repos/effect/packages/effect/src/Function.ts b/.repos/effect/packages/effect/src/Function.ts new file mode 100644 index 000000000..f12e50a74 --- /dev/null +++ b/.repos/effect/packages/effect/src/Function.ts @@ -0,0 +1,1385 @@ +/** + * Provides small helpers for defining and reusing TypeScript functions. + * + * The main helpers are `pipe` and `flow` for left-to-right composition and + * `dual` for APIs that support both direct and pipe-friendly call styles. The + * module also contains small identity, constant, tuple, type-level, and + * memoization helpers used across the library. + * + * @since 2.0.0 + */ +import type { TypeLambda } from "./HKT.ts" +import { pipeArguments } from "./Pipeable.ts" + +/** + * Type lambda for function types, used for higher-kinded type operations. + * + * **When to use** + * + * Use when defining higher-kinded abstractions that must accept function types + * as one of their type-lambda inputs. + * + * **Example** (Creating a function type with a type lambda) + * + * ```ts import.meta.vitest + * import type { Function, HKT } from "effect" + * + * // Create a function type using the type lambda + * type StringToNumber = HKT.Kind + * // Equivalent to: (a: string) => number + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export interface FunctionTypeLambda extends TypeLambda { + readonly type: (a: this["In"]) => this["Target"] +} + +/** + * Creates a function that can be called in data-first style or data-last + * (`pipe`-friendly) style. + * + * **When to use** + * + * Use to expose one implementation through both direct and `pipe`-friendly + * call styles. + * + * **Details** + * + * Pass either the arity of the uncurried function or a predicate that decides + * whether the current call is data-first. Arity is the common case. Use a + * predicate when optional arguments make arity ambiguous. + * + * **Example** (Selecting data-first or data-last style by arity) + * + * ```ts import.meta.vitest + * import { Function, pipe } from "effect" + * + * const sum = Function.dual< + * (that: number) => (self: number) => number, + * (self: number, that: number) => number + * >(2, (self, that) => self + that) + * + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 + * ``` + * + * **Example** (Defining overloads with call signatures) + * + * ```ts import.meta.vitest + * import { Function, pipe } from "effect" + * + * const sum: { + * (that: number): (self: number) => number + * (self: number, that: number): number + * } = Function.dual(2, (self: number, that: number): number => self + that) + * + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 + * ``` + * + * **Example** (Selecting data-first or data-last style with a predicate) + * + * ```ts import.meta.vitest + * import { Function, pipe } from "effect" + * + * const sum = Function.dual< + * (that: number) => (self: number) => number, + * (self: number, that: number) => number + * >( + * (args) => args.length === 2, + * (self, that) => self + that + * ) + * + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const dual: { + ) => any, DataFirst extends (...args: Array) => any>( + arity: Parameters["length"], + body: DataFirst + ): DataLast & DataFirst + ) => any, DataFirst extends (...args: Array) => any>( + isDataFirst: (args: IArguments) => boolean, + body: DataFirst + ): DataLast & DataFirst +} = function(arity, body) { + if (typeof arity === "function") { + return function(this: any) { + return arity(arguments) + ? body.apply(this, arguments as any) + : ((self: any) => body(self, ...arguments)) as any + } + } + + switch (arity) { + case 0: + case 1: + throw new RangeError(`Invalid arity ${arity}`) + + case 2: + return function(a, b) { + if (arguments.length >= 2) { + return body(a, b) + } + return function(self: any) { + return body(self, a) + } + } + + case 3: + return function(a, b, c) { + if (arguments.length >= 3) { + return body(a, b, c) + } + return function(self: any) { + return body(self, a, b) + } + } + + default: + return function() { + if (arguments.length >= arity) { + // @ts-expect-error + return body.apply(this, arguments) + } + const args = arguments + return function(self: any) { + return body(self, ...args) + } + } + } +} +/** + * Applies a function to a given value. + * + * **When to use** + * + * Use to pass a fixed value into a unary function, especially when the function + * is the value flowing through `pipe`. + * + * **Details** + * + * `apply(a)(f)` is equivalent to `f(a)`. + * + * **Example** (Applying an argument to a function) + * + * ```ts import.meta.vitest + * import { Function, pipe, String } from "effect" + * + * pipe(String.length, Function.apply("hello")) // => 5 + * ``` + * + * @see {@link pipe} for building left-to-right pipelines + * + * @category combinators + * @since 2.0.0 + */ +export const apply = (a: A) => (self: (a: A) => B): B => self(a) + +/** + * A zero-argument function that produces a value when invoked. + * + * **When to use** + * + * Use to type a lazy value provider that should not run until called. + * + * **Example** (Creating a lazy argument) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * const constNull: Function.LazyArg = Function.constant(null) + * constNull() // => null + * ``` + * + * @category models + * @since 2.0.0 + */ +export type LazyArg = () => A + +/** + * Represents a function with multiple arguments. + * + * **When to use** + * + * Use to describe a function whose argument list is represented as a tuple + * type. + * + * **Example** (Typing a variadic function) + * + * ```ts import.meta.vitest + * import type { Function } from "effect" + * + * const sum: Function.FunctionN<[number, number], number> = (a, b) => a + b + * sum(2, 3) // => 5 + * ``` + * + * @category models + * @since 2.0.0 + */ +export type FunctionN, B> = (...args: A) => B + +/** + * Returns its input argument unchanged. + * + * **When to use** + * + * Use to return a value unchanged where a function is required. + * + * **Example** (Returning the same value) + * + * ```ts import.meta.vitest + * import { identity } from "effect" + * + * identity(5) // => 5 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const identity = (a: A): A => a + +/** + * Ensures that the type of an expression matches some type, + * without changing the resulting type of that expression. + * + * **When to use** + * + * Use to check assignability while preserving the expression's precise inferred + * type. + * + * **Example** (Checking an expression against a type) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * 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' + * ``` + * + * @see {@link cast} for changing only the static TypeScript type + * + * @category utility types + * @since 2.0.0 + */ +export const satisfies = () => (b: B) => b + +/** + * Returns the input value with a different static type. + * + * **When to use** + * + * Use when you need an explicit type-level cast and accept that the value is + * returned unchanged at runtime. + * + * **Gotchas** + * + * This is a type-level cast only; it performs no runtime validation or + * conversion. + * + * @see {@link satisfies} for checking assignability without changing the resulting type + * + * @category utility types + * @since 4.0.0 + */ +export const cast: (a: A) => B = identity as any + +/** + * Creates a zero-argument function that always returns the provided value. + * + * **When to use** + * + * Use when you need a thunk or callback that returns the same value on every + * invocation. + * + * **Example** (Creating a constant thunk) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * const constNull = Function.constant(null) + * + * constNull() // => null + * constNull() // => null + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const constant = (value: A): LazyArg => () => value + +/** + * Returns `true` when called. + * + * **When to use** + * + * Use when you need a thunk that returns `true` on every invocation. + * + * **Example** (Returning true from a thunk) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * Function.constTrue() // => true + * ``` + * + * @category constants + * @since 2.0.0 + */ +export const constTrue: LazyArg = constant(true) + +/** + * Returns `false` when called. + * + * **When to use** + * + * Use when you need a thunk that returns `false` on every invocation. + * + * **Example** (Returning false from a thunk) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * Function.constFalse() // => false + * ``` + * + * @category constants + * @since 2.0.0 + */ +export const constFalse: LazyArg = constant(false) + +/** + * Returns `null` when called. + * + * **When to use** + * + * Use when you need a thunk that returns `null` on every invocation. + * + * **Example** (Returning null from a thunk) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * Function.constNull() // => null + * ``` + * + * @category constants + * @since 2.0.0 + */ +export const constNull: LazyArg = constant(null) + +/** + * Returns `undefined` when called. + * + * **When to use** + * + * Use when you need a thunk that returns `undefined` on every invocation. + * + * **Example** (Returning undefined from a thunk) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * Function.constUndefined() // => undefined + * ``` + * + * @category constants + * @since 2.0.0 + */ +export const constUndefined: LazyArg = constant(undefined) + +/** + * Returns no meaningful value when called. + * + * **When to use** + * + * Use when you need a thunk that is called only for its effect and has no + * meaningful return value. + * + * **Example** (Returning void from a thunk) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * Function.constVoid() // => undefined + * ``` + * + * @category constants + * @since 2.0.0 + */ +export const constVoid: LazyArg = constUndefined + +/** + * Reverses the order of arguments for a curried function. + * + * **When to use** + * + * Use to adapt a curried function when its argument groups need to be supplied + * in the opposite order. + * + * **Example** (Flipping curried arguments) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * const f = (a: number) => (b: string) => a - b.length + * + * Function.flip(f)("aaa")(2) // => -1 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const flip = , B extends Array, C>( + f: (...a: A) => (...b: B) => C +): (...b: B) => (...a: A) => C => +(...b) => +(...a) => f(...a)(...b) + +/** + * Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`. + * The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`. + * + * **When to use** + * + * Use to compose exactly two unary functions into a reusable unary function. + * + * **Example** (Composing two functions) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * const increment = (n: number) => n + 1 + * const square = (n: number) => n * n + * + * Function.compose(increment, square)(2) // => 9 + * ``` + * + * @see {@link flow} for composing a left-to-right sequence of functions + * @see {@link pipe} for applying a value through a left-to-right sequence immediately + * + * @category combinators + * @since 2.0.0 + */ +export const compose: { + (bc: (b: B) => C): (self: (a: A) => B) => (a: A) => C + (self: (a: A) => B, bc: (b: B) => C): (a: A) => C +} = dual(2, (ab: (a: A) => B, bc: (b: B) => C): (a: A) => C => (a) => bc(ab(a))) + +/** + * Marks an impossible branch by accepting a `never` value and returning any + * type. + * + * **When to use** + * + * Use when you need a return value in a branch that exhaustive checks prove + * cannot be reached. + * + * **Gotchas** + * + * Calling `absurd` throws, because a value of type `never` should be + * impossible at runtime. + * + * **Example** (Handling impossible values) + * + * ```ts import.meta.vitest + * import { absurd } from "effect" + * + * const handleNever = (value: never) => { + * return absurd(value) // This will throw an error if called + * } + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export const absurd = (_: never): A => { + throw new Error("Called `absurd` function which should be uncallable") +} + +/** + * Creates a tupled version of this function: instead of `n` arguments, it accepts a single tuple argument. + * + * **When to use** + * + * Use to adapt a multi-argument function so it accepts one tuple argument. + * + * **Example** (Converting arguments to a tuple) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * const sumTupled = Function.tupled((x: number, y: number): number => x + y) + * + * sumTupled([1, 2]) // => 3 + * ``` + * + * @see {@link untupled} for adapting a tuple-argument function back to multiple arguments + * + * @category combinators + * @since 2.0.0 + */ +export const tupled = , B>(f: (...a: A) => B): (a: A) => B => (a) => f(...a) + +/** + * Converts a tupled function back to an uncurried function. + * + * **When to use** + * + * Use to adapt a tuple-argument function so it accepts multiple arguments. + * + * **Example** (Converting a tuple to arguments) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * const getFirst = Function.untupled((tuple: [A, B]): A => tuple[0]) + * + * getFirst(1, 2) // => 1 + * ``` + * + * @see {@link tupled} for adapting a multi-argument function to one tuple argument + * + * @category combinators + * @since 2.0.0 + */ +export const untupled = , B>(f: (a: A) => B): (...a: A) => B => (...a) => f(a) + +/** + * Pipes the value of an expression through a left-to-right sequence of + * functions. + * + * **When to use** + * + * Use when you need to compose data-last functions into readable + * transformation pipelines instead of method-style chains. + * + * **Details** + * + * Takes an initial value, passes it to the first function, then passes each + * result to the next function in order. The final function result is returned. + * + * **Gotchas** + * + * Each function passed after the initial value must accept a single argument, + * because `pipe` calls each step with only the previous result. + * + * **Example** (Piping values through functions) + * + * In this example, `1` is passed to the first function, and each result becomes + * the input for the next function. + * + * ```ts import.meta.vitest + * import { pipe } from "effect" + * + * pipe( + * 1, + * (n) => n + 1, + * (n) => n * 2, + * (n) => `result: ${n}` + * ) // => "result: 4" + * ``` + * + * **Example** (Rewriting method chains with pipe) + * + * The same transformation can be written with data-last functions. + * + * ```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 + * + * pipe( + * numbers, + * Array.map(double), + * Array.filter(greaterThanFour) + * ) // => [6, 8] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export function pipe(a: A): A +export function pipe(a: A, ab: (a: A) => B): B +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C +): C +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D +): D +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E +): E +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F +): F +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G +): G +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H +): H +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I +): I +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J +): J +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K +): K +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L +): L +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M +): M +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M, + mn: (m: M) => N +): N +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M, + mn: (m: M) => N, + no: (n: N) => O +): O +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M, + mn: (m: M) => N, + no: (n: N) => O, + op: (o: O) => P +): P +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M, + mn: (m: M) => N, + no: (n: N) => O, + op: (o: O) => P, + pq: (p: P) => Q +): Q +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M, + mn: (m: M) => N, + no: (n: N) => O, + op: (o: O) => P, + pq: (p: P) => Q, + qr: (q: Q) => R +): R +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never, + S = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M, + mn: (m: M) => N, + no: (n: N) => O, + op: (o: O) => P, + pq: (p: P) => Q, + qr: (q: Q) => R, + rs: (r: R) => S +): S +export function pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never, + S = never, + T = never +>( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J, + jk: (j: J) => K, + kl: (k: K) => L, + lm: (l: L) => M, + mn: (m: M) => N, + no: (n: N) => O, + op: (o: O) => P, + pq: (p: P) => Q, + qr: (q: Q) => R, + rs: (r: R) => S, + st: (s: S) => T +): T +export function pipe(a: unknown, ...args: Array): unknown { + return pipeArguments(a, args as any) +} + +/** + * Performs left-to-right function composition. + * + * **When to use** + * + * Use to build a reusable function from a left-to-right sequence of + * transformations. + * + * **Details** + * + * The first function may have any arity. Every following function must be + * unary. + * + * **Example** (Composing functions left to right) + * + * ```ts import.meta.vitest + * import { flow } from "effect" + * + * const len = (s: string): number => s.length + * const double = (n: number): number => n * 2 + * + * const f = flow(len, double) + * + * f("aaa") // => 6 + * ``` + * + * @see {@link pipe} for applying a value through a left-to-right sequence immediately + * @see {@link compose} for composing exactly two functions + * + * @category combinators + * @since 2.0.0 + */ +export function flow, B = never>( + ab: (...a: A) => B +): (...a: A) => B +export function flow, B = never, C = never>( + ab: (...a: A) => B, + bc: (b: B) => C +): (...a: A) => C +export function flow< + A extends ReadonlyArray, + B = never, + C = never, + D = never +>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D): (...a: A) => D +export function flow< + A extends ReadonlyArray, + B = never, + C = never, + D = never, + E = never +>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E +): (...a: A) => E +export function flow< + A extends ReadonlyArray, + B = never, + C = never, + D = never, + E = never, + F = never +>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F +): (...a: A) => F +export function flow< + A extends ReadonlyArray, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never +>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G +): (...a: A) => G +export function flow< + A extends ReadonlyArray, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never +>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H +): (...a: A) => H +export function flow< + A extends ReadonlyArray, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never +>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I +): (...a: A) => I +export function flow< + A extends ReadonlyArray, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never +>( + ab: (...a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, + gh: (g: G) => H, + hi: (h: H) => I, + ij: (i: I) => J +): (...a: A) => J +export function flow( + ab: Function, + bc?: Function, + cd?: Function, + de?: Function, + ef?: Function, + fg?: Function, + gh?: Function, + hi?: Function, + ij?: Function +): unknown { + switch (arguments.length) { + case 1: + return ab + case 2: + return function(this: unknown) { + return bc!(ab.apply(this, arguments)) + } + case 3: + return function(this: unknown) { + return cd!(bc!(ab.apply(this, arguments))) + } + case 4: + return function(this: unknown) { + return de!(cd!(bc!(ab.apply(this, arguments)))) + } + case 5: + return function(this: unknown) { + return ef!(de!(cd!(bc!(ab.apply(this, arguments))))) + } + case 6: + return function(this: unknown) { + return fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments)))))) + } + case 7: + return function(this: unknown) { + return gh!(fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments))))))) + } + case 8: + return function(this: unknown) { + return hi!(gh!(fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments)))))))) + } + case 9: + return function(this: unknown) { + return ij!(hi!(gh!(fg!(ef!(de!(cd!(bc!(ab.apply(this, arguments))))))))) + } + } + return +} + +/** + * Creates a compile-time placeholder for a value of any type. + * + * **When to use** + * + * Use as a temporary typed placeholder while developing incomplete code. + * + * **Gotchas** + * + * `hole` is intended for temporary development use. If the placeholder is + * evaluated at runtime, it throws. + * + * **Example** (Creating a development placeholder) + * + * ```ts import.meta.vitest + * import { hole } from "effect" + * + * // Intentionally not called: `hole` throws if the placeholder is evaluated. + * const buildUser = (id: number): { readonly id: number; readonly name: string } => ({ + * id, + * name: hole() + * }) + * + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export const hole: () => T = cast(absurd) + +/** + * Returns the second argument and discards the first. The SK combinator is + * a fundamental combinator in the lambda calculus and the SKI combinator + * calculus. + * + * **When to use** + * + * Use to discard the first argument and return the second argument. + * + * **Example** (Discarding the first argument) + * + * ```ts import.meta.vitest + * import { Function } from "effect" + * + * Function.SK(0, "hello") // => "hello" + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const SK = (_: A, b: B): B => b + +/** + * Creates a memoized function whose input is an object, caching results by + * object identity. + * + * **When to use** + * + * Use to reuse the result of a synchronous computation whose output is stable + * for a given object reference. + * + * **Details** + * + * Each memoized wrapper owns a private `WeakMap` keyed by object identity. + * + * **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. + * + * @category caching + * @since 4.0.0 + */ +export function memoize(f: (a: A) => O): (ast: A) => O { + const cache = new WeakMap() + return (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/.repos/effect/packages/effect/src/Graph.ts b/.repos/effect/packages/effect/src/Graph.ts new file mode 100644 index 000000000..4a099e305 --- /dev/null +++ b/.repos/effect/packages/effect/src/Graph.ts @@ -0,0 +1,8528 @@ +/** + * Models relationships between indexed nodes and edges. + * + * This module provides immutable and scoped-mutable graph data structures. A + * graph can be directed or undirected, and it can store user-defined data on + * both nodes and edges. The module includes traversal, analysis, path-finding, + * transformation, and diagram export utilities. + * + * @since 4.0.0 + */ + +import * as Data from "./Data.ts" +import * as Equal from "./Equal.ts" +import { dual } from "./Function.ts" +import * as Hash from "./Hash.ts" +import type { Inspectable } 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 { hasProperty } from "./Predicate.ts" +import type { Covariant, Invariant } from "./Types.ts" + +const TypeId = internal.TypeId + +/** + * Node index for node identification using plain numbers. + * + * **When to use** + * + * Use when storing or passing the stable identifier of a graph node between + * `Graph` operations. + * + * **Details** + * + * `addNode` allocates node identifiers from the graph's next node index. + * + * **Gotchas** + * + * A `NodeIndex` is an identifier, not an array offset. Removed node identifiers + * are not reused. + * + * @see {@link EdgeIndex} for edge identifiers instead of node identifiers + * + * @category models + * @since 3.18.0 + */ +export type NodeIndex = number + +/** + * Edge index for edge identification using plain numbers. + * + * **When to use** + * + * Use when you need to keep the identifier for a graph edge so you can later + * read, update, remove, or compare that edge. + * + * **Gotchas** + * + * An `EdgeIndex` is an identifier, not an array offset. Removed edge + * identifiers are not reused. + * + * @see {@link NodeIndex} for node identifiers instead of edge identifiers + * + * @category models + * @since 3.18.0 + */ +export type EdgeIndex = number + +/** + * Represents edge data containing source, target, and user data. + * + * **When to use** + * + * Use as the graph edge value that carries source node, target node, and stored + * edge data together. + * + * @category models + * @since 3.18.0 + */ +export interface Edge { + readonly source: NodeIndex + readonly target: NodeIndex + readonly data: E +} + +/** + * Graph type for distinguishing directed and undirected graphs. + * + * **When to use** + * + * Use when writing graph-polymorphic types or helpers that need to preserve + * whether a graph is directed or undirected. + * + * @category models + * @since 3.18.0 + */ +export type Kind = "directed" | "undirected" + +/** + * 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** + * + * 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 4.0.0 + */ +export interface Snapshot { + readonly type: T + readonly nodes: ReadonlyArray> + readonly edges: ReadonlyArray> +} + +/** + * Common public protocol for graph values. + * + * **Details** + * + * Contains only the runtime marker and shared protocols. Graph storage is kept + * internal; use module functions such as `nodes`, `edges`, `getNode`, and + * `getEdge` to inspect graph contents. + * + * @category protocols + * @since 3.18.0 + */ +export interface Proto extends Iterable, Equal.Equal, Pipeable, Inspectable { + readonly [TypeId]: Graph.Variance +} + +/** + * Immutable graph interface. + * + * **When to use** + * + * 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 + * + * @category models + * @since 3.18.0 + */ +export interface Graph extends Proto { + 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. + * + * **When to use** + * + * 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 + * + * @category models + * @since 3.18.0 + */ +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. + * + * **When to use** + * + * Use as the immutable graph type when edge direction is part of the model and + * traversal or neighbor queries should follow source-to-target edges. + * + * **Details** + * + * `DirectedGraph` is a `Graph` with node data of type + * `N` and edge data of type `E`. + * + * @see {@link directed} for constructing directed graphs + * @see {@link UndirectedGraph} for graphs whose edges connect both endpoints + * + * @category models + * @since 3.18.0 + */ +export type DirectedGraph = Graph + +/** + * Immutable graph type for relationships without source-to-target direction. + * + * **When to use** + * + * Use when modeling relationships where each edge connects both endpoints + * without a source-to-target direction. + * + * **Details** + * + * `UndirectedGraph` is a `Graph`. + * + * @see {@link undirected} for constructing undirected graphs + * @see {@link DirectedGraph} for graphs whose edges have source-to-target direction + * + * @category models + * @since 3.18.0 + */ +export type UndirectedGraph = Graph + +/** + * Mutable directed graph type alias. + * + * **When to use** + * + * Use when annotating a temporary graph value that can be changed in place and + * whose edges have source-to-target direction. + * + * @see {@link MutableGraph} for the generic mutable graph type + * @see {@link MutableUndirectedGraph} for mutable graphs without edge direction + * + * @category models + * @since 3.18.0 + */ +export type MutableDirectedGraph = MutableGraph + +/** + * Mutable undirected graph type alias. + * + * **When to use** + * + * Use when annotating a temporary graph value that can be changed in place and + * whose edges connect both endpoints without direction. + * + * @see {@link MutableDirectedGraph} for mutable graphs with directed edges + * @see {@link MutableGraph} for the generic mutable graph type + * + * @category models + * @since 3.18.0 + */ +export type MutableUndirectedGraph = MutableGraph + +// ============================================================================= +// Errors +// ============================================================================= + +// TODO: Do we need safe variants for these? + +/** + * Error thrown by graph operations when the requested graph structure is + * invalid, such as referencing a missing node or using unsupported edge + * weights. + * + * **When to use** + * + * Use when handling failures thrown by graph operations that reject invalid + * graph structure or unsupported algorithm inputs. + * + * @category errors + * @since 3.18.0 + */ +export class GraphError extends Data.TaggedError("GraphError")<{ + readonly message: string +}> {} + +/** @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 an immutable or mutable graph. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a graph value. + * + * **Gotchas** + * + * This guard checks the shared graph runtime type identifier and does not + * distinguish immutable graphs from mutable graphs or directed graphs from + * undirected graphs. + * + * @category guards + * @since 4.0.0 + */ +export const isGraph = ( + u: U | Graph | MutableGraph +): u is Graph | MutableGraph => hasProperty(u, TypeId) + +/** + * 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 import.meta.vitest + * import { Graph } from "effect" + * + * // Directed graph with initial nodes and edges + * 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, 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) => 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 import.meta.vitest + * import { Graph } from "effect" + * + * // Undirected graph with initial nodes and edges + * const graph = 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, "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) => undefined +) => UndirectedGraph = make("undirected") + +// ============================================================================= +// Scoped Mutable API +// ============================================================================= + +/** + * 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 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 => { + 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 import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed() + * const mutable = Graph.beginMutation(graph) + * // ... perform mutations on 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 => { + 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) +} + +/** + * Returns an immutable graph after applying scoped mutations to a structural copy. + * + * **When to use** + * + * Use for the usual immutable update workflow 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 import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed() + * const newGraph = Graph.mutate(graph, (mutable) => { + * const nodeA = Graph.addNode(mutable, "A") + * const nodeB = Graph.addNode(mutable, "B") + * Graph.addEdge(mutable, nodeA, nodeB, 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) => undefined + ): (graph: Graph) => Graph + ( + graph: Graph, + f: (mutable: MutableGraph) => undefined + ): Graph +} = dual(2, ( + graph: Graph, + f: (mutable: MutableGraph) => undefined +): Graph => { + const mutable = beginMutation(graph) + return mutateScoped(mutable, f) +}) + +// ============================================================================= +// 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))) + } +} + +/** + * Configures node and edge identity for graph set operations. + * + * **When to use** + * + * Use when logical graph membership should be based on a stable key rather + * than the complete node or edge payload. + * + * **Details** + * + * 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** + * + * 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 configuration + * @since 4.0.0 + */ +export interface IdentityOptions { + readonly nodeIdentity?: (node: N) => NI + readonly edgeIdentity?: (edge: E) => EI +} + +/** @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) + } + + return { byIdentity, byIndex } +} + +/** @internal */ +const nodeIdentityAt = (maps: NodeMaps, index: NodeIndex): I => maps.byIndex.get(index) as I + +/** @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` }) + } +} + +/** + * Composes two graphs, merging nodes by identity. + * + * **When to use** + * + * Use when combining graphs that describe overlapping logical entities and + * should merge those entities by payload or a projected identity. + * + * **Details** + * + * 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. + * + * `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}` + * + * **Gotchas** + * + * 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. + * + * **Example** (Combining graphs) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * 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 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 + * }) + * + * Graph.nodeCount(result) // => 3 + * Graph.edgeCount(result) // => 2 + * ``` + * + * @see {@link sum} for combining graphs without merging equal nodes + * @category set operations + * @since 4.0.0 + */ +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 intersection of two graphs, matching nodes by identity. + * + * **When to use** + * + * Use when extracting nodes and edges that represent the same logical + * structure in both graphs. + * + * **Details** + * + * 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. + * + * `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}` + * + * **Gotchas** + * + * 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 import.meta.vitest + * import { Graph } from "effect" + * + * const left = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * Graph.addEdge(mutable, a, b, "shared") + * }) + * + * 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) + * + * Graph.nodeCount(result) // => 2 + * Graph.edgeCount(result) // => 1 + * ``` + * + * @see {@link compose} for identity-based graph union + * @category set operations + * @since 4.0.0 + */ +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) + } + } + + 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) + } + } + }) +}) + +/** + * Returns `self` without edges also present in `that`. + * + * **When to use** + * + * Use when retaining all logical nodes from one graph while removing edge + * relationships also represented by another graph. + * + * **Details** + * + * 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. + * + * `G1 \ G2 = {V1, E1 \ E2}` + * + * **Gotchas** + * + * 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. + * + * **Example** (Removing shared edges) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * 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.difference(left, right) + * + * Graph.nodeCount(result) // => 3 + * Graph.edgeCount(result) // => 1 + * ``` + * + * @see {@link symmetricDifference} for retaining edges unique to either graph + * @category set operations + * @since 4.0.0 + */ +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) + } + } + }) +}) + +/** + * Returns edges present in exactly one of two graphs. + * + * **When to use** + * + * Use when comparing graphs and retaining relationships unique to either one. + * + * **Details** + * + * 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. + * + * `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}` + * + * **Gotchas** + * + * 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 import.meta.vitest + * import { Graph } from "effect" + * + * 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") + * const d = Graph.addNode(mutable, "D") + * Graph.addEdge(mutable, b, c, "B-C") + * Graph.addEdge(mutable, c, d, "C-D") + * }) + * + * const result = Graph.symmetricDifference(left, right) + * + * Graph.nodeCount(result) // => 4 + * Graph.edgeCount(result) // => 2 + * ``` + * + * @see {@link difference} for removing only the edges found in another graph + * @category set operations + * @since 4.0.0 + */ +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) + } + + 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) + } + } + }) +}) + +/** + * Returns the complement over the existing node set. + * + * **When to use** + * + * Use when materializing every relationship that is currently absent between + * distinct nodes. + * + * **Details** + * + * 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`. + * + * `G' = {V, ((V x V) without self-pairs) \ E}` + * + * **Gotchas** + * + * Self-loops are never created. If any edge already connects a pair, parallel + * complement edges are not added. The result allocates new identifiers. + * + * **Example** (Finding missing relationships) + * + * ```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 result = Graph.complement(graph, (source, target) => `${source}-${target}`) + * + * Graph.edgeCount(result) // => 1 + * ``` + * + * @see {@link hasEdge} for testing one relationship + * @category set operations + * @since 4.0.0 + */ +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) + + return make(self.type)((mutable) => { + const newIndices = new Uint32Array(cache.nodeIds.length) + + for (let i = 0; i < cache.nodeIds.length; i++) { + newIndices[i] = addNode(mutable, cache.nodeData[i] as N) + } + + 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 + + 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 +} + +/** + * Returns the induced subgraph containing nodes within a radius of a node. + * + * **When to use** + * + * Use when extracting a local reachable region around one node. + * + * **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 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 result = Graph.neighborhood(graph, 1, { radius: 1 }) + * + * Graph.nodeCount(result) // => 2 + * ``` + * + * @see {@link inducedSubgraph} for selecting nodes while preserving identifiers + * @category set operations + * @since 4.0.0 + */ +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() + + for (const index of indices(bfs(self, { start: [nodeIndex], direction, radius }))) { + reached.add(index) + } + + return make(self.type)((mutable) => { + const newIndexMap = new Map() + + 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) + } + } + } + }) +}) + +/** + * Returns the subgraph induced by a collection of node indices. + * + * **When to use** + * + * Use when selecting an exact node set while preserving its active node and + * edge identifiers. + * + * **Details** + * + * 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. + * + * **Gotchas** + * + * 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 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) + } + + 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 }) +}) + +/** + * Returns the disjoint union of two graphs. + * + * **When to use** + * + * Use when combining graphs while keeping every node distinct, even when node + * payloads are equal. + * + * **Details** + * + * 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. + * + * `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}` + * + * **Gotchas** + * + * All node and edge identifiers are newly allocated. + * + * @see {@link compose} for merging overlapping logical nodes by identity + * + * @category set operations + * @since 4.0.0 + */ +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)) + } + + 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) + } + } + } + + copyInto(self) + copyInto(that) + }) +}) + +// ============================================================================= +// Basic Node Operations +// ============================================================================= + +/** + * Adds a new node to a mutable graph and returns its index. + * + * **When to use** + * + * Use to allocate a new node in a mutable graph before storing edges or + * querying it by index. + * + * **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. + * + * **Gotchas** + * + * `NodeIndex` values are identifiers and are not reused after removals. + * + * **Example** (Adding nodes) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * 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 addEdge} for connecting existing nodes + * + * @category mutations + * @since 3.18.0 + */ +export const addNode = ( + mutable: MutableGraph, + 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" }) + } + + // Add node data + impl.nodes.set(nodeIndex, data) + + // Initialize empty adjacency lists + impl.adjacency.set(nodeIndex, []) + impl.reverseAdjacency.set(nodeIndex, []) + + // Update graph allocators + impl.nextNodeIndex = impl.nextNodeIndex + 1 + + return nodeIndex +} + +/** + * Gets the data associated with a node index safely, if it exists. + * + * **Example** (Getting node data) + * + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Node A") + * }) + * + * Graph.getNode(graph, 0) // => Option.some("Node A") + * ``` + * + * @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 => { + const impl = internal.toImpl(graph) + return impl.nodes.has(nodeIndex) ? Option.some(impl.nodes.get(nodeIndex)!) : Option.none() +}) + +/** + * Checks whether a node with the given index exists in the graph. + * + * **Example** (Checking node existence) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Node A") + * }) + * + * Graph.hasNode(graph, 0) // => true + * Graph.hasNode(graph, 999) // => false + * ``` + * + * @category predicates + * @since 3.18.0 + */ +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)) + +/** + * Returns the number of nodes in the graph. + * + * **Example** (Counting nodes) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * 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") + * }) + * + * Graph.nodeCount(graphWithNodes) // => 3 + * ``` + * + * @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 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 => { + 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() + }) +}) + +/** + * Finds all nodes that match the given predicate. + * + * **Example** (Finding matching nodes) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Start A") + * Graph.addNode(mutable, "Node B") + * Graph.addNode(mutable, "Start C") + * }) + * + * Graph.findNodes(graph, (data) => data.startsWith("Start")) // => [0, 2] + * Graph.findNodes(graph, (data) => data === "Not Found") // => [] + * ``` + * + * @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 impl = internal.toImpl(graph) + return withMutationGuard(graph, () => { + const results: Array = [] + for (const [index, data] of impl.nodes) { + if (predicate(data)) { + results.push(index) + } + } + return results + }) +}) + +/** + * Finds the first edge that matches the given predicate. + * + * **Example** (Finding the first matching edge) + * + * ```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") + * const nodeC = Graph.addNode(mutable, "Node C") + * Graph.addEdge(mutable, nodeA, nodeB, 10) + * Graph.addEdge(mutable, nodeB, nodeC, 20) + * }) + * + * 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 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 Option.none() + }) +}) + +/** + * Finds all edges that match the given predicate. + * + * **Example** (Finding matching 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) + * Graph.addEdge(mutable, nodeC, nodeA, 30) + * }) + * + * Graph.findEdges(graph, (data) => data >= 20) // => [1, 2] + * Graph.findEdges(graph, (data) => data > 100) // => [] + * ``` + * + * @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 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 + }) +}) + +/** + * Updates a single node's data by applying a transformation function. + * + * **When to use** + * + * Use when replacing one node payload while preserving its identifier and + * incident edges. + * + * **Gotchas** + * + * A missing node index is ignored. The transformation may query the graph, but + * cannot mutate or finalize the same graph while it runs. + * + * **Example** (Updating node data) + * + * ```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 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) + }) +} + +/** + * Updates a single edge's data by applying a transformation function. + * + * **When to use** + * + * Use when replacing one edge payload while preserving its identifier and + * endpoints. + * + * **Gotchas** + * + * A missing edge index is ignored. The transformation may query the graph, but + * cannot mutate or finalize the same graph while it runs. + * + * **Example** (Updating edge data) + * + * ```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 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 + }) + }) +} + +/** + * Transforms every node's data in a mutable graph in place using the provided + * mapping function. + * + * **When to use** + * + * Use when updating every node payload without changing graph structure. + * + * **Details** + * + * Node indices and edges are preserved; only the stored node data is replaced. + * + * **Gotchas** + * + * 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. + * + * **Example** (Mapping node data) + * + * ```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.addNode(mutable, "node c") + * Graph.mapNodes(mutable, (data) => data.toUpperCase()) + * }) + * + * Graph.getNode(graph, 0) // => Option.some("NODE A") + * ``` + * + * @see {@link updateNode} for updating one node + * @see {@link filterMapNodes} for mapping while removing nodes + * @category mapping + * @since 3.18.0 + */ +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) + } + }) +} + +/** + * Transforms every edge payload in a mutable graph in place. + * + * **When to use** + * + * Use when updating every edge payload without changing graph structure. + * + * **Details** + * + * Edge identifiers and endpoints are preserved. + * + * **Gotchas** + * + * 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. + * + * **Example** (Mapping edge data) + * + * ```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) + * ``` + * + * @see {@link updateEdge} for updating one edge + * @see {@link filterMapEdges} for mapping while removing edges + * @category mapping + * @since 3.18.0 + */ +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 + }) + } + }) +} + +/** + * Swaps source and target nodes for every edge in a mutable graph. + * + * **When to use** + * + * Use when reversing every relationship in a directed graph, such as creating + * a dependency transpose. + * + * **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 + * }) + * + * Option.map(Graph.getEdge(graph, 0), (edge) => edge.source) // => Option.some(1) + * ``` + * + * @category transforming + * @since 3.18.0 + */ +export const reverse = ( + mutable: MutableGraph +): void => { + const impl = getMutableImplForMutation(mutable) + if (impl.type === "undirected") { + return + } + + // 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 + }) + } + + const adjacency = impl.adjacency + impl.adjacency = impl.reverseAdjacency + impl.reverseAdjacency = adjacency + + // Invalidate cycle flag since edge directions changed + impl.acyclic = Option.none() +} + +/** + * 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. + * + * **Gotchas** + * + * 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** (Filtering and mapping nodes) + * + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * 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) + * + * // 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 filtering + * @since 3.18.0 + */ +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) +} + +/** + * Filters and optionally transforms edges in a mutable graph using a predicate function. + * Edges that return Option.none are removed from the graph. + * + * **Gotchas** + * + * 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** (Filtering and mapping edges) + * + * ```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, 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() + * ) + * }) + * + * Graph.edgeCount(graph) // => 2 + * ``` + * + * @category filtering + * @since 3.18.0 + */ +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) +} + +/** + * 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. + * + * **Gotchas** + * + * The predicate may query the graph, but cannot mutate or finalize the same + * graph while it runs. + * + * **Example** (Filtering nodes) + * + * ```ts import.meta.vitest + * 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") + * }) + * + * Graph.nodeCount(graph) // => 2 + * ``` + * + * @category filtering + * @since 3.18.0 + */ +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) +} + +/** + * 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. + * + * **Gotchas** + * + * The predicate may query the graph, but cannot mutate or finalize the same + * graph while it runs. + * + * **Example** (Filtering edges) + * + * ```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, 5) + * Graph.addEdge(mutable, b, c, 15) + * Graph.addEdge(mutable, c, a, 25) + * + * // Keep only edges with weight >= 10 + * Graph.filterEdges(mutable, (data) => data >= 10) + * }) + * + * Graph.edgeCount(graph) // => 2 + * ``` + * + * @category filtering + * @since 3.18.0 + */ +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) + } + } + }) + + // Remove filtered out edges + removeEdges(mutable, remove) +} + +// ============================================================================= +// 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() + } +} + +/** @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 +// ============================================================================= + +/** + * 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** + * + * 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. + * + * **Gotchas** + * + * The source and target nodes must already exist in the mutable graph; missing + * endpoints throw a `GraphError`. + * + * **Example** (Adding edges) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * 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 + * }) + * ``` + * + * @see {@link mutate} for obtaining a mutable graph from an immutable graph + * @see {@link addNode} for creating node indexes before connecting them + * + * @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. + * + * **Gotchas** + * + * A missing node index is ignored. + * + * **Example** (Removing a node) + * + * ```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") + * Graph.addEdge(mutable, nodeA, nodeB, 42) + * + * // Remove nodeA and all edges connected to it + * Graph.removeNode(mutable, nodeA) + * }) + * Array.of(Graph.nodeCount(result), Graph.edgeCount(result)) // => [1, 0] + * ``` + * + * @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. + * + * **When to use** + * + * Use when deleting a collection of nodes 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 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 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) + + let removed = false + for (const nodeIndex of indices) { + if (removeNodeInternal(impl, nodeIndex)) { + removed = true + } + } + + if (removed) { + invalidateCycleFlagOnRemoval(impl) + } +} + +/** @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 + } + + const edgesToRemove = new Set(impl.adjacency.get(nodeIndex)!) + for (const edgeIndex of impl.reverseAdjacency.get(nodeIndex)!) { + edgesToRemove.add(edgeIndex) + } + + 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) + } + + // Remove the node itself + impl.nodes.delete(nodeIndex) + impl.adjacency.delete(nodeIndex) + impl.reverseAdjacency.delete(nodeIndex) + + 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. + * + * 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. + * + * 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. + * + * 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. + * + * 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. + * + * 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. + * + * 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. + * + * 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 + +/** + * Returns the maximal biconnected node components of an undirected graph. + * + * **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** + * + * Throws a `GraphError` when used with a directed graph. + * + * **Example** (Finding biconnected components) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * 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) + * }) + * + * Graph.biconnectedComponents(graph) // => [[0, 1, 2], [2, 3, 4]] + * ``` + * + * @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 const biconnectedComponents = ( + graph: Graph | MutableGraph +): Array> => analyzeLowLinks(graph).biconnectedComponents + +/** + * Configuration for source-to-target flow algorithms. + * + * **When to use** + * + * Use when defining endpoints and edge capacities for `maximumFlow` or + * `minimumCut`. + * + * **Details** + * + * `capacity` receives stored edge data and must return a finite, + * non-negative number. + * + * **Gotchas** + * + * The source and target must be distinct existing nodes in a directed graph. + * + * @category configuration + * @since 4.0.0 + */ +export interface MaximumFlowConfig { + readonly source: NodeIndex + readonly target: NodeIndex + readonly capacity: (edge: E) => number +} + +/** + * 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 +} + +/** + * 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 +} + +/** @internal */ +interface FlowSolution extends MaximumFlowResult { + readonly sourceSide: Uint8Array + readonly nodeIds: Array +} + +/** @internal */ +interface ResidualArc { + readonly from: number + readonly to: number + readonly capacity: number + readonly edge: number + flow: number +} + +/** @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" }) + } + + 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 }) + } + }) + + 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 + } + } + } + } + if (visited[target] === 0) { + break + } + + 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 + } + + 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) + } + + 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 + } + } + } + + 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 } +} + +/** + * 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** + * + * 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. + * + * **Gotchas** + * + * 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" + * + * 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) + * }) + * + * Graph.maximumFlow(graph, { source: 0, target: 2, capacity: (edge) => edge }).value // => 3 + * ``` + * + * @see {@link minimumCut} for the residual-reachability partition + * @category algorithms + * @since 4.0.0 + */ +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 } +}) + +/** + * 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 + */ +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 { value: solution.value, edges: solution.cut, source, target } +}) + +/** + * Finds weakly connected components in a directed graph. + * + * **When to use** + * + * Use when grouping directed nodes by connectivity while ignoring edge + * orientation. + * + * **Details** + * + * 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 4.0.0 + */ +export const weaklyConnectedComponents = ( + graph: Graph | MutableGraph +): Array> => { + 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 (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]) + + 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) + } + return components +} + +/** + * 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** + * + * Throws a `GraphError` when used with an undirected graph. + * + * **Example** (Finding strongly connected 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") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, "A->B") + * Graph.addEdge(mutable, b, c, "B->C") + * Graph.addEdge(mutable, c, a, "C->A") // Creates SCC: A-B-C + * }) + * + * 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 + */ +export const stronglyConnectedComponents = ( + graph: Graph | MutableGraph +): Array> => { + if ((graph as Graph | MutableGraph).type === "undirected") { + throw new GraphError({ message: "Cannot find strongly connected components of undirected graph" }) + } + + 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]) + + 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() + } + } + } + + 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 component: Array = [] + stack.push(start) + + while (stack.length > 0) { + const node = stack.pop()! + if (visited[node] !== 0) { + continue + } + visited[node] = 1 + component.push(cache.nodeIds[node]) + + for (let j = incoming.rowOffsets[node]; j < incoming.rowOffsets[node + 1]; j++) { + const predecessor = incoming.columnIndices[j] + if (visited[predecessor] === 0) { + stack.push(predecessor) + } + } + } + + components.push(component) + } + + return components +} + +/** @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 + } + } + } + } + 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)) +} + +/** + * 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) +} + +/** + * 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 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 }) +}) + +/** + * 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" }) + } + + 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) + } + } + + 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) + } + } + } + + 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 }) +} + +// ============================================================================= +// Path Finding Algorithms +// ============================================================================= + +/** + * Result of a shortest path computation. + * + * **When to use** + * + * Use to read the successful source-to-target shortest path returned by + * path-finding algorithms, including the ordered node and edge indices, total + * distance, and traversed edge data. + * + * **Details** + * + * 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. + * + * @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. + * + * **When to use** + * + * Use when configuring `dijkstra` to find a shortest path between two existing + * node indices with non-negative edge costs. + * + * **Details** + * + * Specifies the source and target node indices, plus a cost function that maps + * 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 or `NaN`. + * + * @category configuration + * @since 3.18.0 + */ +export interface DijkstraConfig { + source: NodeIndex + target: NodeIndex + cost: (edgeData: E) => number +} + +/** + * 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 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.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, 5) + * Graph.addEdge(mutable, a, c, 10) + * Graph.addEdge(mutable, b, c, 2) + * }) + * + * const result = Graph.dijkstra(graph, { + * source: 0, + * target: 2, + * cost: (edgeData) => edgeData + * }) + * + * 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 + */ +export const dijkstra: { + ( + config: DijkstraConfig + ): (graph: Graph | MutableGraph) => Option.Option> + ( + graph: Graph | MutableGraph, + config: DijkstraConfig + ): Option.Option> +} = dual(2, ( + graph: Graph | MutableGraph, + config: DijkstraConfig +): Option.Option> => { + const impl = internal.toImpl(graph) + // Validate that source and target nodes exist + if (!impl.nodes.has(config.source)) { + throw missingNode(config.source) + } + if (!impl.nodes.has(config.target)) { + throw missingNode(config.target) + } + + 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: [] + }) + } + + 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[currentNode] = 1 + if (currentNode === target) { + break + } + + 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++) + } + } + } + } + + if (distances[target] === Infinity) { + return Option.none() + } + + const path: Array = [] + const edges: Array = [] + const costs: Array = [] + 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, edges, distance: distances[target], costs }) +}) + +/** + * Result of an all-pairs shortest path computation. + * + * **When to use** + * + * Use when storing or passing around the complete output of `floydWarshall` so + * 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, 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 + */ +export interface AllPairsResult { + readonly distances: Map> + readonly paths: Map | null>> + readonly edges: Map>> + readonly costs: Map>> +} + +/** + * 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, 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 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, 3) + * Graph.addEdge(mutable, b, c, 2) + * Graph.addEdge(mutable, a, c, 7) + * }) + * + * const result = Graph.floydWarshall(graph, (edgeData) => edgeData) + * 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 + */ +export const floydWarshall: { + ( + cost: (edgeData: E) => number + ): (graph: Graph | MutableGraph) => AllPairsResult + ( + graph: Graph | MutableGraph, + cost: (edgeData: E) => number + ): AllPairsResult +} = dual(2, ( + graph: Graph | MutableGraph, + cost: (edgeData: E) => number +): AllPairsResult => { + 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 + } + + 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 + } + } + } + }) + + 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 + } + } + } + } + + 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]}` }) + } + } + + const distances = new Map>() + const paths = new Map | null>>() + const edgePaths = new Map>>() + const costs = new Map>>() + 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) { + 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 { + const path = [source] + const pathEdges: Array = [] + const pathCosts: Array = [] + let current = i + while (current !== j) { + const next = nextMatrix[current * size + j] + if (next === -1) { + break + } + 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]) + } + pathRow.set(target, path) + edgePathRow.set(target, pathEdges) + costRow.set(target, pathCosts) + } + } + } + + return { distances, paths, edges: edgePaths, costs } +}) + +/** + * Configuration for finding a shortest path with the A* algorithm. + * + * **When to use** + * + * Use when configuring `astar` for point-to-point shortest-path searches where + * node data can provide a heuristic estimate toward the target. + * + * **Details** + * + * 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** + * + * Heuristic values must be finite and the heuristic must be consistent for A* + * to guarantee a shortest path. + * + * @category configuration + * @since 3.18.0 + */ +export interface AstarConfig { + source: NodeIndex + target: NodeIndex + cost: (edgeData: E) => number + heuristic: (sourceNodeData: N, targetNodeData: N) => number +} + +/** + * 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 not `NaN`. + * `Infinity` is allowed and behaves like an impassable edge. Returns + * `Option.none()` when the target is not reachable. + * + * **Gotchas** + * + * 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`. + * + * **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 }) + * const b = Graph.addNode(mutable, { x: 1, y: 0 }) + * const c = Graph.addNode(mutable, { x: 2, y: 0 }) + * Graph.addEdge(mutable, a, b, 1) + * Graph.addEdge(mutable, b, c, 1) + * }) + * + * // Manhattan distance heuristic + * const heuristic = ( + * nodeData: { x: number; y: number }, + * targetData: { x: number; y: number } + * ) => Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y) + * + * const result = Graph.astar(graph, { + * source: 0, + * target: 2, + * cost: (edgeData) => edgeData, + * heuristic + * }) + * + * 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 + */ +export const astar: { + ( + config: AstarConfig + ): (graph: Graph | MutableGraph) => Option.Option> + ( + graph: Graph | MutableGraph, + config: AstarConfig + ): Option.Option> +} = dual(2, ( + graph: Graph | MutableGraph, + config: AstarConfig +): Option.Option> => { + const impl = internal.toImpl(graph) + // Validate that source and target nodes exist + if (!impl.nodes.has(config.source)) { + throw missingNode(config.source) + } + if (!impl.nodes.has(config.target)) { + throw missingNode(config.target) + } + + 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: [] + }) + } + + 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 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[current] = 1 + if (current === target) { + break + } + + 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++) + } + } + } + + if (scores[target] === Infinity) { + return Option.none() + } + + const path: Array = [] + const edges: Array = [] + const costs: Array = [] + 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, edges, distance: scores[target], costs }) +}) + +/** + * Configuration for finding a shortest path with the Bellman-Ford algorithm. + * + * **When to use** + * + * Use when configuring `bellmanFord` to find a shortest path where edge + * weights may be negative. + * + * **Details** + * + * Specifies the source and target node indices, plus a cost function that maps + * each edge's data to a numeric weight. + * + * @category configuration + * @since 3.18.0 + */ +export interface BellmanFordConfig { + source: NodeIndex + target: NodeIndex + cost: (edgeData: E) => number +} + +/** + * 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, 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.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) // Negative weight allowed + * Graph.addEdge(mutable, b, c, 3) + * Graph.addEdge(mutable, a, c, 5) + * }) + * + * const result = Graph.bellmanFord(graph, { + * source: 0, + * target: 2, + * cost: (edgeData) => edgeData + * }) + * + * 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 + */ +export const bellmanFord: { + ( + config: BellmanFordConfig + ): (graph: Graph | MutableGraph) => Option.Option> + ( + graph: Graph | MutableGraph, + config: BellmanFordConfig + ): Option.Option> +} = dual(2, ( + graph: Graph | MutableGraph, + config: BellmanFordConfig +): Option.Option> => { + const impl = internal.toImpl(graph) + // Validate that source and target nodes exist + if (!impl.nodes.has(config.source)) { + throw missingNode(config.source) + } + if (!impl.nodes.has(config.target)) { + throw missingNode(config.target) + } + + const cache = csr.get(graph) + const edges = csr.getEdges(cache) + const edgeIds = csr.getEdgeIds(cache) + const edgeCache = csr.getEdgeEndpoints(cache) + const outgoing = csr.getOutgoing(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 + } + + 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 + + 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 + } + } + + // 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++) { + 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() + } + + 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 }) +}) + +/** + * 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> {} + +/** + * 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) + } + } + } + } + + 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() + } + } + + 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 + } + 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 }) + } + }) +}) + +/** + * Represents an iterable wrapper used by graph traversal and listing APIs. + * + * **Details** + * + * A `Walker` yields `[index, data]` pairs lazily and can be viewed as just the + * indices, just the values, or mapped entries with `indices`, `values`, + * `entries`, and `visit`. + * + * **Example** (Working with node walkers) + * + * ```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, 1) + * }) + * + * // Both traversal and element iterators return NodeWalker + * const dfsNodes: Graph.NodeWalker = Graph.dfs(graph, { start: [0] }) + * const allNodes: Graph.NodeWalker = Graph.nodes(graph) + * + * // Common interface for working with node iterables + * function processNodes(nodeIterable: Graph.NodeWalker): Array { + * return Array.from(Graph.indices(nodeIterable)) + * } + * + * // Access node data using values() or entries() + * Array.from(Graph.values(dfsNodes)) // => ["A", "B"] + * Array.from(Graph.entries(allNodes)) // => [[0, "A"], [1, "B"]] + * ``` + * + * @category models + * @since 3.18.0 + */ +export class Walker implements Iterable<[T, N]> { + // @ts-ignore + readonly [Symbol.iterator]: () => Iterator<[T, N]> + + /** + * Lazily maps each walker entry with the provided function. + * + * **Details** + * + * The function receives the index and data and runs as the returned iterable + * is consumed. + * + * **Example** (Visiting walker elements) + * + * ```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, 1) + * }) + * + * const dfs = Graph.dfs(graph, { start: [0] }) + * + * // Map to just the node data + * Array.from(dfs.visit((index, data) => data)) // => ["A", "B"] + * + * // Map to custom objects + * Array.from(dfs.visit((index, data) => ({ id: index, name: data }))) // => [{ id: 0, name: "A" }, { id: 1, name: "B" }] + * ``` + * + * @since 4.0.0 + */ + readonly visit: (f: (index: T, data: N) => U) => Iterable + + constructor( + visit: (f: (index: T, data: N) => U) => Iterable + ) { + this.visit = visit + 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 +} + +/** + * Type alias for node iteration using Walker. + * NodeWalker is represented as Walker. + * + * **When to use** + * + * Use as the shared node walker type returned by graph traversal and node + * listing APIs. + * + * @see {@link Walker} for the generic lazy iterator wrapper + * @see {@link EdgeWalker} for edge iterators + * + * @category models + * @since 3.18.0 + */ +export type NodeWalker = Walker + +/** + * Type alias for edge iteration using Walker. + * EdgeWalker is represented as Walker>. + * + * **When to use** + * + * Use to type helpers or parameters that consume edge iterators returned by + * `Graph` APIs, where each item is keyed by an `EdgeIndex` and carries the + * full `Edge`. + * + * @see {@link Walker} for the generic lazy iterator wrapper + * @see {@link NodeWalker} for node iterators + * @see {@link edges} for creating edge walkers + * + * @category models + * @since 3.18.0 + */ +export type EdgeWalker = Walker> + +/** + * Returns an iterator over the indices in the walker. + * + * **Example** (Iterating walker indices) + * + * ```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, 1) + * }) + * + * const dfs = Graph.dfs(graph, { start: [0] }) + * Array.from(Graph.indices(dfs)) // => [0, 1] + * ``` + * + * @category iterators + * @since 3.18.0 + */ +export const indices = (walker: Walker): Iterable => walker.visit((index, _) => index) + +/** + * Returns an iterator over the values (data) in the walker. + * + * **Example** (Iterating walker values) + * + * ```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, 1) + * }) + * + * const dfs = Graph.dfs(graph, { start: [0] }) + * Array.from(Graph.values(dfs)) // => ["A", "B"] + * ``` + * + * @category iterators + * @since 3.18.0 + */ +export const values = (walker: Walker): Iterable => walker.visit((_, data) => data) + +/** + * Returns an iterator over [index, data] entries in the walker. + * + * **Example** (Iterating walker entries) + * + * ```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, 1) + * }) + * + * const dfs = Graph.dfs(graph, { start: [0] }) + * Array.from(Graph.entries(dfs)) // => [[0, "A"], [1, "B"]] + * ``` + * + * @category iterators + * @since 3.18.0 + */ +export const entries = (walker: Walker): Iterable<[T, N]> => + walker.visit((index, data) => [index, data] as [T, N]) + +/** + * Configuration for DFS, BFS, and postorder graph traversals. + * + * **When to use** + * + * Use to configure the starting node indices and edge-following direction for + * lazy graph traversals. + * + * **Details** + * + * `start` supplies the node indices where traversal begins. If it is omitted, + * 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 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. + * + * @category configuration + * @since 3.18.0 + */ +export interface SearchConfig { + readonly start?: Array + 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. 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 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, 1) + * Graph.addEdge(mutable, b, c, 1) + * }) + * + * // Start from a specific node + * Array.from(Graph.indices(Graph.dfs(graph, { start: [0] }))) // => [0, 1, 2] + * + * 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 + */ +export const dfs: { + ( + config?: SearchConfig + ): (graph: Graph | MutableGraph) => NodeWalker + ( + graph: Graph | MutableGraph, + config?: SearchConfig + ): NodeWalker +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + config: SearchConfig = {} +): NodeWalker => { + const radius = traversalRadius(config.radius, Infinity) + const start = traversalStarts(graph, config.start) + const direction = config.direction ?? "outgoing" + + 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 = [] + + 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 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) + } + } + } + + 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) } + } + + 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) + } + } + } + + 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) } + } + + return { done: true, value: undefined } as const + } + } + }) +}) + +/** + * 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. 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 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, 1) + * Graph.addEdge(mutable, b, c, 1) + * }) + * + * // Start from a specific node + * Array.from(Graph.indices(Graph.bfs(graph, { start: [0] }))) // => [0, 1, 2] + * + * 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 + */ +export const bfs: { + ( + config?: SearchConfig + ): (graph: Graph | MutableGraph) => NodeWalker + ( + graph: Graph | MutableGraph, + config?: SearchConfig + ): NodeWalker +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + config: SearchConfig = {} +): NodeWalker => { + const radius = traversalRadius(config.radius, Infinity) + const start = traversalStarts(graph, config.start) + const direction = config.direction ?? "outgoing" + + 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 + } + } + + 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 + } + } + } + + if (radius === Infinity) { + return { + next() { + if (head >= tail) { + return { done: true, value: undefined } as const + } + + 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] + ) + } + + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } + } + } + } + + 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: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } + } + } + }) +}) + +/** + * Configuration for the topological sort iterator. + * + * **When to use** + * + * Use to prioritize specific zero in-degree nodes in a topological sort. + * + * **Details** + * + * `initials` optionally supplies zero in-degree node indices used as + * prioritized initial queue entries. Topological sorting still includes the + * other zero in-degree nodes and produces a complete topological order. + * + * **Gotchas** + * + * Throws a `GraphError` when any initial node has incoming edges. + * + * @category configuration + * @since 3.18.0 + */ +export interface TopoConfig { + readonly initials?: Array +} + +/** + * 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. 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 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, 1) + * Graph.addEdge(mutable, b, c, 1) + * }) + * + * 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 +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + config: TopoConfig = {} +): NodeWalker => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot perform topological sort on undirected graph" }) + } + + // Check if graph is acyclic first + if (!isAcyclic(graph)) { + throw new GraphError({ message: "Cannot perform topological sort on cyclic graph" }) + } + + const initials = Array.from(config.initials ?? []) + + // Validate that all initial nodes exist + for (const nodeIndex of initials) { + if (!hasNode(graph, nodeIndex)) { + throw missingNode(nodeIndex) + } + } + + 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) + } + if (inDegree[node] !== 0) { + throw new GraphError({ message: `Initial node ${initial} has incoming edges` }) + } + 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) + } + } + + 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) + } + } + } + + 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 + } + } + }) +}) + +/** + * 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. 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 import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed((mutable) => { + * const root = Graph.addNode(mutable, "root") + * const child1 = Graph.addNode(mutable, "child1") + * const child2 = Graph.addNode(mutable, "child2") + * Graph.addEdge(mutable, root, child1, 1) + * Graph.addEdge(mutable, root, child2, 1) + * }) + * + * // Postorder: children before parents + * 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 + */ +export const dfsPostOrder: { + ( + config?: SearchConfig + ): (graph: Graph | MutableGraph) => NodeWalker + ( + graph: Graph | MutableGraph, + config?: SearchConfig + ): NodeWalker +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + config: SearchConfig = {} +): NodeWalker => { + const radius = traversalRadius(config.radius, Infinity) + const start = traversalStarts(graph, config.start) + const direction = config.direction ?? "outgoing" + + 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 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 + } + } + } + + 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 + } + + 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) + } + } + + let startPosition = 0 + + 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 + } + } + + stack.pop() + primaryPositions.pop() + secondaryPositions.pop() + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } + } + } + } + }) +}) + +/** + * Creates a walker over all node index and payload entries in the graph. + * + * **Details** + * + * 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 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, 1) + * }) + * + * Array.from(Graph.indices(Graph.nodes(graph))) // => [0, 1, 2] + * ``` + * + * @category iterators + * @since 3.18.0 + */ +export const nodes = ( + graph: Graph | MutableGraph +): NodeWalker => + new Walker((f) => ({ + [Symbol.iterator]() { + const nodeMap = internal.toImpl(graph).nodes + const iterator = nodeMap.entries() + + return { + next() { + const result = iterator.next() + if (result.done) { + return { done: true, value: undefined } + } + const [nodeIndex, nodeData] = result.value + return { done: false, value: withMutationGuard(graph, () => f(nodeIndex, nodeData)) } + } + } + } + })) + +/** + * Creates a walker over all edge index and edge entries in the graph. + * + * **Details** + * + * 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 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, 1) + * Graph.addEdge(mutable, b, c, 2) + * }) + * + * Array.from(Graph.indices(Graph.edges(graph))) // => [0, 1] + * ``` + * + * @category iterators + * @since 3.18.0 + */ +export const edges = ( + graph: Graph | MutableGraph +): EdgeWalker => + new Walker((f) => ({ + [Symbol.iterator]() { + const edgeMap = internal.toImpl(graph).edges + const iterator = edgeMap.entries() + + return { + next() { + const result = iterator.next() + if (result.done) { + return { done: true, value: undefined } + } + const [edgeIndex, edgeData] = result.value + return { done: false, value: withMutationGuard(graph, () => f(edgeIndex, copyEdge(edgeData))) } + } + } + } + })) + +/** + * Configuration for selecting external nodes. + * + * **When to use** + * + * Use to configure how `externals` identifies graph boundary nodes when you + * need sinks with no outgoing edges or sources with no incoming edges. + * + * **Details** + * + * `direction` chooses which missing edge direction makes a node external: + * `"outgoing"` selects nodes with no outgoing edges, and `"incoming"` selects + * nodes with no incoming edges. If omitted, `direction` defaults to + * `"outgoing"`. + * + * @category configuration + * @since 3.18.0 + */ +export interface ExternalsConfig { + readonly direction?: Direction +} + +/** + * 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"`). + * + * **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 import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed((mutable) => { + * const source = Graph.addNode(mutable, "source") // 0 - no incoming + * const middle = Graph.addNode(mutable, "middle") // 1 - has both + * const sink = Graph.addNode(mutable, "sink") // 2 - no outgoing + * const isolated = Graph.addNode(mutable, "isolated") // 3 - no edges + * + * Graph.addEdge(mutable, source, middle, 1) + * Graph.addEdge(mutable, middle, sink, 2) + * }) + * + * // Nodes with no outgoing edges (sinks + isolated) + * Array.from(Graph.indices(Graph.externals(graph, { direction: "outgoing" }))) // => [2, 3] + * + * // Nodes with no incoming edges (sources + isolated) + * Array.from(Graph.indices(Graph.externals(graph, { direction: "incoming" }))) // => [0, 3] + * ``` + * + * @category iterators + * @since 3.18.0 + */ +export const externals: { + ( + config?: ExternalsConfig + ): (graph: Graph | MutableGraph) => NodeWalker + ( + graph: Graph | MutableGraph, + config?: ExternalsConfig + ): NodeWalker +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + config: ExternalsConfig = {} +): NodeWalker => { + const direction = config.direction ?? "outgoing" + + return new Walker((f) => ({ + [Symbol.iterator]: () => { + const impl = internal.toImpl(graph) + const nodeMap = impl.nodes + const adjacencyMap = direction === "incoming" + ? impl.reverseAdjacency + : impl.adjacency + + const nodeIterator = nodeMap.entries() + + const nextMapped = () => { + let current = nodeIterator.next() + while (!current.done) { + const [nodeIndex, nodeData] = current.value + const adjacencyList = adjacencyMap.get(nodeIndex) + + // Node is external if it has no edges in the specified direction + if (adjacencyList === undefined || adjacencyList.length === 0) { + return { done: false, value: withMutationGuard(graph, () => f(nodeIndex, nodeData)) } + } + current = nodeIterator.next() + } + + return { done: true, value: undefined } as const + } + + return { next: nextMapped } + } + })) +}) diff --git a/.repos/effect/packages/effect/src/HKT.ts b/.repos/effect/packages/effect/src/HKT.ts new file mode 100644 index 000000000..ae7a5608d --- /dev/null +++ b/.repos/effect/packages/effect/src/HKT.ts @@ -0,0 +1,219 @@ +/** + * Provides type-level helpers for generic code over container-like types. + * + * TypeScript cannot directly abstract over shapes such as `Option
`, + * `ReadonlyArray`, or `Effect`. This module represents those shapes + * with `TypeLambda` and applies concrete type arguments with `Kind`. It is + * mostly useful when defining generic helpers or type classes that should work + * across several data types. + * + * @since 2.0.0 + */ +import type * as Types from "./Types.ts" + +/** + * Defines the unique symbol used to associate `TypeClass` implementations with their `TypeLambda`. + * + * **When to use** + * + * Use when you need to define a custom type class that exposes the `TypeLambda` + * it operates on. + * + * **Details** + * + * This symbol links a type class shape with its compile-time type lambda. It is + * intended for type-class definitions and has no runtime behavior. + * + * **Example** (Linking a type class to a type lambda) + * + * ```ts import.meta.vitest + * import type { HKT } from "effect" + * + * interface IdentityTypeLambda extends HKT.TypeLambda { + * readonly type: this["Target"] + * } + * + * interface IdentityTypeClass extends HKT.TypeClass { + * readonly [HKT.URI]?: IdentityTypeLambda + * readonly of: (value: A) => HKT.Kind + * } + * + * const identity: IdentityTypeClass = { + * of: (value) => value + * } + * + * type LinkedTypeLambda = typeof identity[typeof HKT.URI] + * + * const value: HKT.Kind, never, never, never, string> = identity.of("ok") + * ``` + * + * @category symbols + * @since 2.0.0 + */ +export declare const URI: unique symbol + +/** + * Base interface for type classes that work with Higher-Kinded Types. + * + * **When to use** + * + * Use to define type class interfaces parameterized by a `TypeLambda`. + * + * **Details** + * + * A `TypeClass` defines operations that can be performed on any type constructor + * that matches the given `TypeLambda`. This enables writing generic code that + * works across different container types like Array, Option, Effect, etc. + * + * **Example** (Defining higher-kinded type classes) + * + * ```ts import.meta.vitest + * import type { HKT } from "effect" + * + * // Define a Functor type class + * interface Functor extends HKT.TypeClass { + * map( + * fa: HKT.Kind, + * f: (a: A) => B + * ): HKT.Kind + * } + * + * // Define a Monad type class + * interface Monad extends Functor { + * flatMap( + * fa: HKT.Kind, + * f: (a: A) => HKT.Kind + * ): HKT.Kind + * } + * + * const witness: keyof Monad = "flatMap" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface TypeClass { + readonly [URI]?: F +} + +/** + * Base interface for defining Higher-Kinded Type parameters. + * + * **When to use** + * + * Use to encode a type constructor for higher-kinded generic programming. + * + * **Details** + * + * A `TypeLambda` encodes the "shape" of a type constructor, specifying how many + * type parameters it takes and their variance (contravariant, covariant, or + * invariant). The four parameters are `In` for contravariant input, `Out2` for + * covariant output often used for errors, `Out1` for covariant output often used + * for context or environment, and `Target` for the invariant main type. + * + * **Example** (Defining type lambdas) + * + * ```ts import.meta.vitest + * import type { Effect, HKT } from "effect" + * + * // TypeLambda for Array + * interface ArrayTypeLambda extends HKT.TypeLambda { + * readonly type: Array + * } + * + * // TypeLambda for Effect + * interface EffectTypeLambda extends HKT.TypeLambda { + * readonly type: Effect.Effect + * } + * + * // TypeLambda for function (A) => B + * interface FunctionTypeLambda extends HKT.TypeLambda { + * readonly type: (a: this["In"]) => this["Target"] + * } + * + * const witness: HKT.Kind = ["ok"] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface TypeLambda { + readonly In: unknown + readonly Out2: unknown + readonly Out1: unknown + readonly Target: unknown +} + +/** + * Applies type parameters to a `TypeLambda` to get the concrete type. + * + * **When to use** + * + * Use to apply a `TypeLambda` to type parameters and obtain its concrete type. + * + * **Details** + * + * This type-level function takes a `TypeLambda` and four type parameters, then + * "applies" them to get the actual type. It handles variance correctly, ensuring + * contravariant parameters are used as inputs and covariant parameters as + * outputs. This is the core mechanism that allows HKT to transform abstract type + * constructors into concrete types by applying arguments. + * + * **Example** (Applying type lambdas) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * import type { Effect, HKT } from "effect" + * + * // Define TypeLambdas + * interface OptionTypeLambda extends HKT.TypeLambda { + * readonly type: Option.Option + * } + * + * interface EffectTypeLambda extends HKT.TypeLambda { + * readonly type: Effect.Effect + * } + * + * // Apply type parameters to get concrete types + * type OptionString = HKT.Kind + * // Result: Option.Option + * + * type EffectStringNumberBoolean = HKT.Kind< + * EffectTypeLambda, + * never, + * number, + * boolean, + * string + * > + * // Result: Effect.Effect + * + * // TypeLambdas enable generic programming over type constructors + * type StringType = HKT.Kind< + * F, + * never, + * never, + * never, + * string + * > + * + * const witness: OptionString = Option.some("ok") + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export type Kind = F extends { + readonly type: unknown +} ? (F & { + readonly In: In + readonly Out2: Out2 + readonly Out1: Out1 + readonly Target: Target + })["type"] + : { + readonly F: F + readonly In: Types.Contravariant + readonly Out2: Types.Covariant + readonly Out1: Types.Covariant + readonly Target: Types.Invariant + } diff --git a/.repos/effect/packages/effect/src/Hash.ts b/.repos/effect/packages/effect/src/Hash.ts new file mode 100644 index 000000000..0a12cca93 --- /dev/null +++ b/.repos/effect/packages/effect/src/Hash.ts @@ -0,0 +1,516 @@ +/** + * Computes Effect hash values and defines the interface for objects that want + * to provide their own hash implementation. Hashes are small numeric + * fingerprints used by Effect data structures to bucket values quickly; they + * are not cryptographic digests and they are not proof that two values are + * equal. The module also includes helpers for primitive, structure, array, and + * reference-based hashes, plus functions for combining and optimizing numeric + * hash values. + * + * @since 2.0.0 + */ +import { dual } from "./Function.ts" +import { byReferenceInstances, getAllObjectKeys } from "./internal/equal.ts" +import { hasProperty } from "./Predicate.ts" + +/** + * Defines the unique identifier used to identify objects that implement the Hash interface. + * + * **When to use** + * + * Use as the computed property key for the method that supplies a custom hash + * value on a `Hash` implementor. + * + * @see {@link Hash} for the interface implemented with this symbol + * @see {@link isHash} for checking whether a value implements `Hash` + * @see {@link hash} for computing hash values + * + * @category symbols + * @since 2.0.0 + */ +export const symbol = "~effect/interfaces/Hash" + +/** + * A type that represents an object that can be hashed. + * + * **When to use** + * + * Use to let a custom type provide its own stable hash value. + * + * **Details** + * + * Objects implementing this interface provide a method to compute their hash value, + * which is used for efficient comparison and storage operations. + * + * **Example** (Implementing Hash) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * class MyClass implements Hash.Hash { + * constructor(private value: number) {} + * + * [Hash.symbol](): number { + * return Hash.hash(this.value) + * } + * } + * + * new MyClass(42)[Hash.symbol]() // => 42 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Hash { + [symbol](): number +} + +/** + * Computes a hash value for any given value. + * + * **When to use** + * + * Use to compute an Effect hash for primitives, collections, and hashable + * objects. + * + * **Details** + * + * This function can hash primitives (numbers, strings, booleans, etc.) as well as + * objects, arrays, and other complex data structures. It automatically handles + * different types and provides a consistent hash value for equivalent inputs. + * + * **Gotchas** + * + * Objects being hashed must be treated as immutable after their first hash + * computation. Hash results are cached, so mutating an object after hashing will + * lead to stale cached values and broken hash-based operations. For mutable + * objects, implement a custom `Hash` interface that hashes the object reference + * rather than its content. + * + * **Example** (Hashing different values) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * 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 + * @since 2.0.0 + */ +export const hash: (self: A) => number = (self: A) => { + switch (typeof self) { + case "number": + 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": + return string("undefined") + case "function": + case "object": { + 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()) + } else { + if (byReferenceInstances.has(self)) { + return random(self) + } + if (hashCache.has(self)) { + return hashCache.get(self)! + } + const h = withVisitedTracking(self, () => { + if (isHash(self)) { + 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) { + return hashMap(self) + } else if (self instanceof Set) { + return hashSet(self) + } + return structure(self) + }) + hashCache.set(self, h) + return h + } + } + default: + throw new Error( + `BUG: unhandled typeof ${typeof self} - please report an issue at https://github.com/Effect-TS/effect/issues` + ) + } +} + +/** + * Generates a random hash value for an object and caches it. + * + * **When to use** + * + * Use to hash an object by reference identity instead of structural content. + * + * **Details** + * + * This function creates a random hash value for objects that don't have their own + * hash implementation. The hash value is cached using a WeakMap, so the same object + * will always return the same hash value during its lifetime. + * + * **Example** (Hashing objects by reference) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * const obj1 = { a: 1 } + * const obj2 = { a: 1 } + * + * Hash.random(obj1) === Hash.random(obj1) // => true + * + * typeof Hash.random(obj2) // => "number" + * ``` + * + * @category hashing + * @since 2.0.0 + */ +export const random: (self: A) => number = (self) => { + if (!randomHashCache.has(self)) { + randomHashCache.set(self, number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))) + } + return randomHashCache.get(self)! +} + +/** + * Combines two hash values into a single hash value. + * + * **When to use** + * + * Use to build a hash for a composite value by folding together hash values for + * its parts. + * + * **Details** + * + * Supports both direct and pipeable usage. The implementation combines two + * hash values with `(self * 53) ^ b`. + * + * **Example** (Combining hash values) + * + * ```ts import.meta.vitest + * import { Hash, pipe } from "effect" + * + * const hash1 = Hash.hash("hello") + * const hash2 = Hash.hash("world") + * + * const combined = Hash.combine(hash2)(hash1) + * combined === pipe(hash1, Hash.combine(hash2)) // => true + * ``` + * + * @see {@link hash} for computing hash values from arbitrary inputs + * @see {@link structureKeys} for hashing selected object fields without manual combination + * + * @category hashing + * @since 2.0.0 + */ +export const combine: { + (b: number): (self: number) => number + (self: number, b: number): number +} = dual(2, (self: number, b: number): number => (self * 53) ^ b) + +/** + * Applies bit manipulation techniques to optimize a hash value. + * + * **When to use** + * + * Use to improve the bit distribution of a raw numeric hash value. + * + * **Details** + * + * This function takes a hash value and applies bitwise operations to improve + * the distribution of hash values, reducing the likelihood of collisions. + * + * **Example** (Optimizing a hash value) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * Hash.optimize(1234567890) // => 160826066 + * ``` + * + * @category hashing + * @since 2.0.0 + */ +export const optimize = (n: number): number => (n & 0xbfffffff) | ((n >>> 1) & 0x40000000) + +/** + * Checks whether a value implements the Hash interface. + * + * **When to use** + * + * Use to detect whether an unknown value provides a custom hash implementation. + * + * **Details** + * + * This function determines whether a given value has the Hash symbol property, + * indicating that it can provide its own hash value implementation. + * + * **Example** (Checking for Hash support) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * class MyHashable implements Hash.Hash { + * [Hash.symbol]() { + * return 42 + * } + * } + * + * Hash.isHash(new MyHashable()) // => true + * Hash.isHash({}) // => false + * Hash.isHash("string") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isHash = (u: unknown): u is Hash => hasProperty(u, symbol) + +/** + * Computes a hash value for a number. + * + * **When to use** + * + * Use to hash a JavaScript number with Effect's numeric hash semantics. + * + * **Details** + * + * This function creates a hash value for numeric inputs, handling special cases + * like NaN, Infinity, and -Infinity with distinct hash values. It uses bitwise operations to ensure good distribution + * of hash values across different numeric inputs. + * + * **Example** (Hashing numbers) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * 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") + } + let h = n | 0 + if (h !== n) { + h ^= n * 0xffffffff + } + while (n > 0xffffffff) { + h ^= n /= 0xffffffff + } + return optimize(h) +} + +/** + * Computes a hash value for a string using the djb2 algorithm. + * + * **When to use** + * + * Use when you need a string field to contribute to a custom structural hash + * implementation. + * + * **Details** + * + * This function implements a variation of the djb2 hash algorithm, which is + * known for its good distribution properties and speed. It processes each + * character of the string to produce a consistent hash value. + * + * **Example** (Hashing strings) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * Hash.string("hello") // => 181380007 + * Hash.string("world") // => 164394279 + * Hash.string("") // => 5381 + * Hash.string("test") === Hash.string("test") // => true + * ``` + * + * @category hashing + * @since 2.0.0 + */ +export const string = (str: string) => { + let h = 5381, i = str.length + while (i) { + h = (h * 33) ^ str.charCodeAt(--i) + } + return optimize(h) +} + +/** + * Computes a hash value for an object using only the specified keys. + * + * **When to use** + * + * Use to hash an object by a selected set of property keys. + * + * **Details** + * + * This function allows you to hash an object by considering only specific keys, + * which is useful when you want to create a hash based on a subset of an object's + * properties. + * + * **Example** (Hashing selected object keys) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * const person = { name: "John", age: 30, city: "New York" } + * + * const hash1 = Hash.structureKeys(person, ["name", "age"]) + * const hash2 = Hash.structureKeys(person, ["name", "city"]) + * + * hash1 // => -590673747 + * hash2 // => 284850673 + * + * const person2 = { name: "John", age: 30, city: "Boston" } + * const hash3 = Hash.structureKeys(person2, ["name", "age"]) + * hash1 === hash3 // => true + * ``` + * + * @category hashing + * @since 2.0.0 + */ +export const structureKeys = (o: object, keys: Iterable) => { + let h = 12289 + for (const key of keys) { + h ^= combine(hash(key), hash((o as any)[key])) + } + return optimize(h) +} + +/** + * Computes a structural hash for an object using Effect's object key collection. + * + * **When to use** + * + * Use to hash an object from all structural keys collected by Effect. + * + * **Details** + * + * The hash is based on the object's structural keys and their values, including + * symbol keys and relevant prototype keys for non-plain objects. + * + * **Example** (Hashing object structures) + * + * ```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 } + * + * Hash.structure(obj1) // => -590673747 + * Hash.structure(obj2) // => -590160631 + * Hash.structure(obj3) // => -590673747 + * Hash.structure(obj1) === Hash.structure(obj3) // => true + * ``` + * + * @category hashing + * @since 2.0.0 + */ +export const structure = (o: A) => structureKeys(o, getAllObjectKeys(o)) + +const iterableWith = (seed: number, f: (el: any) => number) => (iter: Iterable) => { + let h = seed + for (const element of iter) { + h ^= f(element) + } + return optimize(h) +} + +/** + * Computes a hash value for an iterable by hashing all of its elements. + * + * **When to use** + * + * Use to hash the values yielded by an iterable with Effect hash semantics. + * + * **Details** + * + * The implementation folds element hashes from the seed `6151` with XOR and + * then optimizes the final hash. + * + * **Gotchas** + * + * A hash is not an equality proof. Because this implementation uses XOR, + * reordered inputs can produce the same hash. + * + * **Example** (Hashing arrays) + * + * ```ts import.meta.vitest + * import { Hash } from "effect" + * + * const arr1 = [1, 2, 3] + * const arr2 = [1, 2, 3] + * const arr3 = [3, 2, 1] + * + * Hash.array(arr1) // => 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 + * + * @category hashing + * @since 2.0.0 + */ +export const array: (arr: Iterable) => number = iterableWith(6151, hash) + +const hashMap: (map: Iterable) => number = iterableWith( + string("Map"), + ([k, v]) => combine(hash(k), hash(v)) +) +const hashSet: (set: Iterable) => number = iterableWith(string("Set"), hash) + +const randomHashCache = new WeakMap() +const hashCache = new WeakMap() +const visitedObjects = new WeakSet() + +function withVisitedTracking(obj: object, fn: () => T): T { + if (visitedObjects.has(obj)) { + return string("[Circular]") as T + } + visitedObjects.add(obj) + const result = fn() + visitedObjects.delete(obj) + return result +} diff --git a/.repos/effect/packages/effect/src/HashMap.ts b/.repos/effect/packages/effect/src/HashMap.ts new file mode 100644 index 000000000..7a9e80837 --- /dev/null +++ b/.repos/effect/packages/effect/src/HashMap.ts @@ -0,0 +1,1194 @@ +/** + * Stores key/value entries in an immutable hash map. + * + * A `HashMap` hashes keys and resolves matches with Effect's + * structural equality rules. Lookup, insertion, removal, and transformation + * operations return new maps, while temporary mutation helpers support efficient + * batch updates. This module also includes constructors, iteration, conversion, + * mapping, filtering, and reducing helpers. + * + * @since 2.0.0 + */ + +import type { Equal } from "./Equal.ts" +import type { Inspectable } from "./Inspectable.ts" +import * as internal from "./internal/hashMap.ts" +import type { Option } from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { Result } from "./Result.ts" +import type { NoInfer } from "./Types.ts" + +const TypeId = internal.HashMapTypeId + +/** + * A HashMap is an immutable key-value data structure that provides efficient lookup, + * insertion, and deletion operations. It uses a Hash Array Mapped Trie (HAMT) internally + * for structural sharing and optimal performance. + * + * **Example** (Using basic HashMap operations) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * // Create a HashMap + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * + * // Access values + * HashMap.get(map, "a") // => Option.some(1) + * HashMap.get(map, "d") // => Option.none() + * + * // Check if key exists + * HashMap.has(map, "b") // => true + * + * // Add/update values (returns new HashMap) + * HashMap.set(map, "d", 4) // => HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface HashMap extends Iterable<[Key, Value]>, Equal, Pipeable, Inspectable { + readonly [TypeId]: typeof TypeId +} + +/** + * The HashMap namespace contains type-level utilities and helper types + * for working with HashMap instances. + * + * **Example** (Extracting HashMap types) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * // Create a concrete HashMap for type extraction + * const inventory = HashMap.make( + * ["laptop", { quantity: 5, price: 999 }], + * ["mouse", { quantity: 20, price: 29 }] + * ) + * + * // Extract types for reuse + * type ProductId = HashMap.HashMap.Key // string + * type Product = HashMap.HashMap.Value // { quantity: number, price: number } + * type InventoryEntry = HashMap.HashMap.Entry // [string, Product] + * + * // Use extracted types in functions + * const updateInventory = (id: ProductId, product: Product) => + * HashMap.set(inventory, id, product) + * + * const processEntry = ([id, product]: InventoryEntry) => + * `${id}: ${product.quantity} @ $${product.price}` + * + * // 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 + */ +export declare namespace HashMap { + /** + * A function that updates a value based on its current state. + * Takes an Option representing the current value and returns an Option + * representing the new value. + * + * **Example** (Updating values from Options) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2]) + * + * // Increment existing value or set to 1 if not present + * const updateFn = (option: Option.Option) => + * Option.isSome(option) ? Option.some(option.value + 1) : Option.some(1) + * + * const updated = HashMap.modifyAt(map, "a", updateFn) + * HashMap.get(updated, "a") // => Option.some(2) + * ``` + * + * @category models + * @since 2.0.0 + */ + export type UpdateFn = (option: Option) => Option + + /** + * This type-level utility extracts the key type `K` from a `HashMap` type. + * + * **Example** (Extracting key types) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * // Create a HashMap to extract key type from + * const userMap = HashMap.make( + * ["alice", { name: "Alice", age: 30 }], + * ["bob", { name: "Bob", age: 25 }] + * ) + * + * // Extract the key type (string) + * type UserKey = HashMap.HashMap.Key + * + * // Use the extracted type in functions + * const getUserById = (id: UserKey) => HashMap.get(userMap, id) + * getUserById("alice") // => Option.some({ name: "Alice", age: 30 }) + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type Key> = [T] extends [HashMap] ? _K : never + + /** + * This type-level utility extracts the value type `V` from a `HashMap` type. + * + * **Example** (Extracting value types) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * // Create a HashMap with user data + * const userMap = HashMap.make( + * ["alice", { name: "Alice", age: 30, active: true }], + * ["bob", { name: "Bob", age: 25, active: false }] + * ) + * + * // Extract the value type (User object) + * type User = HashMap.HashMap.Value + * + * // Use the extracted type for type-safe operations + * const processUser = (user: User) => { + * return user.active ? `${user.name} (active)` : `${user.name} (inactive)` + * } + * + * // 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 + * @since 2.0.0 + */ + export type Value> = [T] extends [HashMap] ? _V : never + + /** + * This type-level utility extracts the entry type `[K, V]` from a `HashMap` type. + * + * **Example** (Extracting entry types) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * // Create a product catalog HashMap + * const catalog = HashMap.make( + * ["laptop", { price: 999, category: "electronics" }], + * ["book", { price: 29, category: "education" }] + * ) + * + * // Extract the entry type [string, Product] + * type CatalogEntry = HashMap.HashMap.Entry + * + * // Use the extracted type for processing entries + * const processEntry = ([productId, product]: CatalogEntry) => { + * return `${productId}: $${product.price} (${product.category})` + * } + * + * // Convert to entries, process, and sort for deterministic output + * const descriptions = HashMap.toEntries(catalog).map(processEntry).sort() + * descriptions // => ["book: $29 (education)", "laptop: $999 (electronics)"] + * ``` + * + * @category utility types + * @since 3.9.0 + */ + export type Entry> = [Key, Value] +} + +/** + * Checks whether a value is a HashMap. + * + * **Example** (Checking HashMap values) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2]) + * const notMap = { a: 1 } + * + * HashMap.isHashMap(map) // => true + * HashMap.isHashMap(notMap) // => false + * HashMap.isHashMap(null) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isHashMap: { + (u: Iterable): u is HashMap + (u: unknown): u is HashMap +} = internal.isHashMap + +/** + * Creates a new empty `HashMap`. + * + * **Example** (Creating an empty HashMap) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * HashMap.empty() // => HashMap.empty() + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty: () => HashMap = internal.empty + +/** + * Constructs a new `HashMap` from an array of key/value pairs. + * + * **Example** (Creating a HashMap from entries) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * HashMap.make(["a", 1], ["b", 2], ["c", 3]) // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make: >( + ...entries: Entries +) => HashMap< + Entries[number] extends readonly [infer K, any] ? K : never, + Entries[number] extends readonly [any, infer V] ? V : never +> = internal.make + +/** + * Creates a new `HashMap` from an iterable collection of key/value pairs. + * + * **Example** (Creating a HashMap from an iterable) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const entries = [["a", 1], ["b", 2], ["c", 3]] as const + * HashMap.fromIterable(entries) // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable: (entries: Iterable) => HashMap = internal.fromIterable + +/** + * Checks whether the `HashMap` contains no entries. + * + * **Example** (Checking for empty HashMaps) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const emptyMap = HashMap.empty() + * const nonEmptyMap = HashMap.make(["a", 1]) + * + * HashMap.isEmpty(emptyMap) // => true + * HashMap.isEmpty(nonEmptyMap) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty: (self: HashMap) => boolean = internal.isEmpty + +/** + * Looks up the value for the specified key in the `HashMap` safely using the + * internal hashing function. + * + * **Example** (Looking up values) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2]) + * + * HashMap.get(map, "a") // => Option.some(1) + * HashMap.get(map, "c") // => Option.none() + * + * // Using pipe syntax + * HashMap.get("b")(map) // => Option.some(2) + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const get: { + (key: K1): (self: HashMap) => Option + (self: HashMap, key: K1): Option +} = internal.get + +/** + * Looks up the value for the specified key in the `HashMap` safely using a custom hash. + * + * **Example** (Looking up values with a hash) + * + * ```ts import.meta.vitest + * import { Hash, HashMap, Option } from "effect" + * + * // Useful when implementing custom equality for complex keys + * const userMap = HashMap.make( + * ["user123", { name: "Alice", role: "admin" }], + * ["user456", { name: "Bob", role: "user" }] + * ) + * + * // Use precomputed hash for performance in hot paths + * const userId = "user123" + * const precomputedHash = Hash.string(userId) + * + * // Lookup with custom hash (e.g., cached hash value) + * HashMap.getHash(userMap, userId, precomputedHash) // => Option.some({ name: "Alice", role: "admin" }) + * + * // This avoids recomputing the hash when you already have it + * HashMap.getHash(userMap, "user999", Hash.string("user999")) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const getHash: { + (key: K1, hash: number): (self: HashMap) => Option + (self: HashMap, key: K1, hash: number): Option +} = internal.getHash + +/** + * Looks up the value for the specified key in the `HashMap` unsafely using the + * internal hashing function. + * + * **When to use** + * + * Use when reading from a `HashMap` by a key known to exist, and throwing is an + * acceptable programming error for a missing key. + * + * **Gotchas** + * + * This function throws an error if the key is not found. Use `HashMap.get` for + * safe access that returns `Option`. + * + * **Example** (Unsafely looking up values) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * const config = HashMap.make( + * ["api_url", "https://api.example.com"], + * ["timeout", "5000"], + * ["retries", "3"] + * ) + * + * // Safe: use when you're certain the key exists + * HashMap.getUnsafe(config, "api_url") // => "https://api.example.com" + * + * // Preferred: use get() for uncertain keys + * HashMap.get(config, "db_url") // => Option.none() + * + * // This would throw: HashMap.getUnsafe(config, "db_url") + * // Error: "HashMap.getUnsafe: key not found" + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const getUnsafe: { + (key: K1): (self: HashMap) => V + (self: HashMap, key: K1): V +} = internal.getUnsafe + +/** + * Checks whether the specified key has an entry in the `HashMap`. + * + * **Example** (Checking for keys) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2]) + * + * HashMap.has(map, "a") // => true + * HashMap.has(map, "c") // => false + * + * // Using pipe syntax + * HashMap.has("b")(map) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const has: { + (key: K1): (self: HashMap) => boolean + (self: HashMap, key: K1): boolean +} = internal.has + +/** + * Checks whether the specified key has an entry in the `HashMap` using a custom + * hash. + * + * **Example** (Checking keys with a hash) + * + * ```ts import.meta.vitest + * import { Hash, HashMap } from "effect" + * + * // Create a map with case-sensitive keys + * const userMap = HashMap.make( + * ["Admin", { role: "administrator" }], + * ["User", { role: "standard" }] + * ) + * + * // Check with exact hash + * const exactHash = Hash.string("Admin") + * HashMap.hasHash(userMap, "Admin", exactHash) // => true + * + * // A matching hash does not override key equality + * HashMap.hasHash(userMap, "admin", exactHash) // => false + * + * // A different hash also cannot find the existing key + * const lowercaseHash = Hash.string("admin") + * HashMap.hasHash(userMap, "Admin", lowercaseHash) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const hasHash: { + (key: K1, hash: number): (self: HashMap) => boolean + (self: HashMap, key: K1, hash: number): boolean +} = internal.hasHash + +/** + * Checks whether an element matching the given predicate exists in the given `HashMap`. + * + * **Example** (Checking entries by predicate) + * + * ```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 + * ``` + * + * @category predicates + * @since 3.16.0 + */ +export const hasBy: { + (predicate: (value: NoInfer, key: NoInfer) => boolean): (self: HashMap) => boolean + (self: HashMap, predicate: (value: NoInfer, key: NoInfer) => boolean): boolean +} = internal.hasBy + +/** + * Sets the specified key to the specified value using the internal hashing + * function. + * + * **Example** (Setting a value) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map1 = HashMap.make(["a", 1]) + * HashMap.set(map1, "b", 2) // => HashMap.make(["a", 1], ["b", 2]) + * + * // Original map is unchanged + * map1 // => HashMap.make(["a", 1]) + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const set: { + (key: K, value: V): (self: HashMap) => HashMap + (self: HashMap, key: K, value: V): HashMap +} = internal.set + +/** + * Returns an `IterableIterator` of the keys within the `HashMap`. + * + * **Example** (Iterating keys) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * Array.from(HashMap.keys(map)).sort() // => ["a", "b", "c"] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const keys: (self: HashMap) => IterableIterator = internal.keys + +/** + * Returns an `IterableIterator` of the values within the `HashMap`. + * + * **Example** (Iterating values) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * Array.from(HashMap.values(map)).sort() // => [1, 2, 3] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const values: (self: HashMap) => IterableIterator = internal.values + +/** + * Returns an `Array` of the values within the `HashMap`. + * + * **Example** (Converting values to an array) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const employees = HashMap.make( + * ["alice", { department: "engineering", salary: 90000 }], + * ["bob", { department: "marketing", salary: 75000 }], + * ["charlie", { department: "engineering", salary: 95000 }] + * ) + * + * // Extract all employee records + * const allEmployees = HashMap.toValues(employees) + * allEmployees.length // => 3 + * + * // Calculate total salary + * allEmployees.reduce((sum, emp) => sum + emp.salary, 0) // => 260000 + * + * // Filter by department + * allEmployees.filter((emp) => emp.department === "engineering").length // => 2 + * ``` + * + * @category getters + * @since 3.13.0 + */ +export const toValues = (self: HashMap): Array => Array.from(values(self)) + +/** + * Returns an `IterableIterator` of the entries within the `HashMap`. + * + * **Example** (Iterating entries) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * // Create a configuration map + * const config = HashMap.make( + * ["database.host", "localhost"], + * ["database.port", "5432"], + * ["cache.enabled", "true"] + * ) + * + * // Sort the derived array for deterministic output + * const settings = Array.from(HashMap.entries(config)) + * .sort(([left], [right]) => left.localeCompare(right)) + * .map(([key, value]) => `Setting ${key} = ${value}`) + * + * settings // => ["Setting cache.enabled = true", "Setting database.host = localhost", "Setting database.port = 5432"] + * + * // Convert to array when you need all entries at once + * Array.from(HashMap.entries(config)).length // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const entries: (self: HashMap) => IterableIterator<[K, V]> = internal.entries + +/** + * Returns an `Array<[K, V]>` of the entries within the `HashMap`. + * + * **Example** (Converting entries to an array) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const gameScores = HashMap.make( + * ["alice", 1250], + * ["bob", 980], + * ["charlie", 1100] + * ) + * + * // Convert to entries for processing + * const scoreEntries = HashMap.toEntries(gameScores) + * + * // Sort by score (descending) + * const leaderboard = scoreEntries + * .sort(([, a], [, b]) => b - a) + * .map(([player, score], rank) => `${rank + 1}. ${player}: ${score}`) + * + * leaderboard // => ["1. alice: 1250", "2. charlie: 1100", "3. bob: 980"] + * + * // Convert back to HashMap if needed + * HashMap.fromIterable(scoreEntries) // => HashMap.make(["alice", 1250], ["charlie", 1100], ["bob", 980]) + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const toEntries = (self: HashMap): Array<[K, V]> => Array.from(entries(self)) + +/** + * Returns the number of entries within the `HashMap`. + * + * **Example** (Getting the size) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const emptyMap = HashMap.empty() + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * + * HashMap.size(emptyMap) // => 0 + * HashMap.size(map) // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size: (self: HashMap) => number = internal.size + +/** + * Creates a transient mutable `HashMap` for efficient batched updates. + * + * **Details** + * + * Apply updates to the returned map, then call `endMutation` to finish the + * mutation window and use the result as an immutable `HashMap`. + * + * **Example** (Beginning batch mutation) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1]) + * + * // Begin mutation for efficient batch operations + * const mutable = HashMap.beginMutation(map) + * + * // Multiple operations are now more efficient + * HashMap.set(mutable, "b", 2) + * HashMap.set(mutable, "c", 3) + * HashMap.remove(mutable, "a") + * + * // End mutation to get final immutable result + * HashMap.endMutation(mutable) // => HashMap.make(["b", 2], ["c", 3]) + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const beginMutation: (self: HashMap) => HashMap = internal.beginMutation + +/** + * Marks the `HashMap` as immutable, completing the mutation cycle. + * + * **Example** (Ending batch mutation) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * // Start with an existing map + * const original = HashMap.make(["x", 10], ["y", 20]) + * + * // Begin mutation for batch operations + * const mutable = HashMap.beginMutation(original) + * + * // Perform multiple efficient operations + * HashMap.set(mutable, "z", 30) + * HashMap.remove(mutable, "x") + * HashMap.set(mutable, "w", 40) + * + * // End mutation to get final immutable result + * HashMap.endMutation(mutable) // => HashMap.make(["y", 20], ["z", 30], ["w", 40]) + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const endMutation: (self: HashMap) => HashMap = internal.endMutation + +/** + * Runs a batch of updates against a transient mutable copy of the `HashMap` + * and returns the finalized immutable result. + * + * **Details** + * + * The callback may call mutation-oriented helpers such as `set` and `remove` + * on the transient map. + * + * **Example** (Applying batched mutations) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map1 = HashMap.make(["a", 1]) + * const map2 = HashMap.mutate(map1, (mutable) => { + * HashMap.set(mutable, "b", 2) + * HashMap.set(mutable, "c", 3) + * }) + * map2 // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const mutate: { + (f: (self: HashMap) => void): (self: HashMap) => HashMap + (self: HashMap, f: (self: HashMap) => void): HashMap +} = internal.mutate + +/** + * Sets or removes the specified key using an update function. + * + * **Details** + * + * The update function receives `Some(value)` when the key exists or `None` + * when it does not. Returning `Some(newValue)` stores the value, and returning + * `None` removes the key or leaves it absent. + * + * **Example** (Updating values with Options) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2]) + * + * // Increment existing value or set to 1 if not present + * const updateFn = (option: Option.Option) => + * Option.isSome(option) ? Option.some(option.value + 1) : Option.some(1) + * + * const updated = HashMap.modifyAt(map, "a", updateFn) + * HashMap.get(updated, "a") // => Option.some(2) + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const modifyAt: { + (key: K, f: HashMap.UpdateFn): (self: HashMap) => HashMap + (self: HashMap, key: K, f: HashMap.UpdateFn): HashMap +} = internal.modifyAt + +/** + * Sets or removes the specified key using a precomputed hash and an update + * function. + * + * **Details** + * + * The update function receives `Some(value)` when the key exists or `None` + * when it does not. Returning `Some(newValue)` stores the value, and returning + * `None` removes the key or leaves it absent. + * + * **Example** (Updating values with a hash) + * + * ```ts import.meta.vitest + * import { Hash, HashMap, Option } from "effect" + * + * // Useful when working with precomputed hashes for performance + * const counters = HashMap.make(["downloads", 100], ["views", 250]) + * + * // Cache hash computation for frequently accessed keys + * const metricKey = "downloads" + * const cachedHash = Hash.string(metricKey) + * + * // Update function that increments counter or initializes to 1 + * const incrementCounter = (current: Option.Option) => + * Option.isSome(current) ? Option.some(current.value + 1) : Option.some(1) + * + * // Use cached hash for efficient updates in loops + * const updated = HashMap.modifyHash( + * counters, + * metricKey, + * cachedHash, + * incrementCounter + * ) + * HashMap.get(updated, "downloads") // => Option.some(101) + * + * // Add new metric with precomputed hash + * const newMetric = "clicks" + * const clicksHash = Hash.string(newMetric) + * const withClicks = HashMap.modifyHash( + * updated, + * newMetric, + * clicksHash, + * incrementCounter + * ) + * HashMap.get(withClicks, "clicks") // => Option.some(1) + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const modifyHash: { + (key: K, hash: number, f: HashMap.UpdateFn): (self: HashMap) => HashMap + (self: HashMap, key: K, hash: number, f: HashMap.UpdateFn): HashMap +} = internal.modifyHash + +/** + * Updates the value of the specified key within the `HashMap` if it exists. + * + * **Example** (Modifying existing values) + * + * ```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) + * + * HashMap.get(map2, "a") // => Option.some(3) + * HashMap.get(map2, "b") // => Option.some(2) + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const modify: { + (key: K, f: (v: V) => V): (self: HashMap) => HashMap + (self: HashMap, key: K, f: (v: V) => V): HashMap +} = internal.modify + +/** + * Combines two `HashMap`s into one. + * + * **Details** + * + * Entries from `that` are inserted into `self`; when both maps contain an + * equal key, the value from `that` replaces the value from `self`. + * + * **Example** (Combining HashMaps) + * + * ```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) + * + * union // => HashMap.make(["a", 1], ["b", 20], ["c", 3]) + * HashMap.get(union, "b") // => Option.some(20) + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const union: { + (that: HashMap): (self: HashMap) => HashMap + (self: HashMap, that: HashMap): HashMap +} = internal.union + +/** + * Removes the entry for the specified key in the `HashMap` using the internal + * hashing function. + * + * **Example** (Removing a key) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * const map2 = HashMap.remove(map1, "b") + * + * map2 // => HashMap.make(["a", 1], ["c", 3]) + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const remove: { + (key: K): (self: HashMap) => HashMap + (self: HashMap, key: K): HashMap +} = internal.remove + +/** + * Removes all entries in the `HashMap` which have the specified keys. + * + * **Example** (Removing multiple keys) + * + * ```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"]) + * + * map2 // => HashMap.make(["a", 1], ["c", 3]) + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const removeMany: { + (keys: Iterable): (self: HashMap) => HashMap + (self: HashMap, keys: Iterable): HashMap +} = internal.removeMany + +/** + * Sets multiple key-value pairs in the `HashMap`. + * + * **Example** (Setting multiple entries) + * + * ```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) + * + * map2 // => HashMap.make(["a", 10], ["b", 2], ["c", 3], ["d", 4]) + * HashMap.get(map2, "a") // => Option.some(10) + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export const setMany: { + (entries: Iterable): (self: HashMap) => HashMap + (self: HashMap, entries: Iterable): HashMap +} = internal.setMany + +/** + * Maps over the entries of the `HashMap` using the specified function. + * + * **Example** (Mapping values) + * + * ```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}`) + * + * HashMap.get(map2, "a") // => Option.some("a:2") + * HashMap.get(map2, "b") // => Option.some("b:4") + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (value: V, key: K) => A): (self: HashMap) => HashMap + (self: HashMap, f: (value: V, key: K) => A): HashMap +} = internal.map + +/** + * Maps each entry to a `HashMap` and flattens the results. + * + * **Gotchas** + * + * The hash and equality behavior of both maps have to be the same. + * + * **Example** (Flat mapping values) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * const map1 = HashMap.make(["a", 1], ["b", 2]) + * const map2 = HashMap.flatMap( + * map1, + * (value, key) => HashMap.make([key + "1", value], [key + "2", value * 2]) + * ) + * + * map2 // => HashMap.make(["a1", 1], ["a2", 2], ["b1", 2], ["b2", 4]) + * HashMap.get(map2, "b2") // => Option.some(4) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + (f: (value: A, key: K) => HashMap): (self: HashMap) => HashMap + (self: HashMap, f: (value: A, key: K) => HashMap): HashMap +} = internal.flatMap + +/** + * Applies the specified function to the entries of the `HashMap`. + * + * **Example** (Iterating with side effects) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2]) + * const collected: Array<[string, number]> = [] + * + * HashMap.forEach(map, (value, key) => { + * collected.push([key, value]) + * }) + * + * collected.sort() // => [["a", 1], ["b", 2]] + * ``` + * + * @category traversing + * @since 2.0.0 + */ +export const forEach: { + (f: (value: V, key: K) => void): (self: HashMap) => void + (self: HashMap, f: (value: V, key: K) => void): void +} = internal.forEach + +/** + * Reduces the specified state over the entries of the `HashMap`. + * + * **Example** (Reducing values) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * HashMap.reduce(map, 0, (acc, value) => acc + value) // => 6 + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + (zero: Z, f: (accumulator: Z, value: V, key: K) => Z): (self: HashMap) => Z + (self: HashMap, zero: Z, f: (accumulator: Z, value: V, key: K) => Z): Z +} = internal.reduce + +/** + * Filters entries out of a `HashMap` using the specified predicate. + * + * **Example** (Filtering entries) + * + * ```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) + * + * map2 // => HashMap.make(["b", 2], ["d", 4]) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (f: (a: NoInfer, k: K) => boolean): (self: HashMap) => HashMap + (self: HashMap, f: (a: A, k: K) => boolean): HashMap +} = internal.filter + +/** + * Filters out `None` values from a `HashMap` of `Options`s. + * + * **Example** (Compacting Option values) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * const map1 = HashMap.make( + * ["a", Option.some(1)], + * ["b", Option.none()], + * ["c", Option.some(3)] + * ) + * const map2 = HashMap.compact(map1) + * + * map2 // => HashMap.make(["a", 1], ["c", 3]) + * HashMap.get(map2, "a") // => Option.some(1) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const compact: (self: HashMap>) => HashMap = internal.compact + +/** + * Maps over the entries of the `HashMap` using the specified filter and keeps + * only successful results. + * + * **Example** (Filtering and mapping Results) + * + * ```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( + * map1, + * (value) => value % 2 === 0 ? Result.succeed(value * 2) : Result.failVoid + * ) + * + * map2 // => HashMap.make(["b", 4], ["d", 8]) + * HashMap.get(map2, "b") // => Option.some(4) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + (f: (input: A, key: K) => Result): (self: HashMap) => HashMap + (self: HashMap, f: (input: A, key: K) => Result): HashMap +} = internal.filterMap + +/** + * Returns the first element that satisfies the specified + * predicate, or `None` if no such element exists. + * + * **Example** (Finding the first matching entry) + * + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * HashMap.findFirst(map, (value, key) => key === "b" && value > 1) // => Option.some(["b", 2]) + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findFirst: { + (predicate: (a: NoInfer, k: K) => boolean): (self: HashMap) => Option<[K, A]> + (self: HashMap, predicate: (a: A, k: K) => boolean): Option<[K, A]> +} = internal.findFirst + +/** + * Checks whether any entry in a hashmap meets a specific condition. + * + * **Example** (Checking for any matching entry) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * + * HashMap.some(map, (value) => value > 2) // => true + * HashMap.some(map, (value) => value > 5) // => false + * ``` + * + * @category predicates + * @since 3.13.0 + */ +export const some: { + (predicate: (a: NoInfer, k: K) => boolean): (self: HashMap) => boolean + (self: HashMap, predicate: (a: A, k: K) => boolean): boolean +} = internal.some + +/** + * Checks whether all entries in a hashmap meets a specific condition. + * + * **Example** (Checking all entries) + * + * ```ts import.meta.vitest + * import { HashMap } from "effect" + * + * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) + * + * HashMap.every(map, (value) => value > 0) // => true + * HashMap.every(map, (value) => value > 1) // => false + * ``` + * + * @category predicates + * @since 3.14.0 + */ +export const every: { + (predicate: (a: NoInfer, k: K) => boolean): (self: HashMap) => boolean + (self: HashMap, predicate: (a: A, k: K) => boolean): boolean +} = internal.every diff --git a/.repos/effect/packages/effect/src/HashRing.ts b/.repos/effect/packages/effect/src/HashRing.ts new file mode 100644 index 000000000..f85ac52fa --- /dev/null +++ b/.repos/effect/packages/effect/src/HashRing.ts @@ -0,0 +1,427 @@ +/** + * Assigns string inputs to nodes with weighted consistent hashing. + * + * A hash ring minimizes remapping when nodes are added, removed, or reweighted. + * This makes it useful for routing requests, partitioning keys, and + * distributing shards across service instances or storage backends. This module + * can create rings, add or remove nodes by `PrimaryKey`, route an input string + * to a node, and compute shard assignments. + * + * @since 4.0.0 + */ +import { dual } from "./Function.ts" +import * as Hash from "./Hash.ts" +import { PipeInspectableProto } from "./internal/core.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 + +/** + * A weighted consistent-hashing ring for assigning inputs to nodes with stable + * remapping as nodes are added or removed. + * + * **When to use** + * + * Use to maintain a mutable weighted hash ring for routing keys or shards to + * nodes identified by `PrimaryKey`. + * + * **Details** + * + * Nodes are identified by their `PrimaryKey` value and can be iterated from the + * ring. + * + * @category models + * @since 3.19.0 + */ +export interface HashRing extends Pipeable, Iterable { + readonly [TypeId]: typeof TypeId + readonly baseWeight: number + totalWeightCache: number + readonly nodes: Map + ring: Array<[hash: number, node: string]> +} + +/** + * Checks whether a value is a `HashRing`. + * + * **When to use** + * + * Use to narrow an `unknown` value before treating it as a `HashRing`, such as + * values crossing an untyped boundary. + * + * **Details** + * + * The guard checks for the module's internal `TypeId` property and narrows to + * `HashRing`. + * + * **Gotchas** + * + * This is a structural type-id check; it does not validate the ring's `nodes`, + * `ring`, or weight state. + * + * @see {@link HashRing} for the type narrowed by this guard + * @see {@link make} for creating an empty `HashRing` + * + * @category guards + * @since 3.19.0 + */ +export const isHashRing = (u: unknown): u is HashRing => hasProperty(u, TypeId) + +/** + * Creates an empty `HashRing`. + * + * **When to use** + * + * Use to create an empty weighted consistent-hashing ring with the default or + * custom virtual-point density. + * + * **Details** + * + * `baseWeight` controls how many virtual points are added for a node with + * weight `1`; it defaults to `128` and is clamped to at least `1`. + * + * @see {@link add} for registering one node after creation + * @see {@link addMany} for registering several nodes after creation + * + * @category constructors + * @since 3.19.0 + */ +export const make = (options?: { + readonly baseWeight?: number | undefined +}): HashRing => { + const self = Object.create(Proto) + self.baseWeight = Math.max(options?.baseWeight ?? 128, 1) + self.totalWeightCache = 0 + self.nodes = new Map() + self.ring = [] + return self +} + +const Proto = { + ...PipeInspectableProto, + [TypeId]: TypeId, + [Symbol.iterator](this: HashRing): Iterator { + return Iterable.map(this.nodes.values(), ([n]) => n)[Symbol.iterator]() + }, + toJSON(this: HashRing) { + return { + _id: "HashRing", + baseWeight: this.baseWeight, + nodes: this.ring.map(([, n]) => this.nodes.get(n)![0]) + } + } +} + +/** + * Adds new nodes to the ring. If a node already exists in the ring, it + * will be updated. For example, you can use this to update the node's weight. + * + * **When to use** + * + * Use to register or update several nodes in a `HashRing` at the same weight. + * + * @category combinators + * @since 3.19.0 + */ +export const addMany: { + (nodes: Iterable, options?: { + readonly weight?: number | undefined + }): (self: HashRing) => HashRing + (self: HashRing, nodes: Iterable, options?: { + readonly weight?: number | undefined + }): HashRing +} = dual( + (args) => isHashRing(args[0]), + (self: HashRing, nodes: Iterable, options?: { + readonly weight?: number | undefined + }): HashRing => { + const weight = Math.max(options?.weight ?? 1, 0.1) + const keys: Array = [] + let toRemove: Set | undefined + for (const node of nodes) { + 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) + self.totalWeightCache -= entry[1] + self.totalWeightCache += weight + entry[1] = weight + } else { + self.nodes.set(key, [node, weight]) + self.totalWeightCache += weight + } + keys.push(key) + } + if (toRemove) { + self.ring = self.ring.filter(([, n]) => !toRemove.has(n)) + } + addNodesToRing(self, keys, Math.round(weight * self.baseWeight)) + return self + } +) + +function addNodesToRing(self: HashRing, keys: Array, weight: number) { + for (let i = weight; i > 0; i--) { + for (let j = 0; j < keys.length; j++) { + const key = keys[j] + self.ring.push([ + Hash.string(`${key}:${i}`), + key + ]) + } + } + self.ring.sort((a, b) => a[0] - b[0]) +} + +/** + * Adds a new node to the ring. If the node already exists in the ring, it + * will be updated. For example, you can use this to update the node's weight. + * + * **When to use** + * + * Use to register one node in a `HashRing` so lookups and shard assignments can + * return it, or update that node's weight. + * + * **Details** + * + * Nodes are matched by `PrimaryKey.value`. The weight defaults to `1` and is + * clamped to at least `0.1`. + * + * **Gotchas** + * + * This mutates and returns the same ring instance. + * + * @see {@link addMany} for adding or updating several nodes + * @see {@link remove} for unregistering a node + * @see {@link has} for checking primary-key membership + * + * @category combinators + * @since 3.19.0 + */ +export const add: { + (node: A, options?: { + readonly weight?: number | undefined + }): (self: HashRing) => HashRing + (self: HashRing, node: A, options?: { + readonly weight?: number | undefined + }): HashRing +} = dual((args) => isHashRing(args[0]), (self: HashRing, node: A, options?: { + readonly weight?: number | undefined +}): HashRing => addMany(self, [node], options)) + +/** + * Removes the node from the ring. No-op's if the node does not exist. + * + * **When to use** + * + * Use to remove a node that has left the pool so future lookups and shard + * assignments stop returning it. + * + * **Details** + * + * Removal matches by `PrimaryKey.value`, so any value with the same primary key + * removes the same ring member. + * + * **Gotchas** + * + * This mutates and returns the same ring instance. + * + * @see {@link add} for registering or updating a node + * @see {@link has} for checking membership by primary key + * + * @category combinators + * @since 3.19.0 + */ +export const remove: { + (node: A): (self: HashRing) => HashRing + (self: HashRing, node: A): HashRing +} = dual(2, (self: HashRing, node: A): HashRing => { + const key = PrimaryKey.value(node) + const entry = self.nodes.get(key) + if (entry) { + self.nodes.delete(key) + self.ring = self.ring.filter(([, n]) => n !== key) + self.totalWeightCache -= entry[1] + } + return self +}) + +/** + * Checks whether the ring contains a node with the same `PrimaryKey` value. + * + * **When to use** + * + * Use when you need to know whether registering a node would update an existing + * ring member because another node already has the same primary-key identity. + * + * **Details** + * + * Membership is checked with `self.nodes.has(PrimaryKey.value(node))`, so + * matching is by primary key, not object identity or weight. + * + * @see {@link add} for registering or updating nodes + * @see {@link remove} for removing nodes by the same primary-key identity + * @see {@link get} for routing an input string to a node + * + * @category combinators + * @since 3.19.0 + */ +export const has: { + (node: A): (self: HashRing) => boolean + (self: HashRing, node: A): boolean +} = dual( + 2, + (self: HashRing, node: A): boolean => self.nodes.has(PrimaryKey.value(node)) +) + +/** + * Gets the node which should handle the given input. Returns undefined if + * the hashring has no elements with weight. + * + * **When to use** + * + * Use to route a single string input key to the current ring member responsible + * for that key. + * + * @see {@link getShards} for assigning fixed shard indexes instead of routing + * one input string at a time + * + * @category combinators + * @since 3.19.0 + */ +export const get = (self: HashRing, input: string): A | undefined => { + if (self.ring.length === 0) { + return undefined + } + const index = getIndexForInput(self, Hash.string(input))[0] + const node = self.ring[index][1]! + return self.nodes.get(node)![0] +} + +/** + * Computes a balanced shard distribution across the nodes in the ring. + * + * **When to use** + * + * Use to precompute ownership for a fixed number of shard indexes across the + * current ring members. + * + * @category combinators + * @since 3.19.0 + */ +export const getShards = (self: HashRing, count: number): Array | undefined => { + if (self.ring.length === 0) { + return undefined + } + + const shards = new Array(count) + + // for tracking how many shards have been allocated to each node + const allocations = new Map() + // for tracking which shards still need to be allocated + const remaining = new Set() + // for tracking which nodes have reached the max allocation + const exclude = new Set() + + // First pass - allocate the closest nodes, skipping nodes that have reached + // max + const distances = new Array<[shard: number, node: string, distance: number]>(count) + for (let shard = 0; shard < count; shard++) { + const hash = (shardHashes[shard] ??= Hash.string(`shard-${shard}`)) + const [index, distance] = getIndexForInput(self, hash) + const node = self.ring[index][1]! + distances[shard] = [shard, node, distance] + remaining.add(shard) + } + distances.sort((a, b) => a[2] - b[2]) + for (let i = 0; i < count; i++) { + const [shard, node] = distances[i] + if (exclude.has(node)) continue + const [value, weight] = self.nodes.get(node)! + shards[shard] = value + remaining.delete(shard) + const nodeCount = (allocations.get(node) ?? 0) + 1 + allocations.set(node, nodeCount) + const maxPerNode = Math.max(1, Math.floor(count * (weight / self.totalWeightCache))) + if (nodeCount >= maxPerNode) { + exclude.add(node) + } + } + + // Second pass - allocate any remaining shards, skipping nodes that have + // reached max + let allAtMax = exclude.size === self.nodes.size + remaining.forEach((shard) => { + const index = getIndexForInput(self, shardHashes[shard], allAtMax ? undefined : exclude)[0] + const node = self.ring[index][1] + const [value, weight] = self.nodes.get(node)! + shards[shard] = value + + if (allAtMax) return + const nodeCount = (allocations.get(node) ?? 0) + 1 + allocations.set(node, nodeCount) + const maxPerNode = Math.max(1, Math.floor(count * (weight / self.totalWeightCache))) + if (nodeCount >= maxPerNode) { + exclude.add(node) + if (exclude.size === self.nodes.size) { + allAtMax = true + } + } + }) + + return shards +} + +const shardHashes: Array = [] + +function getIndexForInput( + self: HashRing, + hash: number, + exclude?: ReadonlySet | undefined +): readonly [index: number, distance: number] { + const ring = self.ring + const len = ring.length + + let mid: number + let lo = 0 + let hi = len - 1 + + while (lo <= hi) { + mid = ((lo + hi) / 2) >>> 0 + if (ring[mid][0] >= hash) { + hi = mid - 1 + } else { + lo = mid + 1 + } + } + const a = lo === len ? lo - 1 : lo + const distA = Math.abs(ring[a][0] - hash) + if (exclude === undefined) { + const b = lo - 1 + if (b < 0) { + return [a, distA] + } + const distB = Math.abs(ring[b][0] - hash) + return distA <= distB ? [a, distA] : [b, distB] + } else if (!exclude.has(ring[a][1])) { + return [a, distA] + } + const range = Math.max(lo, len - lo) + 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)] + } + index = lo + i + if (index >= 0 && index < len && !exclude.has(ring[index][1])) { + return [index, Math.abs(ring[index][0] - hash)] + } + } + return [a, distA] +} diff --git a/.repos/effect/packages/effect/src/HashSet.ts b/.repos/effect/packages/effect/src/HashSet.ts new file mode 100644 index 000000000..2f088d062 --- /dev/null +++ b/.repos/effect/packages/effect/src/HashSet.ts @@ -0,0 +1,566 @@ +/** + * Stores unique values in an immutable hash set. + * + * A `HashSet` contains at most one value for each equality class according + * to Effect's `Equal` and `Hash` rules. Membership checks, additions, removals, + * and set operations return new sets. This module also includes constructors, + * union, intersection, difference, subset checks, mapping, filtering, and + * reducing helpers. + * + * @since 2.0.0 + */ + +import type { Equal } from "./Equal.ts" +import * as Dual from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import * as internal from "./internal/hashSet.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { Predicate, Refinement } from "./Predicate.ts" +import type { NoInfer } from "./Types.ts" + +const TypeId = internal.HashSetTypeId + +/** + * A HashSet is an immutable set data structure that provides efficient storage + * and retrieval of unique values. It uses a HashMap internally for optimal performance. + * + * **Example** (Creating and updating a HashSet) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * // Create a HashSet + * const set = HashSet.make("apple", "banana", "cherry") + * + * // Check membership + * HashSet.has(set, "apple") // => true + * HashSet.has(set, "grape") // => false + * + * // Add values (returns new HashSet) + * const updated = HashSet.add(set, "grape") + * updated // => HashSet.make("apple", "banana", "cherry", "grape") + * + * // Remove values (returns new HashSet) + * const smaller = HashSet.remove(set, "banana") + * smaller // => HashSet.make("apple", "cherry") + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface HashSet extends Iterable, Equal, Pipeable, Inspectable { + readonly [TypeId]: typeof TypeId +} + +/** + * The HashSet namespace contains type-level utilities and helper types + * for working with HashSet instances. + * + * **Example** (Extracting value types from a HashSet) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * // Create a concrete HashSet for type extraction + * const fruits = HashSet.make("apple", "banana", "cherry") + * + * // Extract the value type for reuse + * type Fruit = HashSet.HashSet.Value // string + * + * // Use extracted type in functions + * const processFruit = (fruit: Fruit) => { + * return `Processing ${fruit}` + * } + * processFruit("apple") // => "Processing apple" + * ``` + * + * @since 2.0.0 + */ +export declare namespace HashSet { + /** + * Extracts the element type from a `HashSet`. + * + * **Details** + * + * For `HashSet.HashSet`, `HashSet.Value<...>` resolves to `A`. + * + * **Example** (Extracting a HashSet value type) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const numbers = HashSet.make(1, 2, 3, 4, 5) + * + * // Extract the value type + * type NumberType = HashSet.HashSet.Value // number + * + * const processNumber = (n: NumberType) => n * 2 + * processNumber(3) // => 6 + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Value = T extends HashSet ? V : never +} + +/** + * Creates an empty HashSet. + * + * **Example** (Creating an empty HashSet) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const set = HashSet.empty() + * + * HashSet.size(set) // => 0 + * HashSet.isEmpty(set) // => true + * + * // Add some values + * const withValues = HashSet.add(HashSet.add(set, "hello"), "world") + * withValues // => HashSet.make("hello", "world") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty: () => HashSet = internal.empty + +/** + * Creates a HashSet from a variable number of values. + * + * **Example** (Creating a HashSet from values) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.make("apple", "banana", "cherry") // => HashSet.make("apple", "banana", "cherry") + * + * HashSet.make(1, 2, 3, 2, 1) // => HashSet.make(1, 2, 3) + * + * HashSet.make("hello", 42, true) // => HashSet.make("hello", 42, true) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make: >( + ...values: Values +) => HashSet = internal.make + +/** + * Creates a HashSet from an iterable collection of values. + * + * **Example** (Creating a HashSet from an iterable) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.fromIterable(["a", "b", "c", "b", "a"]) // => HashSet.make("a", "b", "c") + * + * HashSet.fromIterable(new Set([1, 2, 3])) // => HashSet.make(1, 2, 3) + * + * HashSet.fromIterable("hello") // => HashSet.make("h", "e", "l", "o") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable: (values: Iterable) => HashSet = internal.fromIterable + +/** + * Checks whether a value is a HashSet. + * + * **Example** (Checking for a HashSet) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const set = HashSet.make(1, 2, 3) + * const array = [1, 2, 3] + * + * HashSet.isHashSet(set) // => true + * HashSet.isHashSet(array) // => false + * HashSet.isHashSet(null) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isHashSet: { + (u: Iterable): u is HashSet + (u: unknown): u is HashSet +} = internal.isHashSet + +/** + * Adds a value to the HashSet, returning a new HashSet. + * + * **Example** (Adding values to a HashSet) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const set = HashSet.make("a", "b") + * const withC = HashSet.add(set, "c") + * + * set // => HashSet.make("a", "b") + * withC // => HashSet.make("a", "b", "c") + * HashSet.has(withC, "c") // => true + * + * // Adding existing value has no effect + * HashSet.add(set, "a") // => HashSet.make("a", "b") + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const add: { + (value: V): (self: HashSet) => HashSet + (self: HashSet, value: V): HashSet +} = Dual.dual< + (value: V) => (self: HashSet) => HashSet, + (self: HashSet, value: V) => HashSet +>(2, internal.add) + +/** + * Checks whether the HashSet contains the specified value. + * + * **Example** (Checking HashSet membership) + * + * ```ts import.meta.vitest + * import { Equal, Hash, HashSet } from "effect" + * + * // Works with any type that implements Equal + * + * const set = HashSet.make("apple", "banana", "cherry") + * + * HashSet.has(set, "apple") // => true + * HashSet.has(set, "grape") // => false + * + * class Person implements Equal.Equal { + * constructor(readonly name: string) {} + * + * [Equal.symbol](other: unknown) { + * return other instanceof Person && this.name === other.name + * } + * + * [Hash.symbol](): number { + * return Hash.string(this.name) + * } + * } + * + * const people = HashSet.make(new Person("Alice"), new Person("Bob")) + * HashSet.has(people, new Person("Alice")) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const has: { + (value: V): (self: HashSet) => boolean + (self: HashSet, value: V): boolean +} = Dual.dual< + (value: V) => (self: HashSet) => boolean, + (self: HashSet, value: V) => boolean +>(2, internal.has) + +/** + * Removes a value from the HashSet, returning a new HashSet. + * + * **Example** (Removing values from a HashSet) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const set = HashSet.make("a", "b", "c") + * const withoutB = HashSet.remove(set, "b") + * + * set // => HashSet.make("a", "b", "c") + * withoutB // => HashSet.make("a", "c") + * HashSet.has(withoutB, "b") // => false + * + * // Removing non-existent value has no effect + * HashSet.remove(set, "d") // => HashSet.make("a", "b", "c") + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const remove: { + (value: V): (self: HashSet) => HashSet + (self: HashSet, value: V): HashSet +} = Dual.dual< + (value: V) => (self: HashSet) => HashSet, + (self: HashSet, value: V) => HashSet +>(2, internal.remove) + +/** + * Returns the number of values in the HashSet. + * + * **Example** (Getting the HashSet size) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.size(HashSet.empty()) // => 0 + * + * HashSet.size(HashSet.make("a", "b")) // => 2 + * + * HashSet.size(HashSet.fromIterable(["x", "y", "z", "x", "y"])) // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size: (self: HashSet) => number = internal.size + +/** + * Checks whether the HashSet is empty. + * + * **Example** (Checking whether a HashSet is empty) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.isEmpty(HashSet.empty()) // => true + * + * HashSet.isEmpty(HashSet.make("a")) // => false + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isEmpty: (self: HashSet) => boolean = internal.isEmpty + +/** + * Creates the union of two HashSets. + * + * **Example** (Combining HashSets) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.union(HashSet.make("a", "b"), HashSet.make("b", "c")) // => HashSet.make("a", "b", "c") + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const union: { + (that: HashSet): (self: HashSet) => HashSet + (self: HashSet, that: HashSet): HashSet +} = Dual.dual< + (that: HashSet) => (self: HashSet) => HashSet, + (self: HashSet, that: HashSet) => HashSet +>(2, internal.union) + +/** + * Creates the intersection of two HashSets. + * + * **Example** (Finding common HashSet values) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.intersection(HashSet.make("a", "b", "c"), HashSet.make("b", "c", "d")) // => HashSet.make("b", "c") + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const intersection: { + (that: HashSet): (self: HashSet) => HashSet + (self: HashSet, that: HashSet): HashSet +} = Dual.dual< + (that: HashSet) => (self: HashSet) => HashSet, + (self: HashSet, that: HashSet) => HashSet +>(2, internal.intersection) + +/** + * Creates the difference of two HashSets (elements in the first set that are not in the second). + * + * **Example** (Finding HashSet differences) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.difference(HashSet.make("a", "b", "c"), HashSet.make("b", "d")) // => HashSet.make("a", "c") + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const difference: { + (that: HashSet): (self: HashSet) => HashSet + (self: HashSet, that: HashSet): HashSet +} = Dual.dual< + (that: HashSet) => (self: HashSet) => HashSet, + (self: HashSet, that: HashSet) => HashSet +>(2, internal.difference) + +/** + * Checks whether a HashSet is a subset of another HashSet. + * + * **Example** (Checking subset relationships) + * + * ```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") + * + * HashSet.isSubset(small, large) // => true + * HashSet.isSubset(large, small) // => false + * HashSet.isSubset(small, other) // => false + * HashSet.isSubset(small, small) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isSubset: { + (that: HashSet): (self: HashSet) => boolean + (self: HashSet, that: HashSet): boolean +} = Dual.dual< + (that: HashSet) => (self: HashSet) => boolean, + (self: HashSet, that: HashSet) => boolean +>(2, internal.isSubset) + +/** + * Maps each value in the HashSet using the provided function. + * + * **Example** (Mapping HashSet values) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const numbers = HashSet.make(1, 2, 3) + * const doubled = HashSet.map(numbers, (n) => n * 2) + * + * 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) + * lengths // => HashSet.make(5, 6) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (value: V) => U): (self: HashSet) => HashSet + (self: HashSet, f: (value: V) => U): HashSet +} = Dual.dual< + (f: (value: V) => U) => (self: HashSet) => HashSet, + (self: HashSet, f: (value: V) => U) => HashSet +>(2, internal.map) + +/** + * Filters the HashSet keeping only values that satisfy the predicate. + * + * **Example** (Filtering HashSet values) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * HashSet.filter(HashSet.make(1, 2, 3, 4, 5, 6), (n) => n % 2 === 0) // => HashSet.make(2, 4, 6) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (refinement: Refinement, U>): (self: HashSet) => HashSet + (predicate: Predicate>): (self: HashSet) => HashSet + (self: HashSet, refinement: Refinement): HashSet + (self: HashSet, predicate: Predicate): HashSet +} = Dual.dual< + { + (refinement: Refinement, U>): (self: HashSet) => HashSet + (predicate: Predicate>): (self: HashSet) => HashSet + }, + { + (self: HashSet, refinement: Refinement): HashSet + (self: HashSet, predicate: Predicate): HashSet + } +>(2, internal.filter) + +/** + * Checks whether at least one value in the HashSet satisfies the predicate. + * + * **Example** (Testing whether some values match) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const numbers = HashSet.make(1, 2, 3, 4, 5) + * + * HashSet.some(numbers, (n) => n > 3) // => true + * HashSet.some(numbers, (n) => n > 10) // => false + * + * HashSet.some(HashSet.empty(), (n) => n > 0) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const some: { + (predicate: Predicate): (self: HashSet) => boolean + (self: HashSet, predicate: Predicate): boolean +} = Dual.dual< + (predicate: Predicate) => (self: HashSet) => boolean, + (self: HashSet, predicate: Predicate) => boolean +>(2, internal.some) + +/** + * Checks whether all values in the HashSet satisfy the predicate. + * + * **Example** (Testing whether every value matches) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const numbers = HashSet.make(2, 4, 6, 8) + * + * HashSet.every(numbers, (n) => n % 2 === 0) // => true + * HashSet.every(numbers, (n) => n > 5) // => false + * + * HashSet.every(HashSet.empty(), (n) => n > 0) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const every: { + (predicate: Predicate): (self: HashSet) => boolean + (self: HashSet, predicate: Predicate): boolean +} = Dual.dual< + (predicate: Predicate) => (self: HashSet) => boolean, + (self: HashSet, predicate: Predicate) => boolean +>(2, internal.every) + +/** + * Reduces the HashSet to a single value by iterating through the values and applying an accumulator function. + * + * **Example** (Reducing HashSet values) + * + * ```ts import.meta.vitest + * import { HashSet } from "effect" + * + * const numbers = HashSet.make(1, 2, 3, 4, 5) + * HashSet.reduce(numbers, 0, (acc, n) => acc + n) // => 15 + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + (zero: U, f: (accumulator: U, value: V) => U): (self: HashSet) => U + (self: HashSet, zero: U, f: (accumulator: U, value: V) => U): U +} = Dual.dual< + (zero: U, f: (accumulator: U, value: V) => U) => (self: HashSet) => U, + (self: HashSet, zero: U, f: (accumulator: U, value: V) => U) => U +>(3, internal.reduce) diff --git a/.repos/effect/packages/effect/src/Inspectable.ts b/.repos/effect/packages/effect/src/Inspectable.ts new file mode 100644 index 000000000..47843fffa --- /dev/null +++ b/.repos/effect/packages/effect/src/Inspectable.ts @@ -0,0 +1,331 @@ +/** + * Controls how values appear in logs and debugging output. + * + * Effect data types use `Inspectable` to provide stable string, JSON, and + * Node.js inspection output. This keeps custom values readable in logs, REPLs, + * test failures, and diagnostics. This module defines the Node inspect symbol, + * the `Inspectable` interface, safe conversion helpers, and shared prototype or + * class implementations for custom values. + * + * @since 2.0.0 + */ +import { format, formatJson } from "./Formatter.ts" +import * as Predicate from "./Predicate.ts" +import { redact } from "./Redactable.ts" + +/** + * Defines the symbol used by Node.js for custom object inspection. + * + * **When to use** + * + * Use to implement Node.js custom inspection for a value. + * + * **Details** + * + * This symbol is recognized by Node.js's `util.inspect()` function and the REPL + * for custom object representation. When an object has a method with this symbol, + * it will be called to determine how the object should be displayed. + * + * **Example** (Defining custom Node inspection) + * + * ```ts import.meta.vitest + * import { Inspectable } from "effect" + * + * class CustomObject { + * constructor(private value: string) {} + * + * [Inspectable.NodeInspectSymbol]() { + * return `CustomObject(${this.value})` + * } + * } + * + * const obj = new CustomObject("hello") + * obj[Inspectable.NodeInspectSymbol]() // => "CustomObject(hello)" + * ``` + * + * @category symbols + * @since 2.0.0 + */ +export const NodeInspectSymbol = Symbol.for("nodejs.util.inspect.custom") + +/** + * The type of the Node.js inspection symbol used for custom object inspection. + * This symbol type is used to implement custom inspection behavior in Node.js + * environments. + * + * **When to use** + * + * Use to type methods keyed by the Node.js custom inspection symbol. + * + * **Example** (Typing custom Node inspection) + * + * ```ts import.meta.vitest + * import { Inspectable } from "effect" + * + * class CustomObject { + * constructor(private value: string) {} + * + * [Inspectable.NodeInspectSymbol]() { + * return `CustomObject(${this.value})` + * } + * } + * + * const obj = new CustomObject("test") + * obj[Inspectable.NodeInspectSymbol]() // => "CustomObject(test)" + * ``` + * + * @category symbols + * @since 2.0.0 + */ +export type NodeInspectSymbol = typeof NodeInspectSymbol + +/** + * Interface for objects that can be inspected and provide custom string representations. + * + * **When to use** + * + * Use to define values with custom string, JSON, and Node.js inspection output. + * + * **Details** + * + * Objects implementing this interface can control how they appear in debugging contexts, + * JSON serialization, and Node.js inspection. This is particularly useful for creating + * custom data types that display meaningful information during development. + * + * **Example** (Implementing inspectable objects) + * + * ```ts import.meta.vitest + * import { Formatter, Inspectable } from "effect" + * + * class Result implements Inspectable.Inspectable { + * constructor( + * private readonly tag: "Success" | "Failure", + * private readonly value: unknown + * ) {} + * + * toString(): string { + * return Formatter.format(this.toJSON()) + * } + * + * toJSON() { + * return { _tag: this.tag, value: this.value } + * } + * + * [Inspectable.NodeInspectSymbol]() { + * return this.toJSON() + * } + * } + * + * const success = new Result("Success", 42) + * success.toString() // => "{\"_tag\":\"Success\",\"value\":42}" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Inspectable { + toString(): string + toJSON(): unknown + [NodeInspectSymbol](): unknown +} + +/** + * Converts a value to its structured inspection representation. + * + * **When to use** + * + * Use when you need the structured representation of an inspectable value + * without risking unhandled errors. + * + * **Details** + * + * 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 + * + * @category converting + * @since 4.0.0 + */ +export const toJson = (input: unknown): unknown => { + try { + input = redact(input) + if ( + Predicate.hasProperty(input, "toJSON") && + Predicate.isFunction(input["toJSON"]) && + input["toJSON"].length === 0 + ) { + return input.toJSON() + } else if (Array.isArray(input)) { + return input.map(toJson) + } + return input + } catch { + return "[toJSON threw]" + } +} + +/** + * Converts an unknown value to a string for diagnostics. + * + * **When to use** + * + * Use to produce a diagnostic string from a value whose runtime type is unknown. + * + * **Details** + * + * Strings are returned unchanged. Objects are formatted as JSON using the + * provided whitespace setting when possible, and values that cannot be + * formatted are converted with `String`. + * + * @category converting + * @since 2.0.0 + */ +export const toStringUnknown = (u: unknown, whitespace: number | string | undefined = 2): string => { + if (typeof u === "string") { + return u + } + try { + return typeof u === "object" ? formatJson(u, { space: whitespace }) : format(u, { space: whitespace }) + } catch { + return String(u) + } +} + +/** + * A base prototype object that implements the {@link Inspectable} interface. + * + * **When to use** + * + * Use as a prototype for plain objects that should share standard inspectable behavior. + * + * **Details** + * + * This object provides default implementations for the {@link Inspectable} methods. + * It can be used as a prototype for objects that want to be inspectable, + * or as a mixin to add inspection capabilities to existing objects. + * + * **Example** (Using the base inspectable prototype) + * + * ```ts import.meta.vitest + * import { Inspectable } from "effect" + * + * // Use as prototype + * const myObject = Object.create(Inspectable.BaseProto) + * myObject.name = "example" + * myObject.value = 42 + * + * myObject.toString() // => "\"[toJSON threw]\"" + * + * // Or extend in a constructor + * function MyClass(this: any, name: string) { + * this.name = name + * } + * MyClass.prototype = Object.create(Inspectable.BaseProto) + * MyClass.prototype.constructor = MyClass + * ``` + * + * @category prototypes + * @since 2.0.0 + */ +export const BaseProto: Inspectable = { + toJSON() { + return toJson(this) + }, + [NodeInspectSymbol]() { + return this.toJSON() + }, + toString() { + return format(this.toJSON()) + } +} + +/** + * Provides an abstract base class that implements the Inspectable interface. + * + * **When to use** + * + * Use as a base class for inspectable objects that define their own JSON representation. + * + * **Details** + * + * This class provides a convenient way to create inspectable objects by extending it. + * Subclasses only need to implement the `toJSON()` method, and they automatically + * get proper `toString()` and Node.js inspection support. + * + * **Example** (Extending the inspectable base class) + * + * ```ts import.meta.vitest + * import { Inspectable } from "effect" + * + * class User extends Inspectable.Class { + * constructor( + * public readonly id: number, + * public readonly name: string, + * public readonly email: string + * ) { + * super() + * } + * + * toJSON() { + * return { + * _tag: "User", + * id: this.id, + * name: this.name, + * email: this.email + * } + * } + * } + * + * const user = new User(1, "Alice", "alice@example.com") + * 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 models + * @since 2.0.0 + */ +export abstract class Class { + /** + * Returns a JSON representation of this object. + * + * **When to use** + * + * Use to provide the JSON representation consumed by inherited inspection methods. + * + * **Details** + * + * Subclasses must implement this method to define how the object + * should be serialized for debugging and inspection purposes. + * + * @since 2.0.0 + */ + abstract toJSON(): unknown + /** + * Node.js custom inspection method. + * + * **When to use** + * + * Use to expose the class JSON representation to Node.js inspection. + * + * @since 2.0.0 + */ + [NodeInspectSymbol]() { + return this.toJSON() + } + /** + * Returns a formatted string representation of this object. + * + * **When to use** + * + * Use to format the class JSON representation as a string. + * + * @since 2.0.0 + */ + toString() { + return format(this.toJSON()) + } +} diff --git a/.repos/effect/packages/effect/src/Iterable.ts b/.repos/effect/packages/effect/src/Iterable.ts new file mode 100644 index 000000000..8529204a2 --- /dev/null +++ b/.repos/effect/packages/effect/src/Iterable.ts @@ -0,0 +1,2444 @@ +/** + * Works with JavaScript values that implement `[Symbol.iterator]`. + * + * Iterables include arrays, strings, generators, sets, and custom lazy + * sequences. The helpers in this module let code transform, search, group, and + * fold iterable values while preserving the input as an iterable instead of + * forcing an array first. + * + * @since 2.0.0 + */ + +import type { NonEmptyArray } from "./Array.ts" +import * as Equal from "./Equal.ts" +import { dual } from "./Function.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" +import type * as Record from "./Record.ts" +import type { Result } from "./Result.ts" +import * as R from "./Result.ts" +import * as Tuple from "./Tuple.ts" +import type { NoInfer } from "./Types.ts" + +/** + * Creates an iterable by applying a function to consecutive integers. + * + * **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. + * + * **Example** (Generating values by index) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * // Generate first 5 even numbers + * const evens = Iterable.makeBy((n) => n * 2, { length: 5 }) + * Array.from(evens) // => [0, 2, 4, 6, 8] + * + * // Generate squares + * const squares = Iterable.makeBy((n) => n * n, { length: 4 }) + * 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) + * Array.from(first10) // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +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 + return { + [Symbol.iterator]() { + let i = 0 + return { + next(): IteratorResult { + if (i < max) { + return { value: f(i++), done: false } + } + return { done: true, value: undefined } + } + } + } + } +} + +/** + * Returns an iterable of integers starting at `start` and increasing by `1`. + * + * **Details** + * + * When `end` is provided and `start <= end`, both endpoints are included. When + * `end` is omitted, the iterable is unbounded. When `start > end`, the + * iterable contains only `start`. + * + * **Example** (Creating a range) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * Array.from(Iterable.range(1, 3)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const range = (start: number, end?: number): Iterable => { + if (end === undefined) { + return makeBy((i) => start + i) + } + return makeBy((i) => start + i, { + length: start <= end ? end - start + 1 : 1 + }) +} + +/** + * Returns a `Iterable` containing a value repeated the specified number of times. + * + * **Details** + * + * `n` is normalized to an integer greater than or equal to `1`. + * + * **Example** (Repeating a value) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * Array.from(Iterable.replicate("a", 3)) // => ["a", "a", "a"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const replicate: { + (n: number): (a: A) => Iterable + (a: A, n: number): Iterable +} = dual(2, (a: A, n: number): Iterable => makeBy(() => a, { length: n })) + +/** + * Repeats an iterable `n` times, yielding the full contents of `self` for each + * repetition. + * + * **When to use** + * + * Use to repeat an iterable's contents a specific number of times. + * + * **Details** + * + * The result is lazy. 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 + * @category constructors + * @since 4.0.0 + */ +export const repeat: { + (n: number): (self: Iterable) => Iterable + (self: Iterable, n: number): Iterable +} = dual(2, (self: Iterable, n: number): Iterable => flatten(makeBy(() => self, { length: n }))) + +/** + * Repeats an iterable without an upper bound. + * + * **When to use** + * + * Use to cycle a reusable iterable without an upper bound when a downstream + * consumer controls how many values are taken. + * + * **Gotchas** + * + * The returned iterable is lazy and should usually be bounded with `take` or + * another terminating consumer before materializing it. + * + * @see {@link repeat} for repeating an iterable a specific number of times + * @see {@link take} for bounding the unbounded result before materializing it + * + * @category constructors + * @since 4.0.0 + */ +export const forever = (self: Iterable): Iterable => repeat(self, Infinity) + +/** + * Takes a record and returns an Iterable of tuples containing its keys and values. + * + * **Example** (Converting a record to entries) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const x = { a: 1, b: 2, c: 3 } + * Array.from(Iterable.fromRecord(x)) // => [["a", 1], ["b", 2], ["c", 3]] + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const fromRecord = (self: Readonly>): Iterable<[K, A]> => ({ + *[Symbol.iterator]() { + for (const key in self) { + if (Object.hasOwn(self, key)) { + yield [key, self[key]] + } + } + } +}) + +/** + * Prepends an element to the front of an `Iterable`, creating a new `Iterable`. + * + * **Example** (Prepending an element) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [2, 3, 4] + * const withOne = Iterable.prepend(numbers, 1) + * Array.from(withOne) // => [1, 2, 3, 4] + * + * // Works with any iterable + * const letters = "abc" + * const withZ = Iterable.prepend(letters, "z") + * Array.from(withZ) // => ["z", "a", "b", "c"] + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const prepend: { + (head: B): (self: Iterable) => Iterable + (self: Iterable, head: B): Iterable +} = dual(2, (self: Iterable, head: B): Iterable => prependAll(self, [head])) + +/** + * Prepends the specified prefix iterable to the beginning of the specified iterable. + * + * **Example** (Prepending another iterable) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * Array.from(Iterable.prependAll([1, 2], ["a", "b"])) // => ["a", "b", 1, 2] + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const prependAll: { + (that: Iterable): (self: Iterable) => Iterable + (self: Iterable, that: Iterable): Iterable +} = dual( + 2, + (self: Iterable, that: Iterable): Iterable => appendAll(that, self) +) + +/** + * Appends an element to the end of an `Iterable`, creating a new `Iterable`. + * + * **When to use** + * + * Use to add one element after all elements of an iterable while keeping the + * result as a lazy `Iterable`. + * + * **Details** + * + * The result yields every element from `self` first, then yields `last` after + * `self` is exhausted. + * + * **Gotchas** + * + * If `self` is infinite or never completes, the appended element is never + * reached. + * + * **Example** (Appending an element) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 2, 3] + * Array.from(Iterable.append(numbers, 4)) // => [1, 2, 3, 4] + * ``` + * + * @see {@link prepend} for adding one element before the existing elements + * @see {@link appendAll} for appending all elements from another iterable + * + * @category combining + * @since 2.0.0 + */ +export const append: { + (last: B): (self: Iterable) => Iterable + (self: Iterable, last: B): Iterable +} = dual(2, (self: Iterable, last: B): Iterable => appendAll(self, [last])) + +/** + * Concatenates two iterables, combining their elements. + * + * **When to use** + * + * Use to lazily concatenate two iterables while preserving order, yielding all + * elements from `self` before `that`. + * + * **Details** + * + * The result is lazy. The iterator for `that` is not created or read until + * `self` is exhausted. + * + * **Gotchas** + * + * If `self` is infinite or never completes, `that` is never reached. + * + * **Example** (Concatenating iterables) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * 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) + * 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] + * 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 + * @see {@link prependAll} for yielding another iterable before `self` + * + * @category combining + * @since 2.0.0 + */ +export const appendAll: { + (that: Iterable): (self: Iterable) => Iterable + (self: Iterable, that: Iterable): Iterable +} = dual( + 2, + (self: Iterable, that: Iterable): Iterable => ({ + [Symbol.iterator]() { + const iterA = self[Symbol.iterator]() + let doneA = false + let iterB: Iterator + return { + next() { + if (!doneA) { + const r = iterA.next() + if (r.done) { + doneA = true + iterB = that[Symbol.iterator]() + return iterB.next() + } + return r + } + return iterB.next() + } + } + } + }) +) + +/** + * Reduces an `Iterable` from the left, keeping all intermediate results instead of only the final result. + * + * **Example** (Tracking running results) + * + * ```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) + * 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) + * 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) + * Array.from(runningMax) // => [-Infinity, 3, 3, 4, 4, 5, 9, 9] + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const scan: { + (b: B, f: (b: B, a: A) => B): (self: Iterable) => Iterable + (self: Iterable, b: B, f: (b: B, a: A) => B): Iterable +} = dual(3, (self: Iterable, b: B, f: (b: B, a: A) => B): Iterable => ({ + [Symbol.iterator]() { + let acc = b + let iterator: Iterator | undefined + function next() { + if (iterator === undefined) { + iterator = self[Symbol.iterator]() + return { done: false, value: acc } + } + const result = iterator.next() + if (result.done) { + return result + } + acc = f(acc, result.value) + return { done: false, value: acc } + } + return { next } + } +})) + +/** + * Checks whether an `Iterable` is empty. + * + * **Example** (Checking for emptiness) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * Iterable.isEmpty([]) // => true + * Iterable.isEmpty([1, 2, 3]) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isEmpty = (self: Iterable): self is Iterable => { + const iterator = self[Symbol.iterator]() + return iterator.next().done === true +} + +/** + * Returns the number of elements in a `Iterable`. + * + * **Example** (Counting iterable elements) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * Iterable.size(numbers) // => 5 + * + * const empty = Iterable.empty() + * Iterable.size(empty) // => 0 + * + * // Works with any iterable + * const letters = "hello" + * Iterable.size(letters) // => 5 + * + * // Note: This consumes the entire iterable + * const range = Iterable.range(1, 100) + * Iterable.size(range) // => 100 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: Iterable): number => { + const iterator = self[Symbol.iterator]() + let count = 0 + while (!iterator.next().done) { + count++ + } + return count +} + +/** + * Gets the first element of a `Iterable` safely, or `None` if the `Iterable` is empty. + * + * **Example** (Getting the first element) + * + * ```ts import.meta.vitest + * import { Iterable, Option } from "effect" + * + * const numbers = [1, 2, 3] + * Iterable.head(numbers) // => Option.some(1) + * + * const empty = Iterable.empty() + * 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) + * ) + * firstEven // => Option.some(4) + * + * // Use with Option methods + * const doubled = Option.map(Iterable.head([5, 10, 15]), (x) => x * 2) + * doubled // => Option.some(10) + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const head = (self: Iterable): Option => { + const iterator = self[Symbol.iterator]() + const result = iterator.next() + return result.done ? O.none() : O.some(result.value) +} + +/** + * Gets the first element of an `Iterable` without returning an `Option`. + * + * **When to use** + * + * Use when the `Iterable` is known to be non-empty and direct access to the + * first element is preferred over handling `Option.none`. + * + * **Gotchas** + * + * Throws if the `Iterable` is empty. + * + * **Example** (Getting the first element unsafely) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 2, 3] + * Iterable.headUnsafe(numbers) // => 1 + * + * const letters = "hello" + * 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) + * Iterable.headUnsafe(nonEmpty) // => 1 + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const headUnsafe = (self: Iterable): A => { + const iterator = self[Symbol.iterator]() + const result = iterator.next() + if (result.done) throw new Error("headUnsafe: empty iterable") + return result.value +} + +/** + * Keeps only a max number of elements from the start of an `Iterable`, creating a new `Iterable`. + * + * **Details** + * + * `n` is normalized to a non-negative integer. + * + * **Example** (Taking from the start) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const firstThree = Iterable.take(numbers, 3) + * Array.from(firstThree) // => [1, 2, 3] + * + * // Taking more than available returns all elements + * const firstTen = Iterable.take(numbers, 10) + * Array.from(firstTen) // => [1, 2, 3, 4, 5] + * + * // Taking 0 or negative returns empty + * const none = Iterable.take(numbers, 0) + * Array.from(none) // => [] + * + * // Useful with infinite iterables + * const naturals = Iterable.range(1) + * const firstFive = Iterable.take(naturals, 5) + * Array.from(firstFive) // => [1, 2, 3, 4, 5] + * ``` + * + * @category getters + * @since 2.0.0 + */ +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() + } + return { done: true, value: undefined } + } + } + } +})) + +/** + * Takes the longest initial `Iterable` prefix for which all elements satisfy the + * specified predicate. + * + * **Example** (Taking while a predicate holds) + * + * ```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) + * Array.from(evenPrefix) // => [2, 4, 6, 8] + * + * // With index + * const letters = ["a", "b", "c", "d", "e"] + * const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3) + * 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) + * Array.from(oddPrefix) // => [1, 3, 5] + * + * // Type refinement + * const values: Array = ["a", "b", "c", 1, "d"] + * const stringPrefix = Iterable.takeWhile( + * values, + * (x): x is string => typeof x === "string" + * ) + * Array.from(stringPrefix) // => ["a", "b", "c"] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const takeWhile: { + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Iterable + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Iterable + (self: Iterable, refinement: (a: A, i: number) => a is B): Iterable + (self: Iterable, predicate: (a: A, i: number) => boolean): Iterable +} = dual(2, (self: Iterable, predicate: (a: A, i: number) => boolean): Iterable => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let i = 0 + return { + next() { + const result = iterator.next() + if (result.done || !predicate(result.value, i++)) { + return { done: true, value: undefined } + } + return result + } + } + } +})) + +/** + * Drops a max number of elements from the start of an `Iterable` + * + * **Details** + * + * `n` is normalized to a non-negative integer. + * + * **Example** (Dropping from the start) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * const withoutFirstTwo = Iterable.drop(numbers, 2) + * Array.from(withoutFirstTwo) // => [3, 4, 5] + * + * // Dropping more than available returns empty + * const withoutFirstTen = Iterable.drop(numbers, 10) + * Array.from(withoutFirstTen) // => [] + * + * // Dropping 0 or negative returns all elements + * const all = Iterable.drop(numbers, 0) + * Array.from(all) // => [1, 2, 3, 4, 5] + * + * // Combine with take for slicing + * const slice = Iterable.take(Iterable.drop(numbers, 1), 3) + * Array.from(slice) // => [2, 3, 4] + * ``` + * + * @category getters + * @since 2.0.0 + */ +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 } + } + i++ + } + return iterator.next() + } + } + } +})) + +/** + * Returns the first element that satisfies the specified + * predicate, or `None` if no such element exists. + * + * **Example** (Finding the first match) + * + * ```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) + * firstEven // => Option.some(4) + * + * const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10) + * firstGreaterThan10 // => Option.none() + * + * // With index + * const letters = ["a", "b", "c", "d"] + * const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0) + * atEvenIndex // => Option.some("a") + * + * // Type refinement + * const mixed: Array = [1, "hello", 2, "world"] + * const firstString = Iterable.findFirst( + * mixed, + * (x): x is string => typeof x === "string" + * ) + * 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() + * }) + * findSquareRoot // => Option.some(1) + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findFirst: { + (f: (a: NoInfer, i: number) => Option): (self: Iterable) => Option + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Option + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option + (self: Iterable, f: (a: A, i: number) => Option): Option + (self: Iterable, refinement: (a: A, i: number) => a is B): Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option +} = dual( + 2, + (self: Iterable, f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option)): Option => { + let i = 0 + for (const a of self) { + const o = f(a, i) + if (isBoolean(o)) { + if (o) { + return O.some(a) + } + } else { + if (O.isSome(o)) { + return o + } + } + i++ + } + return O.none() + } +) + +/** + * Finds the last element for which a predicate holds. + * + * **Example** (Finding the last match) + * + * ```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) + * lastEven // => Option.some(2) + * + * const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10) + * lastGreaterThan10 // => Option.none() + * + * // With index + * const letters = ["a", "b", "c", "d", "e"] + * const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0) + * lastAtEvenIndex // => Option.some("e") + * + * // Type refinement + * const mixed: Array = [1, "hello", 2, "world", 3] + * const lastString = Iterable.findLast( + * mixed, + * (x): x is string => typeof x === "string" + * ) + * lastString // => Option.some("world") + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const findLast: { + (f: (a: NoInfer, i: number) => Option): (self: Iterable) => Option + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Option + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Option + (self: Iterable, f: (a: A, i: number) => Option): Option + (self: Iterable, refinement: (a: A, i: number) => a is B): Option + (self: Iterable, predicate: (a: A, i: number) => boolean): Option +} = dual( + 2, + (self: Iterable, f: ((a: A, i: number) => boolean) | ((a: A, i: number) => Option)): Option => { + let i = 0 + let last: Option = O.none() + for (const a of self) { + const o = f(a, i) + if (isBoolean(o)) { + if (o) { + last = O.some(a) + } + } else { + if (O.isSome(o)) { + last = o + } + } + i++ + } + return last + } +) + +/** + * Takes two `Iterable`s and returns an `Iterable` of corresponding pairs. + * + * **Example** (Zipping iterables) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 2, 3] + * const letters = ["a", "b", "c"] + * const zipped = Iterable.zip(numbers, letters) + * 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) + * 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) + * 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) + * Array.from(indexed) // => [[0, "apple"], [1, "banana"], [2, "cherry"]] + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zip: { + (that: Iterable): (self: Iterable) => Iterable<[A, B]> + (self: Iterable, that: Iterable): Iterable<[A, B]> +} = dual( + 2, + (self: Iterable, that: Iterable): Iterable<[A, B]> => zipWith(self, that, Tuple.make) +) + +/** + * Applies a function to pairs of elements at the same index in two `Iterable`s, collecting the results. If one + * input `Iterable` is short, excess elements of the longer `Iterable` are discarded. + * + * **Example** (Zipping with a combining function) + * + * ```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) + * Array.from(sums) // => [11, 22, 33, 44] + * + * // Combine strings + * const firstNames = ["John", "Jane", "Bob"] + * const lastNames = ["Doe", "Smith", "Johnson"] + * const fullNames = Iterable.zipWith( + * firstNames, + * lastNames, + * (first, last) => `${first} ${last}` + * ) + * Array.from(fullNames) // => ["John Doe", "Jane Smith", "Bob Johnson"] + * + * // Different lengths - stops at shorter + * const short = [1, 2] + * const long = ["a", "b", "c", "d"] + * const combined = Iterable.zipWith( + * short, + * long, + * (num, letter) => `${num}${letter}` + * ) + * Array.from(combined) // => ["1a", "2b"] + * + * // Complex transformations + * const prices = [10.99, 25.50, 5.00] + * const quantities = [2, 1, 3] + * const totals = Iterable.zipWith(prices, quantities, (price, qty) => { + * return Math.round(price * qty * 100) / 100 // round to 2 decimal places + * }) + * Array.from(totals) // => [21.98, 25.5, 15] + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWith: { + (that: Iterable, f: (a: A, b: B) => C): (self: Iterable) => Iterable + (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable +} = dual(3, (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable => ({ + [Symbol.iterator]() { + const selfIterator = self[Symbol.iterator]() + const thatIterator = that[Symbol.iterator]() + return { + next() { + const selfResult = selfIterator.next() + const thatResult = thatIterator.next() + if (selfResult.done || thatResult.done) { + return { done: true, value: undefined } + } + return { done: false, value: f(selfResult.value, thatResult.value) } + } + } + } +})) + +/** + * Places a separator between members of an `Iterable`. + * + * **When to use** + * + * Use to lazily insert a separator between adjacent values. + * + * **Details** + * + * If the input is a non-empty array, the result is also a non-empty array. + * + * **Example** (Interspersing separators) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * // Join numbers with separator + * const numbers = [1, 2, 3, 4] + * const withCommas = Iterable.intersperse(numbers, ",") + * Array.from(withCommas) // => [1, ",", 2, ",", 3, ",", 4] + * + * // Join words with spaces + * const words = ["hello", "world", "from", "effect"] + * const sentence = Iterable.intersperse(words, " ") + * Array.from(sentence).join("") // => "hello world from effect" + * + * // Empty iterable remains empty + * const empty = Iterable.empty() + * const stillEmpty = Iterable.intersperse(empty, "-") + * Array.from(stillEmpty) // => [] + * + * // Single element has no separators added + * const single = [42] + * const noSeparator = Iterable.intersperse(single, "|") + * Array.from(noSeparator) // => [42] + * + * // Build CSS-like strings + * const styles = ["color: red", "font-size: 14px", "margin: 10px"] + * const css = Iterable.intersperse(styles, "; ") + * Array.from(css).join("") // => "color: red; font-size: 14px; margin: 10px" + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const intersperse: { + (middle: B): (self: Iterable) => Iterable + (self: Iterable, middle: B): Iterable +} = dual(2, (self: Iterable, middle: B): Iterable => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let next = iterator.next() + let emitted = false + return { + next() { + if (next.done) { + return next + } else if (emitted) { + emitted = false + return { done: false, value: middle } + } + emitted = true + const result = next + next = iterator.next() + return result + } + } + } +})) + +/** + * Returns a function that checks if an `Iterable` contains a given value using a provided `isEquivalent` function. + * + * **Example** (Checking membership with custom equivalence) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * // Custom equivalence for objects + * const byId = (a: { id: number }, b: { id: number }) => a.id === b.id + * const containsById = Iterable.containsWith(byId) + * + * const users = [{ id: 1 }, { id: 2 }] + * const hasUser1 = containsById(users, { id: 1 }) + * hasUser1 // => true + * + * // Case-insensitive string comparison + * const caseInsensitive = (a: string, b: string) => + * a.toLowerCase() === b.toLowerCase() + * const containsCaseInsensitive = Iterable.containsWith(caseInsensitive) + * + * const words = ["Hello", "World"] + * const hasHello = containsCaseInsensitive(words, "hello") + * hasHello // => true + * + * // Approximate number comparison + * const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.1 + * const containsApprox = Iterable.containsWith(approxEqual) + * + * const values = [1.0, 2.0, 3.0] + * const hasAlmostTwo = containsApprox(values, 2.05) + * hasAlmostTwo // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { + (a: A): (self: Iterable) => boolean + (self: Iterable, a: A): boolean +} => + dual(2, (self: Iterable, a: A): boolean => { + for (const i of self) { + if (isEquivalent(a, i)) { + return true + } + } + return false + }) + +/** + * Checks whether an iterable contains a value using Effect's default `Equal` + * equivalence. + * + * **Details** + * + * Can be called as `contains(self, value)` or curried as + * `contains(value)(self)`. + * + * **Example** (Checking membership) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 2, 3, 4, 5] + * Iterable.contains(numbers, 3) // => true + * Iterable.contains(numbers, 6) // => false + * + * const letters = "hello" + * Iterable.contains(letters, "l") // => true + * Iterable.contains(letters, "x") // => false + * + * // Works with any iterable + * const range = Iterable.range(1, 100) + * Iterable.contains(range, 50) // => true + * Iterable.contains(range, 150) // => false + * + * // Curried version + * const containsThree = Iterable.contains(3) + * containsThree([1, 2, 3]) // => true + * containsThree([4, 5, 6]) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const contains: { + (a: A): (self: Iterable) => boolean + (self: Iterable, a: A): boolean +} = containsWith(Equal.asEquivalence()) + +/** + * Splits an `Iterable` into length-`n` pieces. The last piece will be shorter if `n` does not evenly divide the length of + * the `Iterable`. + * + * **Example** (Chunking an iterable) + * + * ```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) + * 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) + * 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) + * Array.from(chunks3) // => [[1, 2]] + * + * // Process data in batches + * const data = Iterable.range(1, 100) + * const batches = Iterable.chunksOf(data, 10) + * const batchSums = Iterable.map( + * batches, + * (batch) => Iterable.reduce(batch, 0, (sum, n) => sum + n) + * ) + * Array.from(Iterable.take(batchSums, 3)) // => [55, 155, 255] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +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)) + return ({ + [Symbol.iterator]() { + let iterator: Iterator | undefined = self[Symbol.iterator]() + return { + next() { + if (iterator === undefined) { + return { done: true, value: undefined } + } + + const chunk: Array = [] + for (let i = 0; i < safeN; i++) { + const result = iterator.next() + if (result.done) { + iterator = undefined + return chunk.length === 0 ? { done: true, value: undefined } : { done: false, value: chunk } + } + chunk.push(result.value) + } + + return { done: false, value: chunk } + } + } + } + }) +}) + +/** + * Groups equal, consecutive elements of an `Iterable` into `NonEmptyArray`s using the provided `isEquivalent` function. + * + * **Example** (Grouping consecutive elements with custom equivalence) + * + * ```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) + * 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) + * 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) + * 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) + * Array.from(scatteredGroups) // => [[1], [2], [1], [2], [1]] + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const groupWith: { + (isEquivalent: (self: A, that: A) => boolean): (self: Iterable) => Iterable> + (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Iterable> +} = dual( + 2, + (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Iterable> => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let nextResult: IteratorResult | undefined + return { + next() { + let result: IteratorResult + if (nextResult !== undefined) { + if (nextResult.done) { + return { done: true, value: undefined } + } + result = nextResult + nextResult = undefined + } else { + result = iterator.next() + if (result.done) { + return { done: true, value: undefined } + } + } + const chunk: NonEmptyArray = [result.value] + + while (true) { + const next = iterator.next() + if (next.done || !isEquivalent(result.value, next.value)) { + nextResult = next + return { done: false, value: chunk } + } + chunk.push(next.value) + } + } + } + } + }) +) + +/** + * Groups equal, consecutive elements of an `Iterable` into `NonEmptyArray`s. + * + * **Example** (Grouping consecutive elements) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 1, 2, 2, 2, 3, 1, 1] + * const grouped = Iterable.group(numbers) + * Array.from(grouped) // => [[1, 1], [2, 2, 2], [3], [1, 1]] + * + * const letters = "aabbccaa" + * const groupedLetters = Iterable.group(letters) + * Array.from(groupedLetters) // => [["a", "a"], ["b", "b"], ["c", "c"], ["a", "a"]] + * + * // Works with objects using deep equality + * const objects = [ + * { type: "A", value: 1 }, + * { type: "A", value: 1 }, + * { type: "B", value: 2 }, + * { type: "A", value: 1 } + * ] + * const groupedObjects = Iterable.group(objects) + * Array.from(groupedObjects).length // => 3 + * // Note: Only consecutive equal objects are grouped together + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const group: (self: Iterable) => Iterable> = groupWith( + Equal.asEquivalence() +) + +/** + * Groups all elements by the string or symbol key returned by `f`. + * + * **Details** + * + * Each property in the returned record contains a non-empty array of elements + * 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 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()) + * 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]) + * byFirstLetter // => { A: ["Alice", "Anna"], B: ["Bob", "Betty"], C: ["Charlie"], D: ["David"] } + * + * // Group by category + * const items = [ + * { name: "apple", category: "fruit" }, + * { name: "carrot", category: "vegetable" }, + * { name: "banana", category: "fruit" }, + * { name: "broccoli", category: "vegetable" } + * ] + * const byCategory = Iterable.groupBy(items, (item) => item.category) + * 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") + * evenOdd // => { odd: [1, 3, 5], even: [2, 4, 6] } + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const groupBy: { + ( + f: (a: A) => K + ): (self: Iterable) => Record.ReadonlyRecord.GroupByResult> + ( + self: Iterable, + f: (a: A) => K + ): Record.ReadonlyRecord.GroupByResult> +} = dual(2, ( + self: Iterable, + f: (a: A) => K +): 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 { + InternalRecord.assignProperty(out, k, [a]) + } + } + return out +}) + +const constEmpty: Iterable = { + [Symbol.iterator]() { + return constEmptyIterator + } +} +const constEmptyIterator: Iterator = { + next() { + return { done: true, value: undefined } + } +} + +/** + * Creates an empty iterable that yields no elements. + * + * **When to use** + * + * Use when you need an empty iterable as a typed "no data" value or a base + * case for iterable operations. + * + * **Example** (Creating an empty iterable) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * Array.from(Iterable.empty()) // => [] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (): Iterable => constEmpty + +/** + * Creates an iterable containing a single element. + * + * **When to use** + * + * Use to wrap a single value in an iterable context so it can be combined + * with other iterable operations. + * + * **Example** (Wrapping a single value) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const single = Iterable.of(42) + * Array.from(single) // => [42] + * + * // Useful for creating homogeneous sequences + * const sequences = [ + * Iterable.of("hello"), + * Iterable.range(1, 3), + * Iterable.empty() + * ] + * + * // Can be used with flatMap for conditional inclusion + * const numbers = [1, 2, 3, 4, 5] + * const evensOnly = Iterable.flatMap( + * numbers, + * (n) => n % 2 === 0 ? Iterable.of(n) : Iterable.empty() + * ) + * Array.from(evensOnly) // => [2, 4] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const of = (a: A): Iterable => [a] + +/** + * Transforms each element of an iterable using a function. + * + * **Details** + * + * This is one of the most fundamental operations for working with iterables. + * It applies a transformation function to each element, creating a new iterable + * with the transformed values. The operation is lazy, so elements are only + * transformed when the iterable is consumed. + * + * **Example** (Mapping elements) + * + * ```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) + * Array.from(squares) // => [1, 4, 9, 16, 25] + * + * // Use index in transformation + * const indexed = Iterable.map(["a", "b", "c"], (char, i) => `${i}: ${char}`) + * Array.from(indexed) // => ["0: a", "1: b", "2: c"] + * + * Array.from(Iterable.map( + * Iterable.map([1, 2, 3], (x) => x * 2), + * (x) => x + 1 + * )) // => [3, 5, 7] + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + ( + f: (a: NoInfer, i: number) => B + ): (self: Iterable) => Iterable + (self: Iterable, f: (a: NoInfer, i: number) => B): Iterable +} = dual(2, (self: Iterable, f: (a: A, i: number) => B): Iterable => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let i = 0 + return { + next() { + const result = iterator.next() + if (result.done) { + return { done: true, value: undefined } + } + return { done: false, value: f(result.value, i++) } + } + } + } +})) + +/** + * Applies a function to each element in an Iterable and returns a new Iterable containing the concatenated mapped elements. + * + * **Example** (Flat mapping iterables) + * + * ```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)) + * Array.from(expanded) // => [1, 1, 2, 1, 2, 3] + * + * // Split strings into characters + * const words = ["hi", "bye"] + * const chars = Iterable.flatMap(words, (word) => word) + * Array.from(chars) // => ["h", "i", "b", "y", "e"] + * + * // Conditional expansion with empty iterables + * const values = [1, 2, 3, 4, 5] + * const evenMultiples = Iterable.flatMap( + * values, + * (n) => n % 2 === 0 ? [n, n * 2, n * 3] : [] + * ) + * Array.from(evenMultiples) // => [2, 4, 6, 4, 8, 12] + * + * // Use index in transformation + * const letters = ["a", "b", "c"] + * const indexed = Iterable.flatMap( + * letters, + * (letter, i) => Iterable.replicate(letter, i + 1) + * ) + * Array.from(indexed) // => ["a", "b", "b", "c", "c", "c"] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + ( + f: (a: NoInfer, i: number) => Iterable + ): (self: Iterable) => Iterable + (self: Iterable, f: (a: NoInfer, i: number) => Iterable): Iterable +} = dual( + 2, + (self: Iterable, f: (a: A, i: number) => Iterable): Iterable => flatten(map(self, f)) +) + +/** + * Flattens an Iterable of Iterables into a single Iterable + * + * **Example** (Flattening nested iterables) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * // Flatten nested arrays + * const nested = [[1, 2], [3, 4], [5, 6]] + * const flat = Iterable.flatten(nested) + * Array.from(flat) // => [1, 2, 3, 4, 5, 6] + * + * // Flatten different iterable types + * const mixed: Array> = ["ab", "cd"] + * const flatMixed = Iterable.flatten(mixed) + * Array.from(flatMixed) // => ["a", "b", "c", "d"] + * + * // Flatten deeply nested (only one level) + * const deepNested = [[[1, 2]], [[3, 4]]] + * const oneLevelFlat = Iterable.flatten(deepNested) + * 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) + * Array.from(flatWithEmpty) // => [1, 2, 3, 4] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatten = (self: Iterable>): Iterable => ({ + [Symbol.iterator]() { + const outerIterator = self[Symbol.iterator]() + let innerIterator: Iterator | undefined + function 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 = undefined + } + } + return { next } + } +}) + +/** + * Transforms elements of an iterable using a function that returns a `Result`, keeping only successful values. + * + * **Details** + * + * This combines mapping and filtering in a single operation. The function is + * applied to each element, and only elements that result in `Result.succeed` + * are included in the result. + * + * **Example** (Filtering and transforming Result values) + * + * ```ts import.meta.vitest + * import { Iterable, Result } from "effect" + * + * // Parse strings to numbers, keeping only valid ones + * const strings = ["1", "2", "invalid", "4", "not-a-number"] + * const numbers = Iterable.filterMap(strings, (s) => { + * const num = parseInt(s) + * return isNaN(num) ? Result.failVoid : Result.succeed(num) + * }) + * Array.from(numbers) // => [1, 2, 4] + * + * // Extract specific properties from objects + * const users = [ + * { name: "Alice", age: 25, email: "alice@example.com" }, + * { name: "Bob", age: 17, email: undefined }, + * { name: "Charlie", age: 30, email: "charlie@example.com" }, + * { name: "David", age: 16, email: undefined } + * ] + * const adultEmails = Iterable.filterMap( + * users, + * (user) => + * user.age >= 18 && user.email ? Result.succeed(user.email) : Result.failVoid + * ) + * Array.from(adultEmails) // => ["alice@example.com", "charlie@example.com"] + * + * // Use index in transformation + * const items = ["a", "b", "c", "d", "e"] + * const evenIndexItems = Iterable.filterMap( + * items, + * (item, i) => i % 2 === 0 ? Result.succeed(`${i}: ${item}`) : Result.failVoid + * ) + * Array.from(evenIndexItems) // => ["0: a", "2: c", "4: e"] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + (f: (input: A, i: number) => Result): (self: Iterable) => Iterable + (self: Iterable, f: (input: A, i: number) => Result): Iterable +} = dual( + 2, + (self: Iterable, f: (input: A, i: number) => Result): Iterable => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let i = 0 + return { + next() { + let result = iterator.next() + while (!result.done) { + const next = f(result.value, i++) + if (R.isSuccess(next)) { + return { done: false, value: next.success } + } + result = iterator.next() + } + return { done: true, value: undefined } + } + } + } + }) +) + +/** + * Transforms all elements of the `Iterable` for as long as the specified function succeeds. + * + * **Example** (Filtering and transforming until failure) + * + * ```ts import.meta.vitest + * import { Iterable, Result } from "effect" + * + * // Parse numbers until we hit an invalid one + * const strings = ["1", "2", "3", "invalid", "4", "5"] + * const numbers = Iterable.filterMapWhile(strings, (s) => { + * const num = parseInt(s) + * return isNaN(num) ? Result.failVoid : Result.succeed(num) + * }) + * Array.from(numbers) // => [1, 2, 3] + * + * // Take elements while they meet a condition and transform them + * const values = [2, 4, 6, 7, 8, 10] + * const doubledEvens = Iterable.filterMapWhile( + * values, + * (n) => n % 2 === 0 ? Result.succeed(n * 2) : Result.failVoid + * ) + * Array.from(doubledEvens) // => [4, 8, 12] + * + * // Process with index until condition fails + * const letters = ["a", "b", "c", "d", "e"] + * const indexedUntilC = Iterable.filterMapWhile( + * letters, + * (letter, i) => letter !== "c" ? Result.succeed(`${i}: ${letter}`) : Result.failVoid + * ) + * Array.from(indexedUntilC) // => ["0: a", "1: b"] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterMapWhile: { + (f: (input: A, i: number) => Result): (self: Iterable) => Iterable + (self: Iterable, f: (input: A, i: number) => Result): Iterable +} = dual(2, (self: Iterable, f: (input: A, i: number) => Result) => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let i = 0 + return { + next() { + const result = iterator.next() + if (result.done) { + return { done: true, value: undefined } + } + const next = f(result.value, i++) + if (R.isSuccess(next)) { + return { done: false, value: next.success } + } + return { done: true, value: undefined } + } + } + } +})) + +/** + * Retrieves the `Some` values from an `Iterable` of `Option`s. + * + * **Example** (Extracting Some values) + * + * ```ts import.meta.vitest + * import { Iterable, Option } from "effect" + * + * Array.from(Iterable.getSomes([Option.some(1), Option.none(), Option.some(2)])) // => [1, 2] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const getSomes = (self: Iterable>): Iterable => { + return { + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + return { + next() { + let result = iterator.next() + while (!result.done) { + if (O.isSome(result.value)) { + return { done: false, value: result.value.value } + } + result = iterator.next() + } + return { done: true, value: undefined } + } + } + } + } +} + +/** + * Returns a lazy iterable containing the failure values from an iterable of + * `Result`s, skipping successful results. + * + * **Example** (Extracting failures) + * + * ```ts import.meta.vitest + * import { Iterable, Result } from "effect" + * + * Array.from(Iterable.getFailures([ + * Result.succeed(1), + * Result.fail("err"), + * Result.succeed(2) + * ])) // => ["err"] + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const getFailures = (self: Iterable>): Iterable => { + return { + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + return { + next() { + let result = iterator.next() + while (!result.done) { + if (R.isFailure(result.value)) { + return { done: false, value: result.value.failure } + } + result = iterator.next() + } + return { done: true, value: undefined } + } + } + } + } +} + +/** + * Returns a lazy iterable containing the success values from an iterable of + * `Result`s, skipping failed results. + * + * **Example** (Extracting successes) + * + * ```ts import.meta.vitest + * import { Iterable, Result } from "effect" + * + * Array.from(Iterable.getSuccesses([ + * Result.succeed(1), + * Result.fail("err"), + * Result.succeed(2) + * ])) // => [1, 2] + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const getSuccesses = (self: Iterable>): Iterable => { + return { + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + return { + next() { + let result = iterator.next() + while (!result.done) { + if (R.isSuccess(result.value)) { + return { done: false, value: result.value.success } + } + result = iterator.next() + } + return { done: true, value: undefined } + } + } + } + } +} + +/** + * Filters an iterable to only include elements that match a predicate. + * + * **Details** + * + * This function creates a new iterable containing only the elements for which + * the predicate function returns true. Like map, this operation is lazy and + * elements are only tested when the iterable is consumed. + * + * **Example** (Filtering elements) + * + * ```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) + * Array.from(evens) // => [2, 4, 6] + * + * // Filter with index + * const items = ["a", "b", "c", "d"] + * const oddPositions = Iterable.filter(items, (_, i) => i % 2 === 1) + * Array.from(oddPositions) // => ["b", "d"] + * + * // Type refinement + * const mixed: Array = ["hello", 42, "world", 100] + * const onlyStrings = Iterable.filter( + * mixed, + * (x): x is string => typeof x === "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 + * ) + * Array.from(processed) // => [30, 40, 50] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (refinement: (a: NoInfer, i: number) => a is B): (self: Iterable) => Iterable + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => Iterable + (self: Iterable, refinement: (a: A, i: number) => a is B): Iterable + (self: Iterable, predicate: (a: A, i: number) => boolean): Iterable +} = dual( + 2, + (self: Iterable, predicate: (a: A, i: number) => boolean): Iterable => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let i = 0 + return { + next() { + let result = iterator.next() + while (!result.done) { + if (predicate(result.value, i++)) { + return { done: false, value: result.value } + } + result = iterator.next() + } + return { done: true, value: undefined } + } + } + } + }) +) + +/** + * Transforms elements using a function that may return null or undefined, filtering out the null/undefined results. + * + * **When to use** + * + * Use when working with APIs or functions that return nullable values, + * providing a clean way to filter out null or undefined while transforming. + * + * **Example** (Flat mapping nullable results) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * // Extract valid elements from nullable function results + * const data = ["1", "2", "invalid", "4"] + * const parsed = Iterable.flatMapNullishOr(data, (s) => { + * const num = parseInt(s) + * return isNaN(num) ? null : num * 2 + * }) + * Array.from(parsed) // => [2, 4, 8] + * + * // Safe property access + * const objects = [ + * { nested: { value: 10 } }, + * { nested: null }, + * { nested: { value: 20 } }, + * {} + * ] + * const values = Iterable.flatMapNullishOr(objects, (obj) => obj.nested?.value) + * Array.from(values) // => [10, 20] + * + * // Working with Map.get (returns undefined for missing keys) + * const map = new Map([ + * ["a", 1], + * ["b", 2], + * ["c", 3] + * ]) + * const keys = ["a", "x", "b", "y", "c"] + * const foundValues = Iterable.flatMapNullishOr(keys, (key) => map.get(key)) + * Array.from(foundValues) // => [1, 2, 3] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const flatMapNullishOr: { + (f: (a: A) => B): (self: Iterable) => Iterable> + (self: Iterable, f: (a: A) => B): Iterable> +} = dual( + 2, + (self: Iterable, f: (a: A) => B): Iterable> => + filterMap(self, (a) => { + const b = f(a) + return b == null ? R.failVoid : R.succeed(b) + }) +) + +/** + * Checks whether a predicate holds true for some `Iterable` element. + * + * **Example** (Checking whether some element matches) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * const numbers = [1, 3, 5, 7, 8] + * const hasEven = Iterable.some(numbers, (x) => x % 2 === 0) + * hasEven // => true + * + * const allOdd = [1, 3, 5, 7] + * const hasEvenInAllOdd = Iterable.some(allOdd, (x) => x % 2 === 0) + * hasEvenInAllOdd // => false + * + * // With index + * const letters = ["a", "b", "c"] + * const hasElementAtIndex2 = Iterable.some(letters, (_, i) => i === 2) + * hasElementAtIndex2 // => true + * + * // Early termination - stops at first match + * const infiniteOdds = Iterable.filter(Iterable.range(1), (x) => x % 2 === 1) + * const hasEvenInInfiniteOdds = Iterable.some( + * Iterable.take(infiniteOdds, 1000), + * (x) => x % 2 === 0 + * ) + * hasEvenInInfiniteOdds // => false + * + * // Type guard usage + * const mixed: Array = [1, 2, "hello"] + * const hasString = Iterable.some( + * mixed, + * (x): x is string => typeof x === "string" + * ) + * hasString // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const some: { + (predicate: (a: A, i: number) => boolean): (self: Iterable) => boolean + (self: Iterable, predicate: (a: A, i: number) => boolean): boolean +} = dual( + 2, + (self: Iterable, predicate: (a: A, i: number) => boolean): boolean => { + let i = 0 + for (const a of self) { + if (predicate(a, i++)) { + return true + } + } + return false + } +) + +/** + * Generates an iterable by repeatedly applying a function that produces the + * next element and state. + * + * **Details** + * + * This is useful for creating iterables from a generating function that + * maintains state. The function should return `Option.some([value, nextState])` + * to continue or `Option.none()` to stop. + * + * **Example** (Unfolding state into values) + * + * ```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) + * 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()) + * 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()) + * Array.from(countdown) // => [5, 4, 3, 2, 1] + * + * // Generate collatz sequence + * const collatz = Iterable.unfold(7, (n) => { + * if (n === 1) return Option.none() + * const next = n % 2 === 0 ? n / 2 : n * 3 + 1 + * return Option.some([n, next]) + * }) + * Array.from(collatz) // => [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unfold = (b: B, f: (b: B) => Option): Iterable => ({ + [Symbol.iterator]() { + let next = b + return { + next() { + const ab = f(next) + if (O.isNone(ab)) { + return { done: true, value: undefined } + } + const [a, b] = ab.value + next = b + return { done: false, value: a } + } + } + } +}) + +/** + * Iterates over the `Iterable`, applying `f` to each element. + * + * **Example** (Iterating with side effects) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * // Collect each visited element + * const numbers = [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) => { + * indexed.push(`${i}: ${letter}`) + * }) + * 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) + * }) + * 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) => { + * processed.push(Array.from(chunk)) + * }) + * processed // => [[1, 2], [3, 4], [5, 6]] + * ``` + * + * @category traversing + * @since 2.0.0 + */ +export const forEach: { + (f: (a: A, i: number) => void): (self: Iterable) => void + (self: Iterable, f: (a: A, i: number) => void): void +} = dual(2, (self: Iterable, f: (a: A, i: number) => void): void => { + let i = 0 + for (const a of self) { + f(a, i++) + } +}) + +/** + * Reduces an iterable to a single value by applying a function to each element and accumulating the result. + * + * **Details** + * + * This function applies a reducing function against an accumulator and each element + * of the iterable (from left to right) to reduce it to a single value. + * + * **Example** (Reducing an iterable) + * + * ```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) + * sum // => 15 + * + * // Find maximum value + * const values = [3, 1, 4, 1, 5, 9, 2] + * 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 + * const obj = Iterable.reduce( + * pairs, + * {} as Record, + * (acc, [key, value]) => { + * acc[key] = value + * return acc + * } + * ) + * obj // => { a: 1, b: 2, c: 3 } + * + * // Use index in the reducer + * const letters = ["a", "b", "c"] + * const indexed = Iterable.reduce( + * letters, + * [] as Array, + * (acc, letter, i) => { + * acc.push(`${i}: ${letter}`) + * return acc + * } + * ) + * indexed // => ["0: a", "1: b", "2: c"] + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + (b: B, f: (b: B, a: A, i: number) => B): (self: Iterable) => B + (self: Iterable, b: B, f: (b: B, a: A, i: number) => B): B +} = dual(3, (self: Iterable, b: B, f: (b: B, a: A, i: number) => B): B => { + if (Array.isArray(self)) { + return self.reduce(f, b) + } + let i = 0 + let result = b + for (const n of self) { + result = f(result, n, i++) + } + return result +}) + +/** + * Deduplicates adjacent elements that are identical using the provided `isEquivalent` function. + * + * **Example** (Deduplicating adjacent elements with custom equivalence) + * + * ```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) + * 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) + * Array.from(dedupedWords) // => ["Hello", "world", "test"] + * + * // Deduplication by object property + * const users = [ + * { id: 1, name: "Alice" }, + * { id: 1, name: "Alice Updated" }, // different name, same id + * { id: 2, name: "Bob" }, + * { id: 2, name: "Bob" }, + * { id: 3, name: "Charlie" } + * ] + * const byId = (a: typeof users[0], b: typeof users[0]) => a.id === b.id + * const dedupedUsers = Iterable.dedupeAdjacentWith(users, byId) + * 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) + * Array.from(dedupedFloats) // => [1, 2, 3] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dedupeAdjacentWith: { + (isEquivalent: (self: A, that: A) => boolean): (self: Iterable) => Iterable + (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Iterable +} = dual(2, (self: Iterable, isEquivalent: (self: A, that: A) => boolean): Iterable => ({ + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let first = true + let last: A + function next(): IteratorResult { + const result = iterator.next() + if (result.done) { + return { done: true, value: undefined } + } + if (first) { + first = false + last = result.value + return result + } + const current = result.value + if (isEquivalent(last, current)) { + return next() + } + last = current + return result + } + return { next } + } +})) + +/** + * Deduplicates adjacent elements that are identical. + * + * **Example** (Deduplicating adjacent elements) + * + * ```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) + * Array.from(deduped) // => [1, 2, 3, 1] + * + * // Remove adjacent duplicate characters + * const letters = "aabbccaa" + * const dedupedLetters = Iterable.dedupeAdjacent(letters) + * Array.from(dedupedLetters) // => ["a", "b", "c", "a"] + * + * // Works with objects using deep equality + * const objects = [ + * { type: "A" }, + * { type: "A" }, + * { type: "B" }, + * { type: "B" }, + * { type: "A" } + * ] + * const dedupedObjects = Iterable.dedupeAdjacent(objects) + * 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) + * Array.from(cleanedData) // => [100, 101, 102, 100] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dedupeAdjacent: (self: Iterable) => Iterable = dedupeAdjacentWith(Equal.asEquivalence()) + +/** + * Zips this Iterable crosswise with the specified Iterable using the specified combiner. + * + * **Example** (Combining cartesian products) + * + * ```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})`) + * 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"] + * const colors = ["red", "blue"] + * const products = Iterable.cartesianWith( + * sizes, + * colors, + * (size, color) => ({ size, color }) + * ) + * 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) + * Array.from(mathProducts) // => [10, 20, 20, 40, 30, 60] + * + * // Create test data combinations + * const userTypes = ["admin", "user"] + * const features = ["read", "write", "delete"] + * const testCases = Iterable.cartesianWith( + * userTypes, + * features, + * (user, feature) => `${user}_can_${feature}` + * ) + * Array.from(testCases) // => ["admin_can_read", "admin_can_write", "admin_can_delete", "user_can_read", "user_can_write", "user_can_delete"] + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const cartesianWith: { + (that: Iterable, f: (a: A, b: B) => C): (self: Iterable) => Iterable + (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable +} = dual( + 3, + (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]() + } + }) +) + +/** + * Zips this Iterable crosswise with the specified Iterable. + * + * **Example** (Generating cartesian pairs) + * + * ```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) + * 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) + * 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) + * 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) + * Array.from(withEmpty) // => [] + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const cartesian: { + (that: Iterable): (self: Iterable) => Iterable<[A, B]> + (self: Iterable, that: Iterable): Iterable<[A, B]> +} = dual( + 2, + (self: Iterable, that: Iterable): Iterable<[A, B]> => cartesianWith(self, that, (a, b) => [a, b]) +) + +/** + * Computes how many elements of the iterable pass the given predicate. + * + * **Example** (Counting matching elements) + * + * ```ts import.meta.vitest + * import { Iterable } from "effect" + * + * Iterable.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) // => 2 + * ``` + * + * @category folding + * @since 3.16.0 + */ +export const countBy: { + (predicate: (a: NoInfer, i: number) => boolean): (self: Iterable) => number + (self: Iterable, predicate: (a: A, i: number) => boolean): number +} = dual( + 2, + ( + self: Iterable, + f: (a: A, i: number) => boolean + ): number => { + let count = 0 + let i = 0 + for (const a of self) { + if (f(a, i)) { + count++ + } + i++ + } + return count + } +) diff --git a/.repos/effect/packages/effect/src/JsonPatch.ts b/.repos/effect/packages/effect/src/JsonPatch.ts new file mode 100644 index 000000000..605d24e2b --- /dev/null +++ b/.repos/effect/packages/effect/src/JsonPatch.ts @@ -0,0 +1,416 @@ +/** + * The `JsonPatch` module computes and applies deterministic patch documents for + * JSON values. A patch is an ordered list of `add`, `remove`, and `replace` + * operations addressed by JSON Pointer paths. Use it to describe the structural + * difference between two JSON documents, serialize that difference, and replay + * it without mutating the original input. + * + * @since 4.0.0 + */ +import * as InternalRecord from "./internal/record.ts" +import { escapeToken, unescapeToken } from "./JsonPointer.ts" +import type * as Schema from "./Schema.ts" + +/** + * A single JSON Patch operation. + * + * **When to use** + * + * Use to manually construct patch operations, accept patch operations from + * callers, or type-check patch operation structures. + * + * **Details** + * + * Represents one transformation step in a JSON Patch document. This is a subset + * of RFC 6902, restricted to operations that can be applied deterministically + * without additional context. All fields are readonly, paths use JSON Pointer + * syntax, and the empty string `""` refers to the root document. Operations are + * discriminated by the `op` field, and the optional `description` field can be + * used for documentation. + * + * **Example** (Defining all operation types) + * + * ```ts import.meta.vitest + * import { JsonPatch } from "effect" + * + * const addOp: JsonPatch.JsonPatchOperation = { + * op: "add", + * path: "/users/-", + * value: { id: 1, name: "Alice" } + * } + * + * const removeOp: JsonPatch.JsonPatchOperation = { + * op: "remove", + * path: "/users/0" + * } + * + * const replaceOp: JsonPatch.JsonPatchOperation = { + * op: "replace", + * 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 + * @see {@link get} to compute operations automatically from value differences + * @see {@link apply} to apply operations to transform documents + * @category models + * @since 4.0.0 + */ +export type JsonPatchOperation = + | { + readonly op: "add" + /** + * JSON Pointer to the target location. For arrays, the last token may be `-` + * to append. + * + * **When to use** + * + * Use to identify where the `add` operation inserts its value. + */ + readonly path: string + readonly value: Schema.Json + readonly description?: string + } + | { + readonly op: "remove" + /** + * JSON Pointer to the target location. + * + * **When to use** + * + * Use to identify which location the `remove` operation deletes. + */ + readonly path: string + readonly description?: string + } + | { + readonly op: "replace" + /** + * JSON Pointer to the target location. Use `""` to replace the root document. + * + * **When to use** + * + * Use to identify which location the `replace` operation overwrites. + */ + readonly path: string + readonly value: Schema.Json + readonly description?: string + } + +/** + * A JSON Patch document (an ordered list of operations). + * + * **When to use** + * + * Use to store, serialize, pass, or validate complete patch documents. + * + * **Details** + * + * Represents a complete transformation as a readonly sequence of immutable + * operations. Operations are applied sequentially from first to last, and later + * operations observe the document state produced by earlier operations. An empty + * array represents a no-op patch and returns the original document. + * + * **Example** (Defining a multi-operation patch) + * + * ```ts import.meta.vitest + * import { JsonPatch } from "effect" + * + * const patch: JsonPatch.JsonPatch = [ + * { op: "add", path: "/items/-", value: "apple" }, + * { op: "replace", path: "/count", value: 5 }, + * { op: "remove", path: "/oldField" } + * ] + * + * JsonPatch.apply(patch, { items: [], count: 3, oldField: "value" }) // => { items: ["apple"], count: 5 } + * ``` + * + * @see {@link JsonPatchOperation} for individual operation types + * @see {@link get} to generate patches from value differences + * @see {@link apply} to execute patches to transform documents + * @category models + * @since 4.0.0 + */ +export type JsonPatch = ReadonlyArray + +/** + * Computes a structural patch that transforms `oldValue` into `newValue`. + * + * **When to use** + * + * Use to compute a JSON Patch from before and after JSON documents, detect + * structural changes, or create deterministic update operations. + * + * **Details** + * + * Generates a structural diff between two JSON values, producing a patch that + * yields `newValue` when applied to `oldValue`. It returns an empty array when + * values are identical, recursively diffs nested structures, emits root + * `replace` operations for primitive changes, and processes object keys in + * sorted order for stable output. + * + * **Gotchas** + * + * Arrays are compared by index position, with no move or copy detection. Array + * removals are emitted from highest to lowest index to prevent index shifting. + * The output is deterministic but not guaranteed to be minimal. + * + * **Example** (Computing object diff) + * + * ```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) + * 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 + * @see {@link JsonPatchOperation} for the operation types in the patch + * @category transforming + * @since 4.0.0 + */ +export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { + 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 + + // Compare shared prefix by index + const shared = Math.min(len1, len2) + for (let i = 0; i < shared; i++) { + 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: `${path}/${i}` }) + } + + // Add from beginning to end. + for (let i = len1; i < len2; i++) { + patches.push({ op: "add", path: `${path}/${i}`, value: newValue[i] }) + } + + return + } + + if (isJsonObject(oldValue) && isJsonObject(newValue)) { + const keys1 = Object.keys(oldValue) + const keys2 = Object.keys(newValue) + const allKeys = Array.from(new Set([...keys1, ...keys2])).sort() + + for (const key of allKeys) { + const keyPath = `${path}/${escapeToken(key)}` + const hasKey1 = Object.hasOwn(oldValue, key) + const hasKey2 = Object.hasOwn(newValue, key) + + if (hasKey1 && hasKey2) { + getLoop(oldValue[key], newValue[key], keyPath, patches) + } else if (!hasKey1 && hasKey2) { + patches.push({ op: "add", path: keyPath, value: newValue[key] }) + } else { + patches.push({ op: "remove", path: keyPath }) + } + } + + return + } + + patches.push({ op: "replace", path, value: newValue }) +} + +/** + * Applies a JSON Patch to a JSON document. + * + * **When to use** + * + * Use to execute patches generated by {@link get}, transform documents + * with manually constructed patches, or process patch operations from external + * sources. + * + * **Details** + * + * Executes patch operations sequentially, so later operations see changes made + * by earlier operations. It never mutates the input document; array and object + * operations copy the affected containers. An empty patch returns the original + * reference, and a root replace (`path: ""`) returns the provided value + * directly. + * + * **Gotchas** + * + * Invalid paths, missing properties, and out-of-bounds array indices throw + * errors. + * + * **Example** (Applying a patch) + * + * ```ts import.meta.vitest + * import { JsonPatch } from "effect" + * + * const document = { items: [1, 2, 3], total: 6 } + * const patch: JsonPatch.JsonPatch = [ + * { op: "add", path: "/items/-", value: 4 }, + * { op: "replace", path: "/total", value: 10 } + * ] + * + * JsonPatch.apply(patch, document) // => { items: [1, 2, 3, 4], total: 10 } + * ``` + * + * @see {@link get} to generate patches from value differences + * @see {@link JsonPatchOperation} for the operation types being applied + * @category transforming + * @since 4.0.0 + */ +export function apply(patch: JsonPatch, oldValue: Schema.Json): Schema.Json { + let doc = oldValue + + for (const op of patch) { + doc = applyOperation(doc, op) + } + + return doc +} + +function isJsonObject(value: unknown): value is Schema.JsonObject { + return typeof value === "object" && value !== null && !Array.isArray(value) +} + +/** + * Tokenize a JSON Pointer into unescaped reference tokens. + * + * - `""` (empty pointer) refers to the root and returns `[]` + * - Non-empty pointers must start with `/` + */ +function tokenize(pointer: string): Array { + if (pointer === "") return [] + if (pointer.charCodeAt(0) !== 47 /* "/" */) { + 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 { + if (!/^(0|[1-9]\d*)$/.test(token)) { + throw new Error(`Invalid array index: "${token}"`) + } + return Number(token) +} + +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, op.path) + if (resolved === null) { + 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 === "-" && op.op !== "add") { + throw new Error(`"-" is not valid for ${op.op} at "${op.path}".`) + } + const index = lastToken === "-" ? parent.length : toIndex(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 (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 (op.op !== "add" && !Object.hasOwn(parent, lastToken)) { + throw new Error(`Property "${lastToken}" does not exist at "${op.path}".`) + } + const updated = { ...parent } + if (op.op === "remove") delete updated[lastToken] + else InternalRecord.assignProperty(updated, lastToken, op.value) + return rebuildFromStack(stack, updated) + } + + 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 } + +// Walk to the parent of `pointer`, recording the path. +// Returns null if the parent path cannot be resolved. +function resolveParent( + doc: Schema.Json, + pointer: string +): { readonly stack: ReadonlyArray; readonly parent: unknown; readonly lastToken: string } | null { + const tokens = tokenize(pointer) + if (tokens.length === 0) return null // caller handles root + + const lastToken = tokens[tokens.length - 1] + const stack: Array = [] + let cur: unknown = doc + + for (let i = 0; i < tokens.length - 1; i++) { + const token = tokens[i] + + if (Array.isArray(cur)) { + const idx = toIndex(token) + if (idx >= cur.length) return null + stack.push({ container: cur, token: idx }) + cur = cur[idx] + continue + } + + if (isJsonObject(cur)) { + if (!Object.hasOwn(cur, token)) return null + stack.push({ container: cur, token }) + cur = cur[token] + continue + } + + return null + } + + return { stack, parent: cur, lastToken } +} + +// Rebuild the document by writing `newParent` back through `stack`. +function rebuildFromStack(stack: ReadonlyArray, newParent: Schema.Json): Schema.Json { + let acc: Schema.Json = newParent + + for (let i = stack.length - 1; i >= 0; i--) { + const { container, token } = stack[i] + + if (Array.isArray(container)) { + const copy = container.slice() + copy[token as number] = acc + acc = copy + } else { + const copy = { ...(container as Schema.JsonObject) } + InternalRecord.assignProperty(copy, token as string, acc) + acc = copy + } + } + + return acc +} diff --git a/.repos/effect/packages/effect/src/JsonPointer.ts b/.repos/effect/packages/effect/src/JsonPointer.ts new file mode 100644 index 000000000..385d578b6 --- /dev/null +++ b/.repos/effect/packages/effect/src/JsonPointer.ts @@ -0,0 +1,80 @@ +/** + * 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. + * + * @since 4.0.0 + */ + +/** + * Escapes a JSON Pointer reference token according to RFC 6901 by encoding special characters so the token can be safely used as a segment in a JSON Pointer. + * + * **When to use** + * + * Use when you need to escape a single JSON Pointer path segment. + * + * **Details** + * + * - Returns a new escaped string + * - Replaces `~` (tilde) with `~0` and `/` (forward slash) with `~1` + * - Returns the input unchanged if it contains no special characters + * - Empty strings are valid and returned unchanged + * + * **Gotchas** + * + * The replacement order matters: `~` is replaced before `/` to prevent double-escaping. + * + * **Example** (Escaping special characters) + * + * ```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" + * ``` + * + * @see {@link unescapeToken} The inverse operation for decoding escaped tokens + * @category encoding + * @since 4.0.0 + */ +export function escapeToken(token: string): string { + return token.replace(/~/g, "~0").replace(/\//g, "~1") +} + +/** + * Decodes a JSON Pointer reference token according to RFC 6901 escaping rules. + * + * **When to use** + * + * Use when you need to decode a single escaped JSON Pointer path segment. + * + * **Details** + * + * - Returns a new unescaped string + * - Replaces `~1` with `/` (forward slash) and `~0` with `~` (tilde) + * - Returns the input unchanged if it contains no escaped sequences + * - Empty strings are valid and returned unchanged + * + * **Gotchas** + * + * The replacement order matters: `~1` is replaced before `~0` to prevent incorrect decoding. + * + * **Example** (Unescaping special characters) + * + * ```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" + * ``` + * + * @see {@link escapeToken} The inverse operation for encoding tokens + * @category decoding + * @since 4.0.0 + */ +export function unescapeToken(token: string): string { + return token.replace(/~1/g, "/").replace(/~0/g, "~") +} diff --git a/.repos/effect/packages/effect/src/JsonSchema.ts b/.repos/effect/packages/effect/src/JsonSchema.ts new file mode 100644 index 000000000..054b8e6b6 --- /dev/null +++ b/.repos/effect/packages/effect/src/JsonSchema.ts @@ -0,0 +1,1645 @@ +/** + * 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, 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 InternalRecord from "./internal/record.ts" +import { escapeToken, unescapeToken } from "./JsonPointer.ts" +import * as Predicate from "./Predicate.ts" + +/** + * A plain object representing a single JSON Schema node. + * + * **When to use** + * + * Use to represent an arbitrary JSON Schema object regardless of dialect. + * + * **Details** + * + * This is an open record type (`[x: string]: unknown`) so it can hold any JSON + * Schema keyword. Most functions in this module accept or return this type. + * + * @category models + * @since 4.0.0 + */ +export interface JsonSchema { + [x: string]: unknown +} + +/** + * The set of JSON Schema dialects supported by this module. + * + * **When to use** + * + * Use as the dialect marker for `JsonSchema` documents when parsing, + * converting, or emitting schemas across the supported formats. + * + * **Details** + * + * 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 + * + * @category models + * @since 4.0.0 + */ +export type Dialect = "draft-04" | "draft-07" | "draft-2020-12" | "openapi-3.1" | "openapi-3.0" + +/** + * The JSON Schema primitive type names. + * + * **When to use** + * + * Use to restrict a JSON Schema `type` keyword to the supported primitive names. + * + * @category models + * @since 4.0.0 + */ +export type Type = "string" | "number" | "boolean" | "array" | "object" | "null" | "integer" + +/** + * A record of named JSON Schema definitions, keyed by definition name. + * + * **When to use** + * + * Use as the shared lookup table for named JSON Schema nodes that are + * referenced from JSON Schema documents. + * + * **Details** + * + * The map is dialect-neutral. Conversion APIs emit it as `$defs`, + * `definitions`, or `components.schemas` depending on the target format. + * + * @see {@link Document} for a single root schema with definitions + * @see {@link MultiDocument} for multiple root schemas sharing definitions + * @category models + * @since 4.0.0 + */ +export interface Definitions extends Record {} + +/** + * A structured container for a single JSON Schema and its associated + * definitions. + * + * **When to use** + * + * Use when you need to carry a root schema together with its shared + * definitions, or when converting between dialects with the `from*` and `to*` + * functions. + * + * **Details** + * + * 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-04 and Draft-07, and `#/components/schemas/` for OpenAPI 3.1 + * and OpenAPI 3.0. + * + * **Example** (Inspecting a parsed document) + * + * ```ts import.meta.vitest + * import { JsonSchema } from "effect" + * + * const raw: JsonSchema.JsonSchema = { + * type: "string", + * $defs: { Trimmed: { type: "string", minLength: 1 } } + * } + * + * const doc = JsonSchema.fromSchemaDraft2020_12(raw) + * + * doc.dialect // => "draft-2020-12" + * doc.schema // => { type: "string" } + * doc.definitions // => { Trimmed: { type: "string", minLength: 1 } } + * ``` + * + * @see {@link MultiDocument} + * @see {@link fromSchemaDraft2020_12} + * @category models + * @since 4.0.0 + */ +export interface Document { + readonly dialect: D + readonly schema: JsonSchema + readonly definitions: Definitions +} + +/** + * Like {@link Document}, but carries multiple root schemas that share a + * single definitions pool. + * + * **When to use** + * + * Use when generating several schemas, such as a request body + * and a response body, that reference the same set of definitions. + * + * **Details** + * + * The `schemas` tuple is non-empty and contains at least one element. + * + * @see {@link Document} + * @see {@link toMultiDocumentOpenApi3_1} + * @category models + * @since 4.0.0 + */ +export interface MultiDocument { + readonly dialect: D + readonly schemas: readonly [JsonSchema, ...Array] + 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. + * + * **When to use** + * + * Use when constructing a Draft-07 JSON Schema document and you need a stable + * value for the root `$schema` field. + * + * **Details** + * + * The exported value is the literal string + * `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#" + +/** + * Represents the `$schema` meta-schema URI for JSON Schema Draft 2020-12. + * + * **When to use** + * + * Use when you need to populate the `$schema` field while emitting a JSON + * Schema document that should declare JSON Schema Draft 2020-12. + * + * **Details** + * + * The exported value is the literal string + * `https://json-schema.org/draft/2020-12/schema`. + * + * @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_2020_12 = "https://json-schema.org/draft/2020-12/schema" + +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("#") ? parsePointerFragment(reference) : undefined + return path !== undefined && path[0] === "components" && path[1] === "schemas" + ? formatPointerFragment(["$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">`. + * + * **When to use** + * + * Use when you have a raw JSON Schema object that follows Draft-07 conventions + * and need the canonical Draft-2020-12 document representation. + * + * **Details** + * + * This converts Draft-07 tuple syntax (`items` as array plus + * `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** + * + * 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 import.meta.vitest + * import { JsonSchema } from "effect" + * + * const raw: JsonSchema.JsonSchema = { + * type: "object", + * properties: { + * tags: { + * type: "array", + * items: { type: "string" } + * } + * } + * } + * + * const doc = JsonSchema.fromSchemaDraft07(raw) + * doc.dialect // => "draft-2020-12" + * doc.schema.properties // => { tags: { type: "array", items: { type: "string" } } } + * ``` + * + * @see {@link fromSchemaDraft2020_12} + * @see {@link fromSchemaOpenApi3_0} + * @see {@link toDocumentDraft07} + * @category decoding + * @since 4.0.0 + */ +export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { + return fromSchemaDraft2020_12(convertDraft07(js)) +} + +/** + * Parses a raw Draft-2020-12 JSON Schema into a `Document<"draft-2020-12">`. + * + * **When to use** + * + * Use when you already have a raw JSON Schema object in Draft-2020-12 format. + * + * **Details** + * + * This separates `$defs` from the root schema into the `definitions` field. + * Unlike {@link fromSchemaDraft07}, this performs no keyword rewriting. + * + * **Example** (Parsing a Draft-2020-12 schema) + * + * ```ts import.meta.vitest + * import { JsonSchema } from "effect" + * + * const raw: JsonSchema.JsonSchema = { + * type: "number", + * minimum: 0, + * $defs: { PositiveInt: { type: "integer", minimum: 1 } } + * } + * + * const doc = JsonSchema.fromSchemaDraft2020_12(raw) + * doc.schema // => { type: "number", minimum: 0 } + * doc.definitions // => { PositiveInt: { type: "integer", minimum: 1 } } + * ``` + * + * @see {@link fromSchemaDraft07} + * @see {@link fromSchemaOpenApi3_1} + * @category decoding + * @since 4.0.0 + */ +export function fromSchemaDraft2020_12(js: JsonSchema): Document<"draft-2020-12"> { + const { $defs, ...schema } = js + return { + dialect: "draft-2020-12", + schema, + definitions: Predicate.isObject($defs) ? ($defs as Definitions) : {} + } +} + +/** + * Parses a raw OpenAPI 3.1 JSON Schema into a `Document<"draft-2020-12">`. + * + * **When to use** + * + * Use when you need to consume raw JSON Schema objects from an OpenAPI 3.1 + * specification. + * + * **Details** + * + * 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 import.meta.vitest + * import { JsonSchema } from "effect" + * + * const raw: JsonSchema.JsonSchema = { + * type: "object", + * properties: { + * user: { $ref: "#/components/schemas/User" } + * } + * } + * + * const doc = JsonSchema.fromSchemaOpenApi3_1(raw) + * doc.schema.properties // => { user: { $ref: "#/$defs/User" } } + * ``` + * + * @see {@link fromSchemaOpenApi3_0} + * @see {@link toMultiDocumentOpenApi3_1} + * @category decoding + * @since 4.0.0 + */ +export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> { + 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) +} + +/** + * Parses a raw OpenAPI 3.0 JSON Schema into a `Document<"draft-2020-12">`. + * + * **When to use** + * + * Use when you need to consume raw JSON Schema objects from an OpenAPI 3.0 + * specification. + * + * **Details** + * + * 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 import.meta.vitest + * import { JsonSchema } from "effect" + * + * const raw: JsonSchema.JsonSchema = { + * type: "string", + * nullable: true + * } + * + * const doc = JsonSchema.fromSchemaOpenApi3_0(raw) + * doc.schema.type // => ["string", "null"] + * ``` + * + * @see {@link fromSchemaOpenApi3_1} + * @see {@link fromSchemaDraft07} + * @category decoding + * @since 4.0.0 + */ +export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-12"> { + return fromSchemaDraft2020_12(convertOpenApi30(schema)) +} + +/** + * Converts a `Document<"draft-2020-12">` to a `Document<"draft-07">`. + * + * **When to use** + * + * Use when you need to output a canonical JSON Schema document in Draft-07 + * format. + * + * **Details** + * + * This rewrites `#/$defs/...` refs to `#/definitions/...`, converts + * Draft-2020-12 tuple syntax (`prefixItems` plus `items`) to Draft-07 form + * (`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** + * + * 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 import.meta.vitest + * import { JsonSchema } from "effect" + * + * const doc = JsonSchema.fromSchemaDraft2020_12({ + * type: "array", + * prefixItems: [{ type: "string" }, { type: "number" }], + * items: { type: "boolean" } + * }) + * + * const draft07 = JsonSchema.toDocumentDraft07(doc) + * 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 + */ +export function toDocumentDraft07(document: Document<"draft-2020-12">): Document<"draft-07"> { + return { + dialect: "draft-07", + ...convertDocument(document, draft07Adapter) + } +} + +/** + * 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: {} } + }) + } +} + +/** + * Converts a `MultiDocument<"draft-2020-12">` to a + * `MultiDocument<"openapi-3.1">`. + * + * **When to use** + * + * Use when you need to emit an OpenAPI 3.1 multi-document from canonical JSON + * Schema documents. + * + * **Details** + * + * 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 import.meta.vitest + * import { JsonSchema } from "effect" + * + * const multi: JsonSchema.MultiDocument<"draft-2020-12"> = { + * dialect: "draft-2020-12", + * schemas: [{ $ref: "#/$defs/User" }], + * definitions: { + * User: { type: "object", properties: { name: { type: "string" } } } + * } + * } + * + * const openapi = JsonSchema.toMultiDocumentOpenApi3_1(multi) + * openapi.dialect // => "openapi-3.1" + * openapi.schemas[0] // => { $ref: "#/components/schemas/User" } + * ``` + * + * @see {@link toDocumentDraft07} + * @see {@link MultiDocument} + * @category encoding + * @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() + 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, + 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("#") ? parsePointerFragment(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 inEmbeddedResource + ? reference + : formatPointerFragment(["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, + definitions + } +} + +/** @internal */ +export const VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP = /^[a-zA-Z0-9.\-_]+$/ + +/** + * Returns a sanitized key for an OpenAPI component schema. + * Should match the `^[a-zA-Z0-9.\-_]+$` regular expression. + * + * @internal + */ +export function sanitizeOpenApiComponentsSchemasKey(s: string): string { + return s.length === 0 ? "_" : s.replace(/[^a-zA-Z0-9._-]/gu, "_") +} + +/** @internal */ +export function getReferenceKey($ref: string): string | undefined { + const path = $ref.startsWith("#") ? parsePointerFragment($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 + } +} + +/** @internal */ +export function rewriteRefs(schema: JsonSchema, rewrite: ($ref: string) => string): JsonSchema { + return transformSchema(schema, (schema) => { + rewriteSchemaRef(schema, rewrite) + }) as JsonSchema +} + +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 +} + +type Path = ReadonlyArray +type Convert = (root: unknown, sourcePath?: Path, targetPath?: Path) => unknown + +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 = parsePointerFragment(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 parsePointerFragment(hash: string): Path | undefined { + if (hash.length === 0) return [] + let pointer: string + try { + pointer = decodeURIComponent(hash.slice(1)) + } catch { + return undefined + } + if (!pointer.startsWith("/")) return undefined + return /~(?:[^01]|$)/.test(pointer) ? undefined : pointer.slice(1).split("/").map(unescapeToken) +} + +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}${formatPointerFragment(targetPath)}` +} + +function formatPointerFragment(path: Path): string { + return path.length === 0 + ? "#" + : `#/${path.map((token) => encodeURI(escapeToken(token)).replace(/#/g, "%23")).join("/")}` +} + +function locationKey(resource: string, pointer: Path): string { + return `${resource}\u0000${JSON.stringify(pointer)}` +} + +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, + 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 +} + +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 + + 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) + } + } + + 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")) + } + } + + 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) + } + } + + convertTuple(out, prefixItems, items, context) + + 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) + } + + convertDependencies(source, out, context, "draft-07") + convertLegacyId(source, out, "$id", "Draft-07") + + convertReference(out, reference, context) + + return out +} + +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) + } +} + +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 { + 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) + } + } + + 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 { + InternalRecord.assignProperty(out, "enum", constSchema.enum) + } + } + + convertReference(out, reference, context) + + return out +} + +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 +} + +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 minContains = source.minContains + const maxContains = source.maxContains + if ((minContains !== undefined && minContains !== 1) || maxContains !== undefined) { + unsupported("minContains/maxContains", "Draft-04", "contains cardinality cannot be represented") + } + + 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 appendAllOf(out: JsonSchema, schema: JsonSchema): void { + if (Array.isArray(out.allOf)) out.allOf.push(schema) + else InternalRecord.assignProperty(out, "allOf", [schema]) +} + +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") + } +} + +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) + } +} diff --git a/.repos/effect/packages/effect/src/Latch.ts b/.repos/effect/packages/effect/src/Latch.ts new file mode 100644 index 000000000..62e73d736 --- /dev/null +++ b/.repos/effect/packages/effect/src/Latch.ts @@ -0,0 +1,382 @@ +/** + * Reusable synchronization primitives for coordinating fibers. A `Latch` is + * either open or closed: when it is closed, `await` and `whenOpen` suspend + * until the latch opens or the current waiters are released. The module + * includes effectful and synchronous constructors plus helpers to open, release, + * close, wait, and gate effects behind the latch. + * + * @since 4.0.0 + */ +import type * as Effect from "./Effect.ts" +import * as internal from "./internal/effect.ts" + +/** + * A reusable coordination primitive that lets fibers wait until they are + * released by the latch. + * + * **When to use** + * + * Use to coordinate fibers that must wait for an explicit open or release + * signal before continuing. + * + * **Details** + * + * A closed latch causes `await` and `whenOpen` to suspend. `open` opens the + * latch and releases current and future waiters, `release` releases only + * current waiters without opening it, and `close` makes future waiters suspend + * again. + * + * **Example** (Coordinating fibers with a latch) + * + * ```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() + * 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 + * @see {@link open} for releasing current and future waiters + * @see {@link release} for releasing only the current waiters + * + * @category models + * @since 4.0.0 + */ +export interface Latch { + /** + * Opens the latch, releasing all fibers waiting on it. + * + * **When to use** + * + * Use to let current and future waiters continue. + */ + readonly open: Effect.Effect + + /** + * Opens the latch synchronously, releasing all fibers waiting on it. + * + * **When to use** + * + * Use when synchronous code must open the latch immediately. + */ + openUnsafe(this: Latch): boolean + + /** + * Releases all fibers currently waiting on the latch without opening it. + * + * **When to use** + * + * Use to let current waiters continue while future waiters still suspend. + */ + readonly release: Effect.Effect + + /** + * Waits for the latch to be opened or released. + * + * **When to use** + * + * Use to suspend until the latch allows the current fiber to continue. + */ + readonly await: Effect.Effect + + /** + * Closes the latch so future waiters suspend again. + * + * **When to use** + * + * Use to re-enable waiting after a latch has been opened. + */ + readonly close: Effect.Effect + + /** + * Closes the latch synchronously so future waiters suspend again. + * + * **When to use** + * + * Use when synchronous code must close the latch immediately. + */ + closeUnsafe(this: Latch): boolean + + /** + * Runs the given effect only after the latch allows waiting fibers to + * continue. + * + * **When to use** + * + * Use to gate an effect behind the latch signal. + */ + whenOpen(self: Effect.Effect): Effect.Effect + + /** + * Checks whether the latch is currently open or closed. + * + * **When to use** + * + * Use to check the state of the latch without suspending or changing its state. + */ + isOpen(this: Latch): boolean +} + +/** + * Creates a `Latch` synchronously, outside of `Effect`. + * + * **When to use** + * + * Use when you need to allocate a `Latch` synchronously outside an Effect + * workflow. + * + * **Details** + * + * The latch starts closed by default; pass `true` to create it open. + * + * **Example** (Creating a latch unsafely) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, Latch } from "effect" + * + * const latch = Latch.makeUnsafe(false) + * const waiter = latch.await.pipe(Effect.as("opened")) + * + * const program = Effect.gen(function*() { + * const fiber = yield* Effect.forkChild(waiter) + * yield* latch.open + * return yield* Fiber.join(fiber) + * }) + * + * await Effect.runPromise(program) // => "opened" + * ``` + * + * @see {@link make} for creating a latch inside Effect code + * + * @category constructors + * @since 4.0.0 + */ +export const makeUnsafe: (open?: boolean | undefined) => Latch = internal.makeLatchUnsafe + +/** + * Creates a `Latch` inside `Effect`. + * + * **When to use** + * + * Use to create a latch for coordinating fibers inside Effect code. + * + * **Details** + * + * The latch starts closed by default; pass `true` to create it open. + * + * **Example** (Creating a latch) + * + * ```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 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 + * + * @category constructors + * @since 4.0.0 + */ +export const make: (open?: boolean | undefined) => Effect.Effect = internal.makeLatch + +/** + * Opens the latch and releases fibers waiting on it. + * + * **When to use** + * + * Use to open a latch and release all fibers that are waiting on it. + * + * **Details** + * + * The returned effect succeeds with `true` when this call changed the latch + * from closed to open, or `false` if it was already open. + * + * @see {@link openUnsafe} for a synchronous variant + * @see {@link release} to release waiting fibers without opening the latch + * + * @category combinators + * @since 4.0.0 + */ +export const open = (self: Latch): Effect.Effect => self.open + +/** + * Opens the latch synchronously and releases fibers waiting on it. + * + * **When to use** + * + * Use when you need synchronous code to open a latch immediately and release + * the fibers waiting on it. + * + * **Details** + * + * Returns `true` when this call changed the latch from closed to open, or + * `false` if it was already open. This unsafe variant performs the state + * change immediately instead of returning an `Effect`. + * + * @see {@link open} for the effectful variant + * @see {@link release} to release waiting fibers without opening the latch + * @see {@link closeUnsafe} for the synchronous inverse operation + * + * @category unsafe + * @since 4.0.0 + */ +export const openUnsafe = (self: Latch): boolean => self.openUnsafe() + +/** + * Releases the fibers currently waiting on a closed latch without opening it. + * + * **When to use** + * + * Use to let the fibers currently waiting on a latch proceed while keeping the + * latch closed for future waiters. + * + * **Details** + * + * The returned effect succeeds with `true` when release was requested while + * the latch was closed, or `false` if the latch was already open. Future + * waiters still suspend until the latch is opened or released again. + * + * @see {@link open} for opening the latch for current and future waiters + * + * @category combinators + * @since 4.0.0 + */ +export const release = (self: Latch): Effect.Effect => self.release + +const _await = (self: Latch): Effect.Effect => self.await + +export { + /** + * Waits for the latch to be opened. + * + * **When to use** + * + * Use to suspend the current fiber until the latch is opened or the current + * set of waiters is released. + * + * **Details** + * + * Awaiting an already open latch completes immediately. Awaiting a closed + * latch suspends until `open` or `release` resumes the waiters. + * + * **Gotchas** + * + * `release` can resume current waiters without opening the latch, so later + * waiters may still suspend. + * + * @see {@link open} for opening the latch for current and future waiters + * @see {@link release} for resuming current waiters without opening the latch + * @see {@link whenOpen} for waiting before running another effect + * + * @category getters + * @since 4.0.0 + */ + _await as await +} + +/** + * Closes the latch so future `await` and `whenOpen` calls suspend. + * + * **When to use** + * + * Use to re-enable waiting on a latch after it was opened, so later `await` + * and `whenOpen` calls suspend again. + * + * **Details** + * + * The returned effect succeeds with `true` when this call changed the latch + * from open to closed, or `false` if it was already closed. + * + * @see {@link closeUnsafe} for a synchronous variant + * @see {@link open} for opening the latch for current and future waiters + * + * @category combinators + * @since 4.0.0 + */ +export const close = (self: Latch): Effect.Effect => self.close + +/** + * Closes the latch synchronously so future `await` and `whenOpen` calls + * suspend. + * + * **When to use** + * + * Use to close a latch synchronously when the state change must happen outside + * an `Effect`. + * + * **Details** + * + * Returns `true` when this call changed the latch from open to closed, or + * `false` if it was already closed. This unsafe variant performs the state + * change immediately instead of returning an `Effect`. + * + * @see {@link close} for the effectful variant + * @see {@link openUnsafe} to synchronously open the latch and release waiting + * fibers + * + * @category unsafe + * @since 4.0.0 + */ +export const closeUnsafe = (self: Latch): boolean => self.closeUnsafe() + +/** + * Waits on the latch, then runs the provided effect. + * + * **When to use** + * + * Use to gate another effect so it starts only after the latch is opened or + * the current waiters are released. + * + * **Details** + * + * If the latch is open, the effect runs immediately. If it is closed, the + * returned effect suspends until the latch is opened or the current waiters are + * released. The provided effect's success, failure, and requirements are + * preserved. + * + * @see `await` for waiting without running another effect + * @see {@link open} for opening the latch for current and future waiters + * @see {@link release} for resuming current waiters without opening the latch + * + * @category combinators + * @since 4.0.0 + */ +export const whenOpen: { + (self: Latch): (effect: Effect.Effect) => Effect.Effect + (self: Latch, effect: Effect.Effect): Effect.Effect +} = ((...args: Array) => { + if (args.length === 1) { + const [self] = args + return (effect: Effect.Effect) => self.whenOpen(effect) + } + const [self, effect] = args + return self.whenOpen(effect) +}) as any + +/** + * Checks whether the latch is currently open or closed. + * + * **When to use** + * + * Use to check the state of the latch without suspending or changing its state. + * + * @category predicates + * @since 4.0.0 + */ +export const isOpen = (self: Latch): boolean => self.isOpen() diff --git a/.repos/effect/packages/effect/src/Layer.ts b/.repos/effect/packages/effect/src/Layer.ts new file mode 100644 index 000000000..ede9373c1 --- /dev/null +++ b/.repos/effect/packages/effect/src/Layer.ts @@ -0,0 +1,2798 @@ +/** + * Builds and wires services for Effect applications. + * + * A `Layer` describes how to acquire one or more services, which + * services are required to build them, and which errors can occur during + * acquisition. Layers can manage scoped resources, memoize shared services, + * combine with other layers, provide services to effects or streams, and attach + * error handling, tracing, or lifecycle hooks. + * + * @since 2.0.0 + */ +import type { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.ts" +import type * as Cause from "./Cause.ts" +import type * as Channel from "./Channel.ts" +import * as Context from "./Context.ts" +import * as Deferred from "./Deferred.ts" +import type { Effect } from "./Effect.ts" +import type * as Exit from "./Exit.ts" +import type { LazyArg } from "./Function.ts" +import { constant, constTrue, constUndefined, dual, identity } from "./Function.ts" +import * as core from "./internal/core.ts" +import * as internalEffect from "./internal/effect.ts" +import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" +import * as internalTracer from "./internal/tracer.ts" +import { type Pipeable, pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import { CurrentStackFrame } from "./References.ts" +import * as Scope from "./Scope.ts" +import type * as Stream from "./Stream.ts" +import * as Tracer from "./Tracer.ts" +import type * as Types from "./Types.ts" +import type * as Unify from "./Unify.ts" + +const TypeId = "~effect/Layer" + +/** + * A `Layer` describes how to build one or more services for dependency injection. + * + * **When to use** + * + * Use to model construction of application services for dependency injection, + * especially when services have dependencies, can fail during construction, or + * need scoped setup and release. + * + * **Details** + * + * A `Layer` represents `ROut` as the services this layer + * provides, `E` as the possible errors during layer construction, and `RIn` as + * the services this layer requires as dependencies. + * + * @category models + * @since 2.0.0 + */ +export interface Layer extends Variance, Pipeable { + /** @internal */ + build(memoMap: MemoMap, scope: Scope.Scope): Effect, E, RIn> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: LayerUnify + [Unify.ignoreSymbol]?: LayerUnifyIgnore +} + +/** + * Type-level hook that allows `Layer` values to participate in `Unify` + * inference. + * + * **Details** + * + * This is used by Effect's pipe and unification machinery to preserve the + * provided services, error, and requirements of a `Layer`. + * + * @category models + * @since 4.0.0 + */ +export interface LayerUnify { + Layer?: () => A[Unify.typeSymbol] extends Layer | infer _ ? Layer< + Success>, + Error>, + Services> + > + : never +} + +/** + * Type-level marker used by `Unify` for `Layer` types that should be ignored + * during unification. + * + * @category models + * @since 4.0.0 + */ +export interface LayerUnifyIgnore {} + +/** + * The variance interface for Layer type parameters. + * + * @category models + * @since 2.0.0 + */ +export interface Variance { + readonly [TypeId]: { + readonly _ROut: Types.Contravariant + readonly _E: Types.Covariant + readonly _RIn: Types.Covariant + } +} +/** + * A type-level constraint for working with any `Layer` type. + * + * **When to use** + * + * Use to constrain generic parameters or layer collections to any `Layer` + * value while preserving its provided, error, and required service types for + * inference. + * + * **Details** + * + * This interface is used to constrain generic types to `Layer` values without + * specifying exact type parameters. + * + * @see {@link Layer} for the concrete layer interface + * @see {@link Services} for extracting required services from a layer type + * @see {@link Error} for extracting construction errors from a layer type + * @see {@link Success} for extracting provided services from a layer type + * + * @category utility types + * @since 3.9.0 + */ +export interface Any { + readonly [TypeId]: { + readonly _ROut: any + readonly _E: any + readonly _RIn: any + } +} +/** + * Extracts the service requirements (`RIn`) from a `Layer` type. + * + * **When to use** + * + * Use to derive the dependency requirements of a generic or inferred `Layer` + * without restating its `RIn` type parameter. + * + * @see {@link Success} for extracting the services provided by the same `Layer` + * @see {@link Error} for extracting the construction failure type from the same `Layer` + * + * @category utility types + * @since 4.0.0 + */ +export type Services = T extends infer L + ? L extends Layer ? _RIn : never + : never +/** + * Extracts the error type (`E`) from a `Layer` type. + * + * **When to use** + * + * Use to derive a layer construction error type for helper types, wrappers, or + * APIs that preserve a layer failure channel. + * + * @see {@link Success} for extracting the services provided by the same `Layer` + * @see {@link Services} for extracting the dependency requirements of the same `Layer` + * + * @category utility types + * @since 2.0.0 + */ +export type Error = T extends Layer ? _E : never +/** + * Extracts the service output type (`ROut`) from a `Layer` type. + * + * **When to use** + * + * Use to derive the services provided by an existing or generic `Layer` without + * restating its `ROut` type parameter. + * + * @see {@link Error} for extracting the layer construction error type instead + * @see {@link Services} for extracting the layer input service requirements instead + * + * @category utility types + * @since 2.0.0 + */ +export type Success = T extends Layer ? _ROut : never + +const MemoMapTypeId = "~effect/Layer/MemoMap" + +/** + * A `MemoMap` is used to memoize layer construction and ensure sharing of + * layers. + * + * **Details** + * + * The `MemoMap` prevents duplicate construction of the same layer instance, + * enabling efficient resource sharing across layer dependencies. + * + * **Example** (Sharing layer construction with a memo map) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * // Create a custom MemoMap for manual layer building + * const program = Effect.gen(function*() { + * const memoMap = yield* Layer.makeMemoMap + * const scope = yield* Effect.scope + * + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * const context = yield* Layer.buildWithMemoMap(dbLayer, memoMap, scope) + * + * return Context.get(context, Database) + * }) + * + * const database = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(database.query("SELECT 1")) // => "result" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface MemoMap { + readonly [MemoMapTypeId]: typeof MemoMapTypeId + readonly get: ( + layer: Layer, + scope: Scope.Scope + ) => Effect, E, RIn> | undefined + readonly getOrElseMemoize: ( + layer: Layer, + scope: Scope.Scope, + build: (memoMap: MemoMap, scope: Scope.Scope) => Effect, E, RIn> + ) => Effect, E, RIn> +} + +type MemoMapEntry = { + observers: number + effect: Effect, any> + readonly finalizer: (exit: Exit.Exit) => Effect +} + +const memoMapReuse = ( + entry: MemoMapEntry, + scope: Scope.Scope +): Effect, E, RIn> => { + entry.observers++ + return internalEffect.andThen( + internalEffect.scopeAddFinalizerExit(scope, (exit) => entry.finalizer(exit)), + entry.effect + ) +} + +/** + * Returns `true` if the specified value is a `Layer`, `false` otherwise. + * + * **Example** (Checking whether a value is a layer) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * const notALayer = { someProperty: "value" } + * + * Layer.isLayer(dbLayer) // => true + * Layer.isLayer(notALayer) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isLayer = (u: unknown): u is Layer => hasProperty(u, TypeId) + +const LayerProto = { + [TypeId]: { + _ROut: identity, + _E: identity, + _RIn: identity + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +const fromBuildUnsafe = ( + build: ( + memoMap: MemoMap, + scope: Scope.Scope + ) => Effect, E, RIn> +): Layer => { + const self = Object.create(LayerProto) + self.build = build + return self +} + +/** + * Constructs a `Layer` from a function that uses a `MemoMap` and `Scope` to + * build the layer. + * + * **Details** + * + * The function receives a `MemoMap` for memoization and a `Scope` for resource management. + * A child scope is created, and if the build fails, the child scope is closed. + * + * **Example** (Constructing a layer from a build function) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * const databaseLayer = Layer.fromBuild(() => + * Effect.sync(() => + * Context.make(Database, { + * query: (sql: string) => Effect.succeed("result") + * }) + * ) + * ) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "result" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromBuild = ( + build: ( + memoMap: MemoMap, + scope: Scope.Scope + ) => Effect, E, RIn> +): Layer => + fromBuildUnsafe((memoMap: MemoMap, scope: Scope.Scope) => { + const layerScope = Scope.forkUnsafe(scope) + return internalEffect.onExit( + build(memoMap, layerScope), + (exit) => exit._tag === "Failure" ? Scope.close(layerScope, exit) : internalEffect.void + ) + }) + +/** + * Constructs a `Layer` from a function that uses a `MemoMap` and `Scope` to + * build the layer, with automatic memoization. + * + * **Details** + * + * This is similar to `fromBuild` but provides automatic memoization of the layer construction. + * The layer will be memoized based on the provided `MemoMap`. + * + * **Example** (Memoizing layer construction) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * const databaseLayer = Layer.fromBuildMemo(() => + * Effect.sync(() => + * Context.make(Database, { + * query: (sql: string) => Effect.succeed("result") + * }) + * ) + * ) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "result" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromBuildMemo = ( + build: ( + memoMap: MemoMap, + scope: Scope.Scope + ) => Effect, E, RIn> +): Layer => { + const self: Layer = fromBuild((memoMap, scope) => memoMap.getOrElseMemoize(self, scope, build)) + return self +} + +const memoMapBuild = ( + memoMap: MemoMapImpl, + layer: Layer, + scope: Scope.Scope, + build: (memoMap: MemoMap, scope: Scope.Scope) => Effect, E, RIn> +): Effect, E, RIn> => { + const layerScope = Scope.makeUnsafe() + const deferred = Deferred.makeUnsafe, E>() + const entry: MemoMapEntry = { + observers: 1, + effect: Deferred.await(deferred), + finalizer: (exit: Exit.Exit) => + internalEffect.suspend(() => { + entry.observers-- + if (entry.observers === 0) { + memoMap.map.delete(layer) + return Scope.close(layerScope, exit) + } + return internalEffect.void + }) + } + memoMap.map.set(layer, entry) + return internalEffect.scopeAddFinalizerExit(scope, entry.finalizer).pipe( + internalEffect.flatMap(() => build(memoMap, layerScope)), + internalEffect.onExit((exit) => { + entry.effect = exit + return Deferred.done(deferred, exit) + }) + ) +} + +class MemoMapImpl implements MemoMap { + get [MemoMapTypeId](): typeof MemoMapTypeId { + return MemoMapTypeId + } + + readonly parent: MemoMap | undefined + + constructor(parent?: MemoMap) { + this.parent = parent + } + + readonly map = new Map, MemoMapEntry>() + + get( + layer: Layer, + scope: Scope.Scope + ): Effect, E, RIn> | undefined { + const local = this.map.get(layer) + if (local) { + return memoMapReuse(local, scope) + } + return this.parent?.get(layer, scope) + } + + getOrElseMemoize( + layer: Layer, + scope: Scope.Scope, + build: (memoMap: MemoMap, scope: Scope.Scope) => Effect, E, RIn> + ): Effect, E, RIn> { + return internalEffect.suspend(() => { + const existing = this.get(layer, scope) + if (existing) { + return existing + } + return memoMapBuild(this, layer, scope, build) + }) + } +} + +/** + * Constructs a `MemoMap` synchronously so it can be used to build additional layers. + * + * **Example** (Creating a memo map unsafely) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * // Create a memo map for manual layer building + * const program = Effect.gen(function*() { + * const memoMap = Layer.makeMemoMapUnsafe() + * const scope = yield* Effect.scope + * + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * const context = yield* Layer.buildWithMemoMap(dbLayer, memoMap, scope) + * + * return Context.get(context, Database) + * }) + * + * const database = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(database.query("SELECT 1")) // => "result" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeMemoMapUnsafe = (): MemoMap => new MemoMapImpl() + +/** + * Constructs a child `MemoMap` synchronously, allowing it to reuse layers + * already memoized in the parent while isolating any new layer allocations to + * the child map. + * + * **When to use** + * + * Use to synchronously fork a memo map for manual layer building when child + * builds should see parent memoized layers without writing newly built layers + * back to the parent. + * + * @see {@link forkMemoMap} for allocating the child memo map inside `Effect` + * @see {@link makeMemoMapUnsafe} for creating a root memo map without a parent + * + * @category constructors + * @since 4.0.0 + */ +export const forkMemoMapUnsafe = (parent: MemoMap): MemoMap => new MemoMapImpl(parent) + +/** + * Constructs a `MemoMap` effectfully so it can be used to build additional layers. + * + * **Example** (Creating a memo map in an effect) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * // Create a memo map safely within an Effect + * const program = Effect.gen(function*() { + * const memoMap = yield* Layer.makeMemoMap + * const scope = yield* Effect.scope + * + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * const context = yield* Layer.buildWithMemoMap(dbLayer, memoMap, scope) + * + * return Context.get(context, Database) + * }) + * + * const database = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(database.query("SELECT 1")) // => "result" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const makeMemoMap: Effect = internalEffect.sync(makeMemoMapUnsafe) + +/** + * Constructs a child `MemoMap` effectfully, allowing it to reuse layers already + * memoized in the parent while isolating any new layer allocations to the child + * map. + * + * **When to use** + * + * Use when a layer build should inherit already memoized layers from an + * existing `MemoMap` while keeping newly memoized layers out of the parent map. + * + * @see {@link makeMemoMap} for creating a root memo map in an `Effect` + * @see {@link forkMemoMapUnsafe} for the synchronous constructor variant + * @see {@link buildWithMemoMap} for building layers with an explicit memo map + * + * @category constructors + * @since 4.0.0 + */ +export const forkMemoMap = (parent: MemoMap): Effect => internalEffect.sync(() => forkMemoMapUnsafe(parent)) + +/** + * Context service for the current `MemoMap` used in layer construction. + * + * **When to use** + * + * Use when building custom layer operations that need to access the current + * memoization map from the fiber context. + * + * **Details** + * + * This service wraps a `MemoMap` as a `Context.Service`, making it available + * for dependency injection during layer construction. + * + * @see {@link MemoMap} the memoization map type wrapped by this service + * + * @category services + * @since 3.13.0 + */ +export class CurrentMemoMap extends Context.Service()("effect/Layer/CurrentMemoMap") { + static forkOrCreate(self: Context.Context): MemoMap { + const current = Context.getOrUndefined(self, CurrentMemoMap) + return current ? forkMemoMapUnsafe(current) : makeMemoMapUnsafe() + } +} + +/** + * Builds a layer into an `Effect` value, using the specified `MemoMap` to memoize + * the layer construction. + * + * **Example** (Building layers with an explicit memo map) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Logger extends Context.Service Effect.Effect + * }>()("Logger") {} + * + * const logs: Array = [] + * + * // Build layers with explicit memoization control + * const program = Effect.gen(function*() { + * const memoMap = yield* Layer.makeMemoMap + * const scope = yield* Effect.scope + * + * // Build database layer with memoization + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * const dbContext = yield* Layer.buildWithMemoMap(dbLayer, memoMap, scope) + * + * // 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(() => logs.push(msg))) + * }) + * const loggerContext = yield* Layer.buildWithMemoMap( + * loggerLayer, + * memoMap, + * scope + * ) + * + * return { + * database: Context.get(dbContext, Database), + * logger: Context.get(loggerContext, Logger) + * } + * }) + * + * const services = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(services.logger.log("ready")) + * logs // => ["ready"] + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const buildWithMemoMap: { + ( + memoMap: MemoMap, + scope: Scope.Scope + ): (self: Layer) => Effect, E, RIn> + ( + self: Layer, + memoMap: MemoMap, + scope: Scope.Scope + ): Effect, E, RIn> +} = dual(3, ( + self: Layer, + memoMap: MemoMap, + scope: Scope.Scope +): Effect, E, RIn> => + internalEffect.provideService( + internalEffect.map(self.build(memoMap, scope), Context.add(CurrentMemoMap, memoMap)), + CurrentMemoMap, + memoMap + )) + +/** + * Builds a layer into a scoped value. + * + * **Example** (Building a layer into a context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * // Build a layer to get its services + * const program = Effect.gen(function*() { + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * + * // Build the layer into Context - automatically manages scope and memoization + * const context = yield* Layer.build(dbLayer) + * + * // Extract the specific service from the built layer + * const database = Context.get(context, Database) + * + * return yield* database.query("SELECT * FROM users") + * }) + * + * Effect.runSync(Effect.scoped(program)) // => "result" + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const build = ( + self: Layer +): Effect, E, RIn | Scope.Scope> => + core.withFiber((fiber) => + buildWithMemoMap( + self, + CurrentMemoMap.forkOrCreate(fiber.context), + Context.getUnsafe(fiber.context, Scope.Scope) + ) + ) + +/** + * Builds a layer using an explicit scope. + * + * **When to use** + * + * Use to control the lifetime of layer resources with a scope supplied by the + * caller. + * + * **Details** + * + * Resources created by the layer are released when the supplied scope is + * closed, unless a resource extends its own scope. + * + * **Example** (Building a layer with an explicit scope) + * + * ```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*() { + * logs.push("Initializing database...") + * yield* Scope.addFinalizer( + * scope, + * Effect.sync(() => logs.push("Database closed")) + * ) + * return { query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) } + * })) + * + * // Build with specific scope - resources tied to this scope + * const context = yield* Layer.buildWithScope(dbLayer, scope) + * const database = Context.get(context, Database) + * + * 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 + * @since 2.0.0 + */ +export const buildWithScope: { + (scope: Scope.Scope): (self: Layer) => Effect, E, RIn> + (self: Layer, scope: Scope.Scope): Effect, E, RIn> +} = dual(2, ( + self: Layer, + scope: Scope.Scope +): Effect, E, RIn> => + core.withFiber((fiber) => + buildWithMemoMap( + self, + CurrentMemoMap.forkOrCreate(fiber.context), + scope + ) + )) + +/** + * Constructs a layer that provides a single service from an already available + * value. + * + * **When to use** + * + * Use when you need a `Layer` that provides a service from an already + * constructed implementation without effectful acquisition. + * + * **Example** (Creating a layer from a service implementation) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("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 + * + * @category constructors + * @since 2.0.0 + */ +export const succeed: { + (service: Context.Key): (resource: S) => Layer + (service: Context.Key, resource: Types.NoInfer): Layer +} = function() { + if (arguments.length === 1) { + return (resource: any) => succeedContext(Context.make(arguments[0], resource)) + } + return succeedContext(Context.make(arguments[0], arguments[1])) +} as any + +/** + * Constructs a layer that provides all services in an already available + * `Context`. + * + * **When to use** + * + * Use when you need a `Layer` built from an existing `Context`, including when + * you need to provide multiple services at once. + * + * **Details** + * + * This is a more general version of `succeed` that allows you to provide + * multiple services at once through a `Context`. + * + * **Example** (Providing multiple services from a context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Logger 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(() => 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 + * + * @category constructors + * @since 2.0.0 + */ +export const succeedContext = (context: Context.Context): Layer => + fromBuildUnsafe(constant(internalEffect.succeed(context))) + +/** + * An empty layer that provides no services, cannot fail, has no requirements, + * and performs no construction or finalization work. + * + * **When to use** + * + * Use as the no-op branch when conditionally composing layers. + * + * **Example** (Disabling optional lifecycle work) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Option } from "effect" + * + * 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 + * + * @category constructors + * @since 2.0.0 + */ +export const empty: Layer = succeedContext(Context.empty()) + +/** + * Constructs a layer lazily that provides a single service. + * + * **When to use** + * + * Use when you need a `Layer` that provides one service whose value is created + * synchronously, but creation should be deferred until the layer is built. + * + * **Details** + * + * This is a lazy version of `succeed` where the service value is computed + * synchronously only when the layer is built. + * + * **Example** (Lazily providing a service) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * 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 + * + * @category constructors + * @since 2.0.0 + */ +export const sync: { + (service: Context.Key): (evaluate: LazyArg) => Layer + (service: Context.Key, evaluate: LazyArg>): Layer +} = function() { + if (arguments.length === 1) { + return (evaluate: LazyArg) => syncContext(() => Context.make(arguments[0], evaluate())) + } + return syncContext(() => Context.make(arguments[0], arguments[1]())) +} as any + +/** + * Constructs a layer lazily that provides all services in a `Context`. + * + * **When to use** + * + * Use when you need a `Layer` that creates multiple services synchronously but + * defers that work until the layer is built. + * + * **Details** + * + * This is a lazy version of `succeedContext` where the `Context` is computed + * synchronously only when the layer is built. + * + * **Example** (Lazily providing a context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * const layer = Layer.syncContext(() => + * Context.make(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 sync} for lazily providing a single service + * @see {@link succeedContext} for providing an already available context + * + * @category constructors + * @since 2.0.0 + */ +export const syncContext = (evaluate: LazyArg>): Layer => + fromBuildMemo(constant(internalEffect.sync(evaluate))) + +/** + * Constructs a layer from an effect that produces a single service. + * + * **When to use** + * + * Use when you need to construct a `Layer`-provided service with an `Effect`, + * dependencies, or scoped resource acquisition. + * + * **Details** + * + * This allows you to create a `Layer` from an `Effect` that produces a service. + * The `Effect` is executed in the scope of the layer, allowing for proper + * resource management. + * + * **Example** (Creating a layer from an effect) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * const layer = Layer.effect(Database, + * Effect.sync(() => ({ + * 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 + * @see {@link effectDiscard} for running construction work without providing services + * + * @category constructors + * @since 2.0.0 + */ +export const effect: { + (service: Context.Key): ( + effect: Effect + ) => Layer> + ( + service: Context.Key, + effect: Effect, E, R> + ): Layer> +} = function() { + if (arguments.length === 1) { + return (effect: any) => effectImpl(arguments[0], effect) + } + return effectImpl(arguments[0], arguments[1]) +} as any + +const effectImpl = ( + service: Context.Key, + effect: Effect +): Layer> => + effectContext(internalEffect.map(effect, (value) => Context.make(service, value))) + +/** + * Constructs a layer from an effect that produces all services in a `Context`. + * + * **When to use** + * + * Use when you need a `Layer` that effectfully constructs a `Context` with + * multiple services. + * + * **Details** + * + * This allows you to create a `Layer` from an effectful computation that + * returns multiple services. The `Effect` is executed in the scope of the + * layer. + * + * **Example** (Creating a layer from an effectful context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service< + * Database, + * { readonly query: (sql: string) => Effect.Effect } + * >()("Database") {} + * + * const layer = Layer.effectContext( + * Effect.succeed(Context.make(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 effect} for effectfully providing a single service + * + * @category constructors + * @since 2.0.0 + */ +export const effectContext = ( + effect: Effect, E, R> +): Layer> => fromBuildMemo((_, scope) => Scope.provide(effect, scope)) + +/** + * Constructs a layer from an effect, discarding its value and providing no + * services. + * + * **When to use** + * + * Use when layer construction should run an Effect for its side effects while providing no + * services. + * + * **Example** (Running an effect during layer construction) + * + * ```ts import.meta.vitest + * import { Effect, Layer } from "effect" + * + * const logs: Array = [] + * const initLayer = Layer.effectDiscard( + * Effect.sync(() => { + * 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 + * + * @category constructors + * @since 2.0.0 + */ +export const effectDiscard = (effect: Effect): Layer> => + effectContext(internalEffect.as(effect, Context.empty())) + +/** + * Constructs a layer lazily using the specified factory. + * + * **Details** + * + * The factory is evaluated only when the suspended layer is first built, and + * the result is memoized with normal layer sharing semantics. + * + * **Example** (Choosing a layer lazily) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Config extends Context.Service()("Config") {} + * + * const useProd = true + * + * const layer = Layer.suspend(() => + * useProd + * ? Layer.succeed(Config, "https://api.example.com") + * : Layer.succeed(Config, "http://localhost:3000") + * ) + * Effect.runSync(Effect.provide(Config, layer)) // => "https://api.example.com" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const suspend = (evaluate: LazyArg>): Layer => + fromBuildMemo((memoMap, scope) => internalEffect.suspend(() => evaluate().build(memoMap, scope))) + +/** + * Unwraps a `Layer` from an `Effect`, flattening the nested structure. + * + * **When to use** + * + * Use when you have an `Effect` that produces a `Layer` and you want to + * use that layer directly. + * + * **Details** + * + * The resulting Layer will have the combined error and dependency types from + * both the outer Effect and the inner Layer. + * + * **Example** (Unwrapping an effectful layer) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * const layerEffect = Effect.succeed( + * Layer.succeed(Database, { query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) }) + * ) + * + * const unwrappedLayer = Layer.unwrap(layerEffect) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, unwrappedLayer)) // => "result" + * ``` + * + * @category converting + * @since 4.0.0 + */ +export const unwrap = ( + self: Effect, E, R> +): Layer> => { + const service = Context.Service>("effect/Layer/unwrap") + return flatMap(effect(service)(self), Context.get(service)) +} + +const mergeAllEffect = , ...Array>]>( + layers: Layers, + memoMap: MemoMap, + scope: Scope.Scope +): Effect< + Context.Context<{ [k in keyof Layers]: Success }[number]>, + { [k in keyof Layers]: Error }[number], + { [k in keyof Layers]: Services }[number] +> => { + const parentScope = Scope.forkUnsafe(scope, "parallel") + return internalEffect.forEach(layers, (layer) => layer.build(memoMap, Scope.forkUnsafe(parentScope, "sequential")), { + concurrency: layers.length + }).pipe( + internalEffect.map((context) => Context.mergeAll(...(context as any))) + ) +} + +/** + * Combines all the provided layers concurrently, creating a new layer with + * merged input, error, and output types. + * + * **When to use** + * + * Use when you need to combine multiple independent layers. + * + * **Details** + * + * All layers are built concurrently, and their outputs are merged into a single layer. + * + * If multiple merged layers depend on the same layer value, that dependency is + * shared by default. Reuse a named layer value when you want services to share + * the same resource, such as one database pool. + * + * **Example** (Merging independent layers) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Logger extends Context.Service Effect.Effect + * }>()("Logger") {} + * + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * const logs: Array = [] + * const loggerLayer = Layer.succeed(Logger, { + * 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 + * + * @category zipping + * @since 2.0.0 + */ +export const mergeAll = , ...Array>]>( + ...layers: Layers +): Layer< + Success, + Error, + Services +> => fromBuild((memoMap, scope) => mergeAllEffect(layers, memoMap, scope)) + +/** + * Merges this layer with another layer concurrently, producing a new layer with + * combined input, error, and output types. + * + * **When to use** + * + * Use to combine an existing `Layer` with another `Layer` or an array of + * layers while preserving pipeline style. + * + * **Details** + * + * This is a binary version of `mergeAll` that merges exactly two layers or one + * layer with an array of layers. The layers are built concurrently and their + * outputs are combined. + * + * **Example** (Merging two layers) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Logger extends Context.Service Effect.Effect + * }>()("Logger") {} + * + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) + * }) + * const loggerLayer = Layer.succeed(Logger, { + * 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 + * + * @category zipping + * @since 2.0.0 + */ +export const merge: { + ( + that: Layer + ): (self: Layer) => Layer + ]>( + that: Layers + ): ( + self: Layer + ) => Layer< + A | Success, + E | Error, + | Services + | R + > + ( + self: Layer, + that: Layer + ): Layer + ]>( + self: Layer, + that: Layers + ): Layer< + A | Success, + E | Error, + | Services + | R + > +} = dual(2, ( + self: Layer, + that: Layer | ReadonlyArray> +) => mergeAll(self, ...(Array.isArray(that) ? that : [that]))) + +const provideWith = ( + self: Layer, + that: Layer | ReadonlyArray>, + f: ( + selfContext: Context.Context, + thatContext: Context.Context + ) => Context.Context +) => + fromBuild((memoMap, scope) => + internalEffect.flatMap( + Array.isArray(that) + ? mergeAllEffect(that as NonEmptyArray>, memoMap, scope) + : (that as Layer).build(memoMap, scope), + (context) => + self.build(memoMap, scope).pipe( + internalEffect.provideContext(context), + internalEffect.map((merged) => f(merged, context)) + ) + ) + ) + +/** + * Feeds the output services of the dependency layer into the requirements of + * this layer, returning a layer that only provides the services from this layer. + * + * **When to use** + * + * Use when you need to hide an implementation dependency layer from callers. + * + * **Details** + * + * In `serviceLayer.pipe(Layer.provide(dependencyLayer))`, the dependency layer is + * built first and is used to satisfy the requirements of `serviceLayer`. + * + * **Example** (Providing layer dependencies) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class UserService extends Context.Service Effect.Effect<{ + * id: string + * name: string + * }> + * }>()("UserService") {} + * + * class Logger extends Context.Service Effect.Effect + * }>()("Logger") {} + * + * // Create dependency layers + * const databaseLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`)) + * }) + * + * const logs: Array = [] + * const loggerLayer = Layer.succeed(Logger, { + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`))) + * }) + * + * // UserService depends on Database and Logger + * const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() { + * const database = yield* Database + * const logger = yield* Logger + * + * return { + * getUser: Effect.fn("UserService.getUser")(function*(id: string) { + * yield* logger.log(`Looking up user ${id}`) + * const result = yield* database.query( + * `SELECT * FROM users WHERE id = ${id}` + * ) + * return { id, name: result } + * }) + * } + * })) + * + * // Provide dependencies to UserService layer + * const userServiceWithDependencies = userServiceLayer.pipe( + * Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer)) + * ) + * + * // Now UserService layer has no dependencies + * const program = Effect.gen(function*() { + * const userService = yield* UserService + * return yield* userService.getUser("123") + * }).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 + * + * @category providing services + * @since 2.0.0 + */ +export const provide: { + ( + that: Layer + ): (self: Layer) => Layer> + ]>( + that: Layers + ): ( + self: Layer + ) => Layer< + A, + E | Error, + | Services + | Exclude> + > + ( + self: Layer, + that: Layer + ): Layer> + ]>( + self: Layer, + that: Layers + ): Layer< + A, + E | Error, + | Services + | Exclude> + > +} = dual(2, ( + self: Layer, + that: Layer | ReadonlyArray> +) => provideWith(self, that, identity)) + +/** + * Feeds the output services of the dependency layer into the requirements of + * this layer, returning a layer that provides both sets of services. + * + * **When to use** + * + * Use when you need to compose `Layer`s while keeping both the constructed + * service and the dependency used to build it available. + * + * **Details** + * + * Prefer {@link provide} when the dependency should stay private. + * + * **Example** (Providing dependencies while retaining services) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Logger extends Context.Service Effect.Effect + * }>()("Logger") {} + * + * class UserService extends Context.Service Effect.Effect<{ + * id: string + * name: string + * }> + * }>()("UserService") {} + * + * // Create dependency layers + * const databaseLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`)) + * }) + * + * const logs: Array = [] + * const loggerLayer = Layer.succeed(Logger, { + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`))) + * }) + * + * // UserService depends on Database and Logger + * const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() { + * const database = yield* Database + * const logger = yield* Logger + * + * return { + * getUser: Effect.fn("UserService.getUser")(function*(id: string) { + * yield* logger.log(`Looking up user ${id}`) + * const result = yield* database.query( + * `SELECT * FROM users WHERE id = ${id}` + * ) + * return { id, name: result } + * }) + * } + * })) + * + * // Provide dependencies and merge all services together + * const allServicesLayer = userServiceLayer.pipe( + * Layer.provideMerge(Layer.mergeAll(databaseLayer, loggerLayer)) + * ) + * + * // Now the resulting layer provides UserService, Database, AND Logger + * const program = Effect.gen(function*() { + * const userService = yield* UserService + * const logger = yield* Logger // Still available! + * const database = yield* Database // Still available! + * + * const user = yield* userService.getUser("123") + * yield* logger.log(`Found user: ${user.name}`) + * + * return user + * }).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 + * + * @category providing services + * @since 2.0.0 + */ +export const provideMerge: { + ( + that: Layer + ): (self: Layer) => Layer> + ]>( + that: Layers + ): ( + self: Layer + ) => Layer< + A | Success, + E | Error, + | Services + | Exclude> + > + ( + self: Layer, + that: Layer + ): Layer> + ]>( + self: Layer, + that: Layers + ): Layer< + A | Success, + E | Error, + | Services + | Exclude> + > +} = dual(2, ( + self: Layer, + that: Layer | ReadonlyArray> +) => + provideWith( + self, + that, + (self, that) => Context.merge(that, self) + )) + +/** + * Constructs a layer dynamically based on the output of this layer. + * + * **Example** (Creating services from layer output) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Config extends Context.Service()("Config") {} + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Logger extends Context.Service Effect.Effect + * }>()("Logger") {} + * + * const logs: Array = [] + * + * // Base config layer + * const configLayer = Layer.succeed(Config, { + * dbUrl: "postgres://localhost:5432/mydb", + * logLevel: "debug" + * }) + * + * // Dynamically create services based on config + * const dynamicServiceLayer = configLayer.pipe( + * Layer.flatMap((context) => { + * const config = Context.get(context, Config) + * + * // Create database layer based on config + * const dbLayer = Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => + * Effect.succeed( + * `Querying ${config.dbUrl}: ${sql}` + * )) + * }) + * + * // Create logger layer based on config + * const loggerLayer = Layer.succeed(Logger, { + * log: Effect.fn("Logger.log")((msg: string) => + * config.logLevel === "debug" + * ? Effect.sync(() => logs.push(`[DEBUG] ${msg}`)) + * : Effect.sync(() => logs.push(msg)) + * ) + * }) + * + * // Return combined layer + * return Layer.mergeAll(dbLayer, loggerLayer) + * }) + * ) + * + * // Use the dynamic services + * const program = Effect.gen(function*() { + * const database = yield* Database + * const logger = yield* Logger + * + * yield* logger.log("Starting database query") + * const result = yield* database.query("SELECT * FROM users") + * + * return result + * }).pipe( + * Effect.provide(dynamicServiceLayer) + * ) + * Effect.runSync(program) // => "Querying postgres://localhost:5432/mydb: SELECT * FROM users" + * logs // => ["[DEBUG] Starting database query"] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + ( + f: (context: Context.Context) => Layer + ): (self: Layer) => Layer + ( + self: Layer, + f: (context: Context.Context) => Layer + ): Layer +} = dual(2, ( + self: Layer, + f: (context: Context.Context) => Layer +): Layer => + fromBuild((memoMap, scope) => + internalEffect.flatMap( + self.build(memoMap, scope), + (context) => f(context).build(memoMap, scope) + ) + )) + +/** + * Performs the specified effect if this layer succeeds. + * + * **When to use** + * + * Use to run an effectful observation after a layer has been built + * successfully, such as logging or metrics, without changing the services the + * layer provides. + * + * **Details** + * + * The callback receives the services produced by this layer. Its result is + * discarded, and the original layer output is preserved. + * + * @see {@link tapError} for running an effect when layer construction fails with a typed error + * @see {@link tapCause} for running an effect when layer construction fails with any cause + * + * @category sequencing + * @since 2.0.0 + */ +export const tap: { + ( + f: (context: Context.Context) => Effect + ): (self: Layer) => Layer> + ( + self: Layer, + f: (context: Context.Context) => Effect + ): Layer> +} = dual(2, ( + self: Layer, + f: (context: Context.Context) => Effect +): Layer> => + fromBuild((memoMap, scope) => + internalEffect.flatMap( + self.build(memoMap, scope), + (context) => Scope.provide(internalEffect.as(f(context as Context.Context), context), scope) + ) + )) + +/** + * Performs the specified effect if this layer fails. + * + * **When to use** + * + * Use to run logging, metrics, or other effects when layer construction fails + * while preserving the original typed error. + * + * **Details** + * + * The callback receives the typed error. If the callback succeeds, the layer + * still fails with the original error; if the callback fails, that failure is + * added to the layer's error type. + * + * @see {@link tap} for running an effect when layer construction succeeds + * @see {@link tapCause} for inspecting the full failure cause, including defects and interruption + * + * @category sequencing + * @since 2.0.0 + */ +export const tapError: { + ( + f: (e: XE) => Effect + ): (self: Layer) => Layer> + ( + self: Layer, + f: (e: XE) => Effect + ): Layer> +} = dual(2, ( + self: Layer, + f: (e: XE) => Effect +): Layer> => + fromBuild((memoMap, scope) => + internalEffect.catch_( + self.build(memoMap, scope), + (error) => Scope.provide(internalEffect.andThen(f(error as XE), internalEffect.fail(error)), scope) + ) + )) + +/** + * Performs the specified effect when this layer fails with any cause. + * + * **When to use** + * + * Use to run diagnostics or reporting when layer construction fails and the + * full `Cause` is needed. + * + * **Details** + * + * The callback receives the layer's `Cause`, so it can inspect typed errors, + * defects, and interruption information. If the callback succeeds, the layer + * fails again with the original cause; if the callback fails, that failure is + * added to the layer's error type. + * + * @see {@link tapError} for observing only typed layer construction errors + * @see {@link catchCause} for recovering from a layer construction failure by switching to another layer + * + * @category sequencing + * @since 4.0.0 + */ +export const tapCause: { + ( + f: (cause: Cause.Cause) => Effect + ): (self: Layer) => Layer> + ( + self: Layer, + f: (cause: Cause.Cause) => Effect + ): Layer> +} = dual(2, ( + self: Layer, + 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) + ) + )) + +/** + * Converts layer construction failures into defects, removing them from the + * layer's error type. + * + * **Details** + * + * Use this only when failures should be treated as unrecoverable defects rather + * than typed errors that callers can handle. + * + * **Example** (Converting layer failures to defects) + * + * ```ts import.meta.vitest + * import { Context, Data, Effect, Exit, Layer } from "effect" + * + * class DatabaseError extends Data.TaggedError("DatabaseError")<{ + * message: string + * }> {} + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * // Layer that can fail during construction + * 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) + * + * // Now the layer type is Layer - no error in type + * const program = Effect.gen(function*() { + * const database = yield* Database + * return yield* database.query("SELECT * FROM users") + * }).pipe( + * Effect.provide(reliableDatabaseLayer) + * ) + * + * Effect.runSync(Effect.exit(program)) // => Exit.die(error) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const orDie = (self: Layer): Layer => + fromBuildUnsafe((memoMap, scope) => internalEffect.orDie(self.build(memoMap, scope))) + +const catch_: { + ( + onError: (error: E) => Layer + ): (self: Layer) => Layer + ( + self: Layer, + onError: (error: E) => Layer + ): Layer +} = dual(2, ( + self: Layer, + onError: (error: E) => Layer +): Layer => + fromBuildUnsafe((memoMap, scope) => + internalEffect.catch_( + self.build(memoMap, scope), + (e) => onError(e).build(memoMap, scope) + ) as any + )) + +export { + /** + * Recovers from all typed errors by switching to another layer. + * + * **When to use** + * + * Use when every typed `Layer` construction error should use the same + * recovery path. + * + * @see {@link catchTag} for recovering from specific tagged errors + * @see {@link catchCause} for recovering with access to the full cause + * + * @category error handling + * @since 4.0.0 + */ + catch_ as catch +} + +/** + * Recovers from specific tagged errors. + * + * **When to use** + * + * Use when only some tagged `Layer` construction errors should be recovered. + * + * **Example** (Recovering from tagged layer errors) + * + * ```ts import.meta.vitest + * import { Context, Data, Effect, Layer } from "effect" + * + * class ConfigError extends Data.TaggedError("ConfigError") {} + * + * class Config extends Context.Service()("Config") {} + * + * const configLayer = Layer.effect(Config, Effect.fail(new ConfigError())) + * + * const fallbackLayer = Layer.succeed(Config, { apiUrl: "http://localhost" }) + * + * 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 + * + * @category error handling + * @since 4.0.0 + */ +export const catchTag: { + | NonEmptyReadonlyArray>, E, RIn2, E2, ROut2>( + k: K, + f: ( + e: Types.ExtractTag, K extends NonEmptyReadonlyArray ? K[number] : K> + ) => Layer + ): ( + self: Layer + ) => Layer< + ROut & ROut2, + E2 | Types.ExcludeTag ? K[number] : K>, + RIn2 | RIn + > + < + RIn, + E, + ROut, + const K extends Types.Tags | NonEmptyReadonlyArray>, + RIn2, + E2, + ROut2 + >( + self: Layer, + k: K, + f: (e: Types.ExtractTag ? K[number] : K>) => Layer + ): Layer< + ROut & ROut2, + E2 | Types.ExcludeTag ? K[number] : K>, + RIn | RIn2 + > +} = dual(3, < + RIn, + E, + ROut, + const K extends Types.Tags | NonEmptyReadonlyArray>, + RIn2, + E2, + ROut2 +>( + self: Layer, + k: K, + f: (e: Types.ExtractTag ? K[number] : K>) => Layer +): Layer ? K[number] : K>, RIn | RIn2> => + fromBuildUnsafe((memoMap, scope) => + internalEffect.catchTag( + self.build(memoMap, scope), + k, + (error) => f(error).build(memoMap, scope) + ) as any + )) + +/** + * Recovers from any failure cause by switching to another layer. + * + * **When to use** + * + * Use when you need `Layer` recovery to inspect more than the typed error, + * such as defects or interruption information. + * + * **Details** + * + * The handler receives the full `Cause` of the failed layer, including typed + * errors, unexpected defects, and interruption information, and returns the + * fallback layer to build instead. Finalizers for resources acquired by the + * failed layer are still run before the fallback layer is acquired. + * + * **Example** (Recovering from layer failures by cause) + * + * ```ts import.meta.vitest + * import { Context, Data, Effect, Layer } from "effect" + * + * class DatabaseError extends Data.TaggedError("DatabaseError")<{ + * message: string + * }> {} + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * const primaryDatabaseLayer = Layer.effect(Database, + * Effect.fail(new DatabaseError({ message: "Primary DB unreachable" })) + * ) + * + * const databaseWithFallback = primaryDatabaseLayer.pipe( + * Layer.catchCause(() => { + * return Layer.succeed(Database, { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Memory: ${sql}`)) + * }) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const database = yield* Database + * return yield* database.query("SELECT * FROM users") + * }).pipe( + * Effect.provide(databaseWithFallback) + * ) + * + * await Effect.runPromise(program) // => "Memory: SELECT * FROM users" + * ``` + * + * @see {@link catchTag} for recovering from specific tagged errors + * + * @category error handling + * @since 4.0.0 + */ +export const catchCause: { + ( + onError: (cause: Cause.Cause) => Layer + ): (self: Layer) => Layer + ( + self: Layer, + onError: (cause: Cause.Cause) => Layer + ): Layer +} = dual(2, ( + self: Layer, + onError: (cause: Cause.Cause) => Layer +): Layer => + fromBuildUnsafe((memoMap, scope) => + internalEffect.catchCause( + self.build(memoMap, scope), + (cause) => onError(cause).build(memoMap, scope) + ) as any + )) + +/** + * Updates a service in the context with a new implementation. + * + * **When to use** + * + * Use to adapt or extend a service's behavior during the creation of a + * layer. + * + * **Details** + * + * This function modifies the existing implementation of a service in the + * context. It retrieves the current service, applies the provided + * transformation function `f`, and replaces the old service with the + * transformed one. + * + * @category providing services + * @since 3.13.0 + */ +export const updateService: { + ( + service: Context.Key, + f: (a: Types.NoInfer) => A + ): (layer: Layer) => Layer + ( + layer: Layer, + service: Context.Key, + f: (a: Types.NoInfer) => A + ): Layer +} = dual( + 3, + ( + layer: Layer, + service: Context.Key, + f: (a: Types.NoInfer) => A + ): Layer => provide(layer, effect(service, internalEffect.map(service, f))) +) + +/** + * Creates a fresh version of this layer that will not be shared. + * + * **When to use** + * + * Use when you need two parts of an application to receive separate instances + * of a resource, such as two independent client sessions. + * + * **Gotchas** + * + * Do not use it just to work around confusing composition. By default, sharing + * the same layer value is usually the desired behavior. + * + * **Example** (Creating non-shared layer instances) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Ref } from "effect" + * + * class Counter extends Context.Service()("Counter") {} + * + * class Left extends Context.Service()("Left") {} + * + * class Right extends Context.Service()("Right") {} + * + * const leftLayer = Layer.effect(Left, Effect.gen(function*() { + * const counter = yield* Counter + * return { counterId: counter.id } + * })) + * + * const rightLayer = Layer.effect(Right, Effect.gen(function*() { + * const counter = yield* Counter + * return { counterId: counter.id } + * })) + * + * const compareIds = Effect.gen(function*() { + * const left = yield* Left + * const right = yield* Right + * return left.counterId === right.counterId + * }) + * + * const program = Effect.gen(function*() { + * const nextId = yield* Ref.make(0) + * + * const counterLayer = Layer.effect(Counter, Effect.gen(function*() { + * const id = yield* Ref.updateAndGet(nextId, (n) => n + 1) + * return { id } + * })) + * + * const shared = Layer.merge( + * Layer.provide(leftLayer, counterLayer), + * Layer.provide(rightLayer, counterLayer) + * ) + * + * const sharedResult = yield* Effect.provide(compareIds, shared) + * + * const freshCounterLayer = Layer.fresh(counterLayer) + * const fresh = Layer.merge( + * Layer.provide(leftLayer, freshCounterLayer), + * Layer.provide(rightLayer, freshCounterLayer) + * ) + * + * const freshResult = yield* Effect.provide(compareIds, fresh) + * + * return { shared: sharedResult, fresh: freshResult } + * }) + * + * await Effect.runPromise(program) // => { shared: true, fresh: false } + * ``` + * + * @category layers + * @since 2.0.0 + */ +export const fresh = (self: Layer): Layer => + fromBuildUnsafe((_, scope) => self.build(makeMemoMapUnsafe(), scope)) + +/** + * Builds this layer and keeps it alive until the returned effect is interrupted. + * + * **When to use** + * + * Use when you model your entire application as a layer, such as an HTTP + * server. + * + * **Details** + * + * When the returned effect is interrupted, the layer scope is closed and all + * finalizers registered during layer acquisition are run. + * + * **Example** (Launching an application layer) + * + * ```ts import.meta.vitest + * import { Context, Deferred, Effect, Fiber, Layer, Ref } from "effect" + * + * class HttpServer extends Context.Service()("HttpServer") {} + * + * const program = Effect.gen(function*() { + * const events = yield* Ref.make>([]) + * const started = yield* Deferred.make() + * + * 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 fiber = yield* Effect.forkChild(Layer.launch(serverLayer)) + * yield* Deferred.await(started) + * yield* Fiber.interrupt(fiber) + * return yield* Ref.get(events) + * }) + * + * await Effect.runPromise(program) // => ["Starting HTTP server..."] + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const launch = (self: Layer): Effect => + internalEffect.scoped(internalEffect.andThen(build(self), internalEffect.never)) + +/** + * A utility type for creating partial mocks of services in testing. + * + * **When to use** + * + * Use to type partial test service implementations where only exercised + * effectful members are stubbed. + * + * **Details** + * + * This type makes `Effect`, `Stream`, and `Channel` values and functions + * returning them optional, while keeping non-effectful properties required. + * This allows you to provide only the methods you need to test while leaving + * others unimplemented. + * + * @see {@link mock} for creating a mock layer from a partial service implementation + * + * @category testing + * @since 3.17.0 + */ +export type PartialEffectful = Types.Simplify< + & { + [K in keyof A as A[K] extends AnyEffectOrStream ? K : never]?: A[K] + } + & { + [K in keyof A as A[K] extends AnyEffectOrStream ? never : K]: A[K] + } +> + +type AnyEffectOrStream = + | Effect + | Stream.Stream + | Channel.Channel + | ((...args: any) => Effect) + | ((...args: any) => Stream.Stream) + | ((...args: any) => Channel.Channel) + +/** + * Creates a mock layer for testing purposes. You can provide a partial + * implementation of the service. Any missing members that are `Effect`s, + * `Stream`s, `Channel`s, or functions returning them will fail with an + * unimplemented defect when used. + * + * **Details** + * + * Missing members are represented by a value that can be used as an `Effect`, + * `Stream`, `Channel`, or as a function returning an `Effect`. This lets the + * mock preserve the shape of common service methods while still failing loudly + * when an unimplemented member is exercised. + * + * **Example** (Mocking services for tests) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class UserService extends Context.Service Effect.Effect<{ id: string; name: string }, Error> + * readonly deleteUser: (id: string) => Effect.Effect + * readonly updateUser: ( + * id: string, + * data: object + * ) => Effect.Effect<{ id: string; name: string }, Error> + * }>()("UserService") {} + * + * // Create a partial mock - only implement what you need for testing + * const testUserLayer = Layer.mock(UserService, { + * config: { apiUrl: "https://test-api.com" }, // Required - non-Effect property + * getUser: (id: string) => Effect.succeed({ id, name: "Test User" }) // Mock implementation + * // deleteUser and updateUser are omitted - will throw UnimplementedError if called + * }) + * + * // Use in tests + * const testProgram = Effect.gen(function*() { + * const userService = yield* UserService + * + * // This works - we provided an implementation + * const user = yield* userService.getUser("123") + * + * // This would throw - we didn't implement deleteUser + * // yield* userService.deleteUser("123") // UnimplementedError + * + * return user.name + * }).pipe( + * Effect.provide(testUserLayer) + * ) + * Effect.runSync(testProgram) // => "Test User" + * ``` + * + * @category testing + * @since 3.17.0 + */ +export const mock: { + (service: Context.Key): (implementation: PartialEffectful) => Layer + (service: Context.Key, implementation: Types.NoInfer>): Layer +} = function() { + if (arguments.length === 1) { + return (implementation: any) => mockImpl(arguments[0], implementation) + } + return mockImpl(arguments[0], arguments[1]) +} as any + +const mockImpl = (service: Context.Key, implementation: PartialEffectful): Layer => + succeed(service)( + new Proxy({ ...implementation as object } as S, { + get(target, prop, _receiver) { + if (prop in target) { + return target[prop as keyof S] + } + const prevLimit = getStackTraceLimit() + setStackTraceLimit(2) + const error = new Error(`${service.key}: Unimplemented method "${prop.toString()}"`) + setStackTraceLimit(prevLimit) + error.name = "UnimplementedError" + return makeUnimplemented(error) + }, + has: constTrue + }) + ) + +const makeUnimplemented = (error: globalThis.Error) => { + const dead = Object.assign(internalEffect.die(error), { + [StreamTypeId]: StreamTypeId, + channel: { + [ChannelTypeId]: ChannelTypeId, + transform: () => internalEffect.succeed(dead), + pipe() { + return pipeArguments(this, arguments) + } + }, + [ChannelTypeId]: ChannelTypeId, + transform: () => internalEffect.succeed(dead) + }) + function unimplemented() { + return dead + } + // @effect-diagnostics-next-line floatingEffect:off + Object.assign(unimplemented, dead) + Object.setPrototypeOf(unimplemented, Object.getPrototypeOf(dead)) + return unimplemented +} + +const StreamTypeId: Stream.TypeId = "~effect/Stream" +const ChannelTypeId: Channel.TypeId = "~effect/Channel" + +// ----------------------------------------------------------------------------- +// Type constraints +// ----------------------------------------------------------------------------- + +/** + * Ensures that a layer's success type extends a given type `ROut`. + * + * **Details** + * + * This function provides compile-time type checking to ensure that the success + * value of a layer conforms to a specific type constraint. + * + * **Example** (Constraining layer success types) + * + * ```ts import.meta.vitest + * import { Context, Layer } from "effect" + * + * 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(numberLayer) + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const satisfiesSuccessType = + () => (layer: Layer): Layer => layer + +/** + * Ensures that a layer's error type extends a given type `E`. + * + * **Details** + * + * This function provides compile-time type checking to ensure that the error + * type of a layer conforms to a specific type constraint. + * + * **Example** (Constraining layer error types) + * + * ```ts import.meta.vitest + * import { Effect, Layer } from "effect" + * + * 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) + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const satisfiesErrorType = + () => (layer: Layer): Layer => layer + +/** + * Ensures that a layer's requirements type extends a given type `R`. + * + * **Details** + * + * This function provides compile-time type checking to ensure that the + * requirements type of a layer conforms to a specific type constraint. + * + * **Example** (Constraining layer service requirements) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * 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(numberLayer) + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const satisfiesServicesType = + () => (layer: Layer): Layer => layer + +// ----------------------------------------------------------------------------- +// Tracing +// ----------------------------------------------------------------------------- + +/** + * Represents options that can be used to control the behavior of spans created + * for layers. + * + * **When to use** + * + * Use to configure tracing metadata, stack trace capture, and `onEnd` + * finalization for spans created by `Layer.span` and `Layer.withSpan` during + * layer construction. + * + * **Details** + * + * Extends `Tracer.SpanOptions` with `onEnd`, which runs when the layer span + * ends as the layer scope closes. + * + * @see {@link span} for creating a layer span + * @see {@link withSpan} for wrapping layer construction in a span + * + * @category options + * @since 4.0.0 + */ +export interface SpanOptions extends Tracer.SpanOptions { + /** + * Runs when the span associated with the layer ends, which happens when the + * layer scope is closed. + */ + readonly onEnd?: + | ((span: Tracer.Span, exit: Exit.Exit) => Effect) + | undefined +} + +/** + * Constructs a new `Layer` which creates a span and registers it as the current + * parent span. + * + * **Details** + * + * This allows you to create a traced scope for layer construction, making all + * operations within the layer constructor part of the same trace span. The span + * is automatically ended when the layer's scope is closed. If `onEnd` is + * provided, it receives the span and the layer scope's exit value when the span + * ends. + * + * **Example** (Tracing layer construction with a span) + * + * ```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 + * logs.push("Connecting to database") + * logs.push("Database connected") + * + * const parentSpan = yield* Effect.currentParentSpan + * 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", { + * onEnd: (span, exit) => + * 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 + * @since 2.0.0 + */ +export const span = ( + name: string, + options?: SpanOptions +): Layer => { + options = internalTracer.addSpanStackTrace(options) + return effect( + Tracer.ParentSpan, + options?.onEnd + ? internalEffect.tap( + internalEffect.makeSpanScoped(name, options), + (span) => internalEffect.addFinalizer((exit) => options.onEnd!(span, exit)) + ) + : internalEffect.makeSpanScoped(name, options) + ) +} + +/** + * Constructs a layer that provides an existing span as the current parent span. + * + * **Details** + * + * The supplied span is made available through `Tracer.ParentSpan` for layers + * that are built with this layer. This API does not create, end, or close the + * span; the caller remains responsible for the span's lifetime. + * + * **Example** (Referencing an existing parent span) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Tracer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * // Create a layer that uses an existing span as parent + * const databaseLayer = Layer.effect( + * Database, + * Effect.gen(function*() { + * const parentSpan = yield* Effect.currentParentSpan + * + * return { + * spanId: parentSpan.spanId, + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) + * } + * }) + * ).pipe(Layer.provide(Layer.parentSpan(Tracer.externalSpan({ + * 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 + * @since 2.0.0 + */ +export const parentSpan = (span: Tracer.AnySpan): Layer => + succeedContext(Tracer.ParentSpan.context(span)) + +/** + * Wraps a `Layer` with a new tracing span, making all operations in the layer + * constructor part of the named trace span. + * + * **Details** + * + * This creates a new span for the layer's construction and execution. The span + * is automatically ended when the layer's scope is closed. This is useful for + * tracking the lifecycle and performance of layer initialization. + * + * **Example** (Wrapping a layer with a span) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Logger extends Context.Service Effect.Effect + * }>()("Logger") {} + * + * const logs: Array = [] + * + * // Create layers with tracing + * const databaseLayer = Layer.effect(Database, Effect.gen(function*() { + * return { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) + * } + * })).pipe(Layer.withSpan("database-initialization", { + * attributes: { dbType: "postgres" } + * })) + * + * const loggerLayer = Layer.succeed(Logger, { + * 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(() => logs.push(`Application initialization completed: ${exit._tag}`)) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const database = yield* Database + * const logger = yield* Logger + * + * 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 + * @since 2.0.0 + */ +export const withSpan: { + ( + name: string, + options?: SpanOptions + ): ( + self: Layer + ) => Layer> + ( + self: Layer, + name: string, + options?: SpanOptions + ): Layer> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const options = internalTracer.addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) as SpanOptions + if (dataFirst) { + const self = arguments[0] + return unwrap( + internalEffect.map( + options?.onEnd !== undefined + ? internalEffect.tap( + internalEffect.makeSpanScoped(name, options), + (span) => internalEffect.addFinalizer((exit) => options.onEnd!(span, exit)) + ) + : internalEffect.makeSpanScoped(name, options), + (span) => withParentSpan(self, span) + ) + ) + } + return (self: Layer) => + unwrap( + internalEffect.map( + options?.onEnd !== undefined + ? internalEffect.tap( + internalEffect.makeSpanScoped(name, options), + (span) => internalEffect.addFinalizer((exit) => options.onEnd!(span, exit)) + ) + : internalEffect.makeSpanScoped(name, options), + (span) => withParentSpan(self, span) + ) + ) +} as any + +/** + * Wraps a layer so spans created during its construction use the supplied span + * as their parent. + * + * **Details** + * + * Use this to attach layer construction to an existing trace hierarchy. This API + * does not create or end the supplied parent span. + * + * When the supplied span is a native `Span`, layer construction also receives + * diagnostic information that helps associate failures with the layer call site. + * External spans are only installed as the parent span and do not add this + * diagnostic call-site information. + * + * **Example** (Attaching layers to an existing parent span) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Tracer } from "effect" + * + * class Database extends Context.Service Effect.Effect + * }>()("Database") {} + * + * class Cache extends Context.Service Effect.Effect + * }>()("Cache") {} + * + * // Create layers + * const DatabaseLayer = Layer.effect(Database, Effect.gen(function*() { + * return { + * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`)) + * } + * })) + * + * const CacheLayer = Layer.effect(Cache, Effect.gen(function*() { + * return { + * get: Effect.fn("Cache.get")((key: string) => Effect.succeed(`Cache: ${key}`)) + * } + * })) + * + * // Use with an existing parent span from Effect.withSpan + * const program = Effect.withSpan("application-startup")( + * Effect.gen(function*() { + * const parentSpan = yield* Tracer.ParentSpan + * + * // Both layers will be children of "application-startup" span + * const AppLayer = Layer.mergeAll(DatabaseLayer, CacheLayer).pipe( + * Layer.withParentSpan(parentSpan) + * ) + * + * const context = yield* Layer.build(AppLayer) + * const database = Context.get(context, Database) + * const cache = Context.get(context, Cache) + * + * const dbResult = yield* database.query("SELECT * FROM users") + * const cacheResult = yield* cache.get("user:123") + * + * return { dbResult, cacheResult } + * }) + * ) + * Effect.runSync(Effect.scoped(program)) // => { dbResult: "DB: SELECT * FROM users", cacheResult: "Cache: user:123" } + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const withParentSpan: { + ( + span: Tracer.AnySpan, + options?: Tracer.TraceOptions + ): ( + self: Layer + ) => Layer> + ( + self: Layer, + span: Tracer.AnySpan, + options?: Tracer.TraceOptions + ): Layer> +} = function() { + const dataFirst = isLayer(arguments[0]) + const span: Tracer.AnySpan = dataFirst ? arguments[1] : arguments[0] + let options = dataFirst ? arguments[2] : arguments[1] + let provideStackFrame: (self: Layer) => Layer = identity + if (span._tag === "Span") { + options = internalTracer.addSpanStackTrace(options) + provideStackFrame = provideSpanStackFrame(span.name, options?.captureStackTrace) + } + const parentSpanLayer = parentSpan(span) + if (dataFirst) { + return provide(provideStackFrame(arguments[0]), parentSpanLayer) + } + return (self: Layer) => provide(provideStackFrame(self), parentSpanLayer) +} as any + +const provideSpanStackFrame = (name: string, stack: (() => string | undefined) | undefined) => { + stack = typeof stack === "function" ? stack : constUndefined + return updateService(CurrentStackFrame, (parent) => ({ + name, + stack, + parent + })) +} diff --git a/.repos/effect/packages/effect/src/LayerMap.ts b/.repos/effect/packages/effect/src/LayerMap.ts new file mode 100644 index 000000000..eabd4ec59 --- /dev/null +++ b/.repos/effect/packages/effect/src/LayerMap.ts @@ -0,0 +1,519 @@ +/** + * Caches scoped services selected by key and built from layers. + * + * A `LayerMap` turns a key into a cached service `Context` and + * exposes that context as either a `Layer` or a scoped effect. Entries can be + * invalidated explicitly or released after they sit unused. This is useful for + * keyed resource families such as tenant clients, regional connections, or + * environment-specific services. + * + * @since 3.14.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 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" + +const TypeId = "~effect/LayerMap" + +type IdleTimeToLiveInput = Duration.Input | ((key: K) => Duration.Input) + +/** + * A scoped, keyed map of layer-built service contexts. + * + * **Details** + * + * A `LayerMap` builds resources for a key on demand, exposes them as a `Layer` + * or scoped `Context`, and can invalidate cached resources for a key. + * + * **Example** (Managing keyed layers) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerMap } from "effect" + * + * // Define a service key + * const DatabaseService = Context.Service<{ + * readonly query: (sql: string) => Effect.Effect + * }>("Database") + * + * // Create a LayerMap that provides different database configurations + * const createDatabaseLayerMap = LayerMap.make((env: string) => + * Layer.succeed(DatabaseService)({ + * query: Effect.fn("DatabaseService.query")((sql) => Effect.succeed(`${env}: ${sql}`)) + * }) + * ) + * + * // Use the LayerMap + * const program = Effect.gen(function*() { + * const layerMap = yield* createDatabaseLayerMap + * + * // Get a layer for a specific environment + * const development = yield* Effect.provide( + * DatabaseService.use((database) => database.query("SELECT 1")), + * layerMap.get("development") + * ) + * + * // Get context directly + * 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 + * @since 3.14.0 + */ +export interface LayerMap { + readonly [TypeId]: typeof TypeId + + /** + * The internal RcMap that stores the resources. + */ + readonly rcMap: RcMap.RcMap, E> + + /** + * Retrieves a Layer for the resources associated with the key. + */ + get(key: K): Layer.Layer + + /** + * Retrieves the context associated with the key. + */ + 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. + */ + invalidate(key: K): Effect.Effect +} + +/** + * Creates a `LayerMap` that dynamically provides resources based on a key. + * + * **Example** (Creating a layer map) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerMap } from "effect" + * + * // Define a service key + * const DatabaseService = Context.Service<{ + * readonly query: (sql: string) => Effect.Effect + * }>("Database") + * + * // Create a LayerMap that provides different database configurations + * const program = Effect.gen(function*() { + * const layerMap = yield* LayerMap.make( + * (env: string) => + * Layer.succeed(DatabaseService)({ + * query: Effect.fn("DatabaseService.query")((sql) => Effect.succeed(`${env}: ${sql}`)) + * }), + * { idleTimeToLive: "5 seconds" } + * ) + * + * // Get a layer for a specific environment + * const devLayer = layerMap.get("development") + * + * // Use the layer to provide the service + * return yield* Effect.provide( + * Effect.gen(function*() { + * const db = yield* DatabaseService + * return yield* db.query("SELECT * FROM users") + * }), + * devLayer + * ) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => "development: SELECT * FROM users" + * ``` + * + * @category constructors + * @since 3.14.0 + */ +export const make: < + K, + L extends Layer.Layer, + PreloadKeys extends Iterable | undefined = undefined +>( + lookup: (key: K) => L, + options?: { + readonly idleTimeToLive?: IdleTimeToLiveInput | undefined + readonly preloadKeys?: PreloadKeys + } | undefined +) => Effect.Effect< + LayerMap, Layer.Error>, + PreloadKeys extends undefined ? never : Layer.Error, + Scope.Scope | Layer.Services +> = Effect.fnUntraced(function*( + lookup: (key: K) => Layer.Layer, + options?: { + readonly idleTimeToLive?: IdleTimeToLiveInput | undefined + readonly preloadKeys?: Iterable | undefined + } | undefined +) { + const context = yield* Effect.context() + const memoMap = Layer.CurrentMemoMap.forkOrCreate(context) + + const rcMap = yield* RcMap.make({ + lookup: (key: K) => + Effect.contextWith((_: Context.Context) => + Layer.buildWithMemoMap(lookup(key), memoMap, Context.get(_, Scope.Scope)) + ), + 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) + }) +}) + +/** + * Creates a `LayerMap` from a record of predefined layers. + * + * **Details** + * + * The record keys become the keys accepted by the returned `LayerMap`, and the + * record values are the layers built for those keys. + * + * **Example** (Creating a layer map from a record) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerMap } from "effect" + * + * // Define a service key + * const Database = Context.Service<{ + * readonly query: (sql: string) => Effect.Effect + * }>("Database") + * + * // Create predefined layers + * const layers = { + * development: Layer.succeed(Database)({ + * query: Effect.fn("DevDatabase.query")((sql) => Effect.succeed(`DEV: ${sql}`)) + * }), + * production: Layer.succeed(Database)({ + * query: Effect.fn("ProdDatabase.query")((sql) => Effect.succeed(`PROD: ${sql}`)) + * }) + * } as const + * + * // Create a LayerMap from the record + * const program = Effect.gen(function*() { + * const layerMap = yield* LayerMap.fromRecord(layers, { + * idleTimeToLive: "10 seconds" + * }) + * + * 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") + * ) + * + * return { development, production } + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => { development: "DEV: SELECT 1", production: "PROD: SELECT 1" } + * ``` + * + * @category constructors + * @since 3.14.0 + */ +export const fromRecord = < + const Layers extends Record>, + const Preload extends boolean = false +>( + layers: Layers, + options?: { + readonly idleTimeToLive?: IdleTimeToLiveInput | undefined + readonly preload?: Preload | undefined + } | undefined +): Effect.Effect< + LayerMap< + keyof Layers, + Layer.Success, + Layer.Error + >, + Preload extends true ? Layer.Error : never, + Scope.Scope | (Layers[keyof Layers] extends Layer.Layer ? _R : never) +> => + make((key: keyof Layers) => layers[key], { + ...options, + preloadKeys: options?.preload ? Object.keys(layers) : undefined + }) as any + +/** + * Service class shape produced by `LayerMap.Service`. + * + * **When to use** + * + * Use as the public type for classes returned by `LayerMap.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 `LayerMap` with default layers + * and helper accessors for retrieving, using, and invalidating keyed resources. + * + * @see {@link Service} for creating concrete `LayerMap` service classes + * + * @category services + * @since 3.14.0 + */ +export interface TagClass< + in out Self, + in out Id extends string, + in out K, + in out I, + in out E, + in out R, + in out LE, + in out Deps extends Layer.Layer +> extends Context.ServiceClass> { + /** + * A default layer for the `LayerMap` service. + */ + readonly layer: Layer.Layer< + Self, + (Deps extends Layer.Layer ? _E : never) | LE, + | Exclude ? _A : never)> + | (Deps extends Layer.Layer ? _R : never) + > + + /** + * A default layer for the `LayerMap` service without the dependencies provided. + */ + readonly layerNoDeps: Layer.Layer + + /** + * Retrieves a Layer for the resources associated with the key. + */ + readonly get: (key: K) => Layer.Layer + + /** + * Retrieves the context associated with the key. + */ + 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. + */ + readonly invalidate: (key: K) => Effect.Effect +} + +/** + * Create a `LayerMap` service that provides a dynamic set of resources based on + * a key. + * + * **Example** (Defining a layer map service) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerMap } from "effect" + * + * // Define a service key + * const Greeter = Context.Service<{ + * readonly greet: Effect.Effect + * }>("Greeter") + * + * // Create a service that wraps a LayerMap + * class GreeterMap extends LayerMap.Service()("GreeterMap", { + * // Define the lookup function for the layer map + * lookup: (name: string) => + * Layer.succeed(Greeter)({ + * greet: Effect.succeed(`Hello, ${name}!`) + * }), + * + * // If a layer is not used for a certain amount of time, it can be removed + * idleTimeToLive: "5 seconds" + * }) {} + * + * // Usage + * const program = Effect.gen(function*() { + * // Access and use the Greeter service + * const greeter = yield* Greeter + * return yield* greeter.greet + * }).pipe( + * // Use the GreeterMap service to provide a variant of the Greeter service + * Effect.provide(GreeterMap.get("John")) + * ).pipe( + * // Provide the GreeterMap layer + * Effect.provide(GreeterMap.layer) + * ) + * + * await Effect.runPromise(program) // => "Hello, John!" + * ``` + * + * @category services + * @since 3.14.0 + */ +export const Service = () => +< + const Id extends string, + const Options extends + | NoExcessProperties<{ + readonly lookup: (key: any) => Layer.Layer + readonly dependencies?: ReadonlyArray> | undefined + readonly idleTimeToLive?: IdleTimeToLiveInput | undefined + readonly preloadKeys?: + | Iterable any } ? K : never> + | undefined + }, Options> + | NoExcessProperties<{ + readonly layers: Record> + readonly dependencies?: ReadonlyArray> | undefined + readonly idleTimeToLive?: IdleTimeToLiveInput | undefined + readonly preload?: boolean | undefined + }, Options> +>( + id: Id, + options: Options +): TagClass< + Self, + Id, + Options extends { readonly lookup: (key: infer K) => any } ? K + : Options extends { readonly layers: infer Layers } ? keyof Layers + : never, + Service.Success, + Options extends { readonly preload: true } ? never : Service.Error, + Service.Services, + Options extends { readonly preload: true } ? Service.Error + : Options extends { readonly preloadKeys: Iterable } ? Service.Error + : never, + 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) + + 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_)( + "lookup" in options + ? make(options.lookup, options) + : fromRecord(options.layers as any, options) as any + ) + TagClass_.layer = options.dependencies && options.dependencies.length > 0 ? + Layer.provide(TagClass_.layerNoDeps, options.dependencies as any) : + TagClass_.layerNoDeps + + 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 +} + +/** + * Type helpers for values created with `LayerMap.Service`. + * + * @since 3.14.0 + */ +export declare namespace Service { + /** + * Extracts the key type accepted by a `LayerMap.Service` definition. + * + * @category utility types + * @since 3.14.0 + */ + export type Key = Options extends { readonly lookup: (key: infer K) => any } ? K + : Options extends { readonly layers: infer Layers } ? keyof Layers + : never + + /** + * Extracts the layer type produced by a `LayerMap.Service` definition. + * + * @category utility types + * @since 3.14.0 + */ + export type Layers = Options extends { readonly lookup: (key: infer _K) => infer Layers } ? Layers + : Options extends { readonly layers: infer Layers } ? Layers[keyof Layers] + : never + + /** + * Extracts the services provided by the layers in a `LayerMap.Service` + * definition. + * + * @category utility types + * @since 3.14.0 + */ + export type Success = Layers extends Layer.Layer ? _A : never + + /** + * Extracts the error type of the layers in a `LayerMap.Service` definition. + * + * @category utility types + * @since 3.14.0 + */ + export type Error = Layers extends Layer.Layer ? _E : never + + /** + * Extracts the service requirements of the layers in a `LayerMap.Service` + * definition. + * + * @category utility types + * @since 4.0.0 + */ + export type Services = Layers extends Layer.Layer ? _R : never +} diff --git a/.repos/effect/packages/effect/src/LayerRef.ts b/.repos/effect/packages/effect/src/LayerRef.ts new file mode 100644 index 000000000..a96100773 --- /dev/null +++ b/.repos/effect/packages/effect/src/LayerRef.ts @@ -0,0 +1,381 @@ +/** + * 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 Err = globalThis.Error as any + const limit = getStackTraceLimit() + setStackTraceLimit(2) + const creationError = new Err() + 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/.repos/effect/packages/effect/src/LogLevel.ts b/.repos/effect/packages/effect/src/LogLevel.ts new file mode 100644 index 000000000..837e1bd5a --- /dev/null +++ b/.repos/effect/packages/effect/src/LogLevel.ts @@ -0,0 +1,384 @@ +/** + * Log-level types and helpers used by Effect logging. The module defines all + * accepted log levels, the concrete emitted severities, the ordered level list, + * equality and ordering instances, threshold comparison helpers, and an effect + * for checking whether a level is enabled by the current logging settings. + * + * @since 2.0.0 + */ +import type * as Effect from "./Effect.ts" +import * as Equ from "./Equivalence.ts" +import * as core from "./internal/core.ts" +import * as effect from "./internal/effect.ts" +import * as Ord from "./Order.ts" +import * as References from "./References.ts" + +/** + * Represents every level used by Effect logging, including concrete message + * severities and the `All` and `None` sentinel levels. + * + * **When to use** + * + * Use to type values that may be either concrete log message severities or + * logging configuration sentinels. + * + * **Details** + * + * The levels are ordered from most severe to least severe: + * - `All` - Special level that allows all messages + * - `Fatal` - System is unusable, immediate attention required + * - `Error` - Error conditions that should be investigated + * - `Warn` - Warning conditions that may indicate problems + * - `Info` - Informational messages about normal operation + * - `Debug` - Debug information useful during development + * - `Trace` - Very detailed trace information + * - `None` - Special level that suppresses all messages + * + * **Example** (Using log levels) + * + * ```ts import.meta.vitest + * import { Effect, References } from "effect" + * + * // Using log levels with Effect logging + * const program = Effect.gen(function*() { + * yield* Effect.logFatal("System failure") + * yield* Effect.logError("Database error") + * yield* Effect.logWarning("High memory usage") + * yield* Effect.logInfo("User logged in") + * yield* Effect.logDebug("Processing request") + * yield* Effect.logTrace("Variable state") + * }) + * + * // 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 + * @since 2.0.0 + */ +export type LogLevel = "All" | "Fatal" | "Error" | "Warn" | "Info" | "Debug" | "Trace" | "None" + +/** + * Log levels that represent actual message severities, excluding the `All` and + * `None` sentinel levels. + * + * **When to use** + * + * Use when typing emitted log message severities, such as explicit log calls, + * current log level references, or error-report severity annotations, where + * `All` and `None` are not valid values. + * + * @see {@link LogLevel} for the wider log-level type that also accepts the + * `All` and `None` sentinel levels + * @see {@link values} for the runtime list of all accepted `LogLevel` values, + * including sentinels + * + * @category models + * @since 4.0.0 + */ +export type Severity = "Fatal" | "Error" | "Warn" | "Info" | "Debug" | "Trace" + +/** + * Returns all `LogLevel` values in order from `All` through the concrete severities to + * `None`. + * + * **When to use** + * + * Use to enumerate or validate all accepted `LogLevel` string values, including + * the `All` and `None` sentinel levels. + * + * **Details** + * + * The array order matches the module severity order: `All`, concrete + * severities from `Fatal` to `Trace`, then `None`. + * + * **Gotchas** + * + * This list includes `All` and `None`, so it is not limited to concrete emitted + * severities. + * + * @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 constants + * @since 4.0.0 + */ +export const values: ReadonlyArray = ["All", "Fatal", "Error", "Warn", "Info", "Debug", "Trace", "None"] + +/** + * Order instance for `LogLevel` that defines the severity ordering. + * + * **When to use** + * + * Use to sort or compare log levels according to Effect's severity order. + * + * **Details** + * + * This order treats "All" as the least restrictive level and "None" as the most restrictive, + * with Fatal being the most severe actual log level. + * + * **Example** (Ordering log levels) + * + * ```ts import.meta.vitest + * import { LogLevel } from "effect" + * + * LogLevel.Order("Error", "Info") // => 1 + * LogLevel.Order("Debug", "Error") // => -1 + * LogLevel.Order("Info", "Info") // => 0 + * ``` + * + * @category ordering + * @since 2.0.0 + */ +export const Order: Ord.Order = effect.LogLevelOrder + +/** + * Equivalence instance for log levels using strict equality (`===`). + * + * **When to use** + * + * Use to compare two `LogLevel` values when only the exact same level should + * match. + * + * **Details** + * + * Each log level string, including `All` and `None`, only matches itself. + * + * **Example** (Comparing log levels) + * + * ```ts import.meta.vitest + * import { LogLevel } from "effect" + * + * LogLevel.Equivalence("Error", "Error") // => true + * LogLevel.Equivalence("Error", "Info") // => false + * ``` + * + * @see {@link Order} for severity ordering rather than exact level equality + * @see {@link isGreaterThanOrEqualTo} for minimum-threshold checks + * + * @category instances + * @since 4.0.0 + */ +export const Equivalence: Equ.Equivalence = Equ.strictEqual() + +/** + * Returns the ordinal value of the log level. + * + * **When to use** + * + * Use to project a `LogLevel` into the numeric sort key used by + * `LogLevel.Order` when custom ordering code or an integration needs a number + * instead of an `Order` comparison. + * + * **Details** + * + * The mapping is `All` to `Number.MIN_SAFE_INTEGER`, `Trace` to `0`, `Debug` to + * `10000`, `Info` to `20000`, `Warn` to `30000`, `Error` to `40000`, `Fatal` to + * `50000`, and `None` to `Number.MAX_SAFE_INTEGER`. + * + * **Gotchas** + * + * These ordinals are internal sort keys; do not treat them as external severity + * numbers. + * + * @see {@link Order} for comparing log levels without exposing numeric keys + * @see {@link isGreaterThanOrEqualTo} for minimum-threshold filtering + * + * @category ordering + * @since 4.0.0 + */ +export const getOrdinal = (self: LogLevel): number => effect.logLevelToOrder(self) + +/** + * Determines if the first log level is more severe than the second. + * + * **When to use** + * + * Use to check whether one log level is strictly more severe than another. + * + * **Details** + * + * Returns `true` if `self` represents a more severe level than `that`. + * + * **Example** (Checking higher severity) + * + * ```ts import.meta.vitest + * import { LogLevel } from "effect" + * + * 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") + * isFatal // => true + * isError // => true + * isDebug // => false + * + * // Curried usage + * const isMoreSevereThanInfo = LogLevel.isGreaterThan("Info") + * isMoreSevereThanInfo("Error") // => true + * isMoreSevereThanInfo("Debug") // => false + * ``` + * + * @category ordering + * @since 4.0.0 + */ +export const isGreaterThan: { + (that: LogLevel): (self: LogLevel) => boolean + (self: LogLevel, that: LogLevel): boolean +} = effect.isLogLevelGreaterThan + +/** + * Determines if the first log level is more severe than or equal to the second. + * + * **When to use** + * + * Use to implement minimum log-level filtering by checking whether a message + * level meets a threshold. + * + * **Details** + * + * Returns `true` if `self` represents a level that is more severe than or equal to `that`. + * + * **Example** (Filtering by minimum log level) + * + * ```ts import.meta.vitest + * import { LogLevel } from "effect" + * + * LogLevel.isGreaterThanOrEqualTo("Error", "Error") // => true + * LogLevel.isGreaterThanOrEqualTo("Error", "Info") // => true + * LogLevel.isGreaterThanOrEqualTo("Debug", "Info") // => false + * + * const isInfoOrAbove = LogLevel.isGreaterThanOrEqualTo("Info") + * isInfoOrAbove("Error") // => true + * ``` + * + * @category ordering + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo: { + (that: LogLevel): (self: LogLevel) => boolean + (self: LogLevel, that: LogLevel): boolean +} = Ord.isGreaterThanOrEqualTo(Order) + +/** + * Determines if the first log level is less severe than the second. + * + * **When to use** + * + * Use to check whether one log level is strictly less severe than another. + * + * **Details** + * + * Returns `true` if `self` represents a less severe level than `that`. + * + * **Example** (Checking lower severity) + * + * ```ts import.meta.vitest + * import { LogLevel } from "effect" + * + * 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") + * isFatalVerbose // => false + * isErrorVerbose // => false + * isTraceVerbose // => true + * + * // Curried usage + * const isLessSevereThanError = LogLevel.isLessThan("Error") + * isLessSevereThanError("Info") // => true + * isLessSevereThanError("Fatal") // => false + * ``` + * + * @category ordering + * @since 4.0.0 + */ +export const isLessThan: { + (that: LogLevel): (self: LogLevel) => boolean + (self: LogLevel, that: LogLevel): boolean +} = Ord.isLessThan(Order) + +/** + * Determines if the first log level is less severe than or equal to the second. + * + * **When to use** + * + * Use to implement maximum log-level filtering by checking whether a level is + * at or below a threshold. + * + * **Details** + * + * Returns `true` if `self` represents a level that is less severe than or equal to `that`. + * + * **Example** (Filtering by maximum log level) + * + * ```ts import.meta.vitest + * import { LogLevel } from "effect" + * + * LogLevel.isLessThanOrEqualTo("Info", "Info") // => true + * LogLevel.isLessThanOrEqualTo("Debug", "Info") // => true + * LogLevel.isLessThanOrEqualTo("Error", "Info") // => false + * + * const isInfoOrBelow = LogLevel.isLessThanOrEqualTo("Info") + * isInfoOrBelow("Debug") // => true + * ``` + * + * @category ordering + * @since 4.0.0 + */ +export const isLessThanOrEqualTo: { + (that: LogLevel): (self: LogLevel) => boolean + (self: LogLevel, that: LogLevel): boolean +} = Ord.isLessThanOrEqualTo(Order) + +/** + * Checks whether a given log level is enabled for the current fiber. + * + * **When to use** + * + * Use to check whether a log level would be emitted under the current fiber's + * minimum log level. + * + * **Details** + * + * A log level is enabled when it is greater than or equal to + * `References.MinimumLogLevel`. + * + * **Example** (Checking current fiber log level) + * + * ```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") + * + * return { debugEnabled, errorEnabled } + * }) + * + * const warnOnly = program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Warn") + * ) + * + * await Effect.runPromise(warnOnly) // => { debugEnabled: false, errorEnabled: true } + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isEnabled = (self: LogLevel): Effect.Effect => + core.withFiber((fiber) => effect.succeed(!isGreaterThan(fiber.getRef(References.MinimumLogLevel), self))) diff --git a/.repos/effect/packages/effect/src/Logger.ts b/.repos/effect/packages/effect/src/Logger.ts new file mode 100644 index 000000000..168a53870 --- /dev/null +++ b/.repos/effect/packages/effect/src/Logger.ts @@ -0,0 +1,1036 @@ +/** + * Defines loggers and log-event data for Effect programs. + * + * A `Logger` receives each log event as `Options` and turns it + * into output such as a formatted string, structured object, console write, + * file write, JSON line, or trace span event. This module also includes active + * logger references, console routing helpers, built-in formatters, batching, + * file logging, and layers for installing loggers. + * + * @since 2.0.0 + */ +import * as Array from "./Array.ts" +import type * as Cause from "./Cause.ts" +import type * as Context from "./Context.ts" +import type * as Duration from "./Duration.ts" +import type * as Effect from "./Effect.ts" +import type * as Fiber from "./Fiber.ts" +import * as FileSystem from "./FileSystem.ts" +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" +import type { PlatformError } from "./PlatformError.ts" +import * as Predicate from "./Predicate.ts" +import { CurrentLogAnnotations, CurrentLogSpans } from "./References.ts" +import type * as Scope from "./Scope.ts" + +const TypeId = "~effect/Logger" + +/** + * A logger that transforms a runtime log event into an output value. + * + * **Details** + * + * The runtime calls `log` with the message, level, cause, fiber, and timestamp + * for each log event. Use `Logger.layer` to install one or more loggers for an + * effect. + * + * **Example** (Creating custom loggers) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const messages: Array = [] + * const stringLogger = Logger.make((options) => { + * messages.push(`[${options.logLevel}] ${options.message}`) + * }) + * + * const program = Effect.log("Hello World").pipe( + * Effect.provide(Logger.layer([stringLogger])) + * ) + * + * Effect.runSync(program) + * messages // => ["[Info] Hello World"] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Logger extends Pipeable { + readonly [TypeId]: typeof TypeId + log(options: Options): Output +} + +/** + * Information supplied to a `Logger` for a single log event. + * + * **Details** + * + * Includes the logged message, log level, cause, current fiber, and timestamp. + * + * **Example** (Accessing logger options) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const outputs: Array = [] + * const detailedLogger = Logger.make((options) => { + * outputs.push({ + * message: options.message, + * level: options.logLevel, + * 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 + * @since 2.0.0 + */ +export interface Options { + readonly message: Message + readonly logLevel: LogLevel.LogLevel + readonly cause: Cause.Cause + readonly fiber: Fiber.Fiber + readonly date: Date +} + +/** + * Returns `true` if the specified value is a `Logger`, otherwise returns `false`. + * + * **Example** (Checking logger values) + * + * ```ts import.meta.vitest + * import { Logger } from "effect" + * + * const myLogger = Logger.make(() => undefined) + * + * Logger.isLogger(myLogger) // => true + * Logger.isLogger("not a logger") // => false + * Logger.isLogger({ log: () => {} }) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isLogger = (u: unknown): u is Logger => Predicate.hasProperty(u, TypeId) + +/** + * Context reference containing the active loggers for the current fiber. + * + * **Details** + * + * By default this set includes the default logger and the tracer logger. + * Providing `Logger.layer` replaces or merges with this set depending on its + * options. + * + * **Example** (Accessing current loggers) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const messages: Array = [] + * const customLogger = Logger.make((options) => { + * messages.push(options.message) + * }) + * const program = Effect.gen(function*() { + * const currentLoggers = yield* Effect.service(Logger.CurrentLoggers) + * 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 services + * @since 4.0.0 + */ +export const CurrentLoggers: Context.Reference>> = effect.CurrentLoggers + +/** + * Context reference that routes the built-in default logger and TTY pretty + * console logger to stderr. + * + * **When to use** + * + * Use to route built-in logger output to stderr while keeping stdout reserved + * for protocol messages or data output. + * + * **Details** + * + * The reference defaults to `false`. Providing `true` makes the affected + * loggers call `console.error` instead of `console.log`. + * + * @see {@link defaultLogger} for the runtime logger affected by this 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 services + * @since 4.0.0 + */ +export const LogToStderr: Context.Reference = effect.LogToStderr + +/** + * Transforms the output of a `Logger` using the provided function. + * + * **When to use** + * + * Use when an existing logger's output should be transformed without recreating the + * logging logic. + * + * **Example** (Transforming logger output) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const outputs: Array = [] + * const structuredLogger = Logger.make((options) => ({ + * message: options.message + * })) + * + * // 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 + * @since 2.0.0 + */ +export const map = dual< + ( + f: (output: Output) => Output2 + ) => ( + self: Logger + ) => Logger, + ( + self: Logger, + f: (output: Output) => Output2 + ) => Logger +>(2, (self, f) => effect.loggerMake((options) => f(self.log(options)))) + +/** + * Returns a new `Logger` that writes all output of the specified `Logger` to + * the console using `console.log`. + * + * **When to use** + * + * Use when a logger's string or object output should be routed to `console.log` for + * development or debugging. + * + * **Example** (Writing logger output with console.log) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" + * + * // Create a custom formatter + * const customFormatter = Logger.make((options) => + * `${options.logLevel}: ${options.message}` + * ) + * + * const consoleLogger = Logger.withConsoleLog(customFormatter) + * + * 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 + * @since 2.0.0 + */ +export const withConsoleLog = ( + self: Logger +): Logger => + effect.loggerMake((options) => { + const console = options.fiber.getRef(effect.ConsoleRef) + return console.log(self.log(options)) + }) +/** + * Returns a new `Logger` that writes all output of the specified `Logger` to + * the console using `console.error`. + * + * **When to use** + * + * Use when logger output should be routed to `console.error`, such as error logs that + * should appear on stderr instead of stdout. + * + * **Example** (Writing logger output with console.error) + * + * ```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.message}` + * ) + * + * const errorLogger = Logger.withConsoleError(errorFormatter) + * + * 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 + * @since 2.0.0 + */ +export const withConsoleError = ( + self: Logger +): Logger => + effect.loggerMake((options) => { + const console = options.fiber.getRef(effect.ConsoleRef) + return console.error(self.log(options)) + }) +/** + * Returns a new `Logger` that writes all output of the specified `Logger` to + * the console. + * + * **Details** + * + * Will use the appropriate console method (i.e. `console.log`, `console.error`, + * etc.) based upon the current `LogLevel`. + * + * `Debug` uses `console.debug`, `Info` uses `console.info`, `Trace` uses + * `console.trace`, `Warn` uses `console.warn`, `Error` and `Fatal` use + * `console.error`, and all other levels use `console.log`. + * + * **Example** (Writing logs with level-based console methods) + * + * ```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}` + * ) + * + * const leveledLogger = Logger.withLeveledConsole(formatter) + * + * const program = Effect.gen(function*() { + * yield* Effect.logInfo("Info message") // -> console.info + * yield* Effect.logWarning("Warning") // -> console.warn + * yield* Effect.logError("Error occurred") // -> console.error + * }).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 + * @since 3.8.0 + */ +export const withLeveledConsole = ( + self: Logger +): Logger => + effect.loggerMake((options) => { + const console = options.fiber.getRef(effect.ConsoleRef) + const output = self.log(options) + switch (options.logLevel) { + case "Debug": + return console.debug(output) + case "Info": + return console.info(output) + case "Trace": + return console.trace(output) + case "Warn": + return console.warn(output) + case "Error": + case "Fatal": + return console.error(output) + default: + return console.log(output) + } + }) + +/** + * Match strings that do not contain any whitespace characters, double quotes, + * or equal signs. + */ +const textOnly = /^[^\s"=]*$/ + +/** + * Escapes double quotes in a string. + */ +const escapeDoubleQuotes = (s: string) => `"${s.replace(/\\([\s\S])|(")/g, "\\$1$2")}"` + +/** + * Formats the identifier of a `Fiber` by prefixing it with a hash tag. + */ +const formatFiberId = (fiberId: number) => `#${fiberId}` + +/** + * Used by both {@link formatSimple} and {@link formatLogFmt} to render a log + * message. + * + * @internal + */ +const format = ( + quoteValue: (s: string) => string, + space?: number | string | undefined +) => +({ cause, date, fiber, logLevel, message }: Options): string => { + const formatUnknown = (value: unknown): string => + typeof value === "string" ? value : Formatter.format(value, { space }) + const formatValue = (value: string): string => value.match(textOnly) ? value : quoteValue(value) + const format = (label: string, value: string): string => `${effect.formatLabel(label)}=${formatValue(value)}` + const append = (label: string, value: string): string => " " + format(label, value) + + let out = format("timestamp", date.toISOString()) + out += append("level", logLevel.toUpperCase()) + out += append("fiber", formatFiberId(fiber.id)) + + const messages = Array.ensure(message) + for (let i = 0; i < messages.length; i++) { + out += append("message", formatUnknown(messages[i])) + } + + if (cause.reasons.length > 0) { + out += append("cause", effect.causePretty(cause)) + } + + const now = date.getTime() + const spans = fiber.getRef(CurrentLogSpans) + for (const span of spans) { + out += " " + effect.formatLogSpan(span, now) + } + + const annotations = fiber.getRef(CurrentLogAnnotations) + for (const [label, value] of Object.entries(annotations)) { + out += append(label, formatUnknown(value)) + } + + return out +} + +/** + * Creates a new `Logger` from a log function. + * + * **Details** + * + * The log function receives an options object containing the message, log level, + * cause, fiber information, and timestamp, and should return the desired output. + * + * **Example** (Creating loggers from functions) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const outputs: Array = [] + * const textLogger = Logger.make((options) => + * `${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([collector])) + * ) + * Effect.runSync(program) + * outputs // => ["Info: Hello World"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make: ( + log: (options: Options) => Output +) => Logger = effect.loggerMake + +/** + * The default logging implementation used by the Effect runtime. + * + * **Example** (Referencing the default logger) + * + * ```ts import.meta.vitest + * import { Logger } from "effect" + * + * Logger.isLogger(Logger.defaultLogger) // => true + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const defaultLogger: Logger = effect.defaultLogger + +/** + * A `Logger` which outputs logs as a string. + * + * **Details** + * + * For example, a simple log entry is rendered as + * `timestamp=2025-01-03T14:22:47.570Z level=INFO fiber=#1 message=hello`. + * + * **Example** (Formatting logs as simple strings) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" + * + * // Use the simple format logger + * const stableSimple = Logger.map(Logger.formatSimple, (output) => + * output + * .replace(/timestamp=\S+ /, "") + * .replace(/fiber=#\d+ /, "") + * ) + * const program = Effect.gen(function*() { + * 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 + * @since 4.0.0 + */ +export const formatSimple = effect.loggerMake(format(escapeDoubleQuotes)) + +/** + * A `Logger` which outputs logs using the [logfmt](https://brandur.org/logfmt) + * style. + * + * **Details** + * + * For example, a logfmt entry is rendered as + * `timestamp=2025-01-03T14:22:47.570Z level=INFO fiber=#1 message=hello`. + * + * **Example** (Formatting logs as logfmt) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" + * + * 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)) + * + * await Effect.runPromise(program) // => ["level=INFO message=\"User login\""] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const formatLogFmt = effect.loggerMake(format(JSON.stringify, 0)) + +/** + * A `Logger` which outputs logs using a structured format. + * + * **Details** + * + * For example, a structured entry can contain `message: [ "hello" ]`, + * `level: "INFO"`, `timestamp: "2025-01-03T14:25:39.666Z"`, + * `annotations: { key: "value" }`, `spans: { label: 0 }`, and + * `fiberId: "#1"`. + * + * **Example** (Formatting logs as structured objects) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" + * + * 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)) + * + * await Effect.runPromise(program) // => [{ message: "User action", level: "INFO" }] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const formatStructured: Logger + readonly spans: Record +}> = effect.loggerMake(({ cause, date, fiber, logLevel, message }) => { + const annotationsObj: Record = {} + const spansObj: Record = {} + + const annotations = fiber.getRef(CurrentLogAnnotations) + for (const [key, value] of Object.entries(annotations)) { + InternalRecord.assignProperty(annotationsObj, key, effect.structuredMessage(value)) + } + + const now = date.getTime() + const spans = fiber.getRef(CurrentLogSpans) + for (const [label, timestamp] of spans) { + InternalRecord.assignProperty(spansObj, label, now - timestamp) + } + + const messageArr = Array.ensure(message) + return { + message: messageArr.length === 1 + ? effect.structuredMessage(messageArr[0]) + : messageArr.map(effect.structuredMessage), + level: logLevel.toUpperCase(), + timestamp: date.toISOString(), + cause: cause.reasons.length > 0 ? effect.causePretty(cause) : undefined, + annotations: annotationsObj, + spans: spansObj, + fiberId: formatFiberId(fiber.id) + } +}) + +/** + * A `Logger` which outputs logs using a structured format serialized as JSON + * on a single line. + * + * **Details** + * + * For example, a JSON entry can render as `{"message":["hello"],"level":"INFO", + * "timestamp":"2025-01-03T14:28:57.508Z","annotations":{"key":"value"}, + * "spans":{"label":0},"fiberId":"#1"}`. + * + * **Example** (Formatting logs as JSON) + * + * ```ts import.meta.vitest + * import { Effect, Formatter, Logger } from "effect" + * import { TestConsole } from "effect/testing" + * + * 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)) + * + * await Effect.runPromise(program) // => ["{\"message\":\"Server started\",\"level\":\"INFO\"}"] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const formatJson = map(formatStructured, Formatter.formatJson) + +/** + * Creates a scoped logger that batches the output of another logger. + * + * **Details** + * + * The returned effect starts a scoped background process that periodically + * passes buffered outputs to `flush`. When the scope closes, the background + * process is interrupted and any remaining buffered entries are flushed. + * + * **Example** (Batching logger output) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const flushed: Array> = [] + * const messageLogger = Logger.make((options) => String(options.message)) + * const batchedLogger = Logger.batched(messageLogger, { + * window: "1 hour", + * flush: (messages) => + * Effect.sync(() => { + * flushed.push(messages) + * }) + * }) + * + * const program = Effect.scoped(Effect.gen(function*() { + * const logger = yield* batchedLogger + * 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 + * @since 2.0.0 + */ +export const batched = dual< + (options: { + readonly window: Duration.Input + readonly flush: (messages: Array>) => Effect.Effect + }) => ( + self: Logger + ) => Effect.Effect, never, Scope.Scope>, + ( + self: Logger, + options: { + readonly window: Duration.Input + readonly flush: (messages: Array>) => Effect.Effect + } + ) => Effect.Effect, never, Scope.Scope> +>(2, ( + self: Logger, + options: { + readonly window: Duration.Input + readonly flush: (messages: Array>) => Effect.Effect + } +): Effect.Effect, never, Scope.Scope> => + effect.flatMap(effect.scope, (scope) => { + let buffer: Array = [] + const flush = effect.suspend(() => { + if (buffer.length === 0) { + return effect.void + } + const arr = buffer + buffer = [] + return options.flush(arr) + }) + + return effect.uninterruptibleMask((restore) => + restore( + effect.sleep(options.window).pipe( + effect.andThen(flush), + effect.forever + ) + ).pipe( + effect.forkDetach, + effect.flatMap((fiber) => effect.scopeAddFinalizerExit(scope, () => effect.fiberInterrupt(fiber))), + effect.andThen(effect.addFinalizer(() => flush)), + effect.as( + effect.loggerMake((options) => { + buffer.push(self.log(options)) + }) + ) + ) + ) + })) + +/** + * A `Logger` which outputs logs in a "pretty" format and writes them to the + * 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 { Logger } from "effect" + * + * const prettyLogger = Logger.consolePretty({ colors: false }) + * Logger.isLogger(prettyLogger) // => true + * ``` + * + * @category constructors + * @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. + * + * **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 + * @since 4.0.0 + */ +export const consoleLogFmt: Logger = withConsoleLog(formatLogFmt) + +/** + * A `Logger` which outputs logs using a structured format and writes them to + * the console. + * + * **Details** + * + * For example, console structured output can contain + * `message: [ "info", "message" ]`, `level: "INFO"`, + * `timestamp: "2025-01-03T14:25:39.666Z"`, + * `annotations: { key: "value" }`, `spans: { label: 0 }`, and + * `fiberId: "#1"`. + * + * **Example** (Logging structured output to the console) + * + * ```ts import.meta.vitest + * import { Logger } from "effect" + * + * Logger.isLogger(Logger.consoleStructured) // => true + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const consoleStructured: Logger = withConsoleLog(formatStructured) + +/** + * A `Logger` which outputs logs using a structured format serialized as JSON + * on a single line and writes them to the console. + * + * **Details** + * + * For example, console JSON output can render as + * `{"message":["hello"],"level":"INFO","timestamp":"2025-01-03T14:28:57.508Z", + * "annotations":{"key":"value"},"spans":{"label":0},"fiberId":"#1"}`. + * + * **Example** (Logging JSON output to the console) + * + * ```ts import.meta.vitest + * import { Logger } from "effect" + * + * Logger.isLogger(Logger.consoleJson) // => true + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const consoleJson: Logger = withConsoleLog(formatJson) + +/** + * A `Logger` which includes log messages as tracer span events. + * + * **Details** + * + * This logger integrates logging with distributed tracing by recording + * all log messages as events on the current trace span, making them visible + * in tracing tools like OpenTelemetry, Jaeger, or Zipkin. + * + * This logger is included in the default set of loggers for all Effect programs, + * so log messages automatically appear as span events unless you override the + * default loggers. + * + * **Example** (Recording logs as trace span events) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const program = Effect.log("span event").pipe( + * Effect.withSpan("operation"), + * Effect.provide(Logger.layer([Logger.tracerLogger])) + * ) + * Effect.runSync(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const tracerLogger: Logger = effect.tracerLogger + +/** + * Creates a `Layer` which will overwrite the current set of loggers with the + * specified array of `loggers`. + * + * **Details** + * + * If the specified array of `loggers` should be _merged_ with the current set + * of loggers (instead of overwriting them), set `mergeWithExisting` to `true`. + * + * **Example** (Providing logger layers) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * 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(CustomLoggerLayer) + * ) + * Effect.runSync(program) + * messages // => [["Application started"]] + * ``` + * + * @category layers + * @since 4.0.0 + */ +export const layer = < + const Loggers extends ReadonlyArray | Effect.Effect, any, any>> +>( + loggers: Loggers, + options?: { readonly mergeWithExisting?: boolean | undefined } | undefined +): Layer.Layer< + never, + Loggers extends readonly [] ? never : Effect.Error, + Exclude< + Loggers extends readonly [] ? never : Effect.Services, + Scope.Scope + > +> => + Layer.effect( + CurrentLoggers, + withFiber(effect.fnUntraced(function*(fiber) { + const currentLoggers = new Set(options?.mergeWithExisting === true ? fiber.getRef(effect.CurrentLoggers) : []) + for (const logger of loggers) { + currentLoggers.add(isEffect(logger) ? yield* logger : logger) + } + return currentLoggers + })) + ) + +/** + * Creates a scoped logger that writes string logger output to a file. + * + * **Details** + * + * The returned effect requires `FileSystem` and `Scope`. The file logger batches + * string output, writes each batch to the specified path, and flushes remaining + * entries when the scope closes. + * + * **Example** (Writing JSON logs to a file) + * + * ```ts import.meta.vitest + * import { Effect, FileSystem, Logger } from "effect" + * + * const writes: Array = [] + * const file = { + * write: (buffer: Uint8Array) => Effect.sync(() => { + * writes.push(new TextDecoder().decode(buffer).trim()) + * return FileSystem.Size(buffer.length) + * }) + * } 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.meta.vitest + * import { Effect, FileSystem, Logger } from "effect" + * + * const writes: Array = [] + * const file = { + * write: (buffer: Uint8Array) => Effect.sync(() => { + * writes.push(new TextDecoder().decode(buffer).trim()) + * return FileSystem.Size(buffer.length) + * }) + * } 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/app.log", { + * batchWindow: "1 hour" + * }) + * 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 logging + * @since 4.0.0 + */ +export const toFile = dual< + ( + path: string, + options?: { + readonly flag?: FileSystem.OpenFlag | undefined + readonly mode?: number | undefined + readonly batchWindow?: Duration.Input | undefined + } | undefined + ) => ( + self: Logger + ) => Effect.Effect, PlatformError, Scope.Scope | FileSystem.FileSystem>, + ( + self: Logger, + path: string, + options?: { + readonly flag?: FileSystem.OpenFlag | undefined + readonly mode?: number | undefined + readonly batchWindow?: Duration.Input | undefined + } | undefined + ) => Effect.Effect, PlatformError, Scope.Scope | FileSystem.FileSystem> +>( + (args) => isLogger(args[0]), + (self, path, options) => + effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const logFile = yield* fs.open(path, { flag: "a+", ...options }) + const encoder = new TextEncoder() + return yield* batched(self, { + window: options?.batchWindow ?? 1000, + flush: (output) => effect.ignore(logFile.write(encoder.encode(output.join("\n") + "\n"))) + }) + }) +) diff --git a/.repos/effect/packages/effect/src/ManagedRuntime.ts b/.repos/effect/packages/effect/src/ManagedRuntime.ts new file mode 100644 index 000000000..90117459f --- /dev/null +++ b/.repos/effect/packages/effect/src/ManagedRuntime.ts @@ -0,0 +1,396 @@ +/** + * Runs many effects against services built once from a `Layer`. + * + * A `ManagedRuntime` builds the services from a layer, keeps those services + * available for repeated effect runs, and releases acquired resources when it + * is disposed. This module includes the runtime type, a constructor, a guard, + * and runners for connecting Effect programs to JavaScript entry points such as + * promises, callbacks, and synchronous code. + * + * @since 2.0.0 + */ +import type * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import * as Fiber from "./Fiber.ts" +import * as Layer from "./Layer.ts" +import { hasProperty } from "./Predicate.ts" +import * as Scope from "./Scope.ts" +import type { Mutable } from "./Types.ts" + +const TypeId = "~effect/ManagedRuntime" + +/** + * Checks whether the provided argument is a `ManagedRuntime`. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a `ManagedRuntime`. + * + * **Details** + * + * The guard checks the internal `ManagedRuntime` marker property. It does not + * build the layer or inspect the runtime's services. + * + * **Gotchas** + * + * Disposed runtimes still carry the marker, so this guard does not prove the + * runtime is still usable. + * + * @see {@link make} for creating managed runtimes this guard recognizes + * + * @category guards + * @since 3.9.0 + */ +export const isManagedRuntime = (input: unknown): input is ManagedRuntime => + hasProperty(input, TypeId) + +/** + * Type helpers associated with `ManagedRuntime`. + * + * **When to use** + * + * Use to reference type-level helpers for extracting managed runtime services + * and layer errors. + * + * @since 3.4.0 + */ +export declare namespace ManagedRuntime { + /** + * Extracts the services available from a `ManagedRuntime`. + * + * **When to use** + * + * Use to derive the service requirements provided by an existing + * `ManagedRuntime` type. + * + * @category utility types + * @since 3.4.0 + */ + export type Services> = [T] extends [ManagedRuntime] ? R + : never + /** + * Extracts the layer construction error type of a `ManagedRuntime`. + * + * **When to use** + * + * Use to derive the layer construction error type from an existing + * `ManagedRuntime` type. + * + * @category utility types + * @since 3.4.0 + */ + export type Error> = [T] extends [ManagedRuntime] ? E : never +} + +/** + * A runtime built from a layer that can execute effects requiring that layer's + * services. + * + * **When to use** + * + * Use as the reusable runtime value returned by `make` when application entry + * points or integration code need to run many effects against the same + * layer-built services. + * + * **Details** + * + * The runtime builds and caches its service context and owns the scope for + * resources acquired by the layer. + * + * **Gotchas** + * + * Dispose the runtime with `dispose` or `disposeEffect` when it is no longer + * needed. + * + * @see {@link make} for constructing a managed runtime from a layer + * @see {@link Layer.build} for lower-level scoped layer construction + * + * @category models + * @since 2.0.0 + */ +export interface ManagedRuntime { + readonly [TypeId]: typeof TypeId + readonly memoMap: Layer.MemoMap + readonly contextEffect: Effect.Effect, ER> + readonly context: () => Promise> + + // internal + readonly scope: Scope.Closeable + // internal + cachedContext: Context.Context | undefined + + /** + * Executes the effect using the provided Scheduler or using the global + * Scheduler if not provided + * + * **When to use** + * + * Use to fork an effect against this runtime's services and get the running + * fiber. + */ + readonly runFork: ( + self: Effect.Effect, + options?: Effect.RunOptions + ) => Fiber.Fiber + + /** + * Executes the effect synchronously returning the exit. + * + * **When to use** + * + * Use when invoking this effectful method at the edges of your + * program. + */ + readonly runSyncExit: (effect: Effect.Effect) => Exit.Exit + + /** + * Executes the effect synchronously throwing in case of errors or async boundaries. + * + * **When to use** + * + * Use when invoking this effectful method at the edges of your + * program. + */ + readonly runSync: (effect: Effect.Effect) => A + + /** + * Executes the effect asynchronously, eventually passing the exit value to + * the specified callback. + * + * **When to use** + * + * Use when invoking this effectful method at the edges of your + * program. + */ + readonly runCallback: ( + effect: Effect.Effect, + options?: + | Effect.RunOptions & { + readonly onExit: (exit: Exit.Exit) => void + } + | undefined + ) => (interruptor?: number | undefined) => void + + /** + * Runs the `Effect`, returning a JavaScript `Promise` that will be resolved + * with the value of the effect once the effect has been executed, or will be + * rejected with the first error or exception throw by the effect. + * + * **When to use** + * + * Use when invoking this effectful method at the edges of your + * program. + */ + readonly runPromise: (effect: Effect.Effect, options?: Effect.RunOptions) => Promise + + /** + * Runs the `Effect`, returning a JavaScript `Promise` that will be resolved + * with the `Exit` state of the effect once the effect has been executed. + * + * **When to use** + * + * Use when invoking this effectful method at the edges of your + * program. + */ + readonly runPromiseExit: ( + effect: Effect.Effect, + options?: Effect.RunOptions + ) => Promise> + + /** + * Dispose of the resources associated with the runtime. + * + * **When to use** + * + * Use to release this runtime's layer resources from Promise-based code. + */ + 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. + * + * **When to use** + * + * Use to release this runtime's layer resources from an `Effect` workflow. + */ + readonly disposeEffect: Effect.Effect +} + +/** + * Creates a `ManagedRuntime` from a layer. + * + * **When to use** + * + * Use to create a reusable runtime from a `Layer` for application entry points + * or integration code that runs many effects without rebuilding services. + * + * **Details** + * + * The layer is built lazily on first use and its context is cached for + * subsequent runs. Resources acquired by the layer are owned by the runtime and + * are released when `dispose` or `disposeEffect` is run. `options.memoMap` can + * be used to share layer memoization with other layer builds. + * + * **Gotchas** + * + * Dispose the runtime when it is no longer needed. A runtime cannot be reused + * after disposal. + * + * **Example** (Creating a managed runtime) + * + * ```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(() => notifications.push(message)) + * ) + * }) + * } + * + * const runtime = ManagedRuntime.make(Notifications.layer) + * + * const program = Effect.flatMap( + * Notifications, + * (_) => _.notify("Hello, world!") + * ).pipe(Effect.ensuring(runtime.disposeEffect)) + * + * 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 constructors + * @since 2.0.0 + */ +export const make = ( + layer: Layer.Layer, + options?: { + readonly memoMap?: Layer.MemoMap | undefined + } | undefined +): ManagedRuntime => { + const memoMap = options?.memoMap ?? Layer.makeMemoMapUnsafe() + const scope = Scope.makeUnsafe("parallel") + const layerScope = Scope.forkUnsafe(scope, "sequential") + const defaultRunOptions: Effect.RunOptions = { + onFiberStart: Fiber.runIn(scope) + } + const mergeRunOptions = (options?: O): O => + options + ? { + ...options, + onFiberStart: options.onFiberStart ? + (fiber) => { + defaultRunOptions.onFiberStart!(fiber) + options.onFiberStart!(fiber) + } : + defaultRunOptions.onFiberStart + } + : defaultRunOptions as O + let buildFiber: Fiber.Fiber, ER> | undefined + const contextEffect = Effect.withFiber, ER>((fiber) => { + if (!buildFiber) { + buildFiber = Effect.runFork( + Effect.tap( + Layer.buildWithMemoMap(layer, memoMap, layerScope), + (context) => + Effect.sync(() => { + self.cachedContext = context + }) + ), + { ...defaultRunOptions, scheduler: fiber.currentScheduler } + ) + } + return Effect.flatten(Fiber.await(buildFiber)) + }) + const self: ManagedRuntime = { + [TypeId]: TypeId, + memoMap, + scope, + contextEffect: contextEffect, + cachedContext: undefined, + context() { + return self.cachedContext === undefined ? + Effect.runPromise(self.contextEffect) : + Promise.resolve(self.cachedContext) + }, + 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 + return Scope.close(self.scope, Exit.void) + }), + runFork(effect: Effect.Effect, options?: Effect.RunOptions): Fiber.Fiber { + return self.cachedContext === undefined ? + Effect.runFork(provide(self, effect), mergeRunOptions(options)) : + Effect.runForkWith(self.cachedContext)(effect, mergeRunOptions(options)) + }, + runCallback( + effect: Effect.Effect, + options?: Effect.RunOptions & { + readonly onExit: (exit: Exit.Exit) => void + } + ): (interruptor?: number | undefined) => void { + return self.cachedContext === undefined ? + Effect.runCallback(provide(self, effect), mergeRunOptions(options)) : + Effect.runCallbackWith(self.cachedContext)(effect, mergeRunOptions(options)) + }, + runSyncExit(effect: Effect.Effect): Exit.Exit { + return self.cachedContext === undefined ? + Effect.runSyncExit(provide(self, effect)) : + Effect.runSyncExitWith(self.cachedContext)(effect) + }, + runSync(effect: Effect.Effect): A { + return self.cachedContext === undefined ? + Effect.runSync(provide(self, effect)) : + Effect.runSyncWith(self.cachedContext)(effect) + }, + runPromiseExit(effect: Effect.Effect, options?: Effect.RunOptions): Promise> { + return self.cachedContext === undefined ? + Effect.runPromiseExit(provide(self, effect), mergeRunOptions(options)) : + Effect.runPromiseExitWith(self.cachedContext)(effect, mergeRunOptions(options)) + }, + runPromise(effect: Effect.Effect, options?: { + readonly signal?: AbortSignal | undefined + }): Promise { + return self.cachedContext === undefined ? + Effect.runPromise(provide(self, effect), mergeRunOptions(options)) : + Effect.runPromiseWith(self.cachedContext)(effect, mergeRunOptions(options)) + } + } + return self +} + +function provide( + managed: ManagedRuntime, + effect: Effect.Effect +): Effect.Effect { + return Effect.flatMap( + managed.contextEffect, + (context) => Effect.provideContext(effect, context) + ) +} diff --git a/.repos/effect/packages/effect/src/Match.ts b/.repos/effect/packages/effect/src/Match.ts new file mode 100644 index 000000000..510da5ef6 --- /dev/null +++ b/.repos/effect/packages/effect/src/Match.ts @@ -0,0 +1,2658 @@ +/** + * Builds pattern matchers for TypeScript values. + * + * `Match` lets you add ordered cases and then finish them with a result, + * fallback, `Option`, or exhaustive check. Use `Match.type` to define a + * reusable matcher for a type, or `Match.value` to match one value immediately. + * Cases can match literal values, predicates, object shapes, tags, negated + * patterns, and common checks such as strings, numbers, records, and class + * instances. + * + * @since 4.0.0 + */ +import * as internal from "./internal/matcher.ts" +import type * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import type * as Result from "./Result.ts" +import type * as T from "./Types.ts" +import type { Unify } from "./Unify.ts" + +const TypeId = internal.TypeId + +/** + * 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 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 import.meta.vitest + * import { Match } from "effect" + * + * // Simulated dynamic input that can be a string or a number + * const input: string | number = "some input" + * + * // ┌─── string + * // ▼ + * const result = Match.value(input).pipe( + * // Match if the value is a number + * Match.when(Match.number, (n) => `number: ${n}`), + * // Match if the value is a string + * Match.when(Match.string, (s) => `string: ${s}`), + * // Ensure all possible cases are covered + * Match.exhaustive + * ) + * + * result // => "string: some input" + * ``` + * + * @category models + * @since 4.0.0 + */ +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. + * + * **Details** + * + * A `TypeMatcher` is created when using `Match.type()` and allows you to define + * patterns that will be applied to values of the specified type. It maintains + * type-level information about the input type, applied filters, remaining cases, + * and expected results. + * + * **Example** (Creating a type matcher) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Create a TypeMatcher for string | number + * const matcher = Match.type().pipe( + * Match.when(Match.string, (s) => `String: ${s}`), + * Match.when(Match.number, (n) => `Number: ${n}`), + * Match.exhaustive + * ) + * + * matcher("hello") // => "String: hello" + * matcher(42) // => "Number: 42" + * ``` + * + * @category models + * @since 4.0.0 + */ +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 + readonly _filters: T.Covariant + readonly _remaining: T.Covariant + readonly _result: T.Covariant + readonly _return: T.Covariant + readonly _args: T.Contravariant + } + readonly cases: ReadonlyArray + readonly select: (...args: Array) => unknown + add(_case: Case): TypeMatcher +} + +/** + * Represents a pattern matcher that operates on a specific provided value. + * + * **Details** + * + * A `ValueMatcher` is created when using `Match.value(someValue)` and contains + * the actual value to be matched against. It tracks both the provided value + * and the result of applying patterns to determine matches. Its optional + * seventh type parameter is the matcher flavor and defaults to `ValueFlavor`. + * + * **Example** (Creating a value matcher) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const input = { type: "user", name: "Alice", age: 30 } + * + * // Create a ValueMatcher for the specific input + * const result = Match.value(input).pipe( + * Match.when({ type: "user" }, (user) => `User: ${user.name}`), + * Match.when({ type: "admin" }, (admin) => `Admin: ${admin.name}`), + * Match.orElse(() => "Unknown type") + * ) + * + * result // => "User: Alice" + * ``` + * + * @category models + * @since 4.0.0 + */ +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 +} + +/** + * Represents a single pattern matching case. + * + * **When to use** + * + * Use as the common public type for code that needs to inspect, store, or pass + * either positive or negative pattern matching cases. + * + * **Details** + * + * A `Case` can be either a positive match (`When`) or a negative match (`Not`). + * Cases are the building blocks of pattern matching logic and determine + * how values are tested and transformed. + * + * @see {@link When} for positive cases + * @see {@link Not} for negative cases + * + * @category models + * @since 4.0.0 + */ +export type Case = When | Not + +/** + * Represents a positive pattern matching case. + * + * **Details** + * + * A `When` case contains the logic to test if a value matches a specific pattern + * and the function to evaluate when the pattern matches. It's the primary + * building block for pattern matching conditions. + * + * **Example** (Creating positive match cases) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // When creates cases that match specific patterns + * const stringMatcher = Match.type().pipe( + * Match.when(Match.string, (s: string) => `Got string: ${s}`), + * Match.when(Match.number, (n: number) => `Got number: ${n}`), + * Match.exhaustive + * ) + * + * stringMatcher("hello") // => "Got string: hello" + * stringMatcher(42) // => "Got number: 42" + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface When { + readonly _tag: "When" + guard(u: unknown): boolean + evaluate(input: unknown, ...args: Array): any +} + +/** + * Represents a negative pattern matching case. + * + * **Details** + * + * A `Not` case contains the logic to test if a value does NOT match a specific + * pattern and the function to evaluate when the pattern doesn't match. It's used + * for exclusion-based pattern matching. + * + * **Example** (Creating negative match cases) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Not creates cases that exclude specific patterns + * const matcher = Match.type().pipe( + * // Match any string except "forbidden" + * Match.not("forbidden", (s) => `Allowed: ${s}`), + * Match.orElse(() => "This string is forbidden") + * ) + * + * matcher("hello") // => "Allowed: hello" + * matcher("forbidden") // => "This string is forbidden" + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface Not { + readonly _tag: "Not" + guard(u: unknown): boolean + evaluate(input: unknown, ...args: Array): any +} + +/** + * Creates a matcher for a specific type. + * + * **When to use** + * + * Use to build a reusable matcher function for values of a known input type. + * + * **Details** + * + * This function defines a `Matcher` that operates on a given type, allowing you + * to specify conditions for handling different cases. Once the matcher is + * created, you can use pattern-matching functions like {@link when} to define + * how different values should be processed. + * + * **Example** (Matching Numbers and Strings) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Create a matcher for values that are either strings or numbers + * // + * // ┌─── (u: string | number) => string + * // ▼ + * const match = Match.type().pipe( + * // Match when the value is a number + * Match.when(Match.number, (n) => `number: ${n}`), + * // Match when the value is a string + * Match.when(Match.string, (s) => `string: ${s}`), + * // Ensure all possible cases are handled + * Match.exhaustive + * ) + * + * match(0) // => "number: 0" + * + * match("hello") // => "string: hello" + * ``` + * + * @see {@link value} for creating a matcher from a specific value. + * + * @category constructors + * @since 4.0.0 + */ +export const type: () => Matcher, I, never, never> = internal.type + +/** + * Creates a reusable matcher from a function that selects the value to match. + * + * The compiled matcher keeps the selector's original argument list. Case + * handlers receive the narrowed selected value followed by those arguments. + * + * @example + * ```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. + * + * **When to use** + * + * Use to match one concrete input immediately. + * + * **Details** + * + * This function allows you to define a `Matcher` directly from a given value, + * rather than from a type. This is useful when working with known values, + * enabling structured pattern matching on objects, primitives, or any data + * structure. + * + * Once the matcher is created, you can use pattern-matching functions like + * {@link when} to define how different cases should be handled. + * + * **Example** (Matching an Object by Property) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const input = { name: "John", age: 30 } + * + * // Create a matcher for the specific object + * const result = Match.value(input).pipe( + * // Match when the 'name' property is "John" + * Match.when( + * { name: "John" }, + * (user) => `${user.name} is ${user.age} years old` + * ), + * // Provide a fallback if no match is found + * Match.orElse(() => "Oh, not John") + * ) + * + * result // => "John is 30 years old" + * ``` + * + * @see {@link type} for creating a matcher from a specific type. + * + * @category constructors + * @since 4.0.0 + */ +export const value: ( + i: I +) => Matcher, I, never, ValueFlavor> = internal.value + +/** + * Creates a match function for a specific value with discriminated union handling. + * + * **Details** + * + * This function provides a convenient way to pattern match on discriminated unions + * by providing an object that maps each `_tag` value to its corresponding handler. + * It's similar to a switch statement but with better type safety and exhaustiveness checking. + * + * **Example** (Matching value tags) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type Status = { readonly _tag: "Success"; readonly data: string } + * + * const success: Status = { _tag: "Success", data: "Hello" } + * + * // Simple valueTags usage + * const message = Match.valueTags(success, { + * Success: (result) => `Success: ${result.data}` + * }) + * + * message // => "Success: Hello" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const valueTags: { + < + const I, + P extends + & { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any } + & { readonly [Tag in Exclude>]: never } + >(fields: P): (input: I) => Unify> + < + const I, + P extends + & { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any } + & { readonly [Tag in Exclude>]: never } + >(input: I, fields: P): Unify> +} = internal.valueTags + +/** + * Creates a type-safe match function for discriminated unions based on `_tag` field. + * + * **Details** + * + * This function allows you to define exhaustive pattern matching for discriminated unions + * by providing handlers for each possible `_tag` value. It ensures type safety and + * can optionally enforce a specific return type across all branches. + * + * **Example** (Matching type tags) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type Result = + * | { readonly _tag: "Success"; readonly data: string } + * | { readonly _tag: "Error"; readonly message: string } + * | { readonly _tag: "Loading" } + * + * // Create a matcher with specific return type + * const formatResult = Match.typeTags()({ + * Success: (result) => `Data: ${result.data}`, + * Error: (result) => `Error: ${result.message}`, + * Loading: () => "Loading..." + * }) + * + * formatResult({ _tag: "Success", data: "Hello World" }) // => "Data: Hello World" + * + * formatResult({ _tag: "Error", message: "Network failed" }) // => "Error: Network failed" + * + * // Create a matcher with inferred return type + * const processResult = Match.typeTags()({ + * Success: (result) => ({ type: "ok", value: result.data }), + * Error: (result) => ({ type: "error", error: result.message }), + * Loading: () => ({ type: "pending" }) + * }) + * + * processResult({ _tag: "Loading" }) // => { type: "pending" } + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const typeTags: { + (): < + P extends + & { + readonly [Tag in Types.Tags<"_tag", I> & string]: ( + _: Extract + ) => Ret + } + & { readonly [Tag in Exclude>]: never } + >(fields: P) => (input: I) => Ret + (): < + P extends + & { + readonly [Tag in Types.Tags<"_tag", I> & string]: ( + _: Extract + ) => any + } + & { readonly [Tag in Exclude>]: never } + >(fields: P) => (input: I) => Unify> +} = internal.typeTags + +/** + * Ensures that all branches of a matcher return a specific type. + * + * **Details** + * + * This function enforces a consistent return type across all pattern-matching + * branches. By specifying a return type, TypeScript will check that every + * matching condition produces a value of the expected type. + * + * **Important:** This function must be the first step in the matcher pipeline. + * If used later, TypeScript will not enforce type consistency correctly. + * + * **Example** (Validating return type consistency) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const match = Match.type<{ a: number } | { b: string }>().pipe( + * // Ensure all branches return a string + * Match.withReturnType(), + * // ❌ Type error: 'number' is not assignable to type 'string' + * // @ts-expect-error + * Match.when({ a: Match.number }, (_) => _.a), + * // ✅ Correct: returns a string + * Match.when({ b: Match.string }, (_) => _.b), + * Match.exhaustive + * ) + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const withReturnType: () => >( + self: Matcher +) => [Ret] extends [[A] extends [never] ? any : A] ? Matcher + : "withReturnType constraint does not extend Result type" = internal.withReturnType + +/** + * Defines a condition for matching values. + * + * **When to use** + * + * Use to add one positive pattern case to a `Match.type` or `Match.value` + * pipeline when a direct value, predicate, or structured object pattern should + * run a handler for matching input. + * + * **Details** + * + * Supports both direct value comparisons and predicate functions. If the + * pattern matches, the associated function is executed and the matched input is + * removed from the remaining cases tracked by the matcher. + * + * **Example** (Matching with values and predicates) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Create a matcher for objects with an "age" property + * const match = Match.type<{ age: number }>().pipe( + * // Match when age is greater than 18 + * Match.when( + * { age: (age: number) => age > 18 }, + * (user: { age: number }) => `Age: ${user.age}` + * ), + * // Match when age is exactly 18 + * Match.when({ age: 18 }, () => "You can vote"), + * // Fallback case for all other ages + * Match.orElse((user: { age: number }) => `${user.age} is too young`) + * ) + * + * match({ age: 20 }) // => "Age: 20" + * + * match({ age: 18 }) // => "You can vote" + * + * match({ age: 4 }) // => "4 is too young" + * ``` + * + * @see {@link whenOr} for handling any one of several patterns with the same handler + * @see {@link whenAnd} for requiring all provided patterns to match before running a handler + * @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 + * @since 4.0.0 + */ +export const when: < + R, + const P extends Types.PatternPrimitive | Types.PatternBase, + Ret, + Args extends Array, + Fn extends (_: Types.WhenMatch, ...args: Args) => Ret +>( + pattern: P, + f: Fn +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>, + Types.ApplyFilters>>, + A | ReturnType, + Pr, + Ret, + Args +> = internal.when + +/** + * Matches one of multiple patterns in a single condition. + * + * **Details** + * + * This function allows defining a condition where a value matches any of the + * provided patterns. If a match is found, the associated function is executed. + * It simplifies cases where multiple patterns share the same handling logic. + * + * Unlike {@link when}, which requires separate conditions for each pattern, + * this function enables combining them into a single statement, making the + * matcher more concise. + * + * **Example** (Matching one of several patterns) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type ErrorType = + * | { readonly _tag: "NetworkError"; readonly message: string } + * | { readonly _tag: "TimeoutError"; readonly duration: number } + * | { readonly _tag: "ValidationError"; readonly field: string } + * + * const handleError = Match.type().pipe( + * Match.whenOr( + * { _tag: "NetworkError" }, + * { _tag: "TimeoutError" }, + * () => "Retry the request" + * ), + * Match.when({ _tag: "ValidationError" }, (_) => `Invalid field: ${_.field}`), + * Match.exhaustive + * ) + * + * handleError({ _tag: "NetworkError", message: "No connection" }) // => "Retry the request" + * + * handleError({ _tag: "ValidationError", field: "email" }) // => "Invalid field: email" + * ``` + * + * @category defining patterns + * @since 4.0.0 + */ +export const whenOr: < + R, + const P extends ReadonlyArray | Types.PatternBase>, + Ret, + Args extends Array, + Fn extends (_: Types.WhenMatch, ...args: Args) => Ret +>( + ...args: [...patterns: P, f: Fn] +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>, + Types.ApplyFilters>>, + A | ReturnType, + Pr, + Ret, + Args +> = internal.whenOr + +/** + * Matches a value that satisfies all provided patterns. + * + * **Details** + * + * This function allows defining a condition where a value must match all the + * given patterns simultaneously. If the value satisfies every pattern, the + * associated function is executed. + * + * Unlike {@link when}, which matches a single pattern at a time, this function + * ensures that multiple conditions are met before executing the callback. It is + * useful when checking for values that need to fulfill multiple criteria at + * once. + * + * **Example** (Matching all provided patterns) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type User = { readonly age: number; readonly role: "admin" | "user" } + * + * const checkUser = Match.type().pipe( + * Match.whenAnd( + * { age: (n) => n >= 18 }, + * { role: "admin" }, + * () => "Admin access granted" + * ), + * Match.orElse(() => "Access denied") + * ) + * + * checkUser({ age: 20, role: "admin" }) // => "Admin access granted" + * + * checkUser({ age: 20, role: "user" }) // => "Access denied" + * ``` + * + * @category defining patterns + * @since 4.0.0 + */ +export const whenAnd: < + R, + const P extends ReadonlyArray | Types.PatternBase>, + Ret, + Args extends Array, + Fn extends (_: Types.WhenMatch>, ...args: Args) => Ret +>( + ...args: [...patterns: P, f: Fn] +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>>, + Types.ApplyFilters>>>, + A | ReturnType, + Pr, + Ret, + Args +> = internal.whenAnd + +/** + * Matches values based on a specified discriminant field. + * + * **When to use** + * + * Use to match one or more exact values of a discriminator field. + * + * **Details** + * + * This function is used to define pattern matching on objects that follow a + * **discriminated union** structure, where a specific field (e.g., `type`, + * `kind`, `_tag`) determines the variant of the object. It allows matching + * multiple values of the discriminant and provides a function to handle the + * matched cases. + * + * **Example** (Matching on a discriminator field) + * + * ```ts import.meta.vitest + * import { Match, pipe } from "effect" + * + * const match = pipe( + * Match.type< + * { type: "A"; a: string } | { type: "B"; b: number } | { + * type: "C" + * c: boolean + * } + * >(), + * Match.discriminator("type")("A", "B", (_) => `A or B: ${_.type}`), + * 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 + * @since 4.0.0 + */ +export const discriminator: ( + field: D +) => & string, Ret, Fn extends (_: Extract>) => Ret>( + ...pattern: [first: P, ...values: Array

, f: Fn] +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>>, + Types.ApplyFilters>>>, + ReturnType | A, + Pr, + Ret, + Args +> = internal.tag + +/** + * Matches values where the `_tag` field starts with a given prefix. + * + * **Details** + * + * This function allows you to match on values in a **discriminated union** + * based on whether the `_tag` field starts with a specified prefix. It is + * useful for handling hierarchical or namespaced tags, where multiple related + * cases share a common prefix. + * + * **Example** (Matching tag prefixes) + * + * ```ts import.meta.vitest + * import { Match, pipe } from "effect" + * + * const match = pipe( + * Match.type<{ _tag: "A" } | { _tag: "B" } | { _tag: "A.A" } | {}>(), + * Match.tagStartsWith("A", (_) => 1 as const), + * Match.tagStartsWith("B", (_) => 2 as const), + * Match.orElse((_) => 3 as const) + * ) + * + * match({ _tag: "A" }) // => 1 + * match({ _tag: "B" }) // => 2 + * match({ _tag: "A.A" }) // => 1 + * ``` + * + * @category defining patterns + * @since 4.0.0 + */ +export const tagStartsWith: < + R, + P extends string, + Ret, + Fn extends (_: Extract>) => Ret +>( + pattern: P, + f: Fn +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>>, + Types.ApplyFilters>>>, + ReturnType | A, + Pr, + Ret +> = internal.tagStartsWith + +/** + * Matches values based on their `_tag` field, mapping each tag to a + * corresponding handler. + * + * **Details** + * + * This function provides a way to handle discriminated unions by mapping `_tag` + * values to specific functions. Each handler receives the matched value and + * returns a transformed result. If all possible tags are handled, you can + * enforce exhaustiveness using `Match.exhaustive` to ensure no case is missed. + * + * **Example** (Mapping tag handlers) + * + * ```ts import.meta.vitest + * import { Match, pipe } from "effect" + * + * const match = pipe( + * Match.type< + * { _tag: "A"; a: string } | { _tag: "B"; b: number } | { + * _tag: "C" + * c: boolean + * } + * >(), + * Match.tags({ + * A: (a) => a.a, + * B: (b) => b.b, + * C: (c) => c.c + * }), + * Match.exhaustive + * ) + * match({ _tag: "A", a: "ok" }) // => "ok" + * ``` + * + * @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 } + & { readonly [Tag in Exclude>]: never } +>( + fields: P +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>>, + Types.ApplyFilters>>>, + A | ReturnType, + Pr, + Ret +> = internal.tags + +/** + * Matches values based on their `_tag` field and requires handling of all + * possible cases. + * + * **Details** + * + * This function is designed for **discriminated unions** where every possible + * `_tag` value must have a corresponding handler. Unlike {@link tags}, this + * function ensures **exhaustiveness**, meaning all cases must be explicitly + * handled. If a `_tag` value is missing from the mapping, TypeScript will + * report an error. + * + * **Example** (Handling all tag cases) + * + * ```ts import.meta.vitest + * import { Match, pipe } from "effect" + * + * const match = pipe( + * Match.type< + * { _tag: "A"; a: string } | { _tag: "B"; b: number } | { + * _tag: "C" + * c: boolean + * } + * >(), + * Match.tagsExhaustive({ + * A: (a) => a.a, + * B: (b) => b.b, + * C: (c) => c.c + * }) + * ) + * match({ _tag: "B", b: 42 }) // => 42 + * ``` + * + * @category defining patterns + * @since 4.0.0 + */ +export const tagsExhaustive: < + R, + Ret, + P extends + & { readonly [Tag in Types.Tags<"_tag", R> & string]: (_: Extract>) => Ret } + & { readonly [Tag in Exclude>]: never } +>( + fields: P +) => ( + self: Matcher +) => [Pr] extends [never] ? (u: I) => Unify> : Unify> = + internal.tagsExhaustive + +/** + * Creates a pattern that excludes a specific value while allowing all others. + * + * **When to use** + * + * Use to add a negative pattern case for inputs that should match when another + * pattern does not. + * + * **Details** + * + * Any excluded value bypasses the provided function and continues matching + * through later cases. + * + * **Example** (Ignoring a specific value) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Create a matcher for string or number values + * const match = Match.type().pipe( + * // Match any value except "hi", returning "ok" + * Match.not("hi", () => "ok"), + * // Fallback case for when the value is "hi" + * Match.orElse(() => "fallback") + * ) + * + * match("hello") // => "ok" + * + * match("hi") // => "fallback" + * ``` + * + * @see {@link when} for adding a positive pattern case + * + * @category defining patterns + * @since 4.0.0 + */ +export const not: < + R, + const P extends Types.PatternPrimitive | Types.PatternBase, + Ret, + Args extends Array, + Fn extends (_: Types.NotMatch, ...args: Args) => Ret +>( + pattern: P, + f: Fn +) => ( + self: Matcher +) => Matcher< + I, + Types.AddOnly>, + Types.ApplyFilters>>, + A | ReturnType, + Pr, + Ret, + Args +> = internal.not + +/** + * Matches non-empty strings. + * + * **When to use** + * + * Use to match strings whose length is greater than zero. + * + * **Details** + * + * This predicate matches any string that contains at least one character, + * effectively filtering out empty strings (""). + * + * **Example** (Matching non-empty strings) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const processInput = Match.type() + * .pipe( + * Match.when(Match.nonEmptyString, (str) => `Valid input: ${str}`), + * Match.orElse(() => "Input cannot be empty") + * ) + * + * processInput("hello") // => "Valid input: hello" + * + * processInput("") // => "Input cannot be empty" + * + * processInput(" ") // => "Valid input: " + * ``` + * + * @see {@link string} for matching any string + * + * @category guards + * @since 4.0.0 + */ +export const nonEmptyString: SafeRefinement = internal.nonEmptyString + +/** + * Matches a specific set of literal values (e.g., `Match.is("a", 42, true)`). + * + * **When to use** + * + * Use to match one of several literal primitive or null values. + * + * **Details** + * + * This function creates a predicate that matches any of the provided literal values. + * It's useful for matching against multiple specific values in a single pattern. + * + * **Example** (Matching literal values) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const handleStatus = Match.type() + * .pipe( + * Match.when(Match.is("success", "ok", 200), () => "Operation successful"), + * Match.when(Match.is("error", "failed", 500), () => "Operation failed"), + * Match.when(Match.is(0, false, null), () => "Falsy value"), + * Match.orElse((value) => `Unknown status: ${value}`) + * ) + * + * handleStatus("success") // => "Operation successful" + * + * handleStatus(200) // => "Operation successful" + * + * handleStatus("failed") // => "Operation failed" + * + * handleStatus(0) // => "Falsy value" + * + * handleStatus("pending") // => "Unknown status: pending" + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const is: < + Literals extends ReadonlyArray +>(...literals: Literals) => SafeRefinement = internal.is + +/** + * Matches values of type `string`. + * + * **Details** + * + * This predicate refines unknown values to strings, allowing pattern matching + * on string types. It's commonly used in type-based matchers to handle string cases. + * + * **Example** (Matching string values) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const processValue = Match.type().pipe( + * Match.when(Match.string, (str) => `String: ${str.toUpperCase()}`), + * Match.when(Match.number, (num) => `Number: ${num * 2}`), + * Match.when(Match.boolean, (bool) => `Boolean: ${bool ? "yes" : "no"}`), + * Match.exhaustive + * ) + * + * processValue("hello") // => "String: HELLO" + * processValue(42) // => "Number: 84" + * processValue(true) // => "Boolean: yes" + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const string: Predicate.Refinement = Predicate.isString + +/** + * Matches values of type `number`. + * + * **When to use** + * + * Use to match primitive number values, including `NaN` and infinities. + * + * **Details** + * + * This predicate refines unknown values to numbers, allowing pattern matching + * on numeric types. It matches all number values including integers, floats, + * `Infinity`, `-Infinity`, and `NaN`. + * + * **Example** (Matching number values) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const categorizeNumber = Match.type().pipe( + * Match.when(Match.number, (num) => { + * if (Number.isNaN(num)) return "Not a number" + * if (!Number.isFinite(num)) return "Infinite" + * if (Number.isInteger(num)) return `Integer: ${num}` + * return `Float: ${num.toFixed(2)}` + * }), + * Match.orElse(() => "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 guards + * @since 4.0.0 + */ +export const number: Predicate.Refinement = Predicate.isNumber + +/** + * Matches any value without restrictions. + * + * **When to use** + * + * Use to define an explicit catch-all pattern when the handler should receive + * the unmatched value. + * + * **Details** + * + * This predicate matches every input, including `undefined`, `null`, objects, + * primitives, and functions. + * + * **Gotchas** + * + * `Match.any` should usually be last because cases are checked in order and + * the first matching case wins. + * + * **Example** (Matching any remaining value) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const describeValue = Match.type() + * .pipe( + * Match.when(Match.string, (str) => `String: ${str}`), + * Match.when(Match.number, (num) => `Number: ${num}`), + * Match.when(Match.boolean, (bool) => `Boolean: ${bool}`), + * Match.when(Match.any, (value) => `Other: ${typeof value}`), + * Match.exhaustive + * ) + * + * describeValue("hello") // => "String: hello" + * + * describeValue(42) // => "Number: 42" + * + * describeValue([1, 2, 3]) // => "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 guards + * @since 4.0.0 + */ +export const any: SafeRefinement = internal.any + +/** + * Matches any defined (non-null and non-undefined) value. + * + * **When to use** + * + * Use to exclude only `null` and `undefined` from a match branch. + * + * **Details** + * + * This predicate matches values that are neither `null` nor `undefined`, + * effectively filtering out nullish values while preserving all other types. + * + * **Example** (Matching defined values) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const processValue = Match.type() + * .pipe( + * Match.when(Match.defined, (value) => `Defined value: ${value}`), + * Match.orElse(() => "Value is null or undefined") + * ) + * + * processValue("hello") // => "Defined value: hello" + * + * processValue(42) // => "Defined value: 42" + * + * processValue(0) // => "Defined value: 0" + * + * processValue("") // => "Defined value: " + * + * processValue(null) // => "Value is null or undefined" + * + * processValue(undefined) // => "Value is null or undefined" + * ``` + * + * @see {@link any} for matching every value without excluding nullish inputs + * + * @category guards + * @since 4.0.0 + */ +export const defined: (u: A) => u is A & {} = internal.defined + +/** + * Matches values of type `boolean`. + * + * **When to use** + * + * Use to match primitive boolean values. + * + * **Details** + * + * This predicate refines unknown values to booleans, allowing pattern matching + * on boolean types. It only matches the primitive boolean values `true` and `false`. + * + * **Example** (Matching boolean values) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const describeTruthiness = Match.type().pipe( + * Match.when( + * Match.boolean, + * (bool) => bool ? "Definitely true" : "Definitely false" + * ), + * Match.when(0, () => "Falsy number"), + * Match.when("", () => "Empty string"), + * Match.when(Match.null, () => "Null value"), + * Match.orElse(() => "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 guards + * @since 4.0.0 + */ +export const boolean: Predicate.Refinement = Predicate.isBoolean + +const _undefined: Predicate.Refinement = Predicate.isUndefined +export { + /** + * Matches the value `undefined`. + * + * **When to use** + * + * Use when a matcher should handle only inputs with no defined value. + * + * **Details** + * + * This refinement is backed by `Predicate.isUndefined`, which checks + * `input === undefined`. + * + * @see {@link defined} for matching non-nullish values + * @see {@link is} for matching literal values + * + * @category guards + * @since 4.0.0 + */ + _undefined as undefined +} + +const _null: Predicate.Refinement = Predicate.isNull +export { + /** + * Matches the value `null`. + * + * **When to use** + * + * Use to handle only the `null` literal in a match branch. + * + * **Details** + * + * This refinement is backed by `Predicate.isNull`, which checks + * `input === null`. + * + * @see {@link defined} for matching non-nullish values + * @see {@link is} for matching literal values + * + * @category guards + * @since 4.0.0 + */ + _null as null +} + +/** + * Matches values of type `bigint`. + * + * **When to use** + * + * Use to match primitive bigint values. + * + * **Details** + * + * This predicate refines unknown values to bigints, allowing pattern matching + * on bigint types. BigInts are used for representing integers with arbitrary precision. + * + * **Example** (Matching bigint values) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const processLargeNumber = Match.type().pipe( + * Match.when(Match.bigint, (big) => { + * if (big > 9007199254740991n) { + * return `Large integer: ${big.toString()}` + * } + * return `BigInt: ${big.toString()}` + * }), + * Match.when(Match.number, (num) => `Regular number: ${num}`), + * Match.orElse(() => "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 guards + * @since 4.0.0 + */ +export const bigint: Predicate.Refinement = Predicate.isBigInt + +/** + * Matches values of type `symbol`. + * + * **Details** + * + * This predicate refines unknown values to symbols, allowing pattern matching + * on symbol types. Symbols are unique identifiers that are often used as + * object keys or for creating private properties. + * + * **Example** (Matching symbol values) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const mySymbol = Symbol("my-symbol") + * const globalSymbol = Symbol.for("global-symbol") + * + * const handleSymbol = Match.type().pipe( + * Match.when(Match.symbol, (sym) => { + * const description = sym.description + * if (description) { + * return `Symbol with description: ${description}` + * } + * return "Symbol without description" + * }), + * Match.orElse(() => "Not a symbol") + * ) + * + * handleSymbol(mySymbol) // => "Symbol with description: my-symbol" + * handleSymbol(Symbol()) // => "Symbol without description" + * handleSymbol("string") // => "Not a symbol" + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const symbol: Predicate.Refinement = Predicate.isSymbol + +/** + * Matches values that are instances of `Date`. + * + * **When to use** + * + * Use to match `Date` instances. + * + * **Details** + * + * This predicate refines unknown values to Date instances, allowing pattern + * matching on Date objects. It only matches actual Date instances, not + * date strings or timestamps. + * + * **Example** (Matching Date instances) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const processDateValue = Match.type().pipe( + * Match.when(Match.date, (date) => { + * if (isNaN(date.getTime())) { + * return "Invalid date" + * } + * return `Date: ${date.toISOString().split("T")[0]}` + * }), + * Match.when(Match.string, (str) => `Date string: ${str}`), + * Match.orElse(() => "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 guards + * @since 4.0.0 + */ +export const date: Predicate.Refinement = Predicate.isDate + +/** + * Matches non-null objects other than arrays. + * + * **When to use** + * + * Use to match broad non-null, non-array object values. + * + * **Details** + * + * This predicate uses `Predicate.isObject`: it returns `true` for values whose + * runtime type is `"object"`, are not `null`, and are not arrays. It can match + * `Date`, `RegExp`, and class instances; use `instanceOf` or a more specific + * pattern when those cases need to be distinguished. + * + * **Example** (Matching record objects) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const analyzeValue = Match.type().pipe( + * Match.when(Match.record, (obj) => { + * const keys = Object.keys(obj) + * const valueCount = keys.length + * return `Object with ${valueCount} properties: [${keys.join(", ")}]` + * }), + * Match.when( + * Match.instanceOf(Array), + * (arr) => `Array with ${arr.length} items` + * ), + * Match.orElse(() => "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 guards + * @since 4.0.0 + */ +export const record: Predicate.Refinement = Predicate.isObject + +/** + * Matches instances of a given class. + * + * **When to use** + * + * Use to match values that are instances of a constructor with type-safe + * narrowing. + * + * **Details** + * + * This predicate checks if a value is an instance of the specified constructor, + * providing type-safe matching for class instances and built-in objects. + * + * **Example** (Matching class instances) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * class CustomError extends Error { + * constructor(message: string, public code: number) { + * super(message) + * } + * } + * + * const handleValue = Match.type() + * .pipe( + * Match.when( + * Match.instanceOf(CustomError), + * (err) => `Custom error: ${err.message} (code: ${err.code})` + * ), + * Match.when( + * Match.instanceOf(Error), + * (err) => `Standard error: ${err.message}` + * ), + * Match.when( + * Match.instanceOf(Array), + * (arr) => `Array with ${arr.length} items` + * ), + * Match.when( + * Match.instanceOf(Map), + * (map) => `Map with ${map.size} entries` + * ), + * Match.orElse((value) => `Other: ${typeof value}`) + * ) + * + * 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 guards + * @since 4.0.0 + */ +export const instanceOf: any>( + constructor: A +) => SafeRefinement, never> = internal.instanceOf + +/** + * Checks whether a value is an instance of a constructor without type-safe narrowing. + * + * **When to use** + * + * Use when you need constructor matching to use the unsafe refinement type. + * + * **Details** + * + * This predicate checks if a value is an instance of the specified constructor + * but doesn't provide the same type safety guarantees as the regular `instanceOf`. + * Use this when you need more flexibility but understand the type safety implications. + * + * **Example** (Matching class instances unsafely) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * class CustomError extends Error { + * constructor(message: string, public code: number) { + * super(message) + * } + * } + * + * // When you need to match instances but handle type narrowing manually + * const handleError = Match.type().pipe( + * Match.when(Match.instanceOfUnsafe(CustomError), (err: any) => { + * // Manual type assertion needed + * const customErr = err as CustomError + * return `Custom error ${customErr.code}: ${customErr.message}` + * }), + * Match.orElse(() => "Not a CustomError") + * ) + * handleError(new CustomError("failed", 500)) // => "Custom error 500: failed" + * ``` + * + * @see {@link instanceOf} for type-safe constructor matching + * + * @category guards + * @since 4.0.0 + */ +export const instanceOfUnsafe: any>( + constructor: A +) => SafeRefinement, InstanceType> = internal.instanceOf + +/** + * Provides a fallback value when no patterns match. + * + * **When to use** + * + * Use to finalize a matcher with a fallback for unmatched input. + * + * **Details** + * + * This function ensures that a matcher always returns a valid result, even if + * no defined patterns match. It acts as a default case, similar to the + * `default` clause in a `switch` statement or the final `else` in an `if-else` + * chain. + * + * **Example** (Providing a default value when no patterns match) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Create a matcher for string or number values + * const match = Match.type().pipe( + * // Match when the value is "a" + * Match.when("a", () => "ok"), + * // Fallback when no patterns match + * Match.orElse(() => "fallback") + * ) + * + * match("a") // => "ok" + * + * match("b") // => "fallback" + * ``` + * + * @see {@link option} for finalizing unmatched input as `Option.none` + * @see {@link result} for returning unmatched input as a `Result` failure + * @see {@link orElseAbsurd} for finalizing when unmatched input should be impossible + * + * @category completion + * @since 4.0.0 + */ +export const orElse: , F extends (_: RA, ...args: Args) => Ret>( + f: F +) => ( + 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 +/** + * Returns a matcher that throws an error if no pattern matches. + * + * **When to use** + * + * Use to finalize a matcher when every remaining unmatched case should be + * impossible. + * + * **Details** + * + * This function finalizes a matcher by ensuring that if no patterns match, an + * error is thrown. It is useful when all cases should be covered, and any + * unexpected input should trigger an error instead of returning a default + * value. + * + * When used, this function removes the need for an explicit fallback case and + * ensures that an unmatched value is never silently ignored. + * + * **Example** (Throwing on unmatched input) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const strictMatcher = Match.type<"a" | "b">().pipe( + * Match.when("a", () => "Found A"), + * Match.when("b", () => "Found B"), + * // Will throw if input is neither "a" nor "b" + * Match.orElseAbsurd + * ) + * + * strictMatcher("a") // => "Found A" + * strictMatcher("b") // => "Found B" + * + * // This would throw an error at runtime: + * // strictMatcher("c" as any) // throws + * ``` + * + * @see {@link exhaustive} for compile-time exhaustive matcher finalization + * @see {@link orElse} for providing a fallback for unmatched input + * + * @category completion + * @since 4.0.0 + */ +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 + * cases. + * + * **Details** + * + * This function ensures that the result of a matcher is always wrapped in an + * `Result`, allowing clear differentiation between successful matches + * (`Ok(value)`) and cases where no pattern matched (`Err(unmatched + * value)`). + * + * This approach is particularly useful when handling optional values or when an + * unmatched case should be explicitly handled rather than returning a default + * value or throwing an error. + * + * **Example** (Extracting a user role with `Match.result`) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type User = { readonly role: "admin" | "editor" | "viewer" } + * + * // Create a matcher to extract user roles + * const getRole = Match.type().pipe( + * Match.when({ role: "admin" }, () => "Has full access"), + * Match.when({ role: "editor" }, () => "Can edit content"), + * Match.result // Wrap the result in an Result + * ) + * + * getRole({ role: "admin" })._tag // => "Success" + * + * getRole({ role: "viewer" })._tag // => "Failure" + * ``` + * + * @category completion + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use to finalize a matcher when unmatched input is expected and should become + * `Option.none`. + * + * **Details** + * + * This function ensures that the result of a matcher is wrapped in an `Option`, + * making it easy to handle cases where no pattern matches. If a match is found, + * it returns `Some(value)`, otherwise, it returns `None`. + * + * This is useful in cases where a missing match is expected and should be + * handled explicitly rather than throwing an error or returning a default + * value. + * + * **Example** (Extracting a user role with `Match.option`) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type User = { readonly role: "admin" | "editor" | "viewer" } + * + * // Create a matcher to extract user roles + * const getRole = Match.type().pipe( + * Match.when({ role: "admin" }, () => "Has full access"), + * Match.when({ role: "editor" }, () => "Can edit content"), + * Match.option // Wrap the result in an Option + * ) + * + * getRole({ role: "admin" })._tag // => "Some" + * + * getRole({ role: "viewer" })._tag // => "None" + * ``` + * + * @see {@link result} for preserving unmatched input as a `Result` failure + * @see {@link orElse} for replacing unmatched input with a fallback value + * + * @category completion + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use to require TypeScript to reject incomplete matcher definitions before the + * matcher is turned into a function. + * + * **Details** + * + * If any case is still unmatched, the matcher does not type-check as + * exhaustive. + * + * **Example** (Ensuring all cases are covered) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Create a matcher for string or number values + * const match = Match.type().pipe( + * // Match when the value is a number + * Match.when(Match.number, (n) => `number: ${n}`), + * // Mark the match as exhaustive, ensuring all cases are handled + * // TypeScript will throw an error if any case is missing + * // @ts-expect-error Type 'string' is not assignable to type 'never' + * Match.exhaustive + * ) + * ``` + * + * @category completion + * @since 4.0.0 + */ +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" + +/** + * A safe refinement that narrows types without runtime errors. + * + * **Details** + * + * `SafeRefinement` provides a way to refine types in pattern matching while + * maintaining type safety. Unlike regular predicates, safe refinements can + * transform the matched value's type without throwing runtime errors. + * + * **Example** (Using safe refinements) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Built-in safe refinements + * const processValue = Match.type().pipe( + * Match.when(Match.string, (s) => s.toUpperCase()), + * Match.when(Match.number, (n) => n * 2), + * Match.when(Match.defined, (value) => `Defined: ${value}`), + * Match.orElse(() => "Undefined or null") + * ) + * + * processValue("hello") // => "HELLO" + * processValue(21) // => 42 + * processValue(true) // => "Defined: true" + * processValue(null) // => "Undefined or null" + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface SafeRefinement { + readonly [SafeRefinementId]: (a: A) => R +} + +const Fail = Symbol.for("effect/Fail") +type Fail = typeof Fail + +/** + * A namespace containing utility types for Match operations. + * + * **Details** + * + * This namespace provides advanced type-level utilities used internally by the + * Match module to perform complex pattern matching, type narrowing, and filter + * application. These types enable the sophisticated type inference that makes + * pattern matching both type-safe and ergonomic. + * + * @since 4.0.0 + */ +export declare namespace Types { + /** + * Computes the matched type when a pattern P is applied to type R. + * + * **Details** + * + * This utility type determines what type a value will have after successfully + * matching against a pattern. It handles refinements, predicates, and complex + * object patterns to provide accurate type narrowing. + * + * **Example** (Computing matched types) + * + * ```ts import.meta.vitest + * import type { Match } from "effect" + * + * // WhenMatch computes the narrowed type after pattern matching + * type StringMatch = Match.Types.WhenMatch + * // Result: string + * + * type ObjectMatch = Match.Types.WhenMatch< + * { type: "user"; name: string } | { + * type: "admin" + * permissions: Array + * }, + * { type: "user" } + * > + * // Result: { type: "user"; name: string } + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type WhenMatch = + // check for any + [0] extends [1 & R] ? ResolvePred

: + P extends SafeRefinement ? SP + : P extends Predicate.Refinement + // try to narrow refinement + ? [Extract] extends [infer X] ? [X] extends [never] + // fallback to original refinement + ? RP + : X + : never + : P extends PredicateA ? PP + : ExtractMatch + + /** + * Computes the remaining type when a pattern P is excluded from type R. + * + * **Details** + * + * This utility type determines what type remains after a `Match.not` pattern + * excludes certain values. It's the complement of `WhenMatch`, calculating + * what's left after removing the matched portion. + * + * **Example** (Computing unmatched types) + * + * ```ts import.meta.vitest + * import type { Match } from "effect" + * + * // NotMatch computes what remains after exclusion + * type NotString = Match.Types.NotMatch< + * string | number | boolean, + * typeof Match.string + * > + * // Result: number | boolean + * + * type NotSpecificValue = Match.Types.NotMatch<"a" | "b" | "c", "a"> + * // Result: "b" | "c" + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type NotMatch = Exclude>> + + type PForNotMatch

= [ToInvertedRefinement

] extends [infer X] ? X + : never + + /** + * Resolves a pattern to its matched type for use in type computations. + * + * **Details** + * + * This utility type processes patterns (predicates, refinements, objects) + * and resolves them to their corresponding matched types. It's used internally + * to compute type transformations during pattern matching. + * + * **Example** (Resolving match patterns) + * + * ```ts import.meta.vitest + * import type { Match } from "effect" + * + * // PForMatch resolves patterns to their matched types + * type StringPattern = Match.Types.PForMatch + * // Result: string + * + * type ObjectPattern = Match.Types.PForMatch<{ name: string }> + * // Result: { name: string } + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type PForMatch

= [ResolvePred

] extends [infer X] ? X + : never + + /** + * Computes the excluded type when a pattern P is used for exclusion. + * + * **Details** + * + * This utility type determines what should be excluded from a union type + * when a pattern is used in filtering operations. It transforms patterns + * into their exclusion-safe representations. + * + * **Example** (Computing excluded patterns) + * + * ```ts import.meta.vitest + * import type { Match } from "effect" + * + * // PForExclude computes what to exclude from type operations + * type ExcludeString = Match.Types.PForExclude + * // Used internally to filter out string types + * + * type ExcludeObject = Match.Types.PForExclude<{ type: "admin" }> + * // Used internally to filter out admin objects + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type PForExclude

= [SafeRefinementR>] extends [infer X] ? X + : never + + // utilities + type PredicateA = Predicate.Predicate | Predicate.Refinement + + type SafeRefinementR = A extends never ? never + : A extends SafeRefinement ? R + : A extends Function ? A + : A extends Record ? { [K in keyof A]: SafeRefinementR } + : A + + type ResolvePred = A extends never ? never + : A extends SafeRefinement ? _A + : A extends Predicate.Refinement ? P + : A extends Predicate.Predicate ? P + : A extends Record ? { [K in keyof A]: ResolvePred } + : A + + type ToSafeRefinement = A extends never ? never + : A extends Predicate.Refinement ? SafeRefinement + : A extends Predicate.Predicate ? SafeRefinement + : A extends SafeRefinement ? A + : A extends Record ? { [K in keyof A]: ToSafeRefinement } + : NonLiteralsTo + + type ToInvertedRefinement = A extends never ? never + : A extends Predicate.Refinement ? SafeRefinement

+ : A extends Predicate.Predicate ? SafeRefinement + : A extends SafeRefinement ? SafeRefinement<_R> + : A extends Record ? { [K in keyof A]: ToInvertedRefinement } + : NonLiteralsTo + + type NonLiteralsTo = [A] extends [string | number | boolean | bigint] ? [string] extends [A] ? T + : [number] extends [A] ? T + : [boolean] extends [A] ? T + : [bigint] extends [A] ? T + : A + : A + + /** + * Defines the structure for complex object and array patterns. + * + * **Details** + * + * This type represents patterns that can match against complex data structures + * like objects and arrays. It supports nested pattern matching and partial + * object matching, enabling sophisticated pattern compositions. + * + * **Example** (Describing complex object patterns) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // PatternBase enables complex object patterns + * type UserPattern = Match.Types.PatternBase<{ + * name: string + * age: number + * role: "admin" | "user" + * }> + * // Allows: { name?: string | Predicate, age?: number | Predicate, ... } + * + * // Example usage: + * 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" }) => + * `Admin: ${user.name}` + * ), + * Match.orElse(() => "Not an adult admin") + * ) + * result // => "Admin: Alice" + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type PatternBase = A extends ReadonlyArray ? ReadonlyArray | PatternPrimitive + : A extends Record ? Partial< + { [K in keyof A]: PatternPrimitive | PatternBase } + > + : never + + /** + * Defines primitive patterns that can match simple values. + * + * **Details** + * + * This type represents the building blocks of pattern matching: predicates, + * literal values, and safe refinements. These are the atomic patterns that + * can be composed into more complex matching logic. + * + * @category utility types + * @since 4.0.0 + */ + export type PatternPrimitive = PredicateA | A | SafeRefinement + + /** + * Represents a filter that excludes specific types from a union. + * + * **Details** + * + * `Without` is used internally to track which types should be excluded + * from consideration during pattern matching. It helps implement the + * type-level logic for `Match.not` and other exclusion operations. + * + * **Example** (Tracking excluded types) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Without is used internally when you write: + * 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 utility types + * @since 4.0.0 + */ + export interface Without { + readonly _tag: "Without" + readonly _X: X + } + + /** + * Represents a filter that includes only specific types from a union. + * + * **Details** + * + * `Only` is used internally to track which types should be exclusively + * considered during pattern matching. It helps implement the type-level + * logic for positive matches and type narrowing. + * + * **Example** (Tracking included types) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // Only is used internally when you write: + * 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 utility types + * @since 4.0.0 + */ + export interface Only { + readonly _tag: "Only" + readonly _X: X + } + + /** + * Adds a type to the exclusion filter, expanding what should be filtered out. + * + * **Details** + * + * This utility type manages the accumulation of excluded types during + * pattern matching. When multiple exclusions are applied, it combines + * them into a single filter representation. + * + * **Example** (Accumulating excluded types) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // AddWithout is used when combining multiple exclusions: + * 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 utility types + * @since 4.0.0 + */ + export type AddWithout = [A] extends [Without] ? Without + : [A] extends [Only] ? Only> + : never + + /** + * Adds a type to the inclusion filter, refining what should be included. + * + * **Details** + * + * This utility type manages the refinement of included types during + * pattern matching. It ensures that only the most specific type + * constraints are maintained when multiple positive matches are applied. + * + * **Example** (Refining included types) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * // AddOnly is used when refining positive matches: + * 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 utility types + * @since 4.0.0 + */ + export type AddOnly = [A] extends [Without] ? [X] extends [WX] ? never + : Only + : [A] extends [Only] ? [X] extends [OX] ? Only + : never + : never + + /** + * Applies accumulated filters to an input type, producing the final narrowed type. + * + * **Details** + * + * This utility type takes the collected inclusion/exclusion filters and + * applies them to the input type to compute the final narrowed result. + * It's the culmination of the type-level filtering process. + * + * **Example** (Applying accumulated filters) + * + * ```ts import.meta.vitest + * import type { Match } from "effect" + * + * // ApplyFilters computes the final narrowed type: + * type Result = Match.Types.ApplyFilters< + * string | number | boolean, + * Match.Types.Only + * > + * // Result: string + * + * type ExclusionResult = Match.Types.ApplyFilters< + * string | number | boolean, + * Match.Types.Without + * > + * // Result: number | boolean + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type ApplyFilters = A extends Only ? X + : A extends Without ? Exclude + : never + + /** + * Extracts tag values from a discriminated union based on a discriminant field. + * + * **Details** + * + * This utility type extracts the possible values of a discriminant field + * from a union type. It's used internally to implement tag-based pattern + * matching for discriminated unions. + * + * **Example** (Extracting discriminator tags) + * + * ```ts import.meta.vitest + * import type { Match } from "effect" + * + * type Events = + * | { _tag: "click"; x: number; y: number } + * | { _tag: "keypress"; key: string } + * | { _tag: "scroll"; delta: number } + * + * type EventTags = Match.Types.Tags<"_tag", Events> + * // Result: "click" | "keypress" | "scroll" + * + * type CustomTags = Match.Types.Tags< + * "type", + * | { type: "user"; name: string } + * | { type: "admin"; permissions: Array } + * > + * // Result: "user" | "admin" + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Tags = P extends Record ? X : never + + /** + * Converts an array type to an intersection of its element types. + * + * **Details** + * + * This utility type takes an array of types and converts them into a single + * intersection type. It's used internally when multiple patterns need to + * be satisfied simultaneously (like in `Match.whenAnd`). + * + * **Example** (Converting arrays to intersections) + * + * ```ts import.meta.vitest + * import type { Match } from "effect" + * + * type Combined = Match.Types.ArrayToIntersection<[ + * { name: string }, + * { age: number }, + * { active: boolean } + * ]> + * // Result: { name: string } & { age: number } & { active: boolean } + * // = { name: string; age: number; active: boolean } + * + * // This type utility enables complex type intersections + * // Complex type operations are handled by this utility type + * // for advanced pattern matching scenarios + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type ArrayToIntersection> = T.UnionToIntersection< + A[number] + > + + /** + * Extracts and narrows the matched type from an input type given a pattern. + * + * **Details** + * + * This is the core type utility that performs the actual type extraction + * and narrowing logic. It handles the complex type-level computation that + * determines what type results from applying a pattern to an input type. + * + * **Example** (Extracting matched types) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type StringExtract = Match.Types.ExtractMatch< + * string | number | boolean, + * typeof Match.string + * > + * // Result: string + * + * type ObjectExtract = Match.Types.ExtractMatch< + * { type: "user"; name: string } | { type: "admin"; role: string }, + * { type: "user" } + * > + * // Result: { type: "user"; name: string } + * + * // This powers the type narrowing in: + * Match.when(Match.string, (s) => s.toUpperCase()) + * // ^^^ s is correctly typed as string + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type ExtractMatch = [ExtractAndNarrow] extends [infer EI] ? EI + : never + + type Replace = A extends Function ? A + : A extends Record ? { [K in keyof A]: K extends keyof B ? Replace : A[K] } + : [B] extends [A] ? B + : A + + type MaybeReplace = [P] extends [I] ? P + : [I] extends [P] ? Replace + : Fail + + type BuiltInObjects = + | Function + | Date + | RegExp + | Generator + | { readonly [Symbol.toStringTag]: string } + + type IsPlainObject = T extends BuiltInObjects ? false + : T extends Record ? true + : false + + type Simplify = { [K in keyof A]: A[K] } & {} + + type ExtractAndNarrow = P extends Predicate.Refinement ? + _Out extends Input ? Extract<_Out, Input> + : Extract : + P extends SafeRefinement ? [0] extends [1 & _R] ? Input + : _In extends Input ? Extract<_In, Input> + : Extract + : P extends Predicate.Predicate ? Extract + : Input extends infer I ? Exclude< + I extends ReadonlyArray ? P extends ReadonlyArray ? { + readonly [K in keyof I]: K extends keyof P ? ExtractAndNarrow + : I[K] + } extends infer R ? Fail extends R[keyof R] ? never + : R + : never + : never + : IsPlainObject extends true ? string extends keyof I ? I extends P ? I + : never + : symbol extends keyof I ? I extends P ? I + : never + : Simplify< + & { [RK in Extract]-?: ExtractAndNarrow } + & Omit + > extends infer R ? keyof P extends NonFailKeys ? R + : never + : never + : MaybeReplace extends infer R ? [I] extends [R] ? I + : R + : never, + Fail + > : + never + + type NonFailKeys = keyof A & {} extends infer K ? K extends keyof A ? A[K] extends Fail ? never : K + : never : + never +} diff --git a/.repos/effect/packages/effect/src/Metric.ts b/.repos/effect/packages/effect/src/Metric.ts new file mode 100644 index 000000000..adbf13db7 --- /dev/null +++ b/.repos/effect/packages/effect/src/Metric.ts @@ -0,0 +1,3526 @@ +/** + * Records and reads measurements from Effect programs. + * + * A `Metric` accepts typed update values and stores an aggregated + * state that can be read directly or included in a snapshot. Metrics are used + * for counters, gauges, frequencies, histograms, summaries, and timers. This + * module includes metric constructors, update and read helpers, attributes, + * histogram boundaries, registry snapshots, text dumps, and controls for + * enabling runtime metrics. + * + * @since 2.0.0 + */ + +import * as Arr from "./Array.ts" +import * as Context from "./Context.ts" +import * as Duration from "./Duration.ts" +import type { Effect } from "./Effect.ts" +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" +import { pipeArguments } from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import * as _String from "./String.ts" +import type { Contravariant, Covariant } from "./Types.ts" + +/** + * A `Metric` represents a concurrent metric which accepts update + * values of type `Input` and are aggregated to a value of type `State`. + * + * **Details** + * + * For example, a counter metric would have type `Metric`, + * representing the fact that the metric can be updated with numbers (the amount + * to increment or decrement the counter by), and the state of the counter is a + * number. + * + * There are five primitive metric types supported by Effect: + * + * - Counters + * - Frequencies + * - Gauges + * - Histograms + * - Summaries + * + * **Example** (Using multiple metric types) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create different types of metrics + * const requestCounter: Metric.Counter = Metric.counter("requests", { + * description: "Total requests processed" + * }) + * + * const memoryGauge: Metric.Gauge = Metric.gauge("memory_usage", { + * description: "Current memory usage in MB" + * }) + * + * const statusFrequency: Metric.Frequency = Metric.frequency("status_codes", { + * description: "HTTP status code frequency" + * }) + * + * // All metrics share the same interface for updates and reads + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(memoryGauge, 128) + * yield* Metric.update(statusFrequency, "200") + * + * // All metrics can be read with Metric.value + * const counterState = yield* Metric.value(requestCounter) + * const gaugeState = yield* Metric.value(memoryGauge) + * const frequencyState = yield* Metric.value(statusFrequency) + * + * // Metrics have common properties accessible through the interface: + * // - id: unique identifier + * // - type: metric type ("Counter", "Gauge", "Frequency", etc.) + * // - description: optional human-readable description + * // - attributes: optional key-value attributes for tagging + * + * return { + * counter: { + * id: requestCounter.id, + * type: requestCounter.type, + * state: counterState + * }, + * gauge: { id: memoryGauge.id, type: memoryGauge.type, state: gaugeState }, + * frequency: { + * id: statusFrequency.id, + * type: statusFrequency.type, + * state: frequencyState + * } + * } + * }) + * + * 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 + * @since 2.0.0 + */ +export interface Metric extends Pipeable { + readonly [TypeId]: typeof TypeId + readonly Input: Contravariant + readonly State: Covariant + readonly id: string + readonly type: Metric.Type + readonly description: string | undefined + readonly attributes: Metric.AttributeSet | undefined + readonly valueUnsafe: (context: Context.Context) => State + readonly updateUnsafe: (input: Input, context: Context.Context) => void + readonly modifyUnsafe: (input: Input, context: Context.Context) => void +} + +/** + * A Counter metric that tracks cumulative values that typically only increase. + * + * **When to use** + * + * Use when counters are useful for tracking monotonically increasing values like request counts, + * bytes processed, errors encountered, or any value that accumulates over time. + * + * **Example** (Using counter metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create different types of counters + * const requestCounter: Metric.Counter = Metric.counter( + * "http_requests", + * { + * description: "Total HTTP requests processed", + * incremental: true // Only allows increments + * } + * ) + * + * const bytesCounter: Metric.Counter = Metric.counter( + * "bytes_processed", + * { + * description: "Total bytes processed", + * bigint: true, + * attributes: { service: "data-processor" } + * } + * ) + * + * // Update counters + * yield* Metric.update(requestCounter, 1) // Increment by 1 + * yield* Metric.update(requestCounter, 5) // Increment by 5 (total: 6) + * yield* Metric.update(bytesCounter, 1024n) // Add 1024 bytes + * + * // Read counter state + * const requestState: Metric.CounterState = yield* Metric.value( + * requestCounter + * ) + * const bytesState: Metric.CounterState = yield* Metric.value( + * bytesCounter + * ) + * + * // Counter state contains: + * // - count: current accumulated value + * // - incremental: whether only increments are allowed + * + * return { + * requests: { + * count: requestState.count, + * incremental: requestState.incremental + * }, + * 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 models + * @since 2.0.0 + */ +export interface Counter extends Metric> {} + +/** + * State interface for Counter metrics containing the current count and increment mode. + * + * **Example** (Reading counter state) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create different types of counters + * const requestCounter = Metric.counter("http_requests_total") + * const errorCounter = Metric.counter("errors_total", { incremental: true }) + * const byteCounter = Metric.counter("bytes_processed", { bigint: true }) + * + * // Update counters + * yield* Metric.update(requestCounter, 5) // Add 5 requests + * yield* Metric.update(requestCounter, -2) // Subtract 2 (allowed for non-incremental) + * yield* Metric.update(errorCounter, 3) // Add 3 errors + * yield* Metric.update(errorCounter, -1) // Attempt to subtract (ignored for incremental) + * yield* Metric.update(byteCounter, 1024000n) // Add bytes as bigint + * + * // Read counter states + * const requestState: Metric.CounterState = yield* Metric.value( + * requestCounter + * ) + * const errorState: Metric.CounterState = yield* Metric.value( + * errorCounter + * ) + * const byteState: Metric.CounterState = yield* Metric.value( + * byteCounter + * ) + * // CounterState contains: + * // - count: current count value (number or bigint based on counter type) + * // - incremental: whether counter only allows increases + * + * return { + * requests: { + * total: requestState.count, // 3 (5 - 2, decrements allowed) + * canDecrease: !requestState.incremental // true + * }, + * errors: { + * total: errorState.count, // 3 (subtract ignored) + * canDecrease: !errorState.incremental // false + * }, + * bytes: { + * total: byteState.count, // 1024000n + * canDecrease: !byteState.incremental // true + * } + * } + * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const counts = [result.requests.total, result.errors.total, result.bytes.total] // => [3, 3, 1024000n] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface CounterState { + readonly count: Input extends bigint ? bigint : number + readonly incremental: boolean +} + +/** + * A Frequency metric interface that counts occurrences of discrete string values. + * + * **When to use** + * + * Use when frequency metrics are ideal for tracking categorical data where you want to count + * how many times specific string values occur, such as HTTP status codes, user actions, + * error types, or any discrete string-based events. + * + * **Example** (Using frequency metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * // Function that accepts any Frequency metric + * const analyzeFrequencyMetric = (freq: Metric.Frequency) => + * Effect.gen(function*() { + * const state = yield* Metric.value(freq) + * + * // Access the frequency state + * const occurrences: ReadonlyMap = state.occurrences + * + * // Find most frequent value + * let maxCount = 0 + * let mostFrequent = "" + * for (const [value, count] of occurrences) { + * if (count > maxCount) { + * maxCount = count + * mostFrequent = value + * } + * } + * + * return { mostFrequent, maxCount, totalUniqueValues: occurrences.size } + * }) + * + * const program = Effect.gen(function*() { + * // Create frequency metrics + * const statusCodes: Metric.Frequency = Metric.frequency("http_status", { + * description: "HTTP status code frequency" + * }) + * + * const userActions: Metric.Frequency = Metric.frequency("user_actions", { + * description: "User action frequency" + * }) + * + * // Record some occurrences + * yield* Metric.update(statusCodes, "200") + * yield* Metric.update(statusCodes, "200") + * yield* Metric.update(statusCodes, "404") + * yield* Metric.update(statusCodes, "500") + * yield* Metric.update(statusCodes, "200") + * + * yield* Metric.update(userActions, "login") + * yield* Metric.update(userActions, "view_dashboard") + * yield* Metric.update(userActions, "login") + * + * // Use the function with different frequency metrics + * 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 models + * @since 2.0.0 + */ +export interface Frequency extends Metric {} + +/** + * State interface for Frequency metrics containing occurrence counts for discrete string values. + * + * **Example** (Reading frequency state) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create frequency metrics for different categories + * const statusCodeFreq = Metric.frequency("http_status_codes", { + * description: "HTTP status code distribution" + * }) + * + * const userActionFreq = Metric.frequency("user_actions", { + * description: "User action frequency" + * }) + * + * // Record occurrences + * yield* Metric.update(statusCodeFreq, "200") // Success + * yield* Metric.update(statusCodeFreq, "200") // Another success + * yield* Metric.update(statusCodeFreq, "404") // Not found + * yield* Metric.update(statusCodeFreq, "500") // Server error + * yield* Metric.update(statusCodeFreq, "200") // Another success + * + * yield* Metric.update(userActionFreq, "login") + * yield* Metric.update(userActionFreq, "click") + * yield* Metric.update(userActionFreq, "login") + * yield* Metric.update(userActionFreq, "scroll") + * yield* Metric.update(userActionFreq, "click") + * yield* Metric.update(userActionFreq, "click") + * + * // Read frequency states + * const statusState: Metric.FrequencyState = yield* Metric.value(statusCodeFreq) + * const actionState: Metric.FrequencyState = yield* Metric.value(userActionFreq) + * + * // FrequencyState contains: + * // - occurrences: ReadonlyMap with string values and their counts + * + * // Analyze frequency distributions + * const getMostFrequent = (occurrences: ReadonlyMap) => { + * let maxKey = "" + * let maxCount = 0 + * for (const [key, count] of occurrences) { + * if (count > maxCount) { + * maxKey = key + * maxCount = count + * } + * } + * return { key: maxKey, count: maxCount } + * } + * + * const topStatus = getMostFrequent(statusState.occurrences) + * const topAction = getMostFrequent(actionState.occurrences) + * return { + * statusCodes: { + * totalResponses: Array.from(statusState.occurrences.values()).reduce( + * (a, b) => a + b, + * 0 + * ), // 5 + * mostCommon: topStatus, // { key: "200", count: 3 } + * uniqueCodes: statusState.occurrences.size // 3 + * }, + * userActions: { + * totalActions: Array.from(actionState.occurrences.values()).reduce( + * (a, b) => a + b, + * 0 + * ), // 6 + * mostCommon: topAction, // { key: "click", count: 3 } + * uniqueActions: actionState.occurrences.size // 3 + * } + * } + * }) + * + * 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 models + * @since 4.0.0 + */ +export interface FrequencyState { + readonly occurrences: ReadonlyMap +} + +/** + * A Gauge metric that tracks instantaneous values that can go up or down. + * + * **When to use** + * + * Use when gauges are useful for tracking current state values like memory usage, CPU load, + * active connections, queue sizes, or any value that represents a current level. + * + * **Example** (Using gauge metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create different types of gauges + * const memoryGauge: Metric.Gauge = Metric.gauge("memory_usage_mb", { + * description: "Current memory usage in megabytes" + * }) + * + * const diskSpaceGauge: Metric.Gauge = Metric.gauge("disk_free_bytes", { + * description: "Available disk space in bytes", + * bigint: true, + * attributes: { mount: "/var" } + * }) + * + * // Set gauge values (absolute values) + * yield* Metric.update(memoryGauge, 512) // Set to 512 MB + * yield* Metric.update(memoryGauge, 640) // Set to 640 MB (replaces 512) + * yield* Metric.update(diskSpaceGauge, 5000000000n) // Set to ~5GB free + * + * // Modify gauge values (relative changes) + * yield* Metric.modify(memoryGauge, 128) // Add 128 MB (total: 768) + * yield* Metric.modify(memoryGauge, -64) // Subtract 64 MB (total: 704) + * + * // Read gauge state + * const memoryState: Metric.GaugeState = yield* Metric.value( + * memoryGauge + * ) + * const diskState: Metric.GaugeState = yield* Metric.value( + * diskSpaceGauge + * ) + * // Gauge state contains: + * // - value: current instantaneous value + * + * return { + * memory: { currentValue: memoryState.value }, // 704 + * 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 models + * @since 2.0.0 + */ +export interface Gauge extends Metric> {} + +/** + * State interface for Gauge metrics containing the current instantaneous value. + * + * **Example** (Reading gauge state) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create different types of gauges + * const temperatureGauge = Metric.gauge("room_temperature_celsius", { + * description: "Current room temperature" + * }) + * + * const diskSpaceGauge = Metric.gauge("disk_usage_bytes", { + * description: "Current disk usage", + * bigint: true + * }) + * + * const queueSizeGauge = Metric.gauge("queue_size", { + * description: "Current queue size" + * }) + * + * // Set gauge values (absolute values) + * yield* Metric.update(temperatureGauge, 22.5) // Set to 22.5°C + * yield* Metric.update(diskSpaceGauge, 5000000000n) // Set to 5GB usage + * yield* Metric.update(queueSizeGauge, 10) // Set to 10 items + * + * // Update gauge values (new absolute values) + * yield* Metric.update(temperatureGauge, 23.1) // Temperature changed + * yield* Metric.update(queueSizeGauge, 15) // Queue grew + * + * // Read gauge states + * const tempState: Metric.GaugeState = yield* Metric.value( + * temperatureGauge + * ) + * const diskState: Metric.GaugeState = yield* Metric.value( + * diskSpaceGauge + * ) + * const queueState: Metric.GaugeState = yield* Metric.value( + * queueSizeGauge + * ) + * // GaugeState contains: + * // - value: current instantaneous value (number or bigint based on gauge type) + * + * return { + * environment: { + * temperature: tempState.value, // 23.1 + * temperatureUnit: "°C" + * }, + * system: { + * diskUsage: diskState.value, // 5000000000n + * diskUsageGB: Number(diskState.value) / 1_000_000_000, // 5 + * queueSize: queueState.value // 15 + * } + * } + * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.environment.temperature, result.system.diskUsage, result.system.queueSize] + * values // => [23.1, 5000000000n, 15] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface GaugeState { + readonly value: Input extends bigint ? bigint : number +} + +/** + * A Histogram metric that records observations in configurable buckets to analyze value distributions. + * + * **When to use** + * + * Use when histograms are ideal for measuring request durations, response sizes, and other continuous values + * where you need to understand the distribution of values rather than just aggregates. + * + * **Example** (Using histogram metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create histograms with different boundary strategies + * const responseTimeHistogram: Metric.Histogram = Metric.histogram( + * "http_response_time_ms", + * { + * description: "HTTP response time distribution in milliseconds", + * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 20 }) // 50, 100, ..., 900, Infinity + * } + * ) + * + * const fileSizeHistogram: Metric.Histogram = Metric.histogram( + * "file_size_bytes", + * { + * description: "File size distribution in bytes", + * boundaries: Metric.exponentialBoundaries({ + * start: 1, + * factor: 2, + * count: 10 + * }) // 1, 2, 4, 8, ..., 512 + * } + * ) + * + * // Record observations (values get placed into appropriate buckets) + * yield* Metric.update(responseTimeHistogram, 125) // Goes into 100-150ms bucket + * yield* Metric.update(responseTimeHistogram, 75) // Goes into 50-100ms bucket + * yield* Metric.update(responseTimeHistogram, 200) // Goes into 150-200ms bucket + * yield* Metric.update(responseTimeHistogram, 45) // Goes into 0-50ms bucket + * + * yield* Metric.update(fileSizeHistogram, 3) // Goes into 2-4 bytes bucket + * yield* Metric.update(fileSizeHistogram, 15) // Goes into 8-16 bytes bucket + * yield* Metric.update(fileSizeHistogram, 100) // Goes into 64-128 bytes bucket + * + * // Read histogram state + * const responseTimeState: Metric.HistogramState = yield* Metric.value( + * responseTimeHistogram + * ) + * const fileSizeState: Metric.HistogramState = yield* Metric.value( + * fileSizeHistogram + * ) + * // Histogram state contains: + * // - buckets: Array of [boundary, cumulativeCount] pairs + * // - count: total number of observations + * // - min: smallest observed value + * // - max: largest observed value + * // - sum: sum of all observed values + * + * return { + * responseTime: { + * totalRequests: responseTimeState.count, // 4 + * fastestRequest: responseTimeState.min, // 45 + * slowestRequest: responseTimeState.max, // 200 + * totalTime: responseTimeState.sum, // 445 + * averageTime: responseTimeState.sum / responseTimeState.count // 111.25 + * }, + * fileSize: { + * totalFiles: fileSizeState.count, // 3 + * smallestFile: fileSizeState.min, // 3 + * largestFile: fileSizeState.max, // 100 + * totalBytes: fileSizeState.sum // 118 + * } + * } + * }) + * + * 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 models + * @since 2.0.0 + */ +export interface Histogram extends Metric {} + +/** + * State interface for Histogram metrics containing bucket distributions and aggregate statistics. + * + * **Example** (Reading histogram state) + * + * ```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 }) // 100, 200, ..., 800, Infinity + * }) + * + * // Record observations + * yield* Metric.update(responseTimeHistogram, 50) // Fast response + * yield* Metric.update(responseTimeHistogram, 150) // Average response + * yield* Metric.update(responseTimeHistogram, 750) // Slow response + * yield* Metric.update(responseTimeHistogram, 250) // Average response + * yield* Metric.update(responseTimeHistogram, 95) // Fast response + * + * // Read histogram state + * const state: Metric.HistogramState = yield* Metric.value( + * responseTimeHistogram + * ) + * + * // HistogramState contains: + * // - buckets: Array of [boundary, cumulativeCount] pairs showing distribution + * // - count: total number of observations + * // - min: smallest observed value + * // - max: largest observed value + * // - sum: sum of all observed values + * + * // Analyze bucket distribution + * const analyzeBuckets = (buckets: ReadonlyArray<[number, number]>) => { + * const analysis: Array< + * { range: string; count: number; percentage: number } + * > = [] + * let previousCount = 0 + * const totalCount = buckets[buckets.length - 1]?.[1] ?? 0 + * + * for (let i = 0; i < buckets.length; i++) { + * const [boundary, cumulativeCount] = buckets[i] + * const bucketCount = cumulativeCount - previousCount + * const percentage = totalCount > 0 ? (bucketCount / totalCount) * 100 : 0 + * const prevBoundary = i === 0 ? 0 : buckets[i - 1][0] + * + * analysis.push({ + * range: `${prevBoundary}-${boundary}ms`, + * count: bucketCount, + * percentage: Math.round(percentage * 10) / 10 + * }) + * previousCount = cumulativeCount + * } + * return analysis + * } + * + * const bucketAnalysis = analyzeBuckets(state.buckets) + * return { + * responseTime: { + * totalRequests: state.count, // 5 + * fastestResponse: state.min, // 50 + * slowestResponse: state.max, // 750 + * averageResponse: state.sum / state.count, // 268 + * totalTime: state.sum, // 1340 + * distribution: bucketAnalysis + * // Example distribution: + * // [{ range: "0-100ms", count: 2, percentage: 40.0 }, + * // { range: "100-200ms", count: 1, percentage: 20.0 }, + * // { range: "200-300ms", count: 1, percentage: 20.0 }, + * // { range: "700-800ms", count: 1, percentage: 20.0 }] + * } + * } + * }) + * + * 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 models + * @since 4.0.0 + */ +export interface HistogramState { + readonly buckets: ReadonlyArray<[number, number]> + readonly count: number + readonly min: number + readonly max: number + readonly sum: number +} + +/** + * A Summary metric that calculates quantiles over a sliding time window of observations. + * + * **When to use** + * + * Use when summaries provide statistical insights into value distributions by tracking specific quantiles + * (percentiles) such as median (50th), 95th percentile, 99th percentile, etc. They're ideal for + * understanding performance characteristics like response time distributions. + * + * **Example** (Using summary metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create summaries with different quantile configurations + * const responseTimeSummary: Metric.Summary = Metric.summary( + * "api_response_time_ms", + * { + * description: "API response time distribution in milliseconds", + * maxAge: "5 minutes", // Keep observations for 5 minutes + * maxSize: 1000, // Keep up to 1000 observations + * quantiles: [0.5, 0.95, 0.99] // Track median, 95th, and 99th percentiles + * } + * ) + * + * const requestSizeSummary: Metric.Summary = Metric.summary( + * "request_size_bytes", + * { + * description: "Request payload size distribution", + * maxAge: "10 minutes", + * maxSize: 500, + * quantiles: [0.25, 0.5, 0.75, 0.9] // Track quartiles and 90th percentile + * } + * ) + * + * // Record observations (values are stored in time-based sliding window) + * yield* Metric.update(responseTimeSummary, 120) // Fast response + * yield* Metric.update(responseTimeSummary, 250) // Average response + * yield* Metric.update(responseTimeSummary, 45) // Very fast response + * yield* Metric.update(responseTimeSummary, 890) // Slow response + * yield* Metric.update(responseTimeSummary, 156) // Average response + * + * yield* Metric.update(requestSizeSummary, 1024) // 1KB request + * yield* Metric.update(requestSizeSummary, 512) // 512B request + * yield* Metric.update(requestSizeSummary, 2048) // 2KB request + * + * // Read summary state + * const responseTimeState: Metric.SummaryState = yield* Metric.value( + * responseTimeSummary + * ) + * const requestSizeState: Metric.SummaryState = yield* Metric.value( + * requestSizeSummary + * ) + * + * // Summary state contains: + * // - quantiles: Array of [quantile, optionalValue] pairs + * // - count: total number of observations in window + * // - min: smallest observed value in window + * // - max: largest observed value in window + * // - sum: sum of all observed values in window + * + * // Extract quantile values safely + * const getQuantileValue = ( + * quantiles: ReadonlyArray, + * q: number + * ) => quantiles.find(([quantile]) => quantile === q)?.[1] + * + * 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 + * fastestResponse: responseTimeState.min, // 45 + * slowestResponse: responseTimeState.max, // 890 + * totalTime: responseTimeState.sum, // 1461 + * averageTime: responseTimeState.sum / responseTimeState.count, // 292.2 + * medianTime: median ?? null, // ~156 + * p95Time: p95 ?? null, // ~890 + * p99Time: p99 ?? null // ~890 + * }, + * requestSize: { + * totalRequests: requestSizeState.count, // 3 + * averageSize: requestSizeState.sum / requestSizeState.count // ~1194.7 + * } + * } + * }) + * + * 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 models + * @since 2.0.0 + */ +export interface Summary extends Metric {} + +/** + * State interface for Summary metrics containing quantile calculations and aggregate statistics. + * + * **Example** (Reading summary state) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create summary with specific quantiles + * const responseTimeSummary = Metric.summary("api_response_latency", { + * description: "API response time distribution with quantiles", + * maxAge: "5 minutes", + * maxSize: 1000, + * quantiles: [0.5, 0.95, 0.99] // Track median, 95th, and 99th percentiles + * }) + * + * // Record observations over time + * yield* Metric.update(responseTimeSummary, 120) // Fast response + * yield* Metric.update(responseTimeSummary, 250) // Average response + * yield* Metric.update(responseTimeSummary, 45) // Very fast response + * yield* Metric.update(responseTimeSummary, 890) // Slow response + * yield* Metric.update(responseTimeSummary, 156) // Average response + * yield* Metric.update(responseTimeSummary, 78) // Fast response + * yield* Metric.update(responseTimeSummary, 340) // Slower response + * + * // Read summary state + * const state: Metric.SummaryState = yield* Metric.value(responseTimeSummary) + * + * // SummaryState contains: + * // - quantiles: Array of [quantile, optionalValue] pairs showing percentile values + * // - count: total number of observations in current window + * // - min: smallest observed value in window + * // - max: largest observed value in window + * // - sum: sum of all observed values in window + * + * // Extract quantile information safely + * const extractQuantiles = ( + * quantiles: ReadonlyArray + * ) => { + * const result: Record = {} + * for (const [quantile, valueOption] of quantiles) { + * const percentile = Math.round(quantile * 100) + * result[`p${percentile}`] = valueOption ?? null + * } + * return result + * } + * + * const quantileValues = extractQuantiles(state.quantiles) + * return { + * latencyAnalysis: { + * totalRequests: state.count, // 7 + * fastestResponse: state.min, // 45 + * slowestResponse: state.max, // 890 + * averageResponse: state.sum / state.count, // ~268.4 + * totalLatency: state.sum, // 1879 + * percentiles: quantileValues, + * // Example percentiles: + * // { p50: 156, p95: 890, p99: 890 } + * performance: { + * fast: quantileValues.p50 !== null && quantileValues.p50 < 200 + * ? "Good" + * : "Needs improvement", + * reliability: quantileValues.p95 !== null && quantileValues.p95 < 500 + * ? "Reliable" + * : "Concerning" + * } + * } + * } + * }) + * + * 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 models + * @since 4.0.0 + */ +export interface SummaryState { + readonly quantiles: ReadonlyArray + readonly count: number + readonly min: number + readonly max: number + readonly sum: number +} + +/** + * The `Metric` namespace provides a comprehensive system for collecting, aggregating, and observing + * application metrics in Effect applications. + * + * **Example** (Collecting application metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create different types of metrics + * const requestCounter = Metric.counter("http_requests_total") + * const responseTimeHistogram = Metric.histogram("http_response_time", { + * boundaries: Metric.linearBoundaries({ start: 0, width: 10, count: 10 }) + * }) + * const activeConnectionsGauge = Metric.gauge("active_connections") + * const statusFrequency = Metric.frequency("http_status_codes") + * + * // Update metrics + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(responseTimeHistogram, 45.2) + * yield* Metric.update(activeConnectionsGauge, 12) + * yield* Metric.update(statusFrequency, "200") + * + * // Get metric values + * const counterValue = yield* Metric.value(requestCounter) + * const histogramValue = yield* Metric.value(responseTimeHistogram) + * const gaugeValue = yield* Metric.value(activeConnectionsGauge) + * const frequencyValue = yield* Metric.value(statusFrequency) + * + * return { + * counter: counterValue, + * histogram: histogramValue, + * gauge: gaugeValue, + * 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 + */ +export declare namespace Metric { + /** + * Union type representing all available metric types in the Effect metrics system. + * + * **Example** (Inspecting metric types) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * 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 }) + * }), + * Metric.summary("latency", { + * maxAge: "5 minutes", + * maxSize: 1000, + * quantiles: [0.5, 0.95, 0.99] + * }) + * ] + * + * const types: ReadonlyArray = metrics.map((metric) => metric.type) + * const actual = types // => ["Counter", "Gauge", "Frequency", "Histogram", "Summary"] + * ``` + * + * @category models + * @since 4.0.0 + */ + export type Type = "Counter" | "Frequency" | "Gauge" | "Histogram" | "Summary" + + /** + * Union type for metric attributes that can be provided as either an object or array of tuples. + * + * **Example** (Providing attributes in different formats) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Different ways to specify attributes + * const attributesAsObject = { + * service: "api", + * environment: "production", + * version: "1.2.3" + * } + * + * const attributesAsArray: ReadonlyArray<[string, string]> = [ + * ["service", "api"], + * ["environment", "production"], + * ["version", "1.2.3"] + * ] + * + * // Create metrics with different attribute formats + * const requestCounter1 = Metric.counter("requests", { + * description: "Total requests", + * attributes: attributesAsObject // Using object format + * }) + * + * const requestCounter2 = Metric.counter("requests", { + * description: "Total requests", + * attributes: attributesAsArray // Using array format + * }) + * + * // Function to normalize attributes to object format + * const normalizeAttributes = ( + * attrs: typeof attributesAsObject | ReadonlyArray<[string, string]> + * ) => { + * if (Array.isArray(attrs)) { + * return Object.fromEntries(attrs) + * } + * return attrs + * } + * + * // Add runtime attributes using withAttributes + * const contextualCounter = Metric.withAttributes(requestCounter1, { + * method: "GET", + * endpoint: "/api/users" + * }) + * + * // Update metrics with different attribute combinations + * yield* Metric.update(contextualCounter, 1) + * + * // 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" } + * } + * } + * }) + * + * 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 models + * @since 4.0.0 + */ + export type Attributes = AttributeSet | ReadonlyArray<[string, string]> + + /** + * Type for metric attributes as a readonly record of string key-value pairs. + * + * **Example** (Combining metric attribute sets) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Define attribute sets for different contexts + * const serviceAttributes = { + * service: "user-api", + * version: "2.1.0", + * environment: "production" + * } + * + * const operationAttributes = { + * operation: "create_user", + * method: "POST", + * endpoint: "/api/users" + * } + * + * const infrastructureAttributes = { + * region: "us-east-1", + * datacenter: "dc1", + * host: "api-server-01" + * } + * + * // Create metrics with predefined attribute sets + * const requestCounter = Metric.counter("http_requests_total", { + * description: "Total HTTP requests", + * attributes: serviceAttributes + * }) + * + * // Combine attribute sets + * const combineAttributes = (...attributeSets: Array>) => + * Object.assign({}, ...attributeSets) + * + * const fullAttributes = combineAttributes( + * serviceAttributes, + * operationAttributes, + * infrastructureAttributes + * ) + * + * // Create metric with combined attributes + * const detailedCounter = Metric.withAttributes(requestCounter, fullAttributes) + * + * // Helper to validate attribute keys (all must be strings) + * const validateAttributeSet = (attrs: Record): boolean => { + * return Object.entries(attrs).every(([key, value]) => + * typeof key === "string" && typeof value === "string" + * ) + * } + * + * yield* Metric.update(detailedCounter, 1) + * + * return { + * attributes: { + * service: serviceAttributes, + * operation: operationAttributes, + * infrastructure: infrastructureAttributes, + * combined: fullAttributes, + * isValid: validateAttributeSet(fullAttributes), // true + * totalKeys: Object.keys(fullAttributes).length // 9 + * } + * } + * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const validation = [result.attributes.isValid, result.attributes.totalKeys] // => [true, 9] + * ``` + * + * @category models + * @since 4.0.0 + */ + export type AttributeSet = Readonly> + + /** + * Utility type to extract the Input type from a Metric type. + * + * **Example** (Extracting metric input types) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * // Create various metric types + * const numberCounter = Metric.counter("requests") + * const bigintCounter = Metric.counter("bytes", { bigint: true }) + * const stringFrequency = Metric.frequency("status_codes") + * const numberGauge = Metric.gauge("cpu_usage") + * const numberHistogram = Metric.histogram("response_time", { + * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 10 }) + * }) + * + * // The Input utility type extracts the input type from metric types: + * // - Counter: number + * // - Counter: bigint + * // - Frequency: string + * // - Gauge: number + * // - Histogram: number + * + * // Helper function that works with any metric + * const createMetricInfo = (metric: Metric.Metric) => ({ + * id: metric.id, + * type: metric.type + * }) + * + * const metrics = [ + * createMetricInfo(numberCounter), // { id: "requests", type: "Counter" } + * createMetricInfo(bigintCounter), // { id: "bytes", type: "Counter" } + * createMetricInfo(stringFrequency), // { id: "status_codes", type: "Frequency" } + * createMetricInfo(numberGauge), // { id: "cpu_usage", type: "Gauge" } + * createMetricInfo(numberHistogram) // { id: "response_time", type: "Histogram" } + * ] + * + * // Type safety is enforced at compile time: + * // Metric.update(numberCounter, 123) // ✓ Valid (number) + * // 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 utility types + * @since 4.0.0 + */ + export type Input = A extends Metric ? _Input + : never + + /** + * Utility type to extract the State type from a Metric type. + * + * **Example** (Extracting metric state types) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * // Create various metric types + * const requestCounter = Metric.counter("requests") + * const cpuGauge = Metric.gauge("cpu_usage") + * const statusFrequency = Metric.frequency("status_codes") + * const responseHistogram = Metric.histogram("response_time", { + * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 10 }) + * }) + * const latencySummary = Metric.summary("latency", { + * maxAge: "5 minutes", + * maxSize: 1000, + * quantiles: [0.5, 0.95, 0.99] + * }) + * + * // The State utility type extracts the state type from metric types: + * // - Counter: CounterState + * // - Gauge: GaugeState + * // - Frequency: FrequencyState + * // - Histogram: HistogramState + * // - Summary: SummaryState + * + * // Type-safe state analysis functions + * const program = Effect.gen(function*() { + * // Update metrics first + * yield* Metric.update(requestCounter, 10) + * yield* Metric.update(cpuGauge, 85.5) + * yield* Metric.update(statusFrequency, "200") + * yield* Metric.update(responseHistogram, 150) + * yield* Metric.update(latencySummary, 120) + * + * // Extract states with proper typing + * const counterState = yield* Metric.value(requestCounter) + * const gaugeState = yield* Metric.value(cpuGauge) + * 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 } + * frequency: { uniqueValues: frequencyState.occurrences.size }, // { uniqueValues: 1 } + * histogram: { totalObservations: histogramState.count }, // { totalObservations: 1 } + * 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 utility types + * @since 4.0.0 + */ + export type State = A extends Metric ? _State + : never + + /** + * Interface defining the core hooks for metric operations: get, update, and modify. + * + * **Example** (Using metric hooks) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a counter metric + * const requestCounter = Metric.counter("requests_total", { + * description: "Total number of requests" + * }) + * + * // The Hooks interface provides three core operations for metrics: + * // 1. get: retrieve current state + * // 2. update: add/set a value + * // 3. modify: transform the current state + * + * // These are low-level APIs. Most users should use high-level APIs: + * // - Metric.value() for getting state + * // - Metric.update() for updating values + * // - Metric.modify() for modifying values + * + * // Example using high-level APIs (recommended) + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(requestCounter, 5) + * const state = yield* Metric.value(requestCounter) + * + * return { + * currentCount: state.count, // 6 + * isIncremental: state.incremental // false + * } + * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const state = result // => { currentCount: 6, isIncremental: false } + * ``` + * + * @category models + * @since 4.0.0 + */ + export interface Hooks { + readonly get: (context: Context.Context) => State + readonly update: (input: Input, context: Context.Context) => void + readonly modify: (input: Input, context: Context.Context) => void + } + + /** + * Interface containing complete metadata information about a metric. + * + * **Example** (Inspecting metric metadata) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create metrics with different configurations + * const requestCounter = Metric.counter("http_requests_total", { + * description: "Total number of HTTP requests", + * attributes: { service: "api", version: "1.0" } + * }) + * + * const memoryGauge = Metric.gauge("memory_usage_bytes", { + * description: "Current memory usage in bytes" + * }) + * + * const statusFrequency = Metric.frequency("http_status_codes") + * + * // The Metadata interface contains complete information about a metric: + * // - id: metric identifier + * // - type: metric type ("Counter", "Gauge", etc.) + * // - description: optional description + * // - attributes: optional key-value attributes + * // - hooks: low-level operations interface + * + * // Each metric has associated metadata that can be inspected + * yield* Metric.update(requestCounter, 10) + * yield* Metric.update(memoryGauge, 256000000) + * yield* Metric.update(statusFrequency, "200") + * + * return { + * counter: { + * id: requestCounter.id, // "http_requests_total" + * type: requestCounter.type, // "Counter" + * description: requestCounter.description // "Total number of HTTP requests" + * }, + * gauge: { + * id: memoryGauge.id, // "memory_usage_bytes" + * type: memoryGauge.type, // "Gauge" + * description: memoryGauge.description // "Current memory usage in bytes" + * }, + * frequency: { + * id: statusFrequency.id, // "http_status_codes" + * type: statusFrequency.type, // "Frequency" + * description: statusFrequency.description // undefined + * } + * } + * }) + * + * 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 models + * @since 4.0.0 + */ + export interface Metadata { + readonly id: string + readonly type: Type + readonly description: string | undefined + readonly attributes: Metric.AttributeSet | undefined + readonly hooks: Hooks + } + + /** + * Protocol interface for metric snapshots containing metadata and current state. + * + * **Example** (Inspecting metric snapshot protocols) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create and update metrics + * const requestCounter = Metric.counter("requests", { + * description: "Request count", + * attributes: { service: "api" } + * }) + * + * const responseTimeHistogram = Metric.histogram("response_time", { + * description: "Response time distribution", + * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 10 }) + * }) + * + * yield* Metric.update(requestCounter, 25) + * yield* Metric.update(responseTimeHistogram, 150) + * yield* Metric.update(responseTimeHistogram, 75) + * + * // Take snapshot of all metrics + * const snapshots = yield* Metric.snapshot + * + * // Each snapshot follows the SnapshotProto interface: + * // - id: metric identifier + * // - type: specific metric type + * // - description: optional description + * // - attributes: optional attributes + * // - state: current metric state + * + * const counterSnapshot = snapshots.find((s) => s.id === "requests") + * const histogramSnapshot = snapshots.find((s) => s.id === "response_time") + * + * return { + * counter: counterSnapshot ? + * { + * id: counterSnapshot.id, // "requests" + * type: counterSnapshot.type, // "Counter" + * description: counterSnapshot.description, // "Request count" + * hasAttributes: counterSnapshot.attributes !== undefined, // true + * count: (counterSnapshot.state as any).count // 25 + * } : + * null, + * histogram: histogramSnapshot ? + * { + * id: histogramSnapshot.id, // "response_time" + * type: histogramSnapshot.type, // "Histogram" + * observations: (histogramSnapshot.state as any).count // 2 + * } : + * null + * } + * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const counts = [result.counter?.count, result.histogram?.observations] // => [25, 2] + * ``` + * + * @category models + * @since 4.0.0 + */ + export interface SnapshotProto { + readonly id: string + readonly type: T + readonly description: string | undefined + readonly attributes: Metric.AttributeSet | undefined + readonly state: State + } + + /** + * Union type representing all possible metric snapshot types with their corresponding states. + * + * **Example** (Analyzing metric snapshots) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create different types of metrics + * const requestCounter = Metric.counter("requests_total") + * const cpuGauge = Metric.gauge("cpu_usage_percent") + * const statusFrequency = Metric.frequency("http_status") + * const responseHistogram = Metric.histogram("response_time_ms", { + * boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 10 }) + * }) + * const latencySummary = Metric.summary("request_latency", { + * maxAge: "1 minute", + * maxSize: 100, + * quantiles: [0.5, 0.95, 0.99] + * }) + * + * // Update all metrics + * yield* Metric.update(requestCounter, 150) + * yield* Metric.update(cpuGauge, 45.7) + * yield* Metric.update(statusFrequency, "200") + * yield* Metric.update(statusFrequency, "404") + * yield* Metric.update(responseHistogram, 250) + * yield* Metric.update(latencySummary, 120) + * + * // Take snapshot of all metrics + * const allSnapshots = yield* Metric.snapshot + * + * // Type-safe snapshot analysis using discriminated union + * const analyzeSnapshot = (snapshot: any) => { + * switch (snapshot.type) { + * case "Counter": + * return { type: "Counter", count: snapshot.state.count } + * case "Gauge": + * return { type: "Gauge", value: snapshot.state.value } + * case "Frequency": + * return { + * type: "Frequency", + * uniqueValues: snapshot.state.occurrences.size + * } + * case "Histogram": + * return { type: "Histogram", observations: snapshot.state.count } + * case "Summary": + * return { type: "Summary", observations: snapshot.state.count } + * } + * } + * + * const analysis = allSnapshots.map(analyzeSnapshot) + * + * return { + * totalMetrics: allSnapshots.length, // 5 + * metricTypes: allSnapshots.map((s) => s.type), // ["Counter", "Gauge", "Frequency", "Histogram", "Summary"] + * analysis + * } + * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const types = result.metricTypes // => ["Counter", "Gauge", "Frequency", "Histogram", "Summary"] + * ``` + * + * @category models + * @since 4.0.0 + */ + export type Snapshot = + | SnapshotProto<"Counter", CounterState> + | SnapshotProto<"Gauge", GaugeState> + | SnapshotProto<"Frequency", FrequencyState> + | SnapshotProto<"Histogram", HistogramState> + | SnapshotProto<"Summary", SummaryState> +} + +/** + * Service key for the current metric attributes context. + * + * **Example** (Accessing the current metric attributes key) + * + * ```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 + * const key = Metric.CurrentMetricAttributesKey + * + * // Create metrics with base attributes + * const requestCounter = Metric.counter("requests_total", { + * description: "Total HTTP requests" + * }) + * + * // The CurrentMetricAttributes service provides default attributes + * // that get applied to all metrics in the current context + * const baseAttributes = { service: "api", version: "1.0" } + * + * // Use withAttributes to apply attributes to metrics + * const taggedCounter1 = Metric.withAttributes(requestCounter, baseAttributes) + * const program1 = Metric.update(taggedCounter1, 1) + * + * const taggedCounter2 = Metric.withAttributes(requestCounter, { + * ...baseAttributes, + * endpoint: "/users" + * }) + * const program2 = Metric.update(taggedCounter2, 5) + * + * yield* program1 + * yield* program2 + * + * return { + * keyValue: key, // "effect/Metric/CurrentMetricAttributes" + * keyType: typeof key, // "string" + * 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 constants + * @since 4.0.0 + */ +export const CurrentMetricAttributesKey = "effect/Metric/CurrentMetricAttributes" as const + +/** + * Context reference for metric attributes applied from the current Effect + * context. + * + * **When to use** + * + * Use to provide default attributes that should be merged into metric updates + * and reads in a scoped part of a program. + * + * **Details** + * + * The default value is an empty attribute set. Metric reads and updates merge + * these contextual attributes with the metric's own attributes to select the + * metric series being accessed. + * + * **Example** (Providing current metric attributes) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Access current metric attributes + * yield* Metric.CurrentMetricAttributes + * + * // Set new attributes context + * const newAttributes = { service: "api", version: "1.0" } + * const result = yield* Effect.provideService( + * Effect.gen(function*() { + * const updatedAttributes = yield* Metric.CurrentMetricAttributes + * return updatedAttributes + * }), + * Metric.CurrentMetricAttributes, + * newAttributes + * ) + * + * return result + * }) + * + * const attributes = await Effect.runPromise(program) + * const actual = attributes // => { service: "api", version: "1.0" } + * ``` + * + * @category services + * @since 4.0.0 + */ +export const CurrentMetricAttributes = Context.Reference(CurrentMetricAttributesKey, { + defaultValue: () => ({}) +}) + +const MetricRegistryKey = "~effect/observability/Metric/MetricRegistryKey" + +/** + * Context reference for the metric registry in the current context. + * + * **When to use** + * + * Use when you need a custom metric registry for an isolated program or test + * instead of the default registry. + * + * **Details** + * + * By default, the reference creates an empty `Map` the first time it is + * resolved. Metrics register their metadata and hooks lazily in this map when + * they are read or updated. + * + * **Gotchas** + * + * Because `Context.Reference` caches default values, the default `Map` is + * shared by contexts that do not provide an override. Provide `MetricRegistry` + * with a fresh `Map` when isolation matters. + * + * @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 services + * @since 4.0.0 + */ +export const MetricRegistry = Context.Reference>>( + MetricRegistryKey, + { defaultValue: () => new Map() } +) + +const TypeId = "~effect/observability/Metric" + +abstract class Metric$ implements Metric { + readonly [TypeId] = TypeId + + abstract readonly type: Metric.Type + + declare readonly Input: Contravariant + declare readonly State: Covariant + + readonly #metadataCache = new WeakMap>() + #metadata: Metric.Metadata | undefined + + readonly id: string + readonly description: string | undefined + readonly attributes: Metric.AttributeSet | undefined + + constructor( + id: string, + description: string | undefined, + attributes: Metric.AttributeSet | undefined + ) { + this.id = id + this.description = description + this.attributes = attributes + } + + valueUnsafe(context: Context.Context): State { + return this.hook(context).get(context) + } + + modifyUnsafe(input: Input, context: Context.Context): void { + return this.hook(context).modify(input, context) + } + + updateUnsafe(input: Input, context: Context.Context): void { + return this.hook(context).update(input, context) + } + + abstract createHooks(): Metric.Hooks + + 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 + } + const mergedAttributes = mergeAttributes(this.attributes, extraAttributes) + let metadata = this.#metadataCache.get(mergedAttributes) + if (Predicate.isNotUndefined(metadata)) { + return metadata.hooks + } + metadata = this.getOrCreate(context, mergedAttributes) + this.#metadataCache.set(mergedAttributes, metadata) + return metadata.hooks + } + + getOrCreate( + context: Context.Context, + attributes: Metric.Attributes | undefined + ): Metric.Metadata { + const key = makeKey(this, attributes) + const registry = Context.get(context, MetricRegistry) + if (registry.has(key)) { + return registry.get(key)! + } + const hooks = this.createHooks() + const meta: Metric.Metadata = { + id: this.id, + type: this.type, + description: this.description, + attributes: attributesToRecord(attributes), + hooks + } + registry.set(key, meta) + return meta + } + + pipe() { + return pipeArguments(this, arguments) + } +} + +const bigint0 = BigInt(0) + +class CounterMetric extends Metric$> { + readonly type = "Counter" + readonly #bigint: boolean + readonly #incremental: boolean + + constructor(id: string, options?: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly bigint?: boolean | undefined + readonly incremental?: boolean | undefined + }) { + super(id, options?.description, attributesToRecord(options?.attributes)) + this.#bigint = options?.bigint ?? false + this.#incremental = options?.incremental ?? false + } + + createHooks(): Metric.Hooks> { + let count = (this.#bigint ? bigint0 : 0) as any + const canUpdate = this.#incremental + ? this.#bigint + ? (value: bigint | number) => value >= bigint0 + : (value: bigint | number) => value >= 0 + : (_value: bigint | number) => true + const update = (value: Input) => { + if (canUpdate(value)) { + count = (count as any) + value + } + } + return makeHooks(() => ({ count, incremental: this.#incremental }), update) + } +} + +class GaugeMetric extends Metric$> { + readonly type = "Gauge" + readonly #bigint: boolean + + constructor(id: string, options?: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly bigint?: boolean | undefined + }) { + super(id, options?.description, attributesToRecord(options?.attributes)) + this.#bigint = options?.bigint ?? false + } + + createHooks(): Metric.Hooks> { + let value = this.#bigint ? BigInt(0) as any : 0 + const update = (input: number | bigint) => { + value = input + } + const modify = (input: number | bigint) => { + value = value + input + } + return makeHooks(() => ({ value }), update, modify) + } +} + +class FrequencyMetric extends Metric$ { + readonly type = "Frequency" + readonly #preregisteredWords: ReadonlyArray | undefined + + constructor(id: string, options?: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly preregisteredWords?: ReadonlyArray | undefined + }) { + super(id, options?.description, attributesToRecord(options?.attributes)) + this.#preregisteredWords = options?.preregisteredWords + } + + createHooks(): Metric.Hooks { + const occurrences = new Map() + if (Predicate.isNotUndefined(this.#preregisteredWords)) { + for (const word of this.#preregisteredWords) { + occurrences.set(word, 0) + } + } + const update = (word: string) => { + const count = occurrences.get(word) ?? 0 + occurrences.set(word, count + 1) + } + return makeHooks(() => ({ occurrences }), update) + } +} + +class HistogramMetric extends Metric$ { + readonly type = "Histogram" + readonly #boundaries: ReadonlyArray + + constructor(id: string, options: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly boundaries: ReadonlyArray + }) { + super(id, options?.description, attributesToRecord(options?.attributes)) + this.#boundaries = options.boundaries + } + + createHooks(): Metric.Hooks { + const bounds = this.#boundaries + const size = bounds.length + const values = new Uint32Array(size + 1) + const boundaries = new Float64Array(size) + let count = 0 + let sum = 0 + let min = Number.MAX_VALUE + let max = -Number.MAX_VALUE + + Arr.map(Arr.sort(bounds, Order.Number), (n, i) => { + boundaries[i] = n + }) + + // Insert the value into the right bucket with a binary search + const update = (value: number) => { + let from = 0 + let to = size + while (from !== to) { + const mid = Math.floor(from + (to - from) / 2) + const boundary = boundaries[mid] + if (value <= boundary) { + to = mid + } else { + from = mid + } + // The special case when to / from have a distance of one + if (to === from + 1) { + if (value <= boundaries[from]) { + to = from + } else { + from = to + } + } + } + values[from] = values[from] + 1 + count = count + 1 + sum = sum + value + if (value < min) { + min = value + } + if (value > max) { + max = value + } + } + + const getBuckets = (): ReadonlyArray<[number, number]> => { + const builder: Array<[number, number]> = Arr.allocate(size) as any + let cumulated = 0 + for (let i = 0; i < size; i++) { + const boundary = boundaries[i] + const value = values[i] + cumulated = cumulated + value + builder[i] = [boundary, cumulated] + } + return builder + } + + return makeHooks(() => ({ buckets: getBuckets(), count, min, max, sum }), update) + } +} + +class SummaryMetric extends Metric$ { + readonly type = "Summary" + readonly #maxAge: number + readonly #maxSize: number + readonly #quantiles: ReadonlyArray + + constructor(id: string, options: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly maxAge: Duration.Input + readonly maxSize: number + readonly quantiles: ReadonlyArray + }) { + super(id, options?.description, attributesToRecord(options?.attributes)) + this.#maxAge = Math.max(Duration.toMillis(Duration.fromInputUnsafe(options.maxAge)), 0) + this.#maxSize = options.maxSize + this.#quantiles = options.quantiles + } + + createHooks(): Metric.Hooks { + const sortedQuantiles = Arr.sort(this.#quantiles, Order.Number) + const observations = Arr.allocate<[number, number]>(this.#maxSize) + + for (const quantile of this.#quantiles) { + if (quantile < 0 || quantile > 1) { + throw new Error(`Quantile must be between 0 and 1, found: ${quantile}`) + } + } + + let head = 0 + let count = 0 + let sum = 0 + let min = Number.MAX_VALUE + let max = -Number.MAX_VALUE + + const snapshot = (now: number): ReadonlyArray<[number, number | undefined]> => { + const builder: Array = [] + let i = 0 + while (i < this.#maxSize) { + const observation = observations[i] + if (Predicate.isNotUndefined(observation)) { + const [timestamp, value] = observation + const age = now - timestamp + if (age >= 0 && age <= this.#maxAge) { + builder.push(value) + } + } + i = i + 1 + } + const samples = Arr.sort(builder, Order.Number) + const sampleSize = samples.length + if (sampleSize === 0) { + return sortedQuantiles.map((q) => [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`. + return sortedQuantiles.map((q) => { + if (q <= 0) return [q, samples[0]] + if (q >= 1) return [q, samples[sampleSize - 1]] + const index = Math.ceil(q * sampleSize) - 1 + return [q, samples[index]] + }) + } + + const observe = (value: number, timestamp: number) => { + if (this.#maxSize > 0) { + const target = head % this.#maxSize + observations[target] = [timestamp, value] as const + head = head + 1 + } + count = count + 1 + sum = sum + value + if (value < min) { + min = value + } + if (value > max) { + max = value + } + } + + const get = (context: Context.Context) => { + const clock = Context.get(context, InternalEffect.ClockRef) + const quantiles = snapshot(clock.currentTimeMillisUnsafe()) + return { quantiles, count, min, max, sum } + } + + const update = ([value, timestamp]: readonly [value: number, timestamp: number]) => observe(value, timestamp) + + return makeHooks(get, update) + } +} + +class MetricTransform extends Metric$ { + type: Metric.Type + readonly metric: Metric + override readonly valueUnsafe: (context: Context.Context) => State + override readonly updateUnsafe: (input: Input2, context: Context.Context) => void + override readonly modifyUnsafe: (input: Input2, context: Context.Context) => void + + constructor( + metric: Metric, + valueUnsafe: (context: Context.Context) => State, + updateUnsafe: (input: Input2, context: Context.Context) => void, + modifyUnsafe: (input: Input2, context: Context.Context) => void + ) { + super(metric.id, metric.description, metric.attributes) + this.metric = metric + this.valueUnsafe = valueUnsafe + this.updateUnsafe = updateUnsafe + this.modifyUnsafe = modifyUnsafe + this.type = metric.type + } + createHooks(): Metric.Hooks { + return (this.metric as any).createHooks() + } +} + +/** + * Returns `true` if the specified value is a `Metric`, otherwise returns `false`. + * + * **When to use** + * + * Use when you need runtime type checking and ensuring that a value + * conforms to the Metric interface before performing metric operations. + * + * **Example** (Checking metric values) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * Metric.isMetric(Metric.counter("requests")) // => true + * Metric.isMetric({ name: "requests" }) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +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 + * time. Counters can be incremented and decremented and provide a running total + * of changes. + * + * **Details** + * + * The optional `description` describes the counter, and `attributes` attach + * dimensions to it. Set `bigint` to create a counter that accepts `bigint` + * inputs. Set `incremental` to `true` to create a counter that can only ever be + * incremented. + * + * **Example** (Creating counter metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a basic counter for tracking requests + * const requestCounter = Metric.counter("http_requests_total", { + * description: "Total number of HTTP requests processed" + * }) + * + * // Create an incremental-only counter for events + * const eventCounter = Metric.counter("events_processed", { + * description: "Events processed (increment only)", + * incremental: true + * }) + * + * // Create a bigint counter for large values + * const bytesCounter = Metric.counter("bytes_transferred", { + * description: "Total bytes transferred", + * bigint: true, + * attributes: { service: "file-transfer" } + * }) + * + * // Update counters with values + * yield* Metric.update(requestCounter, 1) // Increment by 1 + * yield* Metric.update(requestCounter, 5) // Increment by 5 (total: 6) + * yield* Metric.update(eventCounter, 1) // Increment by 1 + * yield* Metric.update(bytesCounter, 1024n) // Add 1024 bytes + * + * // Get current counter values + * const requestValue = yield* Metric.value(requestCounter) + * const eventValue = yield* Metric.value(eventCounter) + * const bytesValue = yield* Metric.value(bytesCounter) + * + * 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 + * @since 2.0.0 + */ +export const counter: { + ( + name: string, + options?: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly bigint?: false | undefined + readonly incremental?: boolean | undefined + } + ): Counter + ( + name: string, + options: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly bigint: true + readonly incremental?: boolean | undefined + } + ): Counter +} = (name, options) => new CounterMetric(name, options) as any + +/** + * Represents a `Gauge` metric that tracks and reports a single numerical value + * at a specific moment. + * + * **When to use** + * + * Use when you need a metric for instantaneous values, such as memory usage or + * CPU load. + * + * **Details** + * + * The optional `description` describes the gauge, and `attributes` attach + * dimensions to it. Set `bigint` to create a gauge that accepts `bigint` + * inputs. + * + * **Example** (Creating gauge metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a gauge for tracking memory usage + * const memoryGauge = Metric.gauge("memory_usage_mb", { + * description: "Current memory usage in megabytes" + * }) + * + * // Create a gauge for CPU utilization + * const cpuGauge = Metric.gauge("cpu_utilization", { + * description: "Current CPU utilization percentage", + * attributes: { host: "server-01" } + * }) + * + * // Create a bigint gauge for large values + * const diskSpaceGauge = Metric.gauge("disk_free_bytes", { + * description: "Free disk space in bytes", + * bigint: true + * }) + * + * // Set gauge values (replaces current value) + * yield* Metric.update(memoryGauge, 512) // Set to 512 MB + * yield* Metric.update(cpuGauge, 85.5) // Set to 85.5% + * yield* Metric.update(diskSpaceGauge, 1024000000n) // Set to ~1GB + * + * // Modify gauge values (adds to current value) + * yield* Metric.modify(memoryGauge, 128) // Increase by 128 MB (total: 640) + * yield* Metric.modify(cpuGauge, -10.5) // Decrease by 10.5% (total: 75%) + * + * // Update with new absolute values + * yield* Metric.update(memoryGauge, 800) // Set to 800 MB (replaces 640) + * + * // Get current gauge values + * const memoryValue = yield* Metric.value(memoryGauge) + * const cpuValue = yield* Metric.value(cpuGauge) + * const diskValue = yield* Metric.value(diskSpaceGauge) + * + * 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 + * @since 2.0.0 + */ +export const gauge: { + (name: string, options?: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly bigint?: false | undefined + }): Gauge + (name: string, options: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly bigint: true + }): Gauge +} = (name, options) => new GaugeMetric(name, options) as any + +/** + * Creates a `Frequency` metric which can be used to count the number of + * occurrences of a string. + * + * **When to use** + * + * Use when you need a metric for counting how often a specific event or + * incident occurs. + * + * **Details** + * + * The optional `description` describes the frequency, and `attributes` attach + * dimensions to it. Use `preregisteredWords` to initialize occurrence counts + * for known string values before updates arrive. + * + * **Example** (Creating frequency metrics) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a frequency metric for HTTP status codes + * const statusFrequency = Metric.frequency("http_status_codes", { + * description: "Frequency of HTTP response status codes", + * preregisteredWords: ["200", "404", "500"] // Pre-register common codes + * }) + * + * // Create a frequency metric for user actions + * const userActionFrequency = Metric.frequency("user_actions", { + * description: "Frequency of user actions performed", + * attributes: { application: "web-app" } + * }) + * + * // Create a frequency metric for error types + * const errorTypeFrequency = Metric.frequency("error_types", { + * description: "Frequency of different error types" + * }) + * + * // Record different occurrences + * yield* Metric.update(statusFrequency, "200") // Success response + * yield* Metric.update(statusFrequency, "200") // Another success + * yield* Metric.update(statusFrequency, "404") // Not found error + * yield* Metric.update(statusFrequency, "500") // Server error + * yield* Metric.update(statusFrequency, "200") // Another success + * + * yield* Metric.update(userActionFrequency, "login") + * yield* Metric.update(userActionFrequency, "view_dashboard") + * yield* Metric.update(userActionFrequency, "login") + * yield* Metric.update(userActionFrequency, "logout") + * + * yield* Metric.update(errorTypeFrequency, "ValidationError") + * yield* Metric.update(errorTypeFrequency, "NetworkError") + * yield* Metric.update(errorTypeFrequency, "ValidationError") + * + * // Get frequency counts + * const statusCounts = yield* Metric.value(statusFrequency) + * const actionCounts = yield* Metric.value(userActionFrequency) + * const errorCounts = yield* Metric.value(errorTypeFrequency) + * + * // statusCounts.occurrences will be: + * // Map { "200" => 3, "404" => 1, "500" => 1 } + * // actionCounts.occurrences will be: + * // Map { "login" => 2, "view_dashboard" => 1, "logout" => 1 } + * // errorCounts.occurrences will be: + * // Map { "ValidationError" => 2, "NetworkError" => 1 } + * + * 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 + * @since 2.0.0 + */ +export const frequency = (name: string, options?: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly preregisteredWords?: ReadonlyArray | undefined +}): Frequency => new FrequencyMetric(name, options) + +/** + * Represents a `Histogram` metric that records observations into buckets. + * + * **When to use** + * + * Use when you need a metric for measuring the distribution of values within a + * range. + * + * **Details** + * + * The optional `description` describes the histogram, and `attributes` attach + * dimensions to it. The required `boundaries` option defines the histogram + * bucket boundaries. + * + * **Example** (Creating histogram metrics) + * + * ```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, ..., 350-400ms, 400ms+ + * }) + * + * // Create a histogram for request payload sizes + * const payloadSizeHistogram = Metric.histogram("payload_size", { + * description: "Distribution of request payload sizes in KB", + * boundaries: Metric.exponentialBoundaries({ start: 1, factor: 2, count: 8 }), + * // Creates exponential buckets: 1KB, 2KB, 4KB, 8KB, 16KB, 32KB, 64KB, 128KB+ + * attributes: { service: "api-gateway" } + * }) + * + * // Create a histogram with custom boundaries + * const customHistogram = Metric.histogram("custom_metric", { + * description: "Custom distribution metric", + * boundaries: [0.1, 0.5, 1, 2.5, 5, 10, 25, 50, 100] + * }) + * + * // Record various response times + * yield* Metric.update(responseTimeHistogram, 25) // Goes in 0-50ms bucket + * yield* Metric.update(responseTimeHistogram, 75) // Goes in 50-100ms bucket + * yield* Metric.update(responseTimeHistogram, 125) // Goes in 100-150ms bucket + * yield* Metric.update(responseTimeHistogram, 200) // Goes in 150-200ms bucket + * yield* Metric.update(responseTimeHistogram, 75) // Another 50-100ms + * + * // Record payload sizes + * yield* Metric.update(payloadSizeHistogram, 3) // Goes in 2-4KB bucket + * yield* Metric.update(payloadSizeHistogram, 15) // Goes in 8-16KB bucket + * yield* Metric.update(payloadSizeHistogram, 0.5) // Goes in 0-1KB bucket + * + * // Get histogram state with distribution data + * const responseTimeState = yield* Metric.value(responseTimeHistogram) + * const payloadSizeState = yield* Metric.value(payloadSizeHistogram) + * + * // responseTimeState will contain: + * // - buckets: [[50, 1], [100, 3], [150, 4], [200, 5], ...] + * // - count: 5, min: 25, max: 200, sum: 500 + * // - Useful for calculating percentiles, averages, etc. + * + * 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 + * @since 2.0.0 + */ +export const histogram = (name: string, options: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly boundaries: ReadonlyArray +}): Histogram => new HistogramMetric(name, options) + +/** + * Creates a `Summary` metric that records observations and calculates quantiles + * which takes a value as input and uses the current time. + * + * **When to use** + * + * Use when you need a metric that records statistical information about a set + * of values, including quantiles. + * + * **Details** + * + * The optional `description` describes the summary, and `attributes` attach + * dimensions to it. `maxAge` controls how long observations are retained, + * `maxSize` controls how many observations are kept, and `quantiles` lists the + * quantiles to calculate, such as `[0.5, 0.9]`. + * + * **Example** (Creating summary metrics) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a summary for API response times + * const responseTimeSummary = Metric.summary("api_response_time", { + * description: "API response time quantiles over 5-minute windows", + * maxAge: Duration.minutes(5), // Keep observations for 5 minutes + * maxSize: 1000, // Maximum 1000 observations in memory + * quantiles: [0.5, 0.9, 0.95, 0.99] // 50th, 90th, 95th, 99th percentiles + * }) + * + * // Create a summary for request payload sizes + * const payloadSizeSummary = Metric.summary("request_payload_size", { + * description: "Request payload size distribution over 2-minute windows", + * maxAge: Duration.minutes(2), // Shorter window for recent trends + * maxSize: 500, // Smaller buffer for memory efficiency + * quantiles: [0.5, 0.75, 0.9], // Median, 75th, 90th percentiles + * attributes: { service: "upload-service" } + * }) + * + * // Record deterministic response times + * const responseTimes = [82, 96, 104, 118, 135, 170, 210, 240] + * for (const responseTime of responseTimes) { + * yield* Metric.update(responseTimeSummary, responseTime) + * } + * + * // Record some payload sizes + * yield* Metric.update(payloadSizeSummary, 1.2) // 1.2KB + * yield* Metric.update(payloadSizeSummary, 5.8) // 5.8KB + * yield* Metric.update(payloadSizeSummary, 15.6) // 15.6KB + * yield* Metric.update(payloadSizeSummary, 3.4) // 3.4KB + * + * // Get summary statistics with quantiles + * const responseStats = yield* Metric.value(responseTimeSummary) + * const payloadStats = yield* Metric.value(payloadSizeSummary) + * + * // 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 + * @since 2.0.0 + */ +export const summary = (name: string, options: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly maxAge: Duration.Input + readonly maxSize: number + readonly quantiles: ReadonlyArray +}): Summary => + mapInput(summaryWithTimestamp(name, options), (input, context) => + [ + input, + Context.get(context, InternalEffect.ClockRef).currentTimeMillisUnsafe() + ] as [number, number]) + +/** + * Creates a `Summary` metric that records observations with explicit + * timestamps and calculates quantiles. + * + * **When to use** + * + * Use when you need a metric that records statistical information about a set + * of values together with timestamps. + * + * **Details** + * + * Inputs to this metric are `[value, timestamp]` pairs; the current clock is + * used when reading quantiles against the configured `maxAge`. + * + * The optional `description` describes the summary, and `attributes` attach + * dimensions to it. `maxAge` controls how long observations are retained, + * `maxSize` controls how many observations are kept, and `quantiles` lists the + * quantiles to calculate, such as `[0.5, 0.9]`. + * + * **Example** (Creating summaries with explicit timestamps) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * const responseTimesSummary = Metric.summaryWithTimestamp( + * "response_times_summary", + * { + * description: "Measures the distribution of response times", + * maxAge: "60 seconds", // Retain observations for 60 seconds. + * maxSize: 1000, // Keep a maximum of 1000 observations. + * 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 + * @since 4.0.0 + */ +export const summaryWithTimestamp = (name: string, options: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly maxAge: Duration.Input + readonly maxSize: number + readonly quantiles: ReadonlyArray +}): Summary<[value: number, timestamp: number]> => new SummaryMetric(name, options) + +/** + * Creates a timer metric, based on a `Histogram`, which keeps track of + * durations in milliseconds. + * + * **Details** + * + * The unit of time will automatically be added to the metric as a tag (i.e. + * `"time_unit: milliseconds"`). + * + * If `options.boundaries` is not provided, the boundaries will be computed + * using `Metric.exponentialBoundaries({ start: 0.5, factor: 2, count: 35 })`. + * + * **Example** (Recording durations with a timer) + * + * ```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", { + * description: "Duration of API requests", + * attributes: { service: "user-api" } + * }) + * + * // Record a measured API operation duration + * const apiOperation = Effect.gen(function*() { + * const duration = Duration.millis(120) + * yield* Metric.update(apiRequestTimer, duration) + * + * const state = yield* Metric.value(apiRequestTimer) + * return { + * count: state.count, + * min: state.min, + * max: state.max, + * sum: state.sum + * } + * }) + * + * await Effect.runPromise( + * Effect.provideService(apiOperation, Metric.MetricRegistry, new Map()) + * ) // => { count: 1, min: 120, max: 120, sum: 120 } + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const timer = (name: string, options?: { + readonly description?: string | undefined + readonly attributes?: Metric.Attributes | undefined + readonly boundaries?: ReadonlyArray +}): Histogram => { + const boundaries = Predicate.isNotUndefined(options?.boundaries) + ? options.boundaries + : exponentialBoundaries({ start: 0.5, factor: 2, count: 35 }) + const attributes = mergeAttributes(options?.attributes, { time_unit: "milliseconds" }) + const metric = new HistogramMetric(name, { ...options, boundaries, attributes }) + return mapInput(metric, Duration.toMillis) +} + +/** + * Retrieves the current state of the specified `Metric`. + * + * **Details** + * + * The returned state depends on the metric type. Counters return + * `CounterState` with `count` and `incremental`, gauges return + * `GaugeState` with `value`, frequencies return + * `FrequencyState` with `occurrences`, histograms return `HistogramState` with + * buckets, count, min, max, and sum, and summaries return `SummaryState` with + * quantiles, count, min, max, and sum. + * + * **Example** (Reading metric state) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const requestCounter = Metric.counter("modify_requests") + * const responseTime = Metric.histogram("response_time", { + * boundaries: [100, 500, 1000, 2000] + * }) + * + * const program = Effect.gen(function*() { + * // Update metrics + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(responseTime, 750) + * + * // Get current values + * const counterState = yield* Metric.value(requestCounter) + * const histogramState = yield* Metric.value(responseTime) + * 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 + * @since 2.0.0 + */ +export const value = ( + self: Metric +): Effect => + InternalEffect.flatMap( + InternalEffect.context(), + (context) => InternalEffect.sync(() => self.valueUnsafe(context)) + ) + +/** + * Modifies the metric with the specified input. + * + * **Details** + * + * The behavior of `modify` depends on the metric type. Counters add the input + * value to the current count, gauges add the input value to the current gauge + * value, frequencies increment the occurrence count for the input string, + * histograms record the input value in the appropriate bucket, and summaries + * record the input observation. + * + * **Example** (Modifying metric values) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const temperatureGauge = Metric.gauge("temperature") + * const requestCounter = Metric.counter("requests") + * + * const program = Effect.gen(function*() { + * // Set initial temperature + * yield* Metric.update(temperatureGauge, 20) + * + * // Modify by adding/subtracting values + * yield* Metric.modify(temperatureGauge, 5) // Now 25 + * yield* Metric.modify(temperatureGauge, -3) // Now 22 + * + * // For counters, modify increments by the specified amount + * yield* Metric.modify(requestCounter, 10) // Add 10 to counter + * yield* Metric.modify(requestCounter, 5) // Add 5 more (total: 15) + * + * const temp = yield* Metric.value(temperatureGauge) + * const requests = yield* Metric.value(requestCounter) + * return [temp.value, requests.count] as const + * }) + * + * await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => [22, 15] + * ``` + * + * @category mutations + * @since 3.6.5 + */ +export const modify: { + (input: Input): (self: Metric) => Effect + (self: Metric, input: Input): Effect +} = dual< + (input: Input) => (self: Metric) => Effect, + (self: Metric, input: Input) => Effect +>(2, (self, input) => + InternalEffect.flatMap( + InternalEffect.context(), + (context) => InternalEffect.sync(() => self.modifyUnsafe(input, context)) + )) + +/** + * Updates the metric with the specified input. + * + * **Details** + * + * The behavior of `update` depends on the metric type. Counters add the input + * value to the current count, gauges replace the current value with the input + * value, frequencies increment the occurrence count for the input string, + * histograms record the input value in the appropriate bucket, and summaries + * record the input value as a new observation. + * + * **Example** (Updating metric values) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const cpuUsage = Metric.gauge("cpu_usage_percent") + * const httpStatus = Metric.frequency("http_status_codes") + * const responseTime = Metric.histogram("response_time_ms", { + * boundaries: [100, 500, 1000, 2000] + * }) + * + * const program = Effect.gen(function*() { + * // Update gauge to specific values + * yield* Metric.update(cpuUsage, 45.2) + * yield* Metric.update(cpuUsage, 67.8) // Replaces previous value + * + * // Track HTTP status code occurrences + * yield* Metric.update(httpStatus, "200") + * yield* Metric.update(httpStatus, "404") + * yield* Metric.update(httpStatus, "200") // Increments 200 count + * + * // Record response times + * yield* Metric.update(responseTime, 250) + * yield* Metric.update(responseTime, 750) + * yield* Metric.update(responseTime, 1500) + * + * // Check current states + * const cpu = yield* Metric.value(cpuUsage) + * const statuses = yield* Metric.value(httpStatus) + * const times = yield* Metric.value(responseTime) + * 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 + * @since 2.0.0 + */ +export const update: { + (input: Input): (self: Metric) => Effect + (self: Metric, input: Input): Effect +} = dual< + (input: Input) => (self: Metric) => Effect, + (self: Metric, input: Input) => Effect +>( + 2, + (self, input) => + InternalEffect.contextWith((services) => InternalEffect.sync(() => self.updateUnsafe(input, services))) +) + +/** + * Returns a new metric that is powered by this one, but which accepts updates + * of the specified new type, which must be transformable to the input type of + * this metric. + * + * **Example** (Mapping metric inputs) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const durationHistogram = Metric.histogram("request_duration_ms", { + * description: "Request duration in milliseconds", + * boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 10 }) + * }) + * + * // Accept duration strings while recording numeric milliseconds + * const durationStringHistogram = Metric.mapInput( + * durationHistogram, + * (input: string) => Number(input) + * ) + * + * const program = Effect.gen(function*() { + * 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 annotations + * @since 2.0.0 + */ +export const mapInput: { + ( + f: (input: Input2, context: Context.Context) => Input + ): (self: Metric) => Metric + ( + self: Metric, + f: (input: Input2, context: Context.Context) => Input + ): Metric +} = dual< + ( + f: (input: Input2, context: Context.Context) => Input + ) => (self: Metric) => Metric, + ( + self: Metric, + f: (input: Input2, context: Context.Context) => Input + ) => Metric +>(2, ( + self: Metric, + f: (input: Input2, context: Context.Context) => Input +): Metric => + new MetricTransform( + self, + (context) => self.valueUnsafe(context), + (input, context) => self.updateUnsafe(f(input, context), context), + (input, context) => self.modifyUnsafe(f(input, context), context) + )) + +/** + * Returns a new metric that is powered by this one, but which accepts updates + * of any type, and translates them to updates with the specified constant + * update value. + * + * **Example** (Ignoring inputs with a constant value) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * // Create a counter that normally expects a number increment + * const requestCounter = Metric.counter("total_requests", { + * description: "Total number of requests processed" + * }) + * + * // Create a version that always increments by 1, regardless of input + * const simpleRequestCounter = Metric.withConstantInput(requestCounter, 1) + * + * const program = Effect.gen(function*() { + * // These all increment the counter by 1, ignoring the input value + * yield* Metric.update(simpleRequestCounter, "any string") + * yield* Metric.update(simpleRequestCounter, { complex: "object" }) + * yield* Metric.update(simpleRequestCounter, 999) // Still increments by 1 + * + * 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 mapping + * @since 2.0.0 + */ +export const withConstantInput: { + (input: Input): (self: Metric) => Metric + (self: Metric, input: Input): Metric +} = dual< + (input: Input) => (self: Metric) => Metric, + (self: Metric, input: Input) => Metric +>(2, (self, input) => mapInput(self, () => input)) + +/** + * Returns a new metric that applies the specified attributes to all operations. + * + * **Details** + * + * Attributes are key-value pairs that provide additional context for metrics, + * enabling filtering, grouping, and more detailed analysis. Each combination + * of attribute values creates a separate metric series. + * + * **Example** (Applying metric attributes) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const requestCounter = Metric.counter("http_requests_total", { + * description: "Total HTTP requests" + * }) + * + * // Create tagged versions of the metric + * const getRequests = Metric.withAttributes(requestCounter, { + * method: "GET", + * endpoint: "/api/users" + * }) + * + * const postRequests = Metric.withAttributes(requestCounter, { + * method: "POST", + * endpoint: "/api/users" + * }) + * + * const program = Effect.gen(function*() { + * // These will be tracked as separate metric series + * yield* Metric.update(getRequests, 1) // http_requests_total{method="GET", endpoint="/api/users"} + * yield* Metric.update(postRequests, 1) // http_requests_total{method="POST", endpoint="/api/users"} + * yield* Metric.update(getRequests, 1) // Increments the GET counter + * + * // You can also chain attributes + * const taggedMetric = requestCounter.pipe( + * Metric.withAttributes({ service: "user-api" }), + * Metric.withAttributes({ version: "v1" }) + * ) + * + * yield* Metric.update(taggedMetric, 1) // http_requests_total{service="user-api", version="v1"} + * }) + * + * 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 mapping + * @since 4.0.0 + */ +export const withAttributes: { + (attributes: Metric.Attributes): (self: Metric) => Metric + (self: Metric, attributes: Metric.Attributes): Metric +} = dual< + (attributes: Metric.Attributes) => (self: Metric) => Metric, + (self: Metric, attributes: Metric.Attributes) => Metric +>(2, ( + self: Metric, + attributes: Metric.Attributes +): Metric => + new MetricTransform( + self, + (context) => self.valueUnsafe(addAttributesToContext(context, attributes)), + (input, context) => self.updateUnsafe(input, addAttributesToContext(context, attributes)), + (input, context) => self.modifyUnsafe(input, addAttributesToContext(context, attributes)) + )) + +// Metric Snapshots + +/** + * Captures a snapshot of all registered metrics in the current context. + * + * **Details** + * + * Returns an array of metric snapshots, each containing the metric's metadata + * (name, description, type) and current state (values, counts, etc.). + * + * **Example** (Capturing metric snapshots) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create and update some metrics + * const requestCounter = Metric.counter("http_requests", { + * description: "Total HTTP requests" + * }) + * const responseTime = Metric.histogram("response_time_ms", { + * description: "Response time in milliseconds", + * boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 5 }) + * }) + * + * // Update the metrics with some values + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(responseTime, 150) + * yield* Metric.update(responseTime, 75) + * + * // Take a snapshot of all metrics + * const snapshots = yield* Metric.snapshot + * + * 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 + * @since 2.0.0 + */ +export const snapshot: Effect> = InternalEffect.map( + InternalEffect.context(), + (context) => snapshotUnsafe(context) +) + +/** + * Returns a human-readable string representation of all currently registered + * metrics in a tabular format. + * + * **Details** + * + * This debugging utility captures a snapshot of all metrics and formats them + * in an easy-to-read table showing names, descriptions, types, attributes, + * and current state values. + * + * **Example** (Dumping metrics as text) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Create and update some metrics for demonstration + * const requestCounter = Metric.counter("http_requests_total", { + * description: "Total HTTP requests" + * }) + * const responseTime = Metric.gauge("response_time_ms", { + * description: "Current response time in milliseconds" + * }) + * const statusFreq = Metric.frequency("http_status_codes", { + * description: "Frequency of HTTP status codes" + * }) + * + * // Update metrics with some values + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(requestCounter, 1) + * yield* Metric.update(responseTime, 125) + * yield* Metric.update(statusFreq, "200") + * yield* Metric.update(statusFreq, "404") + * yield* Metric.update(statusFreq, "200") + * + * // Get formatted dump of all metrics + * const metricsReport = yield* Metric.dump + * 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 formatting + * @since 4.0.0 + */ +export const dump: Effect = InternalEffect.flatMap(InternalEffect.context(), (context) => { + const metrics = snapshotUnsafe(context) + if (metrics.length > 0) { + const maxNameLength = metrics.reduce((max, metric) => { + const length = metric.id.length + return length > max ? length : max + }, 0) + 2 + const maxDescriptionLength = metrics.reduce((max, metric) => { + const length = Predicate.isNotUndefined(metric.description) ? metric.description.length : 0 + return length > max ? length : max + }, 0) + 2 + const maxTypeLength = metrics.reduce((max, metric) => { + const length = metric.type.length + return length > max ? length : max + }, 0) + 2 + const maxAttributesLength = metrics.reduce((max, metric) => { + const length = Predicate.isNotUndefined(metric.attributes) ? attributesToString(metric.attributes).length : 0 + return length > max ? length : max + }, 0) + 2 + const grouped = Object.entries(Arr.groupBy(metrics, (metric) => metric.id)) + const sorted = Arr.sortWith(grouped, (entry) => entry[0], _String.Order) + const rendered = sorted.map(([, group]) => + group.map((metric) => + renderName(metric, maxNameLength) + + renderDescription(metric, maxDescriptionLength) + + renderType(metric, maxTypeLength) + + renderAttributes(metric, maxAttributesLength) + + renderState(metric) + ).join("\n") + ).join("\n") + return InternalEffect.succeed(rendered) + } + return InternalEffect.succeed("") +}) + +/** + * Captures a snapshot of all registered metrics synchronously using the provided + * service context. + * + * **When to use** + * + * Use to read metric snapshots from an explicit `Context` in low-level + * integrations, exporters, or debugging tools that already have the context. + * + * **Details** + * + * This is the "unsafe" version that bypasses Effect's safety guarantees and requires + * manual handling of the services context. Use the safe `snapshot` function for normal + * application code. + * + * **Example** (Capturing snapshots from a context) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const requestCounter = Metric.counter("http_requests") + * const program = Effect.gen(function*() { + * yield* Metric.update(requestCounter, 1) + * const context = yield* Effect.context() + * return Metric.snapshotUnsafe(context).map((snapshot) => snapshot.id) + * }) + * + * await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => ["http_requests"] + * ``` + * + * @category snapshotting + * @since 4.0.0 + */ +export const snapshotUnsafe = (context: Context.Context): ReadonlyArray => { + const registry = Context.get(context, MetricRegistry) + return Array.from(registry.values()).map(({ hooks, ...meta }) => ({ + ...meta, + state: hooks.get(context) + })) +} + +const renderName = (metric: Metric.Snapshot, padTo: number): string => `name=${metric.id.padEnd(padTo, " ")}` + +const renderDescription = (metric: Metric.Snapshot, padTo: number): string => + `description=${(metric.description ?? "").padEnd(padTo, " ")}` + +const renderType = (metric: Metric.Snapshot, padTo: number): string => `type=${metric.type.padEnd(padTo, " ")}` + +const renderAttributes = (metric: Metric.Snapshot, padTo: number): string => { + const attrs = attributesToString(metric.attributes ?? {}) + const padding = " ".repeat(Math.max(0, padTo - attrs.length)) + return `${attrs}${padding}` +} + +const renderState = (metric: Metric.Snapshot): string => { + const prefix: string = "state=" + switch (metric.type) { + case "Counter": { + const state = metric.state as CounterState + return `${prefix}[count: [${state.count}]]` + } + case "Frequency": { + const state = metric.state as FrequencyState + return `${prefix}[occurrences: ${renderKeyValues(state.occurrences)}]` + } + case "Gauge": { + const state = metric.state as GaugeState + return `${prefix}[value: [${state.value}]]` + } + case "Histogram": { + const state = metric.state as HistogramState + const buckets = `buckets: [${renderKeyValues(state.buckets)}]` + const count = `count: [${state.count}]` + const min = `min: [${state.min}]` + const max = `max: [${state.max}]` + const sum = `sum: [${state.sum}]` + return `${prefix}[${buckets}, ${count}, ${min}, ${max}, ${sum}]` + } + case "Summary": { + const state = metric.state as SummaryState + const printableQuantiles = state.quantiles.map(([key, value]) => [key, value ?? 0] as [number, number]) + const quantiles = `quantiles: [${renderKeyValues(printableQuantiles)}]` + const count = `count: [${state.count}]` + const min = `min: [${state.min}]` + const max = `max: [${state.max}]` + const sum = `sum: [${state.sum}]` + return `${prefix}[${quantiles}, ${count}, ${min}, ${max}, ${sum}]` + } + } +} + +const renderKeyValues = (keyValues: Iterable<[number | string, string | number]>): string => + Array.from(keyValues).map(([key, value]) => `(${key} -> ${value})`).join(", ") + +const attributesToString = (attributes: Metric.AttributeSet): string => { + const attrs = Object.entries(attributes) + const sorted = Arr.sortWith(attrs, (attr) => attr[0], _String.Order) + return `attributes=[${sorted.map(([key, value]) => `${key}: ${value}`).join(", ")}]` +} + +// Metric Boundaries + +/** + * Creates histogram bucket boundaries from an iterable set of values. + * + * **Details** + * + * Processes any iterable of numbers by removing duplicates, filtering out + * non-positive values, and automatically appending positive infinity as the + * final boundary. + * + * **Example** (Creating boundaries from values) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * Metric.boundariesFromIterable([-5, 0, 10, 10, 25, 50]) // => [10, 25, 50, Infinity] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const boundariesFromIterable = (iterable: Iterable): ReadonlyArray => + Arr.append(Arr.filter(new Set(iterable), (n) => n > 0), Number.POSITIVE_INFINITY) + +/** + * Creates histogram bucket boundaries from a linear sequence and appends + * positive infinity. + * + * **Details** + * + * 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.meta.vitest + * import { Metric } from "effect" + * + * Metric.linearBoundaries({ start: 10, width: 20, count: 5 }) // => [10, 30, 50, 70, Infinity] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const linearBoundaries = (options: { + readonly start: number + readonly width: number + readonly count: number +}): ReadonlyArray => + boundariesFromIterable(Arr.makeBy(options.count - 1, (n) => options.start + n * options.width)) + +/** + * Creates histogram bucket boundaries with exponentially increasing values. + * + * **Details** + * + * Creates boundaries that grow exponentially, useful for metrics that span + * multiple orders of magnitude. Each boundary is calculated as start * factor^i. + * + * **Example** (Creating exponential boundaries) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * Metric.exponentialBoundaries({ start: 1, factor: 2, count: 5 }) // => [1, 2, 4, 8, Infinity] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const exponentialBoundaries = (options: { + readonly start: number + readonly factor: number + readonly count: number +}): ReadonlyArray => + boundariesFromIterable(Arr.makeBy(options.count - 1, (i) => options.start * Math.pow(options.factor, i))) + +// Fiber Runtime Metrics + +const fibersActive = gauge("child_fibers_active", { + description: "The current count of active child fibers" +}) +const fibersStarted = counter("child_fibers_started", { + description: "The total number of child fibers that have been started", + incremental: true +}) +const fiberSuccesses = counter("child_fiber_successes", { + description: "The total number of child fibers that have succeeded", + incremental: true +}) +const fiberFailures = counter("child_fiber_failures", { + description: "The total number of child fibers that have failed", + incremental: true +}) + +/** + * Service key for the fiber runtime metrics service. + * + * **Example** (Accessing the fiber runtime metrics key) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * Metric.FiberRuntimeMetricsKey // => "effect/observability/Metric/FiberRuntimeMetricsKey" + * ``` + * + * @category constants + * @since 4.0.0 + */ +export const FiberRuntimeMetricsKey: "effect/observability/Metric/FiberRuntimeMetricsKey" = + InternalMetric.FiberRuntimeMetricsKey + +/** + * Interface for the fiber runtime metrics service that tracks fiber lifecycle events. + * + * **Example** (Providing a custom fiber metrics service) + * + * ```ts import.meta.vitest + * import { Context, Exit, Metric } from "effect" + * + * const events: Array = [] + * const customMetricsService: Metric.FiberRuntimeMetricsService = { + * recordFiberStart: () => { + * events.push("start") + * }, + * recordFiberEnd: (_context, exit) => { + * events.push(Exit.isSuccess(exit) ? "success" : "failure") + * } + * } + * + * customMetricsService.recordFiberStart(Context.empty()) + * customMetricsService.recordFiberEnd(Context.empty(), Exit.succeed("ok")) + * events // => ["start", "success"] + * ``` + * + * @category services + * @since 4.0.0 + */ +export interface FiberRuntimeMetricsService { + readonly recordFiberStart: (context: Context.Context) => void + readonly recordFiberEnd: (context: Context.Context, exit: Exit) => void +} + +/** + * Context reference for the optional service that records fiber runtime + * metrics. + * + * **When to use** + * + * Use to provide or inspect the service that receives fiber start and end + * notifications for automatic runtime metrics. + * + * **Details** + * + * When provided, the runtime can notify the service about child-fiber start and + * end events. When the reference is `undefined`, automatic fiber runtime metric + * collection is disabled. + * + * **Example** (Accessing the fiber runtime metrics service) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * const metricsService = yield* Metric.FiberRuntimeMetrics + * return metricsService === Metric.FiberRuntimeMetricsImpl + * }) + * + * const result = await Effect.runPromise( + * Effect.provideService(program, Metric.FiberRuntimeMetrics, Metric.FiberRuntimeMetricsImpl) + * ) + * const isDefault = result // => true + * ``` + * + * @category services + * @since 4.0.0 + */ +export const FiberRuntimeMetrics = Context.Reference( + InternalMetric.FiberRuntimeMetricsKey, + { fiberCached: true, defaultValue: constUndefined } +) + +/** + * Default implementation of the fiber runtime metrics service. + * + * **Example** (Accessing the default fiber metrics implementation) + * + * ```ts import.meta.vitest + * import { Metric } from "effect" + * + * [ + * typeof Metric.FiberRuntimeMetricsImpl.recordFiberStart, + * typeof Metric.FiberRuntimeMetricsImpl.recordFiberEnd + * ] // => ["function", "function"] + * ``` + * + * @category services + * @since 4.0.0 + */ +export const FiberRuntimeMetricsImpl: FiberRuntimeMetricsService = { + recordFiberStart(context: Context.Context) { + fibersStarted.updateUnsafe(1, context) + fibersActive.modifyUnsafe(1, context) + }, + recordFiberEnd(context: Context.Context, exit: Exit) { + fibersActive.modifyUnsafe(-1, context) + if (InternalEffect.exitIsSuccess(exit)) { + fiberSuccesses.updateUnsafe(1, context) + } else { + fiberFailures.updateUnsafe(1, context) + } + } +} + +/** + * Layer that enables automatic collection of fiber runtime metrics across + * an entire Effect application. + * + * **When to use** + * + * Use when you need runtime metrics collection for all Effects in the + * application context rather than wrapping individual Effects. + * + * **Example** (Enabling runtime metrics with a layer) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * const service = yield* Metric.FiberRuntimeMetrics + * return service === Metric.FiberRuntimeMetricsImpl + * }) + * + * await Effect.runPromise(Effect.provide(program, Metric.enableRuntimeMetricsLayer)) // => true + * ``` + * + * @category layers + * @since 4.0.0 + */ +export const enableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(FiberRuntimeMetricsImpl) + +/** + * Layer that disables automatic collection of fiber runtime metrics. + * + * **Example** (Disabling runtime metrics with a layer) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * // Disable runtime metrics collection + * const disabledLayer = Metric.disableRuntimeMetricsLayer + * + * return yield* Effect.gen(function*() { + * // Check that metrics service is disabled + * const metricsService = yield* Metric.FiberRuntimeMetrics + * + * // Run some Effects - no metrics will be collected + * yield* Effect.forkChild(Effect.sleep("50 millis")) + * yield* Effect.forkChild(Effect.sleep("100 millis")) + * yield* Effect.sleep("200 millis") + * + * // Create test metrics to show they still work + * const testCounter = Metric.counter("test_counter") + * yield* Metric.update(testCounter, 1) + * const counterValue = yield* Metric.value(testCounter) + * + * 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 layers + * @since 4.0.0 + */ +export const disableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(undefined) + +/** + * Enables automatic collection of fiber runtime metrics for the provided Effect. + * + * **Details** + * + * When enabled, automatically tracks fiber lifecycle metrics including active fibers, + * started fibers, successful completions, and failures. These metrics provide valuable + * insights into the concurrency patterns and health of your Effect application. + * + * **Example** (Enabling runtime metrics for an effect) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * const service = yield* Metric.FiberRuntimeMetrics + * return service === Metric.FiberRuntimeMetricsImpl + * }) + * + * await Effect.runPromise(Metric.enableRuntimeMetrics(program)) // => true + * ``` + * + * @category providing services + * @since 4.0.0 + */ +export const enableRuntimeMetrics: (self: Effect) => Effect = InternalEffect.provideService( + FiberRuntimeMetrics, + FiberRuntimeMetricsImpl +) + +/** + * Disables automatic collection of fiber runtime metrics for the provided Effect. + * + * **When to use** + * + * Use when you need to disable runtime metrics for a specific effect while + * keeping them enabled elsewhere. + * + * **Example** (Disabling runtime metrics for an effect) + * + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" + * + * const program = Effect.gen(function*() { + * const service = yield* Metric.FiberRuntimeMetrics + * return service === undefined + * }) + * + * await Effect.runPromise(Metric.disableRuntimeMetrics(program)) // => true + * ``` + * + * @category providing services + * @since 4.0.0 + */ +export const disableRuntimeMetrics: (self: Effect) => Effect = InternalEffect.provideService( + FiberRuntimeMetrics, + undefined +) + +// Utilities + +function makeKey( + metric: Metric, + attributes: Metric.Attributes | undefined +) { + let key = `${metric.type}:${metric.id}` + if (Predicate.isNotUndefined(metric.description)) { + key += `:${metric.description}` + } + if (Predicate.isNotUndefined(attributes)) { + key += `:${serializeAttributes(attributes)}` + } + return key +} + +function makeHooks( + get: (context: Context.Context) => State, + update: (input: Input, context: Context.Context) => void, + modify?: (input: Input, context: Context.Context) => void +): Metric.Hooks { + return { get, update, modify: modify ?? update } +} + +function serializeAttributes(attributes: Metric.Attributes): string { + return JSON.stringify(Array.isArray(attributes) ? attributes : Object.entries(attributes)) +} + +function mergeAttributes( + self: Metric.Attributes | undefined, + other: Metric.Attributes | undefined +): Metric.AttributeSet { + return { ...attributesToRecord(self), ...attributesToRecord(other) } +} + +function attributesToRecord(attributes?: Metric.Attributes): Metric.AttributeSet | undefined { + if (Predicate.isNotUndefined(attributes) && Array.isArray(attributes)) { + return attributes.reduce((acc, [key, value]) => { + InternalRecord.assignProperty(acc, key, value) + return acc + }, {} as Metric.AttributeSet) + } + return attributes as Metric.AttributeSet | undefined +} + +function addAttributesToContext( + context: Context.Context, + attributes: Metric.Attributes +): Context.Context { + const current = Context.get(context, CurrentMetricAttributes) + const updated = mergeAttributes(current, attributes) + return Context.add(context, CurrentMetricAttributes, updated) +} diff --git a/.repos/effect/packages/effect/src/MutableHashMap.ts b/.repos/effect/packages/effect/src/MutableHashMap.ts new file mode 100644 index 000000000..c95c6309c --- /dev/null +++ b/.repos/effect/packages/effect/src/MutableHashMap.ts @@ -0,0 +1,824 @@ +/** + * Stores key/value entries in a mutable hash map. + * + * `MutableHashMap` updates the same collection in place and supports fast + * lookup, insertion, removal, clearing, and iteration. It combines a native + * `Map` for ordinary JavaScript keys with hash buckets for keys that implement + * Effect `Equal` and `Hash`, so callers can mix reference-based and structural + * lookup in the same collection. + * + * @since 2.0.0 + */ +import type { NonEmptyArray } from "./Array.ts" +import * as Equal from "./Equal.ts" +import { format } from "./Formatter.ts" +import { dual } from "./Function.ts" +import * as Hash from "./Hash.ts" +import { type Inspectable, NodeInspectSymbol, toJson } from "./Inspectable.ts" +import * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" + +const TypeId = "~effect/collections/MutableHashMap" + +/** + * A mutable hash map that stores key-value pairs and supports both referential + * and Effect structural equality. + * + * **When to use** + * + * Use as a mutable key-value map when in-place updates are acceptable and keys + * may rely on Effect structural equality. + * + * **Details** + * + * Operations mutate the map in place. Keys that implement `Equal` / `Hash` can + * be looked up structurally; other keys use normal JavaScript reference or + * primitive equality. + * + * **Example** (Using a mutable hash map) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * // Create a mutable hash map with string keys and number values + * const map: MutableHashMap.MutableHashMap = MutableHashMap + * .empty() + * + * // Add some data + * MutableHashMap.set(map, "count", 42) + * MutableHashMap.set(map, "total", 100) + * + * Array.from(map) // => [["count", 42], ["total", 100]] + * ``` + * + * @see {@link empty} for creating an empty mutable hash map + * @see {@link get} for reading values by key + * @see {@link set} for mutating entries by key + * + * @category models + * @since 2.0.0 + */ +export interface MutableHashMap extends Iterable<[K, V]>, Pipeable, Inspectable { + readonly [TypeId]: typeof TypeId + readonly backing: Map + readonly buckets: Map> +} + +/** + * Checks whether the specified value is a `MutableHashMap`, `false` otherwise. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a mutable hash map. + * + * **Details** + * + * The check looks for the `MutableHashMap` runtime marker. + * + * **Gotchas** + * + * The check does not validate the key or value types carried by the map. + * + * @see {@link MutableHashMap} for the mutable hash map interface + * + * @category guards + * @since 4.0.0 + */ +export const isMutableHashMap = (value: unknown): value is MutableHashMap => hasProperty(value, TypeId) + +const MutableHashMapProto: Omit, "backing" | "buckets" | "bucketsSize"> = { + [TypeId]: TypeId, + [Symbol.iterator](this: MutableHashMap): Iterator<[unknown, unknown]> { + return this.backing[Symbol.iterator]() + }, + toString() { + return `MutableHashMap(${format(Array.from(this))})` + }, + toJSON() { + return { + _id: "MutableHashMap", + values: toJson(Array.from(this)) + } + }, + [NodeInspectSymbol]() { + return this.toJSON() + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +/** + * Creates an empty MutableHashMap. + * + * **When to use** + * + * Use to create a fresh mutable map before adding entries over time. + * + * **Details** + * + * Each call returns a new empty map instance. + * + * **Example** (Creating an empty map) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * const map = MutableHashMap.empty() + * + * // Add some entries + * MutableHashMap.set(map, "key1", 42) + * MutableHashMap.set(map, "key2", 100) + * + * MutableHashMap.size(map) // => 2 + * ``` + * + * @see {@link make} for creating a map from explicit entries + * @see {@link fromIterable} for creating a map from an iterable of entries + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (): MutableHashMap => { + const self = Object.create(MutableHashMapProto) + self.backing = new Map() + self.buckets = new Map() + return self +} + +/** + * Creates a MutableHashMap from a variable number of key-value pairs. + * + * **When to use** + * + * Use to create a mutable hash map from explicit entries known at the call site. + * + * **Example** (Creating a map from entries) + * + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" + * + * const map = MutableHashMap.make( + * ["key1", 42], + * ["key2", 100], + * ["key3", 200] + * ) + * + * MutableHashMap.get(map, "key1") // => Option.some(42) + * MutableHashMap.size(map) // => 3 + * ``` + * + * @see {@link empty} for creating an empty map + * @see {@link fromIterable} for creating a map from an iterable of entries + * + * @category constructors + * @since 2.0.0 + */ +export const make: >( + ...entries: Entries +) => MutableHashMap< + Entries[number] extends readonly [infer K, any] ? K : never, + Entries[number] extends readonly [any, infer V] ? V : never +> = (...entries) => fromIterable(entries) + +/** + * Creates a MutableHashMap from an iterable collection of key-value pairs. + * + * **When to use** + * + * Use to create a mutable hash map from an existing iterable of entries. + * + * **Example** (Creating a map from an iterable) + * + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" + * + * const entries = [ + * ["apple", 1], + * ["banana", 2], + * ["cherry", 3] + * ] as const + * + * const map = MutableHashMap.fromIterable(entries) + * + * 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]])) + * MutableHashMap.get(fromMap, "x") // => Option.some(10) + * ``` + * + * @see {@link make} for creating a map from explicit entries + * @see {@link empty} for creating an empty map + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = (entries: Iterable): MutableHashMap => { + const self = empty() + for (const [key, value] of entries) { + set(self, key, value) + } + return self +} + +/** + * Looks up a key in the `MutableHashMap` safely. + * + * **When to use** + * + * Use to safely read a `MutableHashMap` value for a key as an `Option`. + * + * **Details** + * + * Returns `Some(value)` when an equal key is present and `None` when the key is + * absent. + * + * **Example** (Getting a value) + * + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" + * + * const map = MutableHashMap.make(["key1", 42], ["key2", 100]) + * + * MutableHashMap.get(map, "key1") // => Option.some(42) + * MutableHashMap.get(map, "key3") // => Option.none() + * + * // Pipe-able version + * 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 getters + * @since 2.0.0 + */ +export const get: { + (key: K): (self: MutableHashMap) => Option.Option + (self: MutableHashMap, key: K): Option.Option +} = dual< + (key: K) => (self: MutableHashMap) => Option.Option, + (self: MutableHashMap, key: K) => Option.Option +>(2, (self: MutableHashMap, key: K): Option.Option => { + if (self.backing.has(key)) { + return Option.some(self.backing.get(key)!) + } else if (isSimpleKey(key)) { + return Option.none() + } + const refKey = referentialKeysCache.get(self) + if (refKey !== undefined) { + return self.backing.has(refKey) ? Option.some(self.backing.get(refKey)!) : Option.none() + } + const hash = Hash.hash(key) + const bucket = self.buckets.get(hash) + if (bucket === undefined) { + return Option.none() + } + return getFromBucket(self, bucket, key) +}) + +const referentialKeysCache = new WeakMap() +const isSimpleKey = (u: unknown): boolean => typeof u !== "object" && typeof u !== "function" + +/** + * Returns an iterable over the keys in the `MutableHashMap`. + * + * **When to use** + * + * Use to iterate over the keys currently stored in a mutable hash map. + * + * **Example** (Reading keys) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * const map = MutableHashMap.make( + * ["apple", 1], + * ["banana", 2], + * ["cherry", 3] + * ) + * + * 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 getters + * @since 3.8.0 + */ +export const keys = (self: MutableHashMap): Iterable => self.backing.keys() + +/** + * Returns an iterable over the values in the `MutableHashMap`. + * + * **When to use** + * + * Use to iterate over the values currently stored in a mutable hash map. + * + * **Example** (Reading values) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * const map = MutableHashMap.make( + * ["apple", 1], + * ["banana", 2], + * ["cherry", 3] + * ) + * + * const allValues = Array.from(MutableHashMap.values(map)) // => [1, 2, 3] + * + * // Useful for calculations + * allValues.reduce((sum, value) => sum + value, 0) // => 6 + * + * // Filter values + * allValues.filter((value) => value > 1) // => [2, 3] + * ``` + * + * @see {@link keys} for iterating over stored keys + * + * @category getters + * @since 3.8.0 + */ +export const values = (self: MutableHashMap): Iterable => self.backing.values() + +const getFromBucket = ( + self: MutableHashMap, + bucket: NonEmptyArray, + key: K +): Option.Option => { + for (let i = 0, len = bucket.length; i < len; i++) { + if (Equal.equals(key, bucket[i])) { + const refKey = bucket[i] + referentialKeysCache.set(key, refKey) + return Option.some(self.backing.get(refKey)!) + } + } + return Option.none() +} + +/** + * Checks whether the MutableHashMap contains the specified key. + * + * **When to use** + * + * Use to test whether a key is present in a `MutableHashMap` without reading + * its value. + * + * **Example** (Checking for a key) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * const map = MutableHashMap.make(["key1", 42], ["key2", 100]) + * + * MutableHashMap.has(map, "key1") // => true + * MutableHashMap.has(map, "key3") // => false + * + * // Pipe-able version + * MutableHashMap.has("key1")(map) // => true + * ``` + * + * @see {@link get} for reading the value as an `Option` + * + * @category predicates + * @since 2.0.0 + */ +export const has: { + (key: K): (self: MutableHashMap) => boolean + (self: MutableHashMap, key: K): boolean +} = dual< + (key: K) => (self: MutableHashMap) => boolean, + (self: MutableHashMap, key: K) => boolean +>(2, (self, key) => Option.isSome(get(self, key))) + +/** + * Sets a key-value pair in the MutableHashMap, mutating the map in place. + * If the key already exists, its value is updated. + * + * **When to use** + * + * Use to insert a new `MutableHashMap` entry or replace an existing entry in + * place. + * + * **Example** (Setting key-value pairs) + * + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" + * + * const map = MutableHashMap.empty() + * + * // Add new entries + * MutableHashMap.set(map, "key1", 42) + * MutableHashMap.set(map, "key2", 100) + * + * MutableHashMap.get(map, "key1") // => Option.some(42) + * MutableHashMap.size(map) // => 2 + * + * // Update existing entry + * MutableHashMap.set(map, "key1", 999) + * MutableHashMap.get(map, "key1") // => Option.some(999) + * + * // Pipe-able version + * MutableHashMap.set("key3", 300)(map) + * MutableHashMap.size(map) // => 3 + * ``` + * + * @see {@link modify} for updating an existing value with a function + * @see {@link modifyAt} for setting or removing based on the current optional value + * @see {@link remove} for deleting an entry by key + * + * @category mutations + * @since 2.0.0 + */ +export const set: { + (key: K, value: V): (self: MutableHashMap) => MutableHashMap + (self: MutableHashMap, key: K, value: V): MutableHashMap +} = dual< + (key: K, value: V) => (self: MutableHashMap) => MutableHashMap, + (self: MutableHashMap, key: K, value: V) => MutableHashMap +>(3, (self: MutableHashMap, key: K, value: V) => { + if (self.backing.has(key) || isSimpleKey(key)) { + self.backing.set(key, value) + return self + } + let refKey = referentialKeysCache.get(self) + if (refKey !== undefined && self.backing.has(refKey)) { + self.backing.set(refKey, value) + return self + } + + const hash = Hash.hash(key) + const bucket = self.buckets.get(hash) + if (bucket === undefined) { + self.buckets.set(hash, [key]) + self.backing.set(key, value) + return self + } + + refKey = getRefKey(bucket, key) + if (refKey === undefined) { + bucket.push(key) + refKey = key + } + self.backing.set(refKey, value) + return self +}) + +const getRefKey = ( + bucket: NonEmptyArray, + key: K +) => { + for (let i = 0, len = bucket.length; i < len; i++) { + if (Equal.equals(key, bucket[i])) { + referentialKeysCache.set(key, bucket[i]) + return bucket[i] + } + } +} + +/** + * Updates the value of the specified key within the MutableHashMap if it exists. + * If the key doesn't exist, the map remains unchanged. + * + * **When to use** + * + * Use to transform an existing `MutableHashMap` value in place without + * inserting missing keys. + * + * **Example** (Modifying existing values) + * + * ```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) + * MutableHashMap.get(map, "count") // => Option.some(6) + * + * // Double existing value + * MutableHashMap.modify(map, "total", (n) => n * 2) + * MutableHashMap.get(map, "total") // => Option.some(200) + * + * // Try to modify non-existent key (no effect) + * MutableHashMap.modify(map, "missing", (n) => n + 1) + * MutableHashMap.has(map, "missing") // => false + * + * // Pipe-able version + * 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 + * @see {@link modifyAt} for handling both missing and existing keys + * + * @category mutations + * @since 2.0.0 + */ +export const modify: { + (key: K, f: (v: V) => V): (self: MutableHashMap) => MutableHashMap + (self: MutableHashMap, key: K, f: (v: V) => V): MutableHashMap +} = dual< + (key: K, f: (v: V) => V) => (self: MutableHashMap) => MutableHashMap, + (self: MutableHashMap, key: K, f: (v: V) => V) => MutableHashMap +>(3, (self: MutableHashMap, key: K, f: (v: V) => V) => { + const hasKey = self.backing.has(key) + if (hasKey || isSimpleKey(key)) { + if (hasKey) { + self.backing.set(key, f(self.backing.get(key)!)) + } + return self + } + let refKey = referentialKeysCache.get(self) + if (refKey !== undefined && self.backing.has(refKey)) { + self.backing.set(refKey, f(self.backing.get(refKey)!)) + return self + } + + const hash = Hash.hash(key) + const bucket = self.buckets.get(hash) + if (bucket === undefined) { + return self + } + + refKey = getRefKey(bucket, key) + if (refKey === undefined) { + return self + } + self.backing.set(refKey, f(self.backing.get(refKey)!)) + return self +}) + +/** + * Updates or removes the specified key using a function from the current + * optional value to the next optional value. + * + * **When to use** + * + * Use to decide whether to insert, update, or remove a key based on its current + * optional value. + * + * **Example** (Updating or removing a key) + * + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" + * + * const map = MutableHashMap.make(["count", 5]) + * + * // Update existing key + * MutableHashMap.modifyAt( + * map, + * "count", + * (option) => Option.map(option, (n) => n * 2) + * ) + * MutableHashMap.get(map, "count") // => Option.some(10) + * + * // Add new key + * MutableHashMap.modifyAt( + * map, + * "new", + * (option) => Option.isNone(option) ? Option.some(42) : option + * ) + * MutableHashMap.get(map, "new") // => Option.some(42) + * + * // Remove key by returning None + * MutableHashMap.modifyAt(map, "count", () => Option.none()) + * MutableHashMap.get(map, "count") // => Option.none() + * + * // Conditional update + * MutableHashMap.modifyAt( + * map, + * "new", + * (option) => Option.filter(option, (n) => n > 50) // Remove if <= 50 + * ) + * MutableHashMap.get(map, "new") // => Option.none() + * ``` + * + * @see {@link modify} for updating only when the key already exists + * @see {@link set} for inserting or replacing directly + * @see {@link remove} for deleting directly + * + * @category mutations + * @since 2.0.0 + */ +export const modifyAt: { + (key: K, f: (value: Option.Option) => Option.Option): (self: MutableHashMap) => MutableHashMap + (self: MutableHashMap, key: K, f: (value: Option.Option) => Option.Option): MutableHashMap +} = dual< + ( + key: K, + f: (value: Option.Option) => Option.Option + ) => (self: MutableHashMap) => MutableHashMap, + ( + self: MutableHashMap, + key: K, + f: (value: Option.Option) => Option.Option + ) => MutableHashMap +>(3, (self, key, f) => { + const current = get(self, key) + const result = f(current) + if (Option.isNone(result)) { + if (Option.isSome(current)) { + remove(self, key) + } + return self + } + set(self, key, result.value) + return self +}) + +/** + * Removes the specified key from the MutableHashMap, mutating the map in place. + * If the key doesn't exist, the map remains unchanged. + * + * **When to use** + * + * Use to delete one key from a mutable hash map in place. + * + * **Example** (Removing a key) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * const map = MutableHashMap.make( + * ["key1", 42], + * ["key2", 100], + * ["key3", 200] + * ) + * + * MutableHashMap.size(map) // => 3 + * + * // Remove existing key + * MutableHashMap.remove(map, "key2") + * MutableHashMap.size(map) // => 2 + * MutableHashMap.has(map, "key2") // => false + * + * // Remove non-existent key (no effect) + * MutableHashMap.remove(map, "nonexistent") + * MutableHashMap.size(map) // => 2 + * + * // Pipe-able version + * MutableHashMap.remove("key1")(map) + * MutableHashMap.size(map) // => 1 + * ``` + * + * @see {@link clear} for removing all entries + * @see {@link modifyAt} for conditionally removing based on the current value + * + * @category mutations + * @since 2.0.0 + */ +export const remove: { + (key: K): (self: MutableHashMap) => MutableHashMap + (self: MutableHashMap, key: K): MutableHashMap +} = dual< + (key: K) => (self: MutableHashMap) => MutableHashMap, + (self: MutableHashMap, key: K) => MutableHashMap +>(2, (self: MutableHashMap, key_: K) => { + if (isSimpleKey(key_)) { + self.backing.delete(key_) + return self + } + + const key = referentialKeysCache.get(self) ?? key_ + const hash = Hash.hash(key) + const bucket = self.buckets.get(hash) + if (bucket === undefined) { + return self + } + for (let i = 0, len = bucket.length; i < len; i++) { + const bkey = bucket[i] + if (bkey === key || Equal.equals(key, bkey)) { + self.backing.delete(bkey) + bucket.splice(i, 1) + break + } + } + if (bucket.length === 0) { + self.buckets.delete(hash) + } + return self +}) + +/** + * Removes all key-value pairs from the MutableHashMap, mutating the map in place. + * The map becomes empty after this operation. + * + * **When to use** + * + * Use to empty a mutable hash map while keeping the same map instance. + * + * **Example** (Clearing all entries) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * const map = MutableHashMap.make( + * ["key1", 42], + * ["key2", 100], + * ["key3", 200] + * ) + * + * MutableHashMap.size(map) // => 3 + * + * // Clear all entries + * MutableHashMap.clear(map) + * + * MutableHashMap.size(map) // => 0 + * MutableHashMap.has(map, "key1") // => false + * + * // Can still add new entries after clearing + * MutableHashMap.set(map, "new", 999) + * Array.from(map) // => [["new", 999]] + * ``` + * + * @see {@link remove} for deleting one key + * @see {@link empty} for creating a fresh empty map + * + * @category mutations + * @since 2.0.0 + */ +export const clear = (self: MutableHashMap) => { + self.backing.clear() + self.buckets.clear() + return self +} + +/** + * Returns the number of key-value pairs in the MutableHashMap. + * + * **When to use** + * + * Use to read how many entries are currently stored in the mutable hash map. + * + * **Example** (Checking map size) + * + * ```ts import.meta.vitest + * import { MutableHashMap } from "effect" + * + * const map = MutableHashMap.empty() + * MutableHashMap.size(map) // => 0 + * + * MutableHashMap.set(map, "key1", 42) + * MutableHashMap.set(map, "key2", 100) + * MutableHashMap.size(map) // => 2 + * + * MutableHashMap.remove(map, "key1") + * MutableHashMap.size(map) // => 1 + * + * MutableHashMap.clear(map) + * MutableHashMap.size(map) // => 0 + * ``` + * + * @see {@link isEmpty} for checking whether the map has no entries + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: MutableHashMap): number => self.backing.size + +/** + * Returns `true` when the `MutableHashMap` contains no key-value pairs. + * + * **When to use** + * + * Use to branch on whether a mutable map currently has any entries. + * + * @see {@link size} for reading the exact number of entries + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: MutableHashMap): boolean => self.backing.size === 0 + +/** + * Runs a callback for each key-value pair in the `MutableHashMap`. + * + * **When to use** + * + * Use to run a synchronous side-effecting callback for every key-value pair in + * an existing mutable map. + * + * **Details** + * + * Iteration follows the backing map's order. The callback receives the value + * first and the key second, matching `Map.prototype.forEach`. + * + * @see {@link keys} for iterating only keys + * @see {@link values} for iterating only values + * + * @category traversing + * @since 2.0.0 + */ +export const forEach: { + (f: (value: V, key: K) => void): (self: MutableHashMap) => void + (self: MutableHashMap, f: (value: V, key: K) => void): void +} = dual(2, (self: MutableHashMap, f: (value: V, key: K) => void) => { + self.backing.forEach(f) +}) diff --git a/.repos/effect/packages/effect/src/MutableHashSet.ts b/.repos/effect/packages/effect/src/MutableHashSet.ts new file mode 100644 index 000000000..3b1965c9b --- /dev/null +++ b/.repos/effect/packages/effect/src/MutableHashSet.ts @@ -0,0 +1,417 @@ +/** + * Stores unique values in a mutable hash set. + * + * `MutableHashSet` updates the same collection in place and supports fast + * membership checks, insertion, removal, clearing, and iteration. It is built + * on `MutableHashMap`: each set value is stored as a map key, so uniqueness + * follows the same hashing and equality rules as the underlying mutable hash + * map. + * + * @since 2.0.0 + */ +import { format } from "./Formatter.ts" +import * as Dual from "./Function.ts" +import { type Inspectable, NodeInspectSymbol, toJson } from "./Inspectable.ts" +import * as MutableHashMap from "./MutableHashMap.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" + +const TypeId = "~effect/collections/MutableHashSet" + +/** + * A mutable hash set for storing unique values with Effect structural equality + * support. + * + * **When to use** + * + * Use to store and mutate a collection of unique values with Effect hashing and + * equality semantics. + * + * **Details** + * + * Operations mutate the set in place. Values that implement `Equal` / `Hash` + * can be de-duplicated structurally; other values use normal JavaScript + * reference or primitive equality. + * + * **Example** (Using a mutable hash set) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * // Create a mutable hash set + * const set: MutableHashSet.MutableHashSet = MutableHashSet.make( + * "apple", + * "banana" + * ) + * + * // Add elements + * MutableHashSet.add(set, "cherry") + * + * // Check if elements exist + * MutableHashSet.has(set, "apple") // => true + * MutableHashSet.has(set, "grape") // => false + * + * // Collect the iterator values + * Array.from(set) // => ["apple", "banana", "cherry"] + * + * // Get size + * MutableHashSet.size(set) // => 3 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface MutableHashSet extends Iterable, Pipeable, Inspectable { + readonly [TypeId]: typeof TypeId + readonly keyMap: MutableHashMap.MutableHashMap +} + +/** + * Checks whether the specified value is a `MutableHashSet`, `false` otherwise. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a mutable hash set. + * + * **Details** + * + * The check looks for the `MutableHashSet` runtime marker. + * + * **Gotchas** + * + * Native `Set` values do not satisfy this check. + * + * @see {@link MutableHashSet} for the mutable hash set interface + * + * @category guards + * @since 4.0.0 + */ +export const isMutableHashSet = (value: unknown): value is MutableHashSet => hasProperty(value, TypeId) + +const MutableHashSetProto: Omit, "keyMap"> = { + [TypeId]: TypeId, + [Symbol.iterator](this: MutableHashSet): Iterator { + return Array.from(this.keyMap).map(([_]) => _)[Symbol.iterator]() + }, + toString() { + return `MutableHashSet(${format(Array.from(this))})` + }, + toJSON() { + return { + _id: "MutableHashSet", + values: toJson(Array.from(this)) + } + }, + [NodeInspectSymbol]() { + return this.toJSON() + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +const fromHashMap = (keyMap: MutableHashMap.MutableHashMap): MutableHashSet => { + const set = Object.create(MutableHashSetProto) + set.keyMap = keyMap + return set +} + +/** + * Creates an empty MutableHashSet. + * + * **When to use** + * + * Use to create a fresh mutable set before adding values over time. + * + * **Details** + * + * Each call returns a new empty set backed by an empty `MutableHashMap`. + * + * **Example** (Creating an empty set) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const set = MutableHashSet.empty() + * + * // Add some values + * MutableHashSet.add(set, "apple") + * MutableHashSet.add(set, "banana") + * MutableHashSet.add(set, "apple") // Duplicate, no effect + * + * MutableHashSet.size(set) // => 2 + * Array.from(set) // => ["apple", "banana"] + * ``` + * + * @see {@link make} for creating a set from explicit values + * @see {@link fromIterable} for creating a set from an iterable of values + * @see {@link clear} for emptying an existing mutable set + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (): MutableHashSet => fromHashMap(MutableHashMap.empty()) + +/** + * Creates a MutableHashSet from an iterable collection of values. + * Duplicates are automatically removed. + * + * **When to use** + * + * Use to build a mutable hash set from any iterable of values. + * + * **Example** (Creating a set from an iterable) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const values = ["apple", "banana", "apple", "cherry", "banana"] + * const set = MutableHashSet.fromIterable(values) + * + * MutableHashSet.size(set) // => 3 + * Array.from(set) // => ["apple", "banana", "cherry"] + * + * // Works with any iterable + * MutableHashSet.size(MutableHashSet.fromIterable(new Set([1, 2, 3]))) // => 3 + * + * // From string characters + * Array.from(MutableHashSet.fromIterable("hello")) // => ["h", "e", "l", "o"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = (keys: Iterable): MutableHashSet => + fromHashMap(MutableHashMap.fromIterable(Array.from(keys).map((k) => [k, true]))) + +/** + * Creates a MutableHashSet from a variable number of values. + * Duplicates are automatically removed. + * + * **When to use** + * + * Use to build a mutable hash set from explicit values. + * + * **Example** (Creating a set from values) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const set = MutableHashSet.make("apple", "banana", "apple", "cherry") + * + * MutableHashSet.size(set) // => 3 + * Array.from(set) // => ["apple", "banana", "cherry"] + * + * // With numbers + * const numbers = MutableHashSet.make(1, 2, 3, 2, 1) + * MutableHashSet.size(numbers) // => 3 + * Array.from(numbers) // => [1, 2, 3] + * + * // Mixed types + * MutableHashSet.size(MutableHashSet.make("hello", 42, true, "hello")) // => 3 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = >( + ...keys: Keys +): MutableHashSet => fromIterable(keys) + +/** + * Adds a value to the MutableHashSet, mutating the set in place. + * If the value already exists, the set remains unchanged. + * + * **When to use** + * + * Use to insert a value into a mutable set while keeping uniqueness. + * + * **Example** (Adding values) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const set = MutableHashSet.empty() + * + * // Add new values + * MutableHashSet.add(set, "apple") + * MutableHashSet.add(set, "banana") + * + * MutableHashSet.size(set) // => 2 + * MutableHashSet.has(set, "apple") // => true + * + * // Add duplicate (no effect) + * MutableHashSet.add(set, "apple") + * MutableHashSet.size(set) // => 2 + * + * // Pipe-able version + * const addFruit = MutableHashSet.add("cherry") + * addFruit(set) + * MutableHashSet.size(set) // => 3 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const add: { + (key: V): (self: MutableHashSet) => MutableHashSet + (self: MutableHashSet, key: V): MutableHashSet +} = Dual.dual< + (key: V) => (self: MutableHashSet) => MutableHashSet, + (self: MutableHashSet, key: V) => MutableHashSet +>(2, (self, key) => (MutableHashMap.set(self.keyMap, key, true), self)) + +/** + * Checks whether the MutableHashSet contains the specified value. + * + * **When to use** + * + * Use to test whether a mutable set currently contains a value. + * + * **Details** + * + * Membership follows the same hashing and equality rules as the underlying + * `MutableHashMap`. + * + * **Example** (Checking for a value) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const set = MutableHashSet.make("apple", "banana", "cherry") + * + * MutableHashSet.has(set, "apple") // => true + * MutableHashSet.has(set, "grape") // => false + * + * // Pipe-able version + * const hasApple = MutableHashSet.has("apple") + * hasApple(set) // => true + * + * // Check after adding + * MutableHashSet.add(set, "grape") + * 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 predicates + * @since 2.0.0 + */ +export const has: { + (key: V): (self: MutableHashSet) => boolean + (self: MutableHashSet, key: V): boolean +} = Dual.dual< + (key: V) => (self: MutableHashSet) => boolean, + (self: MutableHashSet, key: V) => boolean +>(2, (self, key) => MutableHashMap.has(self.keyMap, key)) + +/** + * Removes the specified value from the MutableHashSet, mutating the set in place. + * If the value doesn't exist, the set remains unchanged. + * + * **When to use** + * + * Use to delete a value from a mutable set if it is present. + * + * **Example** (Removing a value) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const set = MutableHashSet.make("apple", "banana", "cherry") + * + * MutableHashSet.size(set) // => 3 + * + * // Remove existing value + * MutableHashSet.remove(set, "banana") + * MutableHashSet.size(set) // => 2 + * MutableHashSet.has(set, "banana") // => false + * + * // Remove non-existent value (no effect) + * MutableHashSet.remove(set, "grape") + * MutableHashSet.size(set) // => 2 + * + * // Pipe-able version + * const removeFruit = MutableHashSet.remove("apple") + * removeFruit(set) + * MutableHashSet.size(set) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const remove: { + (key: V): (self: MutableHashSet) => MutableHashSet + (self: MutableHashSet, key: V): MutableHashSet +} = Dual.dual< + (key: V) => (self: MutableHashSet) => MutableHashSet, + (self: MutableHashSet, key: V) => MutableHashSet +>(2, (self, key) => (MutableHashMap.remove(self.keyMap, key), self)) + +/** + * Returns the number of unique values in the MutableHashSet. + * + * **When to use** + * + * Use to read how many unique values are currently stored in the set. + * + * **Example** (Checking set size) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const set = MutableHashSet.empty() + * MutableHashSet.size(set) // => 0 + * + * MutableHashSet.add(set, "apple") + * MutableHashSet.add(set, "banana") + * MutableHashSet.add(set, "apple") // Duplicate + * MutableHashSet.size(set) // => 2 + * + * MutableHashSet.remove(set, "apple") + * MutableHashSet.size(set) // => 1 + * + * MutableHashSet.clear(set) + * MutableHashSet.size(set) // => 0 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: MutableHashSet): number => MutableHashMap.size(self.keyMap) + +/** + * Removes all values from the MutableHashSet, mutating the set in place. + * The set becomes empty after this operation. + * + * **When to use** + * + * Use to empty a mutable set while keeping the same set instance. + * + * **Example** (Clearing all values) + * + * ```ts import.meta.vitest + * import { MutableHashSet } from "effect" + * + * const set = MutableHashSet.make("apple", "banana", "cherry") + * + * MutableHashSet.size(set) // => 3 + * + * // Clear all values + * MutableHashSet.clear(set) + * + * MutableHashSet.size(set) // => 0 + * MutableHashSet.has(set, "apple") // => false + * Array.from(set) // => [] + * + * // Can still add new values after clearing + * MutableHashSet.add(set, "new") + * MutableHashSet.size(set) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const clear = (self: MutableHashSet): MutableHashSet => (MutableHashMap.clear(self.keyMap), self) diff --git a/.repos/effect/packages/effect/src/MutableList.ts b/.repos/effect/packages/effect/src/MutableList.ts new file mode 100644 index 000000000..e40106a5e --- /dev/null +++ b/.repos/effect/packages/effect/src/MutableList.ts @@ -0,0 +1,674 @@ +/** + * Mutable lists for collecting ordered values and draining them from the front. + * A `MutableList` can append values to the end, prepend values to the + * beginning, take one or more values from the front, inspect its contents as an + * array, filter values, remove values, and clear itself. All operations update + * the same list object in place and keep its `length` field current. Taking + * from an empty list returns the `Empty` symbol. + * + * @since 4.0.0 + */ +import * as Arr from "./Array.ts" + +/** + * A mutable linked list data structure optimized for high-throughput operations. + * MutableList provides efficient append/prepend operations and is ideal for + * producer-consumer patterns, queues, and streaming scenarios. + * + * **Example** (Creating and consuming a mutable list) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list: MutableList.MutableList = MutableList.make() + * MutableList.append(list, 1) + * MutableList.append(list, 2) + * MutableList.prepend(list, 0) + * + * MutableList.takeAll(list) // => [0, 1, 2] + * list.length // => 0 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface MutableList { + head: MutableList.Bucket | undefined + tail: MutableList.Bucket | undefined + length: number +} + +/** + * The MutableList namespace contains type definitions and utilities for working + * with mutable linked lists. + * + * @since 2.0.0 + */ +export declare namespace MutableList { + /** + * Storage node used by the exposed `head` and `tail` fields of a + * `MutableList`. + * + * **Details** + * + * Most code should treat buckets as an implementation detail and use + * `MutableList` operations such as `append`, `prepend`, and `take` instead + * of constructing or mutating buckets directly. + * + * **Example** (Inspecting buckets) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.append(list, 1) + * MutableList.append(list, 2) + * + * const bucket: MutableList.MutableList.Bucket = list.head! + * + * bucket.array // => [1, 2] + * bucket.offset // => 0 + * bucket.mutable // => true + * bucket.next === undefined // => true + * ``` + * + * @category models + * @since 4.0.0 + */ + export interface Bucket { + readonly array: Array + mutable: boolean + offset: number + next: Bucket | undefined + } +} + +/** + * Defines the unique symbol used to represent an empty result when taking elements from a MutableList. + * This symbol is returned by `take` when the list is empty, allowing for safe type checking. + * + * **When to use** + * + * Use to detect that `take` returned no element before handling the result as a + * list item. + * + * **Example** (Checking for empty results) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * + * MutableList.take(list) === MutableList.Empty // => true + * ``` + * + * @category symbols + * @since 4.0.0 + */ +export const Empty: unique symbol = Symbol.for("effect/MutableList/Empty") + +/** + * The type of the Empty symbol, used for type checking when taking elements from a MutableList. + * This provides compile-time safety when checking for empty results. + * + * **Example** (Handling empty results type-safely) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * + * const takeAndDouble = (queue: MutableList.MutableList): number | null => { + * const item: number | MutableList.Empty = MutableList.take(queue) + * return item === MutableList.Empty ? null : item * 2 + * } + * + * takeAndDouble(list) // => null + * MutableList.append(list, 5) + * takeAndDouble(list) // => 10 + * ``` + * + * @category symbols + * @since 4.0.0 + */ +export type Empty = typeof Empty + +/** + * Creates an empty MutableList. + * + * **Example** (Creating an empty mutable list) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * + * list.length // => 0 + * MutableList.append(list, "first") + * MutableList.take(list) // => "first" + * list.length // => 0 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (): MutableList => ({ + head: undefined, + tail: undefined, + length: 0 +}) + +const emptyBucket = (): MutableList.Bucket => ({ + array: [], + mutable: true, + offset: 0, + next: undefined +}) + +/** + * Appends an element to the end of the MutableList. + * This operation is optimized for high-frequency usage. + * + * **Example** (Appending elements) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.append(list, 1) + * MutableList.append(list, 2) + * MutableList.append(list, 3) + * + * MutableList.toArray(list) // => [1, 2, 3] + * list.length // => 3 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const append = (self: MutableList, message: A): void => { + if (!self.tail) { + self.head = self.tail = emptyBucket() + } else if (!self.tail.mutable) { + self.tail.next = emptyBucket() + self.tail = self.tail.next + } + self.tail!.array.push(message) + self.length++ +} + +/** + * Prepends an element to the beginning of the MutableList. + * This operation is optimized for high-frequency usage. + * + * **Example** (Prepending elements) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.append(list, "last") + * MutableList.prepend(list, "third") + * MutableList.prepend(list, "second") + * MutableList.prepend(list, "first") + * + * MutableList.toArray(list) // => ["first", "second", "third", "last"] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const prepend = (self: MutableList, message: A): void => { + self.head = { + array: [message], + mutable: true, + offset: 0, + next: self.head + } + if (!self.tail) self.tail = self.head + self.length++ +} + +/** + * Prepends all elements from an iterable to the beginning of the MutableList. + * The elements are added in order, so the first element in the iterable becomes + * the new head of the list. + * + * **Example** (Prepending multiple elements) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.append(list, 4) + * MutableList.append(list, 5) + * MutableList.prependAll(list, [1, 2, 3]) + * + * MutableList.toArray(list) // => [1, 2, 3, 4, 5] + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const prependAll = (self: MutableList, messages: Iterable): void => + prependAllUnsafe(self, Arr.fromIterable(messages), !Array.isArray(messages)) + +/** + * Prepends all elements from a ReadonlyArray to the beginning of the MutableList. + * This is an optimized version that can reuse the array when mutable=true. + * + * **When to use** + * + * Use when prepending a trusted array directly is worth the optimized path and + * you can transfer ownership of the input when enabling mutation. + * + * **Gotchas** + * + * When mutable=true, ownership of the input array transfers to the list. Do not + * read or modify the array afterward. + * + * **Example** (Transferring an array when prepending) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.append(list, 4) + * const items = [1, 2, 3] + * MutableList.prependAllUnsafe(list, items, true) + * + * MutableList.toArray(list) // => [1, 2, 3, 4] + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const prependAllUnsafe = (self: MutableList, messages: ReadonlyArray, mutable = false): void => { + self.head = { + array: messages as Array, + mutable, + offset: 0, + next: self.head + } + self.length += self.head.array.length +} + +/** + * Appends all elements from an iterable to the end of the MutableList. + * Returns the number of elements added. + * + * **Example** (Appending multiple elements) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.append(list, 1) + * MutableList.append(list, 2) + * + * MutableList.appendAll(list, [3, 4, 5]) // => 3 + * MutableList.toArray(list) // => [1, 2, 3, 4, 5] + * list.length // => 5 + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const appendAll = (self: MutableList, messages: Iterable): number => + appendAllUnsafe(self, Arr.fromIterable(messages), !Array.isArray(messages)) + +/** + * Appends all elements from a ReadonlyArray to the end of the MutableList. + * This is an optimized version that can reuse the array when mutable=true. + * Returns the number of elements added. + * + * **When to use** + * + * Use when appending a trusted array directly is worth the optimized path and + * you can transfer ownership of the input when enabling mutation. + * + * **Gotchas** + * + * When mutable=true, ownership of the input array transfers to the list. Do not + * read or modify the array afterward. + * + * **Example** (Transferring an array when appending) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.append(list, 1) + * const items = [2, 3, 4] + * MutableList.appendAllUnsafe(list, items, true) // => 3 + * + * MutableList.toArray(list) // => [1, 2, 3, 4] + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const appendAllUnsafe = (self: MutableList, messages: ReadonlyArray, mutable = false): number => { + if (messages.length === 0) { + return 0 + } + const chunk: MutableList.Bucket = { + array: messages as Array, + mutable, + offset: 0, + next: undefined + } + if (self.head) { + self.tail = self.tail!.next = chunk + } else { + self.head = self.tail = chunk + } + self.length += messages.length + return messages.length +} + +/** + * Removes all elements from the MutableList, resetting it to an empty state. + * This operation is highly optimized and releases all internal memory. + * + * **Example** (Clearing a mutable list) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.appendAll(list, [1, 2, 3, 4, 5]) + * + * MutableList.clear(list) + * + * MutableList.toArray(list) // => [] + * list.length // => 0 + * MutableList.take(list) === MutableList.Empty // => true + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const clear = (self: MutableList): void => { + self.head = self.tail = undefined + self.length = 0 +} + +/** + * Takes up to N elements from the beginning of the MutableList and returns them as an array. + * The taken elements are removed from the list. This operation is optimized for performance + * and includes zero-copy optimizations when possible. + * + * **Example** (Taking batches) + * + * ```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]) + * + * MutableList.takeN(list, 3) // => [1, 2, 3] + * MutableList.toArray(list) // => [4, 5, 6, 7, 8, 9, 10] + * list.length // => 7 + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const takeN = (self: MutableList, n: number): Array => { + if (n <= 0 || !self.head) return [] + n = Math.min(n, self.length) + if (n === self.length && self.head?.offset === 0 && !self.head.next) { + const array = self.head.array + clear(self) + return array + } + const array = new Array(n) + let index = 0 + let chunk: MutableList.Bucket | undefined = self.head + while (chunk) { + while (chunk.offset < chunk.array.length) { + array[index++] = chunk.array[chunk.offset] + if (chunk.mutable) chunk.array[chunk.offset] = undefined as any + chunk.offset++ + if (index === n) { + self.head = chunk + self.length -= n + if (self.length === 0) clear(self) + return array + } + } + chunk = chunk.next + } + clear(self) + return array +} + +/** + * Removes up to `n` elements from the beginning of the `MutableList` without + * returning them. + * + * **When to use** + * + * Use to discard a bounded number of values from the head of a `MutableList` + * when the removed values are not needed. + * + * **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 + * 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 mutations + * @since 4.0.0 + */ +export const takeNVoid = (self: MutableList, n: number): void => { + if (n <= 0 || !self.head) return + n = Math.min(n, self.length) + if (n === self.length && self.head?.offset === 0 && !self.head.next) { + clear(self) + return + } + let count = 0 + let chunk: MutableList.Bucket | undefined = self.head + while (chunk) { + const size = chunk.array.length - chunk.offset + if (count + size > n) { + chunk.offset += n - count + self.head = chunk + self.length -= n + return + } + count += size + chunk = chunk.next + } + clear(self) + return +} + +/** + * Takes all elements from the MutableList and returns them as an array. + * The list becomes empty after this operation. This is equivalent to takeN(list, list.length). + * + * **Example** (Draining all elements) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.appendAll(list, ["apple", "banana", "cherry"]) + * + * MutableList.takeAll(list) // => ["apple", "banana", "cherry"] + * list.length // => 0 + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const takeAll = (self: MutableList): Array => takeN(self, self.length) + +/** + * Takes a single element from the beginning of the MutableList. + * Returns the element if available, or the Empty symbol if the list is empty. + * The taken element is removed from the list. + * + * **Example** (Taking one element) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.appendAll(list, ["first", "second", "third"]) + * + * MutableList.take(list) // => "first" + * MutableList.toArray(list) // => ["second", "third"] + * list.length // => 2 + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const take = (self: MutableList): Empty | A => { + if (!self.head) return Empty + const message = self.head.array[self.head.offset] + if (self.head.mutable) self.head.array[self.head.offset] = undefined as any + self.head.offset++ + self.length-- + if (self.head.offset === self.head.array.length) { + if (self.head.next) { + self.head = self.head.next + } else { + clear(self) + } + } + return message +} + +/** + * Copies up to `n` elements from the beginning of the `MutableList` into a new + * array without modifying the list. + * + * **When to use** + * + * Use when you need to inspect or snapshot a bounded prefix of the list without + * consuming it. + * + * @see {@link takeN} for removing up to `n` values and returning them as an array + * + * @category converting + * @since 4.0.0 + */ +export const toArrayN = (self: MutableList, n: number): Array => { + if (n <= 0) return [] + const length = Math.min(n, self.length) + const out = new Array(length) + let index = 0 + let bucket = self.head + while (bucket) { + for (let i = bucket.offset; i < bucket.array.length; i++) { + out[index++] = bucket.array[i] + if (index === length) return out + } + bucket = bucket.next + } + return out +} + +/** + * Copies all current elements of the `MutableList` into a new array without + * modifying the list. + * + * **When to use** + * + * Use when you need a snapshot of all current elements while keeping the list + * unchanged. + * + * @see {@link takeAll} for converting all elements to an array and clearing the list + * + * @category converting + * @since 4.0.0 + */ +export const toArray = (self: MutableList): Array => toArrayN(self, self.length) + +/** + * Filters the MutableList in place, keeping only elements that satisfy the predicate. + * This operation modifies the list and rebuilds its internal structure for efficiency. + * + * **Example** (Filtering in place) + * + * ```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]) + * + * MutableList.filter(list, (n) => n % 2 === 0) + * + * MutableList.toArray(list) // => [2, 4, 6, 8, 10] + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const filter = (self: MutableList, f: (value: A, i: number) => boolean): void => { + const array: Array = [] + let chunk: MutableList.Bucket | undefined = self.head + while (chunk) { + for (let i = chunk.offset; i < chunk.array.length; i++) { + if (f(chunk.array[i], i)) { + array.push(chunk.array[i]) + } + } + chunk = chunk.next + } + if (array.length === 0) { + clear(self) + return + } + self.head = self.tail = { + array, + mutable: true, + offset: 0, + next: undefined + } + self.length = array.length +} + +/** + * Removes all occurrences of a value from the `MutableList` using JavaScript + * strict equality semantics. + * + * **When to use** + * + * Use when in-place removal should use JavaScript identity/strict equality + * rather than Effect structural equality. + * + * **Details** + * + * The list is modified in place. + * + * **Gotchas** + * + * Values are compared with `!==`, so this does not use Effect structural + * equality. + * + * **Example** (Removing matching values) + * + * ```ts import.meta.vitest + * import { MutableList } from "effect" + * + * const list = MutableList.make() + * MutableList.appendAll(list, ["apple", "banana", "apple", "cherry", "apple"]) + * + * MutableList.remove(list, "apple") + * + * MutableList.toArray(list) // => ["banana", "cherry"] + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const remove = (self: MutableList, value: A): void => filter(self, (v) => v !== value) diff --git a/.repos/effect/packages/effect/src/MutableRef.ts b/.repos/effect/packages/effect/src/MutableRef.ts new file mode 100644 index 000000000..acd270380 --- /dev/null +++ b/.repos/effect/packages/effect/src/MutableRef.ts @@ -0,0 +1,913 @@ +/** + * Stores synchronous mutable state in a small reference object. + * + * A `MutableRef` stores one current value and exposes it through `.current`. + * Unlike `Ref`, its operations are synchronous and update the same object in + * place. This module includes pipeable helpers for reading, setting, comparing, + * and updating the value, plus numeric increment/decrement helpers and a + * boolean toggle helper. + * + * @since 2.0.0 + */ +import * as Equal from "./Equal.ts" +import * as Dual from "./Function.ts" +import { type Inspectable, toJson } from "./Inspectable.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import type { Pipeable } from "./Pipeable.ts" + +const TypeId = "~effect/MutableRef" + +/** + * A synchronous mutable reference that stores a current value. + * + * **When to use** + * + * Use to keep local mutable state in a stable, pipeable reference. + * + * **Details** + * + * Read or write the value directly through `.current`, or use the `MutableRef` + * helpers for pipeable updates such as `get`, `set`, `update`, and + * `compareAndSet`. All operations mutate the same reference in place. + * + * **Example** (Creating and updating refs) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * // Create a mutable reference + * const ref: MutableRef.MutableRef = MutableRef.make(42) + * + * // Read the current value + * ref.current // => 42 + * MutableRef.get(ref) // => 42 + * + * // Update the value + * ref.current = 100 + * + * MutableRef.get(ref) // => 100 + * + * // Use with complex types + * interface Config { + * timeout: number + * retries: number + * } + * + * const config: MutableRef.MutableRef = MutableRef.make({ + * timeout: 5000, + * retries: 3 + * }) + * + * // Update through the interface + * config.current = { timeout: 10000, retries: 5 } + * + * config.current // => { timeout: 10000, retries: 5 } + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface MutableRef extends Pipeable, Inspectable { + readonly [TypeId]: typeof TypeId + current: T +} + +const MutableRefProto: Omit, "current"> = { + [TypeId]: TypeId, + ...PipeInspectableProto, + toJSON(this: MutableRef) { + return { + _id: "MutableRef", + current: toJson(this.current) + } + } +} + +/** + * Creates a new MutableRef with the specified initial value. + * + * **When to use** + * + * Use to create a synchronous `MutableRef` initialized with a value. + * + * **Example** (Creating mutable refs) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * // Create a counter reference + * const counter = MutableRef.make(0) + * + * MutableRef.get(counter) // => 0 + * + * // Create a configuration reference + * const config = MutableRef.make({ debug: false, timeout: 5000 }) + * + * MutableRef.get(config) // => { debug: false, timeout: 5000 } + * + * // Create a string reference + * const status = MutableRef.make("idle") + * MutableRef.set(status, "running") + * + * MutableRef.get(status) // => "running" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (value: T): MutableRef => { + const ref = Object.create(MutableRefProto) + ref.current = value + return ref +} + +/** + * Sets the value to newValue atomically if the current value equals oldValue. + * Returns true if the value was updated, false otherwise. + * Uses Effect's Equal interface for value comparison. + * + * **When to use** + * + * Use to replace a `MutableRef` value only when the current value still matches + * an expected value. + * + * **Example** (Comparing and setting values) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const ref = MutableRef.make("initial") + * + * // Successful compare and set + * const updated = MutableRef.compareAndSet(ref, "initial", "updated") + * + * updated // => true + * MutableRef.get(ref) // => "updated" + * + * // Failed compare and set (value doesn't match) + * const failed = MutableRef.compareAndSet(ref, "initial", "failed") + * + * failed // => false + * MutableRef.get(ref) // => "updated" + * + * // Thread-safe counter increment + * const counter = MutableRef.make(5) + * let current: number + * do { + * current = MutableRef.get(counter) + * } while (!MutableRef.compareAndSet(counter, current, current + 1)) + * + * MutableRef.get(counter) // => 6 + * + * // Pipe-able version + * const casUpdate = MutableRef.compareAndSet("updated", "final") + * + * casUpdate(ref) // => true + * MutableRef.get(ref) // => "final" + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const compareAndSet: { + (oldValue: T, newValue: T): (self: MutableRef) => boolean + (self: MutableRef, oldValue: T, newValue: T): boolean +} = Dual.dual< + (oldValue: T, newValue: T) => (self: MutableRef) => boolean, + (self: MutableRef, oldValue: T, newValue: T) => boolean +>(3, (self, oldValue, newValue) => { + if (Equal.equals(oldValue, self.current)) { + self.current = newValue + return true + } + return false +}) + +/** + * Decrements a numeric MutableRef by 1 and returns the reference. + * + * **When to use** + * + * Use when you need an in-place `MutableRef` decrement that returns the same + * `MutableRef`. + * + * **Example** (Decrementing numeric refs) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const counter = MutableRef.make(5) + * + * // Decrement the counter + * MutableRef.decrement(counter) + * + * MutableRef.get(counter) // => 4 + * + * // Chain operations + * MutableRef.decrement(counter) + * MutableRef.decrement(counter) + * + * MutableRef.get(counter) // => 2 + * + * // Useful for countdown scenarios + * const countdown = MutableRef.make(10) + * while (MutableRef.get(countdown) > 0) { + * MutableRef.decrement(countdown) + * } + * + * MutableRef.get(countdown) // => 0 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const decrement = (self: MutableRef): MutableRef => update(self, (n) => n - 1) + +/** + * Decrements a numeric MutableRef by 1 and returns the new value. + * + * **When to use** + * + * Use to decrement a numeric `MutableRef` and immediately read the updated + * value. + * + * **Example** (Decrementing and reading refs) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const counter = MutableRef.make(5) + * + * // Decrement and get the new value + * const newValue = MutableRef.decrementAndGet(counter) + * + * newValue // => 4 + * MutableRef.get(counter) // => 4 + * + * // Use in expressions + * const lives = MutableRef.make(3) + * 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) { + * retries += 1 + * } + * + * retries // => 3 + * MutableRef.get(attempts) // => -1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const decrementAndGet = (self: MutableRef): number => updateAndGet(self, (n) => n - 1) + +/** + * Gets the current value of the MutableRef. + * + * **When to use** + * + * Use to read the current `MutableRef` value without mutating it. + * + * **Example** (Reading current values) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const ref = MutableRef.make("hello") + * + * MutableRef.get(ref) // => "hello" + * + * MutableRef.set(ref, "world") + * + * MutableRef.get(ref) // => "world" + * + * // Reading complex objects + * const config = MutableRef.make({ port: 3000, host: "localhost" }) + * const currentConfig = MutableRef.get(config) + * + * currentConfig // => { port: 3000, host: "localhost" } + * + * // Multiple reads return the same value + * const value1 = MutableRef.get(ref) + * const value2 = MutableRef.get(ref) + * + * value1 === value2 // => true + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const get = (self: MutableRef): T => self.current + +/** + * Decrements a numeric MutableRef by 1 and returns the previous value. + * + * **When to use** + * + * Use to read the current numeric `MutableRef` value before decrementing it. + * + * **Example** (Reading before decrementing) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const counter = MutableRef.make(5) + * + * // Get current value and then decrement + * const previousValue = MutableRef.getAndDecrement(counter) + * + * 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) + * 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) + * const nextIndex = MutableRef.get(index) + * + * currentIndex // => 3 + * nextIndex // => 2 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const getAndDecrement = (self: MutableRef): number => getAndUpdate(self, (n) => n - 1) + +/** + * Increments a numeric MutableRef by 1 and returns the previous value. + * + * **When to use** + * + * Use to read the current numeric `MutableRef` value before incrementing it. + * + * **Example** (Reading before incrementing) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const counter = MutableRef.make(5) + * + * // Get current value and then increment + * const previousValue = MutableRef.getAndIncrement(counter) + * + * 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()] + * + * ids // => [0, 1, 2] + * + * // Post-increment semantics (like i++ in other languages) + * const position = MutableRef.make(0) + * const currentPos = MutableRef.getAndIncrement(position) + * 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) + * visited.push(iteration) + * } + * + * visited // => [0, 1, 2, 3, 4] + * MutableRef.get(iterations) // => 5 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const getAndIncrement = (self: MutableRef): number => getAndUpdate(self, (n) => n + 1) + +/** + * Sets the MutableRef to a new value and returns the previous value. + * + * **When to use** + * + * Use to replace the current `MutableRef` value while keeping the previous + * value. + * + * **Example** (Reading before setting) + * + * ```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") + * + * previous // => "old" + * MutableRef.get(ref) // => "new" + * + * // Swapping values + * const counter = MutableRef.make(5) + * const oldValue = MutableRef.getAndSet(counter, 10) + * const newValue = MutableRef.get(counter) + * + * oldValue // => 5 + * newValue // => 10 + * + * // Pipe-able version + * const setValue = MutableRef.getAndSet("final") + * const previousValue = setValue(ref) + * + * previousValue // => "new" + * MutableRef.get(ref) // => "final" + * + * // Useful for atomic swaps in algorithms + * const buffer = MutableRef.make>(["a", "b", "c"]) + * const oldBuffer = MutableRef.getAndSet(buffer, []) + * + * oldBuffer // => ["a", "b", "c"] + * MutableRef.get(buffer) // => [] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const getAndSet: { + (value: T): (self: MutableRef) => T + (self: MutableRef, value: T): T +} = Dual.dual< + (value: T) => (self: MutableRef) => T, + (self: MutableRef, value: T) => T +>(2, (self, value) => { + const ret = self.current + self.current = value + return ret +}) + +/** + * Updates the MutableRef with the result of applying a function to its current value, + * and returns the previous value. + * + * **When to use** + * + * Use to transform the current `MutableRef` value while keeping the previous + * value. + * + * **Example** (Reading before updating) + * + * ```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) + * + * oldValue // => 5 + * MutableRef.get(counter) // => 6 + * + * // Double the value and get the previous one + * const previous = MutableRef.getAndUpdate(counter, (n) => n * 2) + * + * 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()) + * + * oldMessage // => "hello" + * MutableRef.get(message) // => "HELLO" + * + * // Pipe-able version + * const addOne = MutableRef.getAndUpdate((n: number) => n + 1) + * const result = addOne(counter) + * + * 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]) + * + * oldList // => [1, 2, 3] + * MutableRef.get(list) // => [1, 2, 3, 4] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const getAndUpdate: { + (f: (value: T) => T): (self: MutableRef) => T + (self: MutableRef, f: (value: T) => T): T +} = Dual.dual< + (f: (value: T) => T) => (self: MutableRef) => T, + (self: MutableRef, f: (value: T) => T) => T +>(2, (self, f) => getAndSet(self, f(get(self)))) + +/** + * Increments a numeric MutableRef by 1 and returns the reference. + * + * **When to use** + * + * Use when you need an in-place `MutableRef` increment that returns the same + * `MutableRef`. + * + * **Example** (Incrementing numeric refs) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const counter = MutableRef.make(5) + * + * // Increment the counter + * MutableRef.increment(counter) + * + * MutableRef.get(counter) // => 6 + * + * // Chain operations + * MutableRef.increment(counter) + * MutableRef.increment(counter) + * + * MutableRef.get(counter) // => 8 + * + * // Useful for simple counting + * const visits = MutableRef.make(0) + * MutableRef.increment(visits) // User visited + * MutableRef.increment(visits) // Another visit + * + * MutableRef.get(visits) // => 2 + * + * // Returns the reference for chaining + * const result = MutableRef.increment(counter) + * + * result === counter // => true + * MutableRef.get(counter) // => 9 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const increment = (self: MutableRef): MutableRef => update(self, (n) => n + 1) + +/** + * Increments a numeric MutableRef by 1 and returns the new value. + * + * **When to use** + * + * Use to increment a numeric `MutableRef` and immediately read the updated + * value. + * + * **Example** (Incrementing and reading refs) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const counter = MutableRef.make(5) + * + * // Increment and get the new value + * const newValue = MutableRef.incrementAndGet(counter) + * + * newValue // => 6 + * MutableRef.get(counter) // => 6 + * + * // Use in expressions + * const score = MutableRef.make(100) + * 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) + * + * nextLevel // => 1 + * + * // Conditional logic based on incremented value + * const attempts = MutableRef.make(0) + * const tooManyAttempts = MutableRef.incrementAndGet(attempts) > 3 + * + * tooManyAttempts // => false + * MutableRef.get(attempts) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const incrementAndGet = (self: MutableRef): number => updateAndGet(self, (n) => n + 1) + +/** + * Sets the MutableRef to a new value and returns the reference. + * + * **When to use** + * + * Use when you need an in-place `MutableRef` replacement that returns the same + * `MutableRef`. + * + * **Example** (Setting values) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const ref = MutableRef.make("initial") + * + * // Set a new value + * MutableRef.set(ref, "updated") + * + * MutableRef.get(ref) // => "updated" + * + * // Chain set operations (since it returns the ref) + * const result = MutableRef.set(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 }) + * + * MutableRef.get(config) // => { debug: true, verbose: true } + * + * // Pipe-able version + * const setValue = MutableRef.set("new value") + * setValue(ref) + * + * 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 mutations + * @since 2.0.0 + */ +export const set: { + (value: T): (self: MutableRef) => MutableRef + (self: MutableRef, value: T): MutableRef +} = Dual.dual< + (value: T) => (self: MutableRef) => MutableRef, + (self: MutableRef, value: T) => MutableRef +>(2, (self, value) => { + self.current = value + return self +}) + +/** + * Sets the MutableRef to a new value and returns the new value. + * + * **When to use** + * + * Use to replace the current `MutableRef` value and immediately read the + * replacement. + * + * **Example** (Setting and reading values) + * + * ```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") + * + * newValue // => "new" + * MutableRef.get(ref) // => "new" + * + * // Useful for assignments that need the value + * const counter = MutableRef.make(0) + * const currentValue = MutableRef.setAndGet(counter, 42) + * + * currentValue // => 42 + * + * // Pipe-able version + * const setValue = MutableRef.setAndGet("final") + * const result = setValue(ref) + * + * 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 + * + * returnedRef === ref1 // => true + * returnedValue // => 3 + * MutableRef.get(ref1) // => 3 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const setAndGet: { + (value: T): (self: MutableRef) => T + (self: MutableRef, value: T): T +} = Dual.dual< + (value: T) => (self: MutableRef) => T, + (self: MutableRef, value: T) => T +>(2, (self, value) => { + self.current = value + return self.current +}) + +/** + * Updates the MutableRef with the result of applying a function to its current value, + * and returns the reference. + * + * **When to use** + * + * Use when you need an in-place `MutableRef` value transformation that returns + * the same `MutableRef`. + * + * **Example** (Updating values) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const counter = MutableRef.make(5) + * + * // Increment the counter + * MutableRef.update(counter, (n) => n + 1) + * + * MutableRef.get(counter) // => 6 + * + * // Chain updates (since it returns the ref) + * const result = MutableRef.update(counter, (n) => n * 2) + * + * result === counter // => true + * MutableRef.get(counter) // => 12 + * + * // Transform string + * const message = MutableRef.make("hello") + * MutableRef.update(message, (s) => s.toUpperCase()) + * + * MutableRef.get(message) // => "HELLO" + * + * // Update complex objects + * const user = MutableRef.make({ name: "Alice", age: 30 }) + * MutableRef.update(user, (u) => ({ ...u, age: u.age + 1 })) + * + * MutableRef.get(user) // => { name: "Alice", age: 31 } + * + * // Pipe-able version + * const double = MutableRef.update((n: number) => n * 2) + * double(counter) + * + * MutableRef.get(counter) // => 24 + * + * // Array operations + * const list = MutableRef.make>([1, 2, 3]) + * MutableRef.update(list, (arr) => [...arr, 4]) + * + * MutableRef.get(list) // => [1, 2, 3, 4] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const update: { + (f: (value: T) => T): (self: MutableRef) => MutableRef + (self: MutableRef, f: (value: T) => T): MutableRef +} = Dual.dual< + (f: (value: T) => T) => (self: MutableRef) => MutableRef, + (self: MutableRef, f: (value: T) => T) => MutableRef +>(2, (self, f) => set(self, f(get(self)))) + +/** + * Updates the MutableRef with the result of applying a function to its current value, + * and returns the new value. + * + * **When to use** + * + * Use to transform the current `MutableRef` value and immediately read the + * updated value. + * + * **Example** (Updating and reading values) + * + * ```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) + * + * newValue // => 6 + * MutableRef.get(counter) // => 6 + * + * // Double the value and get the result + * const doubled = MutableRef.updateAndGet(counter, (n) => n * 2) + * + * doubled // => 12 + * + * // Transform string and get result + * const message = MutableRef.make("hello") + * const upperCase = MutableRef.updateAndGet(message, (s) => s.toUpperCase()) + * + * upperCase // => "HELLO" + * + * // Pipe-able version + * const increment = MutableRef.updateAndGet((n: number) => n + 1) + * const result = increment(counter) + * + * 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) + * + * newScore // => 150 + * + * // Array transformations + * const list = MutableRef.make>([1, 2, 3]) + * const newList = MutableRef.updateAndGet(list, (arr) => arr.map((x) => x * 2)) + * + * newList // => [2, 4, 6] + * MutableRef.get(list) // => [2, 4, 6] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateAndGet: { + (f: (value: T) => T): (self: MutableRef) => T + (self: MutableRef, f: (value: T) => T): T +} = Dual.dual< + (f: (value: T) => T) => (self: MutableRef) => T, + (self: MutableRef, f: (value: T) => T) => T +>(2, (self, f) => setAndGet(self, f(get(self)))) + +/** + * Switches a boolean `MutableRef` between `true` and `false`, then returns the + * reference. + * + * **When to use** + * + * Use when you need an in-place boolean `MutableRef` toggle that returns the + * same `MutableRef`. + * + * **Example** (Toggling boolean refs) + * + * ```ts import.meta.vitest + * import { MutableRef } from "effect" + * + * const flag = MutableRef.make(false) + * + * // Toggle the flag + * MutableRef.toggle(flag) + * + * MutableRef.get(flag) // => true + * + * // Toggle again + * MutableRef.toggle(flag) + * + * MutableRef.get(flag) // => false + * + * // Useful for state switches + * const isVisible = MutableRef.make(true) + * MutableRef.toggle(isVisible) // Hide + * + * MutableRef.get(isVisible) // => false + * + * // Toggle button implementation + * const darkMode = MutableRef.make(false) + * const toggleDarkMode = () => { + * MutableRef.toggle(darkMode) + * return MutableRef.get(darkMode) ? "ON" : "OFF" + * } + * + * toggleDarkMode() // => "ON" + * toggleDarkMode() // => "OFF" + * + * // Returns the reference for chaining + * const result = MutableRef.toggle(flag) + * + * result === flag // => true + * MutableRef.get(flag) // => true + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const toggle = (self: MutableRef): MutableRef => update(self, (_) => !_) diff --git a/.repos/effect/packages/effect/src/Newtype.ts b/.repos/effect/packages/effect/src/Newtype.ts new file mode 100644 index 000000000..4df67075f --- /dev/null +++ b/.repos/effect/packages/effect/src/Newtype.ts @@ -0,0 +1,326 @@ +/** + * Creates compile-time-only wrappers around existing value types. + * + * A newtype lets TypeScript distinguish values with the same runtime shape, such + * as two different ids that are both strings. The tag exists only in the type + * system, so wrapping does not allocate a runtime object. This module includes + * the base `Newtype` interface, wrapping and unwrapping helpers, optics, and + * helpers for reusing carrier instances such as `Equivalence`, `Order`, + * `Combiner`, and `Reducer`. + * + * @since 4.0.0 + */ +import type * as Combiner from "./Combiner.ts" +import type * as Equivalence from "./Equivalence.ts" +import { cast } from "./Function.ts" +import * as Optic from "./Optic.ts" +import type * as Order from "./Order.ts" +import type * as Reducer from "./Reducer.ts" + +const TypeId = "~effect/Newtype" + +/** + * A tagged interface that wraps a carrier type under a unique key, preventing + * accidental interchange of structurally identical values. + * + * **When to use** + * + * Use to define a newtype as an `interface` extending + * `Newtype<"MyKey", CarrierType>` when structurally identical carrier types + * should remain distinct in TypeScript. + * + * **Details** + * + * The tag is compile-time only, so no runtime wrapper is allocated. Use + * {@link makeIso} to create a two-way conversion, or {@link value} to unwrap. + * + * **Example** (Defining a newtype) + * + * ```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 + * @see {@link value} — unwrap a newtype value + * + * @category models + * @since 4.0.0 + */ +export interface Newtype { + readonly [TypeId]: { + readonly key: Key + readonly carrier: Carrier + } +} + +/** + * Namespace containing type-level helpers for `Newtype` values, including + * constraints and utilities for extracting a newtype's key and carrier type. + * + * **When to use** + * + * Use to access generic constraints and type-level utilities for `Newtype` + * values. + * + * @since 4.0.0 + */ +export declare namespace Newtype { + /** + * A type that matches any `Newtype`, useful as a generic constraint: + * ``. + * + * **When to use** + * + * Use as a generic constraint when a type parameter can be any `Newtype`. + * + * @see {@link Newtype} — the base tagged interface + * + * @category utility types + * @since 4.0.0 + */ + export type Any = Newtype + + /** + * Extracts the key literal type from a newtype. + * + * **When to use** + * + * Use to inspect or constrain a newtype's key in generic code. + * + * @category utility types + * @since 4.0.0 + */ + export type Key = N extends Newtype ? Key : never + + /** + * Extracts the carrier (underlying) type from a newtype. + * + * **When to use** + * + * Use when you need to refer to the wrapped type in generic utilities. + * + * @category utility types + * @since 4.0.0 + */ + export type Carrier = N extends Newtype ? Carrier : never +} + +/** + * Unwraps a newtype value, returning the underlying carrier value. + * + * **When to use** + * + * Use when you need the carrier value from an existing newtype without + * constructing a new newtype value at the same call site. + * + * **Details** + * + * This has zero runtime cost because it is an identity cast. + * + * **Example** (Unwrapping a newtype) + * + * ```ts import.meta.vitest + * import { Newtype } from "effect" + * + * interface Label extends Newtype.Newtype<"Label", string> {} + * + * const iso = Newtype.makeIso` can be consumed anywhere an `Iterable` is expected, + * while also carrying the guarantee that reading the first element is safe. + * + * @since 2.0.0 + */ + +/** + * Defines the type-level symbol used to brand the `NonEmptyIterable` type. + * + * **When to use** + * + * Use as the property key for the type-level brand that marks an `Iterable` as + * non-empty. + * + * **Details** + * + * `NonEmptyIterable` includes `readonly [nonEmpty]: A`, which makes it + * distinct from a plain `Iterable` at compile time while preserving the + * normal iteration shape. + * + * @see {@link NonEmptyIterable} for the branded iterable type that uses this symbol + * + * @category symbols + * @since 2.0.0 + */ +export declare const nonEmpty: unique symbol + +/** + * Represents an iterable that is guaranteed to contain at least one element. + * + * **When to use** + * + * Use to require an iterable input that must provide at least one element. + * + * **Details** + * + * `NonEmptyIterable` extends the standard `Iterable` interface with a type-level + * guarantee of non-emptiness. This allows for safe operations that would otherwise + * require runtime checks or could throw exceptions. + * + * The type is branded with a unique symbol to ensure type safety while maintaining + * full compatibility with JavaScript's iteration protocol. + * + * @category models + * @since 2.0.0 + */ +export interface NonEmptyIterable extends Iterable { + readonly [nonEmpty]: A +} + +/** + * Extracts the first element and remaining elements from a non-empty iterable safely. + * + * **When to use** + * + * Use to split a non-empty iterable into its first element and an iterator for + * the remaining elements. + * + * **Details** + * + * This function provides a safe way to deconstruct a `NonEmptyIterable` into its + * head (first element) and tail (remaining elements as an iterator). Since the + * iterable is guaranteed to be non-empty, the first element is always available. + * + * **Example** (Extracting first and remaining elements) + * + * ```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 + * @since 2.0.0 + */ +export const unprepend = (self: NonEmptyIterable): [firstElement: A, remainingElements: Iterator] => { + const iterator = self[Symbol.iterator]() + const next = iterator.next() + if (next.done) { + throw new Error( + "BUG: NonEmptyIterator should not be empty - please report an issue at https://github.com/Effect-TS/effect/issues" + ) + } + return [next.value, iterator] +} diff --git a/.repos/effect/packages/effect/src/Number.ts b/.repos/effect/packages/effect/src/Number.ts new file mode 100644 index 000000000..89194befd --- /dev/null +++ b/.repos/effect/packages/effect/src/Number.ts @@ -0,0 +1,860 @@ +/** + * Works with TypeScript `number` values. + * + * This module exposes the native `Number` constructor together with helpers for + * checking, parsing, arithmetic, safe division, comparison, range checks, + * clamping, rounding, ordering, equivalence, and numeric aggregation. + * + * @since 2.0.0 + */ +import * as Equ from "./Equivalence.ts" +import { dual } from "./Function.ts" +import * as Option from "./Option.ts" +import * as order from "./Order.ts" +import type { Ordering } from "./Ordering.ts" +import * as predicate from "./Predicate.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Exposes the global number constructor. + * + * **When to use** + * + * Use to access native JavaScript numeric coercion from the Effect module + * namespace. + * + * **Gotchas** + * + * This follows native `Number` coercion rules, including empty strings + * becoming `0` and invalid numeric strings becoming `NaN`. + * + * @see {@link parse} for parsing strings into an `Option` + * + * **Example** (Coercing values to numbers) + * + * ```ts import.meta.vitest + * import { Number as N } from "effect" + * + * N.Number("42") // => 42 + * N.Number("3.14") // => 3.14 + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const Number = globalThis.Number + +/** + * Checks whether a value is a `number`. + * + * **When to use** + * + * Use to validate unknown input and narrow it to `number`. + * + * **Example** (Checking for numbers) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.isNumber(2) // => true + * Number.isNumber("2") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isNumber: (input: unknown) => input is number = predicate.isNumber + +/** + * Provides an addition operation on `number`s. + * + * **When to use** + * + * Use to add two numbers. + * + * **Example** (Adding numbers) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.sum(2, 3) // => 5 + * ``` + * + * @see {@link sumAll} for summing an iterable of numbers + * + * @category math + * @since 2.0.0 + */ +export const sum: { + (that: number): (self: number) => number + (self: number, that: number): number +} = dual(2, (self: number, that: number): number => self + that) + +/** + * Provides a multiplication operation on `number`s. + * + * **When to use** + * + * Use to multiply two numbers. + * + * **Example** (Multiplying numbers) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.multiply(2, 3) // => 6 + * ``` + * + * @see {@link multiplyAll} for multiplying an iterable of numbers + * + * @category math + * @since 2.0.0 + */ +export const multiply: { + (that: number): (self: number) => number + (self: number, that: number): number +} = dual(2, (self: number, that: number): number => self * that) + +/** + * Provides a subtraction operation on `number`s. + * + * **When to use** + * + * Use to subtract one number from another. + * + * **Example** (Subtracting numbers) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.subtract(2, 3) // => -1 + * ``` + * + * @category math + * @since 2.0.0 + */ +export const subtract: { + (that: number): (self: number) => number + (self: number, that: number): number +} = dual(2, (self: number, that: number): number => self - that) + +/** + * Divides `number`s safely, returning `Option.none()` if the divisor is `0`. + * + * **When to use** + * + * Use to divide numbers while representing division by zero as `Option.none`. + * + * **Example** (Dividing numbers safely) + * + * ```ts import.meta.vitest + * import { Number, Option } from "effect" + * + * 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 + * @see {@link remainder} for the numeric remainder operation + * + * @category math + * @since 2.0.0 + */ +export const divide: { + (that: number): (self: number) => Option.Option + (self: number, that: number): Option.Option +} = dual( + 2, + (self: number, that: number): Option.Option => that === 0 ? Option.none() : Option.some(self / that) +) + +/** + * Divides two `number` values without returning an `Option`. + * + * **When to use** + * + * Use to divide `number` values where the divisor is known to be non-zero and + * a plain `number` result is preferred over handling `Option.none`. + * + * **Gotchas** + * + * Throws a `RangeError` if the divisor is `0`. + * + * **Example** (Dividing numbers unsafely) + * + * ```ts import.meta.vitest + * import { Number, Result } from "effect" + * + * Number.divideUnsafe(6, 3) // => 2 + * + * 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 + * + * @category math + * @since 4.0.0 + */ +export const divideUnsafe: { + (that: number): (self: number) => number + (self: number, that: number): number +} = dual( + 2, + (self: number, that: number): number => + Option.getOrThrowWith(divide(self, that), () => new RangeError("Division by zero")) +) + +/** + * Returns the result of adding `1` to a given number. + * + * **When to use** + * + * Use to increment a numeric counter by one. + * + * **Example** (Incrementing a number) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.increment(2) // => 3 + * ``` + * + * @category math + * @since 2.0.0 + */ +export const increment = (n: number): number => n + 1 + +/** + * Decrements a number by `1`. + * + * **When to use** + * + * Use to decrement a numeric counter by one. + * + * **Example** (Decrementing a number) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.decrement(3) // => 2 + * ``` + * + * @category math + * @since 2.0.0 + */ +export const decrement = (n: number): number => n - 1 + +/** + * Order instance for `number` values. + * + * **When to use** + * + * Use when you need to sort or compare numbers through APIs that accept an + * ordering instance. + * + * **Example** (Comparing numbers) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.Order(1, 2) // => -1 + * Number.Order(2, 1) // => 1 + * Number.Order(1, 1) // => 0 + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Order: order.Order = order.Number + +/** + * Equivalence instance for numbers where `NaN` is considered equal to `NaN`. + * + * **When to use** + * + * Use when checking numeric equality through APIs that accept an equivalence + * relation. + * + * **Example** (Comparing numbers for equivalence) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.Equivalence(1, 1) // => true + * Number.Equivalence(1, 2) // => false + * Number.Equivalence(NaN, NaN) // => true + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Equivalence: Equ.Equivalence = Equ.Number + +/** + * Returns `true` if the first argument is less than the second, otherwise `false`. + * + * **When to use** + * + * Use to test whether one number is strictly less than another. + * + * **Example** (Checking less-than comparisons) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.isLessThan(2, 3) // => true + * Number.isLessThan(3, 3) // => false + * Number.isLessThan(4, 3) // => false + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThan: { + (that: number): (self: number) => boolean + (self: number, that: number): boolean +} = order.isLessThan(Order) + +/** + * Returns a function that checks if a given `number` is less than or equal to the provided one. + * + * **When to use** + * + * Use to test whether one number is less than or equal to another. + * + * **Example** (Checking less-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.isLessThanOrEqualTo(2, 3) // => true + * Number.isLessThanOrEqualTo(3, 3) // => true + * Number.isLessThanOrEqualTo(4, 3) // => false + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThanOrEqualTo: { + (that: number): (self: number) => boolean + (self: number, that: number): boolean +} = order.isLessThanOrEqualTo(Order) + +/** + * Returns `true` if the first argument is greater than the second, otherwise `false`. + * + * **When to use** + * + * Use to test whether one number is strictly greater than another. + * + * **Example** (Checking greater-than comparisons) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.isGreaterThan(2, 3) // => false + * Number.isGreaterThan(3, 3) // => false + * Number.isGreaterThan(4, 3) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThan: { + (that: number): (self: number) => boolean + (self: number, that: number): boolean +} = order.isGreaterThan(Order) + +/** + * Returns a function that checks if a given `number` is greater than or equal to the provided one. + * + * **When to use** + * + * Use to test whether one number is greater than or equal to another. + * + * **Example** (Checking greater-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.isGreaterThanOrEqualTo(2, 3) // => false + * Number.isGreaterThanOrEqualTo(3, 3) // => true + * Number.isGreaterThanOrEqualTo(4, 3) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo: { + (that: number): (self: number) => boolean + (self: number, that: number): boolean +} = order.isGreaterThanOrEqualTo(Order) + +/** + * Checks whether a `number` is between a `minimum` and `maximum` value (inclusive). + * + * **When to use** + * + * Use to test whether a number falls inside an inclusive range. + * + * **Example** (Checking inclusive ranges) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * const between = Number.between({ minimum: 0, maximum: 5 }) + * + * between(3) // => true + * between(-1) // => false + * between(6) // => false + * ``` + * + * @see {@link clamp} for forcing a number into an inclusive range + * + * @category predicates + * @since 2.0.0 + */ +export const between: { + (options: { + minimum: number + maximum: number + }): (self: number) => boolean + (self: number, options: { + minimum: number + maximum: number + }): boolean +} = order.isBetween(Order) + +/** + * Restricts the given `number` to be within the range specified by the `minimum` and `maximum` values. + * + * **When to use** + * + * Use to force a number into an inclusive range. + * + * **Details** + * + * - 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`. + * + * **Example** (Clamping to a range) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * const clamp = Number.clamp({ minimum: 1, maximum: 5 }) + * + * clamp(3) // => 3 + * clamp(0) // => 1 + * clamp(6) // => 5 + * ``` + * + * @see {@link between} for checking whether a number is already inside a range + * + * @category math + * @since 2.0.0 + */ +export const clamp: { + (options: { + minimum: number + maximum: number + }): (self: number) => number + (self: number, options: { + minimum: number + maximum: number + }): number +} = order.clamp(Order) + +/** + * Returns the minimum between two `number`s. + * + * **When to use** + * + * Use to select the smaller of two numbers. + * + * **Example** (Finding the minimum) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.min(2, 3) // => 2 + * ``` + * + * @see {@link max} for selecting the larger value + * + * @category math + * @since 2.0.0 + */ +export const min: { + (that: number): (self: number) => number + (self: number, that: number): number +} = order.min(Order) + +/** + * Returns the maximum between two `number`s. + * + * **When to use** + * + * Use to select the larger of two numbers. + * + * **Example** (Finding the maximum) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.max(2, 3) // => 3 + * ``` + * + * @see {@link min} for selecting the smaller value + * + * @category math + * @since 2.0.0 + */ +export const max: { + (that: number): (self: number) => number + (self: number, that: number): number +} = order.max(Order) + +/** + * Determines the sign of a given `number`. + * + * **When to use** + * + * Use to classify a number as negative, zero, or positive. + * + * **Example** (Determining the sign) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.sign(-5) // => -1 + * Number.sign(0) // => 0 + * Number.sign(5) // => 1 + * ``` + * + * @category math + * @since 2.0.0 + */ +export const sign = (n: number): Ordering => Order(n, 0) + +/** + * Takes an `Iterable` of `number`s and returns their sum as a single `number`. + * + * **When to use** + * + * Use to sum all numbers in an iterable. + * + * **Example** (Summing an iterable) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.sumAll([2, 3, 4]) // => 9 + * ``` + * + * @see {@link sum} for adding two numbers + * @see {@link ReducerSum} for summing through APIs that consume a `Reducer` + * + * @category math + * @since 2.0.0 + */ +export const sumAll = (collection: Iterable): number => { + let out = 0 + for (const n of collection) { + out += n + } + return out +} + +/** + * Takes an `Iterable` of `number`s and returns their multiplication as a single `number`. + * + * **When to use** + * + * Use to multiply all numbers in an iterable. + * + * **Example** (Multiplying an iterable) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.multiplyAll([2, 3, 4]) // => 24 + * ``` + * + * @see {@link multiply} for multiplying two numbers + * @see {@link ReducerMultiply} for multiplying through APIs that consume a `Reducer` + * + * @category math + * @since 2.0.0 + */ +export const multiplyAll = (collection: Iterable): number => { + let out = 1 + for (const n of collection) { + if (n === 0) { + return 0 + } + out *= n + } + return out +} + +/** + * Returns the remainder left over when one operand is divided by a second operand, always taking the sign of the dividend. + * + * **When to use** + * + * Use to compute a numeric remainder while preserving decimal precision better + * than direct JavaScript `%` for decimal operands. + * + * **Example** (Calculating remainders) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * 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` + * + * @category math + * @since 2.0.0 + */ +export const remainder: { + (divisor: number): (self: number) => number + (self: number, divisor: number): number +} = dual(2, (self: number, divisor: number): number => { + 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 divisorInt = parseInt(divisor.toFixed(decCount).replace(".", "")) + return (selfInt % divisorInt) / Math.pow(10, decCount) +}) + +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 + } + 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] +} + +/** + * Returns the next power of 2 from the given number. + * + * **When to use** + * + * Use to round a number up to the next power of two. + * + * **Example** (Finding the next power of two) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.nextPow2(5) // => 8 + * Number.nextPow2(17) // => 32 + * ``` + * + * @category math + * @since 2.0.0 + */ +export const nextPow2 = (n: number): number => { + const nextPow = Math.ceil(Math.log(n) / Math.log(2)) + return Math.max(Math.pow(2, nextPow), 2) +} + +/** + * Parses a `number` from a `string` safely using the `Number()` function. + * The following special string values are supported: "NaN", "Infinity", "-Infinity". + * + * **When to use** + * + * Use to parse numeric text without throwing on invalid input. + * + * **Example** (Parsing numbers from strings) + * + * ```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() + * ``` + * + * @see {@link Number} for native constructor coercion + * + * @category constructors + * @since 2.0.0 + */ +export const parse = (s: string): Option.Option => { + if (s === "NaN") { + return Option.some(NaN) + } + if (s === "Infinity") { + return Option.some(Infinity) + } + if (s === "-Infinity") { + return Option.some(-Infinity) + } + if (s.trim() === "") { + return Option.none() + } + const n = Number(s) + return Number.isNaN(n) ? Option.none() : Option.some(n) +} + +/** + * Returns the number rounded with the given precision. + * + * **When to use** + * + * Use to round a number to a fixed number of decimal places. + * + * **Example** (Rounding with precision) + * + * ```ts import.meta.vitest + * import { Number } from "effect" + * + * Number.round(1.1234, 2) // => 1.12 + * Number.round(1.567, 2) // => 1.57 + * ``` + * + * @category math + * @since 3.8.0 + */ +export const round: { + (precision: number): (self: number) => number + (self: number, precision: number): number +} = dual(2, (self: number, precision: number): number => { + const factor = Math.pow(10, precision) + return Math.round(self * factor) / factor +}) + +/** + * Reducer for combining `number`s using addition. + * + * **When to use** + * + * Use to sum many numbers through APIs that consume a `Reducer`. + * + * **Details** + * + * The reducer starts from `0`, so `combineAll([])` returns `0`. + * + * @see {@link sumAll} for summing an iterable directly + * @see {@link ReducerMultiply} for multiplying number values + * + * @category math + * @since 4.0.0 + */ +export const ReducerSum: Reducer.Reducer = Reducer.make((a, b) => a + b, 0) + +/** + * Reducer for combining `number`s using multiplication. + * + * **When to use** + * + * Use to multiply many numbers through APIs that consume a `Reducer`. + * + * **Details** + * + * The reducer starts from `1`, so reducing an empty collection returns `1`. + * + * **Gotchas** + * + * Reducing an iterable short-circuits when it sees `0`, so later elements are + * not consumed. + * + * @see {@link multiplyAll} for multiplying an iterable directly + * + * @category math + * @since 4.0.0 + */ +export const ReducerMultiply: Reducer.Reducer = Reducer.make((a, b) => a * b, 1, (collection) => { + let acc = 1 + for (const n of collection) { + if (n === 0) return 0 + acc *= n + } + return acc +}) + +/** + * Reducer for reducing `number`s by keeping the maximum value. + * + * **When to use** + * + * Use to keep the largest number through APIs that consume a `Reducer`. + * + * **Details** + * + * The reducer starts from `-Infinity`, so reducing an empty collection returns + * `-Infinity`. + * + * **Gotchas** + * + * `NaN` values propagate through `Math.max`. + * + * @see {@link ReducerMin} for keeping the smallest number + * @see {@link max} for comparing two numbers directly + * + * @category math + * @since 4.0.0 + */ +export const ReducerMax: Reducer.Reducer = Reducer.make((a, b) => Math.max(a, b), -Infinity) + +/** + * Reducer for reducing `number`s by keeping the minimum value. + * + * **When to use** + * + * Use to keep the smallest number through APIs that consume a `Reducer`. + * + * **Details** + * + * The reducer starts from `Infinity`, so reducing an empty collection returns + * `Infinity`. + * + * **Gotchas** + * + * `NaN` values propagate through `Math.min`. + * + * @see {@link ReducerMax} for keeping the largest number + * @see {@link min} for comparing two numbers directly + * + * @category math + * @since 4.0.0 + */ +export const ReducerMin: Reducer.Reducer = Reducer.make((a, b) => Math.min(a, b), Infinity) diff --git a/.repos/effect/packages/effect/src/Optic.ts b/.repos/effect/packages/effect/src/Optic.ts new file mode 100644 index 000000000..f132e6617 --- /dev/null +++ b/.repos/effect/packages/effect/src/Optic.ts @@ -0,0 +1,1780 @@ +/** + * Reads and updates focused parts of values without mutating the original + * value. + * + * An optic describes where to look inside a value, such as a record field, a + * union variant, an optional value, or several values in a collection. Different + * optic types describe different kinds of focus: some always find a value, + * some may not, and some can find many. This module includes the optic types, + * constructors, focusing helpers, and operations for replacing, modifying, or + * collecting focused values. + * + * @since 4.0.0 + */ + +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 * as SchemaIssue from "./SchemaIssue.ts" +import * as Struct from "./Struct.ts" +import type { IsUnion, NoInfer } from "./Types.ts" + +/** + * A lossless, reversible conversion between types `S` and `A`. + * + * **When to use** + * + * Use when you have a pair of functions that convert back and forth without losing + * information (e.g. `Record ↔ entries`, `Celsius ↔ Fahrenheit`). + * - You want the strongest optic that can be composed with any other. + * + * **Details** + * + * - `get(s)` always succeeds and returns an `A`. + * - `set(a)` always succeeds and returns an `S`. + * - `get(set(a)) === a` and `set(get(s))` equals `s` (round-trip laws). + * - Extends both {@link Lens} and {@link Prism}. + * + * **Example** (Converting between Celsius and Fahrenheit) + * + * ```ts import.meta.vitest + * import { Optic } from "effect" + * + * const fahrenheit = Optic.makeIso( + * (c) => c * 9 / 5 + 32, + * (f) => (f - 32) * 5 / 9 + * ) + * + * fahrenheit.get(100) // => 212 + * + * 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 models + * @since 4.0.0 + */ +export interface Iso extends Lens, Prism {} + +/** + * Creates an {@link Iso} from a pair of conversion functions. + * + * **When to use** + * + * Use when you have two pure conversion functions that preserve all information + * between `S` and `A`. + * + * **Details** + * + * The returned optic can be composed with any other optic. + * + * **Example** (Wrapping and unwrapping a branded type) + * + * ```ts import.meta.vitest + * import { Optic } from "effect" + * + * type Meters = { readonly value: number } + * const meters = Optic.makeIso( + * (m) => m.value, + * (n) => ({ value: n }) + * ) + * + * meters.get({ value: 100 }) // => 100 + * + * meters.set(42) // => { value: 42 } + * ``` + * + * @see {@link Iso} — the type this function returns + * @see {@link id} — identity iso (no conversion) + * + * @category constructors + * @since 4.0.0 + */ +export function makeIso(get: (s: S) => A, set: (a: A) => S): Iso { + return make(primitiveNode("Iso", get, set)) +} + +/** + * Focuses on exactly one part `A` inside a whole `S`. + * + * **When to use** + * + * Use when you always have a value to read and need the original `S` to produce + * the updated whole, unlike `Iso`. + * + * **Details** + * + * - `get(s)` always succeeds and returns `A`. + * - `replace(a, s)` returns a new `S` with the focused part replaced. + * - Extends {@link Optional}. + * - Composing a Lens with a {@link Prism} or {@link Optional} produces an + * {@link Optional}. + * + * **Example** (Focusing on a struct field) + * + * ```ts import.meta.vitest + * import { Optic } from "effect" + * + * type Person = { readonly name: string; readonly age: number } + * + * const _name = Optic.id().key("name") + * + * _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 models + * @since 4.0.0 + */ +export interface Lens extends Optional { + readonly get: (s: S) => A +} + +/** + * Creates a {@link Lens} from a getter and a replacer. + * + * **When to use** + * + * Use when you can always extract `A` from `S` and produce a new `S` by + * substituting a new `A`. + * + * **Details** + * + * - `replace(a, s)` should return a structurally new `S` with `a` in place + * of the old focus. + * + * **Example** (Focusing on the first element of a pair) + * + * ```ts import.meta.vitest + * import { Optic } from "effect" + * + * const _first = Optic.makeLens( + * (pair) => pair[0], + * (s, pair) => [s, pair[1]] + * ) + * + * _first.get(["hello", 42]) // => "hello" + * + * _first.replace("world", ["hello", 42]) // => ["world", 42] + * ``` + * + * @see {@link Lens} — the type this function returns + * @see {@link makeIso} — when no original `S` is needed for `set` + * + * @category constructors + * @since 4.0.0 + */ +export function makeLens(get: (s: S) => A, replace: (a: A, s: S) => S): Lens { + return make(primitiveNode("Lens", get, replace)) +} + +/** + * Focuses on a part `A` of `S` that may not be present (e.g. a union + * variant or a validated subset). + * + * **When to use** + * + * Use when the focus is conditional — reading can fail (wrong variant, failed + * validation). + * - Building a new `S` from `A` does **not** require the original `S`. + * + * **Details** + * + * - `getResult(s)` returns `Result.Success` when the focus matches, or + * `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 + * {@link Lens} produces an {@link Optional}. + * + * **Example** (Narrowing a tagged union) + * + * ```ts import.meta.vitest + * import { Optic, Result } from "effect" + * + * type Shape = + * | { readonly _tag: "Circle"; readonly radius: number } + * | { readonly _tag: "Rect"; readonly width: number } + * + * const _circle = Optic.id().tag("Circle") + * + * _circle.getResult({ _tag: "Circle", radius: 5 }) // => Result.succeed({ _tag: "Circle", radius: 5 }) + * + * 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 models + * @since 4.0.0 + */ +export interface Prism extends Optional { + readonly set: (a: A) => S +} + +/** + * Creates a {@link Prism} from a fallible getter and an infallible setter. + * + * **When to use** + * + * Use when reading can fail (the part may not exist in `S`), but building `S` + * from `A` always succeeds. + * + * **Details** + * + * - `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.meta.vitest + * import { Optic, Result, SchemaIssue } from "effect" + * + * const numeric = Optic.makePrism( + * (s) => { + * const n = Number(s) + * return Number.isNaN(n) + * ? Result.fail(new SchemaIssue.InvalidValue({ message: "not a number" })) + * : Result.succeed(n) + * }, + * String + * ) + * + * numeric.getResult("42") // => Result.succeed(42) + * + * numeric.set(42) // => "42" + * ``` + * + * @see {@link Prism} — the type this function returns + * @see {@link fromChecks} — build from `Schema` checks instead + * + * @category constructors + * @since 4.0.0 + */ +export function makePrism( + getResult: (s: S) => Result.Result, + set: (a: A) => S +): Prism { + return make(primitiveNode("Prism", getResult, set)) +} + +/** + * Creates a {@link Prism} from one or more `Schema` validation checks. + * + * **When to use** + * + * Use when you want to narrow `T` to the subset that passes certain validation + * rules (e.g. positive integer). + * - You already have `Schema.isGreaterThan`, `Schema.isInt`, etc. + * + * **Details** + * + * - `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 import.meta.vitest + * import { Optic, Result, Schema } from "effect" + * + * const posInt = Optic.fromChecks( + * Schema.isGreaterThan(0), + * Schema.isInt() + * ) + * + * posInt.getResult(3) // => Result.succeed(3) + * + * Result.isFailure(posInt.getResult(-1)) // => true + * ``` + * + * @see {@link makePrism} — constructor with custom getter/setter + * @see {@link Prism} — the type this function returns + * + * @category constructors + * @since 4.0.0 + */ +export function fromChecks(...checks: readonly [SchemaAST.Check, ...Array>]): Prism { + return make([new CheckNode(checks)]) +} + +type Kind = "Iso" | "Lens" | "Prism" | "Optional" + +type Operation = { + readonly kind: Kind + readonly get: (s: any) => any + readonly set: (a: any, s?: any) => any +} + +type PrimitiveStep = Operation & { + readonly _tag: "PrimitiveNode" +} + +type Step = PrimitiveStep | PathNode | CheckNode + +type Node = ReadonlyArray + +function primitiveNode(kind: Kind, get: (s: any) => any, set: (a: any, s?: any) => any): Node { + return [{ _tag: "PrimitiveNode", kind, get, 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) + } +} + +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") { + 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) + } + } + return nodes +} + +type ForbidUnion = IsUnion extends true ? [Message] : [] + +/** + * The most general optic — both reading and writing can fail. + * + * **When to use** + * + * Use when the focus may not exist in `S` and writing a new `A` back may also + * fail, for example when the source no longer matches the expected shape. This + * is the base type extended by {@link Iso}, {@link Lens}, {@link Prism}, and + * {@link Traversal}. + * + * **Details** + * + * - `getResult(s)` returns `Result.Success` or `Result.Failure`. + * - `replaceResult(a, s)` returns `Result.Success` or + * `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 import.meta.vitest + * import { Optic, Result } from "effect" + * + * type Env = { [key: string]: string } + * const _home = Optic.id().at("HOME") + * + * _home.getResult({ HOME: "/root" }) // => Result.succeed("/root") + * + * Result.isFailure(_home.getResult({ PATH: "/bin" })) // => true + * + * // replace returns original on failure + * _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 models + * @since 4.0.0 + */ +export interface Optional { + /** + * Attempts to read the focus `A` from the whole `S`. Returns + * `Result.Success` when the focus exists, or + * `Result.Failure` with a structured issue otherwise. + */ + 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). + */ + readonly replace: (a: A, s: S) => S + /** + * Like {@link replace}, but returns an explicit `Result` so callers can + * detect and handle failure. + */ + 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.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 + */ + compose(this: Iso, that: Iso): Iso + compose(this: Lens, that: Lens): Lens + compose(this: Prism, that: Prism): Prism + compose(this: Optional, that: Optional): Optional + + /** + * Returns a function `(s: S) => S` that applies `f` to the focused value. + * If the optic cannot focus, the original `s` is returned unchanged. + * + * **Example** (Incrementing a nested field) + * + * ```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) + * inc({ a: { b: 1 } }) // => { a: { b: 2 } } + * ``` + */ + modify(f: (a: A) => A): (s: S) => S + + /** + * Focuses on a property of the current struct/tuple focus. + * + * **Details** + * + * - On a {@link Lens}, returns a Lens. + * - On an {@link Optional}, returns an Optional. + * - Does **not** work on union types (compile error). + * + * **Example** (Drilling into nested structs) + * + * ```ts import.meta.vitest + * import { Optic } from "effect" + * + * type S = { readonly a: { readonly b: number } } + * const _b = Optic.id().key("a").key("b") + * + * _b.get({ a: { b: 42 } }) // => 42 + * ``` + */ + key( + this: Lens, + key: Key, + ..._err: ForbidUnion + ): Lens + key( + this: Optional, + key: Key, + ..._err: ForbidUnion + ): Optional + + /** + * Focuses on a key where setting `undefined` **removes** the key from the + * struct (or splices the element from an array/tuple). + * + * **Details** + * + * - The focus type becomes `A[Key] | undefined`. + * - Does **not** work on union types (compile error). + * + * **Example** (Deleting an optional key) + * + * ```ts import.meta.vitest + * import { Optic } from "effect" + * + * type S = { readonly a?: number } + * const _a = Optic.id().optionalKey("a") + * + * _a.replace(undefined, { a: 1 }) // => {} + * + * _a.replace(2, {}) // => { a: 2 } + * ``` + */ + optionalKey( + this: Lens, + key: Key, + ..._err: ForbidUnion + ): Lens + optionalKey( + this: Optional, + key: Key, + ..._err: ForbidUnion + ): Optional + + /** + * Adds one or more `Schema` validation checks to the optic chain. + * `getResult` fails when any check fails; `set` passes through unchanged. + * + * **Details** + * + * - On a {@link Prism}, returns a Prism. + * - On an {@link Optional}, returns an Optional. + * + * **Example** (Focusing only on positive numbers) + * + * ```ts import.meta.vitest + * import { Optic, Result, Schema } from "effect" + * + * const _pos = Optic.id().check(Schema.isGreaterThan(0)) + * + * _pos.getResult(5) // => Result.succeed(5) + * + * Result.isFailure(_pos.getResult(-1)) // => true + * ``` + * + * @see {@link fromChecks} — standalone prism from checks + */ + check(this: Prism, ...checks: readonly [SchemaAST.Check, ...Array>]): Prism + check( + this: Optional, + ...checks: readonly [SchemaAST.Check, ...Array>] + ): Optional + + /** + * Narrows the focus to a subtype `B` using a type guard. + * + * **Details** + * + * - On a {@link Prism}, returns a Prism. + * - On an {@link Optional}, returns an Optional. + * - Pass optional `annotations` to customize the error message. + * + * **Example** (Narrowing a union) + * + * ```ts import.meta.vitest + * import { Optic, Result } from "effect" + * + * type B = { readonly _tag: "b"; readonly b: number } + * type S = { readonly _tag: "a"; readonly a: string } | B + * + * const _b = Optic.id().refine( + * (s: S): s is B => s._tag === "b", + * { expected: `"b" tag` } + * ) + * + * _b.getResult({ _tag: "b", b: 1 }) // => Result.succeed({ _tag: "b", b: 1 }) + * ``` + * + * @see `.tag()` — shorthand for narrowing by `_tag` + */ + refine( + this: Prism, + refinement: (a: A) => a is B, + annotations?: Schema.Annotations.Filter + ): Prism + refine( + this: Optional, + refinement: (a: A) => a is B, + annotations?: Schema.Annotations.Filter + ): Optional + + /** + * Narrows the focus to the variant of a tagged union with the given + * `_tag` value. + * + * **Details** + * + * - 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 import.meta.vitest + * import { Optic, Result } from "effect" + * + * type Shape = + * | { readonly _tag: "Circle"; readonly radius: number } + * | { readonly _tag: "Rect"; readonly width: number } + * + * const _radius = Optic.id().tag("Circle").key("radius") + * + * _radius.getResult({ _tag: "Circle", radius: 5 }) // => Result.succeed(5) + * + * Result.isFailure(_radius.getResult({ _tag: "Rect", width: 10 })) // => true + * ``` + * + * @see `.refine()` — for arbitrary type guards + */ + tag( + this: Prism, + tag: Tag + ): Prism> + tag( + this: Optional, + tag: Tag + ): Optional> + + /** + * Focuses on a key only if it exists (`Object.hasOwn`). Both + * `getResult` and `replaceResult` fail when the key is absent. + * + * **Details** + * + * Unlike `.key()`, which always succeeds on the read side, `.at()` is + * useful for Records or arrays where the key/index may not be present. + * + * - 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 import.meta.vitest + * import { Optic, Result } from "effect" + * + * type Env = { [key: string]: number } + * const _x = Optic.id().at("x") + * + * _x.getResult({ x: 1 }) // => Result.succeed(1) + * + * Result.isFailure(_x.getResult({ y: 2 })) // => true + * ``` + * + * @see `.key()` — when the key is always present + */ + at( + this: Optional, + key: Key, + ..._err: ForbidUnion + ): Optional + + /** + * Focuses on a subset of keys of the current struct focus. + * + * **Details** + * + * - On a {@link Lens}, returns a Lens. + * - On an {@link Optional}, returns an Optional. + * - Does **not** work on union types (compile error). + * + * **Example** (Picking keys) + * + * ```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"]) + * + * _ac.get({ a: "hi", b: 1, c: true }) // => { a: "hi", c: true } + * ``` + * + * @see `.omit()` — the inverse operation + */ + pick>( + this: Lens, + keys: Keys, + ..._err: ForbidUnion + ): Lens> + pick>( + this: Optional, + keys: Keys, + ..._err: ForbidUnion + ): Optional> + + /** + * Focuses on all keys **except** the specified ones. + * + * **Details** + * + * - On a {@link Lens}, returns a Lens. + * - On an {@link Optional}, returns an Optional. + * - Does **not** work on union types (compile error). + * + * **Example** (Omitting keys) + * + * ```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"]) + * + * _ac.get({ a: "hi", b: 1, c: true }) // => { a: "hi", c: true } + * ``` + * + * @see `.pick()` — the inverse operation + * + * @since 4.0.0 + */ + omit>( + this: Lens, + keys: Keys, + ..._err: ForbidUnion + ): Lens> + omit>( + this: Optional, + keys: Keys, + ..._err: ForbidUnion + ): Optional> + + /** + * Filters out `undefined` from the focus, producing a {@link Prism}. + * `getResult` fails when the focus is `undefined`. + * + * **Example** (Filtering undefined values) + * + * ```ts import.meta.vitest + * import { Optic, Result } from "effect" + * + * const _defined = Optic.id().notUndefined() + * + * _defined.getResult(42) // => Result.succeed(42) + * + * Result.isFailure(_defined.getResult(undefined)) // => true + * ``` + * + * @since 4.0.0 + */ + notUndefined(this: Prism): Prism> + notUndefined(this: Optional): Optional> + + /** + * Focuses **all elements** of an array-like focus and optionally narrows + * to a subset using an element-level optic. + * Available only on {@link Traversal} (i.e. when `A` is + * `ReadonlyArray`). Returns a new Traversal focused on the + * selected elements. + * + * **Details** + * + * - **getResult** collects the values focused by `f(id())` for each + * 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 + * {@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 import.meta.vitest + * import { Optic, Schema } from "effect" + * + * type Post = { title: string; likes: number } + * type S = { user: { posts: ReadonlyArray } } + * + * const _likes = Optic.id() + * .key("user") + * .key("posts") + * .forEach((post) => post.key("likes").check(Schema.isGreaterThan(0))) + * + * const addLike = _likes.modifyAll((n) => n + 1) + * + * 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 + * @see `.modifyAll()` — apply a function to every focused element + */ + forEach(this: Traversal, f: (iso: Iso) => Optional): Traversal + + /** + * Applies a function to **every** element focused by the traversal. + * + * **Details** + * + * Available only on {@link Traversal}. Returns a function `(s: S) => S`. + * If the traversal cannot focus, the original `s` is returned unchanged. + * + * Unlike `.modify()`, which operates on the whole array, `modifyAll` + * maps `f` over each individual element. + * + * **Example** (Doubling all focused values) + * + * ```ts import.meta.vitest + * import { Optic, Schema } from "effect" + * + * type S = { readonly items: ReadonlyArray } + * + * const _positive = Optic.id() + * .key("items") + * .forEach((n) => n.check(Schema.isGreaterThan(0))) + * + * const doubled = _positive.modifyAll((n) => n * 2) + * + * doubled({ items: [1, -2, 3] }) // => { items: [2, -2, 6] } + * ``` + * + * @see `.forEach()` — create a sub-traversal + * @see {@link getAll} — extract focused elements + */ + modifyAll(this: Traversal, f: (a: A) => A): (s: S) => S +} + +/** + * Creates an {@link Optional} from a fallible getter and a fallible setter. + * + * **When to use** + * + * Use when you need an optic for a focus that may be missing on read and may + * reject updates on write. + * + * **Details** + * + * - `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.meta.vitest + * import { Optic, Result, SchemaIssue } from "effect" + * + * 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(issue), + * (a, s) => + * Object.hasOwn(s, key) + * ? Result.succeed({ ...s, [key]: a }) + * : Result.fail(issue) + * ) + * } + * + * atKey("x").getResult({ x: 1 }) // => Result.succeed(1) + * ``` + * + * @see {@link Optional} — the type this function returns + * @see {@link makeLens} — when reading always succeeds + * @see {@link makePrism} — when writing always succeeds + * + * @category constructors + * @since 4.0.0 + */ +export function makeOptional( + getResult: (s: S) => Result.Result, + set: (a: A, s: S) => Result.Result +): Optional { + return make(primitiveNode("Optional", getResult, set)) +} + +/** + * An optic that focuses on **zero or more** elements of type `A` inside `S`. + * + * **When to use** + * + * Use when you want to read/update multiple elements at once (e.g. all items in + * an array, or a filtered subset). + * + * **Details** + * + * - Technically `Optional>` — the focused value is an + * array of all matched elements. + * - Use `.forEach()` to add per-element sub-optics (filtering, drilling + * deeper). + * - Use `.modifyAll(f)` to map a function over every focused element. + * - Use {@link getAll} to extract all focused elements as a plain array. + * + * **Example** (Traversing array elements with a filter) + * + * ```ts import.meta.vitest + * import { Optic, Schema } from "effect" + * + * type S = { readonly items: ReadonlyArray } + * + * const _positive = Optic.id() + * .key("items") + * .forEach((n) => n.check(Schema.isGreaterThan(0))) + * + * const getPositive = Optic.getAll(_positive) + * + * getPositive({ items: [1, -2, 3] }) // => [1, 3] + * ``` + * + * @see {@link getAll} — extract focused elements + * @see {@link Optional} — the base type + * + * @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 + constructor( + node: Node, + getResult: (s: S) => Result.Result, + replaceResult: (a: A, s: S) => Result.Result + ) { + this.node = node + this.getResult = getResult + this.replaceResult = replaceResult + } + replace(a: A, s: S): S { + return Result.getOrElse(this.replaceResult(a, s), () => s) + } + modify(f: (a: A) => A): (s: S) => S { + return (s) => Result.getOrElse(Result.flatMap(this.getResult(s), (a) => this.replaceResult(f(a), s)), () => s) + } + compose(that: any): any { + return make(compose(this.node, that.node)) + } + key(key: PropertyKey): any { + return make(compose(this.node, [new PathNode([key])])) + } + optionalKey(key: PropertyKey): any { + return make( + compose( + this.node, + 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) + } else { + delete copy[key] + } + } else { + InternalRecord.assignProperty(copy, key, a) + } + return copy + } + ) + ) + ) + } + check(...checks: readonly [SchemaAST.Check, ...Array>]): any { + 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)])])) + } + tag(tag: string): any { + const err = Result.fail(new SchemaIssue.InvalidValue({ expected: `${JSON.stringify(tag)} tag` })) + return make( + compose( + this.node, + primitiveNode( + "Prism", + (s) => s._tag === tag ? Result.succeed(s) : err, + identity + ) + ) + ) + } + at(key: PropertyKey, ..._rest: Array): any { + const err = Result.fail( + new SchemaIssue.Pointer([key], new SchemaIssue.MissingKey(undefined)) + ) + return make( + compose( + this.node, + primitiveNode( + "Optional", + (s) => Object.hasOwn(s, key) ? Result.succeed(s[key]) : err, + (a, s) => { + if (Object.hasOwn(s, key)) { + const copy = cloneShallow(s) + InternalRecord.assignProperty(copy, key, a) + return Result.succeed(copy) + } else { + return err + } + } + ) + ) + ) + } + pick(keys: any) { + return this.compose(makeLens(Struct.pick(keys), (p, a) => ({ ...a, ...p }))) + } + omit(keys: any) { + return this.compose(makeLens(Struct.omit(keys), (o, a) => ({ ...a, ...o }))) + } + notUndefined(): any { + return this.refine(Predicate.isNotUndefined, { expected: "a value other than `undefined`" }) + } + forEach(this: Traversal, f: (iso: Iso) => Optional): Traversal { + const inner = f(id()) + return makeOptional>( + // GET: collect focused Bs + (s) => + Result.map(this.getResult(s), (as) => { + const bs: Array = [] + for (let i = 0; i < as.length; i++) { + const r = inner.getResult(as[i]) + if (Result.isSuccess(r)) bs.push(r.success) + } + return bs + }), + // SET: bs must match the number of focusable elements + (bs, s) => + Result.flatMap(this.getResult(s), (as) => { + // 1) collect focusable indices + const idxs: Array = [] + for (let i = 0; i < as.length; i++) { + if (Result.isSuccess(inner.getResult(as[i]))) idxs.push(i) + } + + // 2) arity check + if (bs.length !== idxs.length) { + return Result.fail( + new SchemaIssue.InvalidValue({ + message: `each: replacement length mismatch: ${bs.length} !== ${idxs.length}` + }) + ) + } + + // 3) update those indices + const out: Array = as.slice() + for (let k = 0; k < idxs.length; k++) { + const i = idxs[k] + const r = inner.replaceResult(bs[k], as[i]) + if (Result.isFailure(r)) { + return Result.fail(new SchemaIssue.Pointer([i], r.failure)) + } + out[i] = r.success + } + return this.replaceResult(out, s) + }) + ) + } + modifyAll(this: Traversal, f: (a: A) => A): (s: S) => S { + return (s) => + Result.getOrElse( + Result.flatMap(this.getResult(s), (as) => this.replaceResult(as.map(f), s)), + () => s + ) + } +} + +class IsoImpl extends OptionalImpl implements Iso { + readonly get: (s: S) => A + readonly set: (a: A) => S + constructor(node: Node, get: (s: S) => A, set: (a: A) => S) { + super(node, (s) => Result.succeed(get(s)), (a) => Result.succeed(set(a))) + this.get = get + this.set = set + } + override replace(a: A, _: S): S { + return this.set(a) + } + override modify(f: (a: A) => A): (s: S) => S { + return (s) => this.set(f(this.get(s))) + } +} + +class LensImpl extends OptionalImpl implements Lens { + readonly get: (s: S) => A + constructor(node: Node, get: (s: S) => A, replace: (a: A, s: S) => S) { + super(node, (s) => Result.succeed(get(s)), (a, s) => Result.succeed(replace(a, s))) + this.get = get + this.replace = replace + } + override modify(f: (a: A) => A): (s: S) => S { + return (s) => this.replace(f(this.get(s)), s) + } +} + +class PrismImpl extends OptionalImpl implements Prism { + readonly 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 + } + override replace(a: A, _: S): S { + return this.set(a) + } + override modify(f: (a: A) => A): (s: S) => S { + return (s) => Result.getOrElse(Result.map(this.getResult(s), (a) => this.set(f(a))), () => s) + } +} + +function make(node: Node): any { + 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 "Lens": + return new LensImpl(node, op.get, op.set) + case "Prism": + return new PrismImpl(node, op.get, op.set) + case "Optional": + return new OptionalImpl(node, op.get, op.set) + } +} + +function cloneShallow(pojo: T): T { + if (Array.isArray(pojo)) return pojo.slice() as T + if (typeof pojo === "object" && pojo !== null) { + const proto = Object.getPrototypeOf(pojo) + if (proto !== Object.prototype && proto !== null) { + throw new Error("Cannot clone object with non-Object constructor or null prototype") + } + return { ...pojo } as T + } + return pojo +} + +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 + } +} + +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 + } + } + 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) + } + } + 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(kind: Kind): boolean { + return kind === "Prism" || kind === "Optional" +} + +function hasSourceFreeSet(kind: Kind): boolean { + return kind === "Iso" || kind === "Prism" +} + +function composeKind(a: Kind, b: Kind): Kind { + if (a === "Iso") return b + if (b === "Iso" || a === b) return a + return "Optional" +} +// --------------------------------------------- +// Derived APIs +// --------------------------------------------- + +/** + * 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** + * + * Use when you need the focused values as a simple `Array` for further + * processing. + * + * **Details** + * + * - 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 import.meta.vitest + * import { Optic, Schema } from "effect" + * + * type S = { readonly values: ReadonlyArray } + * + * const _pos = Optic.id() + * .key("values") + * .forEach((n) => n.check(Schema.isGreaterThan(0))) + * + * const getPositive = Optic.getAll(_pos) + * + * getPositive({ values: [3, -1, 5] }) // => [3, 5] + * + * getPositive({ values: [-1, -2] }) // => [] + * ``` + * + * @see {@link Traversal} — the optic type this operates on + * + * @category getters + * @since 4.0.0 + */ +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([]) + +/** + * Iso that focuses on the whole value unchanged. + * + * **When to use** + * + * Use when you need to start an optic chain with a focus on the whole value. + * + * **Details** + * + * - `get(s)` returns `s`. + * - `set(a)` returns `a`. + * - Singleton — every call returns the same instance. + * + * **Example** (Starting an optic chain) + * + * ```ts import.meta.vitest + * import { Optic } from "effect" + * + * type S = { readonly x: number } + * + * const _x = Optic.id().key("x") + * + * _x.get({ x: 42 }) // => 42 + * ``` + * + * @see {@link Iso} — the type this function returns + * + * @category constructors + * @since 4.0.0 + */ +export function id(): Iso { + return identityIso +} + +/** + * Iso that converts a `Record` to an array of + * `[key, value]` entries and back. + * + * **When to use** + * + * Use when you want to traverse or manipulate record entries as an array (e.g. + * with `.forEach()`). + * + * **Details** + * + * - `get` uses `Object.entries`. + * - `set` uses `Object.fromEntries`. + * - Round-trip is lossless for `Record`. + * + * **Example** (Traversing record values) + * + * ```ts import.meta.vitest + * import { Optic, Schema } from "effect" + * + * const _positiveValues = Optic.entries() + * .forEach((entry) => entry.key(1).check(Schema.isGreaterThan(0))) + * + * const inc = _positiveValues.modifyAll((n) => n + 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 constructors + * @since 4.0.0 + */ +export function entries(): Iso, ReadonlyArray> { + return make(primitiveNode("Iso", Object.entries, Object.fromEntries)) +} + +/** + * Prism that focuses on the value inside `Option.Some`. + * + * **When to use** + * + * Use when you have an `Option` and want to read/update the inner value only + * when it is `Some`. + * + * **Details** + * + * - `getResult` fails with a structured issue when the option is `None`. + * - `set(a)` wraps `a` in `Option.some(a)`. + * + * **Example** (Accessing Some value) + * + * ```ts import.meta.vitest + * import { Optic, Option, Result } from "effect" + * + * const _some = Optic.id>().compose(Optic.some()) + * + * _some.getResult(Option.some(42)) // => Result.succeed(42) + * + * Result.isFailure(_some.getResult(Option.none())) // => true + * + * _some.set(10) // => Option.some(10) + * ``` + * + * @see {@link none} — focuses on `None` instead + * @see {@link Prism} — the type this function returns + * + * @category constructors + * @since 4.0.0 + */ +export function some(): Prism, A> { + const run = runRefinement(Option.isSome, { expected: "a Some value" }) + return makePrism( + (s) => Result.map(run(s), (s) => s.value), + Option.some + ) +} + +/** + * Prism that focuses on `Option.None`, exposing `undefined`. + * + * **When to use** + * + * Use when you want to match or construct `None` values within an optic chain. + * + * **Details** + * + * - `getResult` succeeds with `undefined` when the option is `None`. + * - `getResult` fails when the option is `Some`. + * - `set(undefined)` produces `Option.none()`. + * + * **Example** (Matching None) + * + * ```ts import.meta.vitest + * import { Optic, Option, Result } from "effect" + * + * const _none = Optic.id>().compose(Optic.none()) + * + * _none.getResult(Option.none()) // => Result.succeed(undefined) + * + * Result.isFailure(_none.getResult(Option.some(1))) // => true + * ``` + * + * @see {@link some} — focuses on `Some` instead + * @see {@link Prism} — the type this function returns + * + * @category constructors + * @since 4.0.0 + */ +export function none(): Prism, undefined> { + const run = runRefinement(Option.isNone, { expected: "a None value" }) + return makePrism( + (s) => Result.map(run(s), () => undefined), + () => Option.none() + ) +} + +/** + * Prism that focuses on the success value of a `Result`. + * + * **When to use** + * + * Use when you have a `Result` and want to read/update `A` only when it + * is a `Success`. + * + * **Details** + * + * - `getResult` fails when the result is a `Failure`. + * - `set(a)` produces `Result.succeed(a)`. + * + * **Example** (Accessing success) + * + * ```ts import.meta.vitest + * import { Optic, Result } from "effect" + * + * const _ok = Optic.id>().compose(Optic.success()) + * + * _ok.getResult(Result.succeed(42)) // => Result.succeed(42) + * + * 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 constructors + * @since 4.0.0 + */ +export function success(): Prism, A> { + const run = runRefinement(Result.isSuccess, { expected: "a Result.Success value" }) + return makePrism( + (s) => Result.map(run(s), (s) => s.success), + Result.succeed + ) +} + +/** + * Prism that focuses on the failure value of a `Result`. + * + * **When to use** + * + * Use when you have a `Result` and want to read/update `E` only when it + * is a `Failure`. + * + * **Details** + * + * - `getResult` fails when the result is a `Success`. + * - `set(e)` produces `Result.fail(e)`. + * + * **Example** (Accessing failure) + * + * ```ts import.meta.vitest + * import { Optic, Result } from "effect" + * + * const _err = Optic.id>().compose(Optic.failure()) + * + * _err.getResult(Result.fail("oops")) // => Result.succeed("oops") + * + * 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 constructors + * @since 4.0.0 + */ +export function failure(): Prism, E> { + const run = runRefinement(Result.isFailure, { expected: "a Result.Failure value" }) + return makePrism( + (s) => Result.map(run(s), (s) => s.failure), + Result.fail + ) +} + +function runRefinement( + refinement: (e: E) => e is T, + annotations?: Schema.Annotations.Filter +): (e: E) => Result.Result { + return (e) => SchemaAST.runChecks([SchemaAST.makeFilterByGuard(refinement, annotations)], e) as any +} diff --git a/.repos/effect/packages/effect/src/Option.ts b/.repos/effect/packages/effect/src/Option.ts new file mode 100644 index 000000000..9e94f8562 --- /dev/null +++ b/.repos/effect/packages/effect/src/Option.ts @@ -0,0 +1,2483 @@ +/** + * Models a value that may be present or absent. + * + * An `Option` is `Some` when a value is available and `None` when it is + * not. This lets code handle missing values explicitly instead of relying on + * `null` or `undefined`. The module includes helpers for creating, checking, + * transforming, combining, and extracting optional values, plus conversions to + * and from common nullable or result-like shapes. It also includes `Option.gen` + * for writing small generator-based computations that stop at the first `None`. + * + * @since 2.0.0 + */ +import * as Combiner from "./Combiner.ts" +import * as Equal from "./Equal.ts" +import * as Equivalence from "./Equivalence.ts" +import type * as Filter from "./Filter.ts" +import type { LazyArg } from "./Function.ts" +import { constNull, constUndefined, dual, identity } from "./Function.ts" +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" +import type { Pipeable } from "./Pipeable.ts" +import type { Predicate, Refinement } from "./Predicate.ts" +import { isFunction } from "./Predicate.ts" +import * as Reducer from "./Reducer.ts" +import type { Result } from "./Result.ts" +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" + +/** + * The `Option` data type represents optional values. An `Option` is either + * `Some`, containing a value of type `A`, or `None`, representing absence. + * + * **When to use** + * + * Use to represent initial values that may not yet exist + * - Returning from partial functions (not defined for all inputs) + * - Managing optional fields in data structures + * + * @see {@link some} for creating a `Some` + * @see {@link none} for creating a `None` + * @see {@link match} for pattern matching + * + * @category models + * @since 2.0.0 + */ +export type Option = None | Some + +/** + * Represents the absence of a value within an {@link Option}. + * + * **When to use** + * + * Use as a type guard target when narrowing via {@link isNone} + * + * **Details** + * + * - `_tag` is always `"None"` + * - Implements `Pipeable`, `Inspectable`, and structural equality + * + * @see {@link isNone} to check if an `Option` is `None` + * @see {@link none} to construct a `None` + * + * @category models + * @since 2.0.0 + */ +export interface None extends Pipeable, Inspectable { + readonly _tag: "None" + readonly _op: "None" + readonly valueOrUndefined: undefined + readonly [TypeId]: { + readonly _A: Covariant + } + [Symbol.iterator](): OptionIterator> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: OptionUnify + [Unify.ignoreSymbol]?: OptionUnifyIgnore +} + +/** + * Iterator protocol used to yield an `Option` inside {@link gen}, returning the + * contained value type back to the generator. + * + * **When to use** + * + * Use when defining or typing `[Symbol.iterator]()` for `Option` values so + * `yield*` can pass the contained value type back into `Option.gen`. + * + * @see {@link gen} for writing generator-based `Option` code that consumes this iterator protocol + * + * @category generators + * @since 4.0.0 + */ +export interface OptionIterator> { + next( + ...args: ReadonlyArray + ): IteratorResult> +} + +/** + * Represents the presence of a value within an {@link Option}. + * + * **When to use** + * + * Use as a type guard target when narrowing via {@link isSome} + * - Access the inner value via `.value` + * + * **Details** + * + * - `_tag` is always `"Some"` + * - `.value` holds the contained value of type `A` + * - Implements `Pipeable`, `Inspectable`, and structural equality + * + * @see {@link isSome} to check if an `Option` is `Some` + * @see {@link some} to construct a `Some` + * + * @category models + * @since 2.0.0 + */ +export interface Some extends Pipeable, Inspectable { + readonly _tag: "Some" + readonly _op: "Some" + readonly value: A + readonly valueOrUndefined: A + readonly [TypeId]: { + readonly _A: Covariant + } + [Symbol.iterator](): OptionIterator> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: OptionUnify + [Unify.ignoreSymbol]?: OptionUnifyIgnore +} + +/** + * Type-level unification support for `Option` values. + * + * **When to use** + * + * Use when extending Effect's type-level unification support for `Option`. + * + * **Details** + * + * This is used by Effect's `Unify` machinery to preserve the contained value + * type when generic code returns or combines `Option` values. Users normally + * do not need to reference this interface directly. + * + * @category models + * @since 2.0.0 + */ +export interface OptionUnify { + Option?: () => A[Unify.typeSymbol] extends Option | infer _ ? Option : never +} + +/** + * Namespace containing utility types for `Option`. + * + * **When to use** + * + * Use to access type-level helpers associated with `Option`. + * + * @since 2.0.0 + */ +export declare namespace Option { + /** + * Extracts the type of the value contained in an `Option`. + * + * **When to use** + * + * Use to infer the inner value type from an existing `Option` type. + * + * **Example** (Extracting the value type) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const myOption: Option.Option = Option.some("value") + * type MyType = Option.Option.Value + * + * const witness: MyType = "value" + * ``` + * + * @category utility types + * @since 2.0.0 + */ + export type Value> = [T] extends [Option] ? _A : never +} + +/** + * Marker interface used by Effect's `Unify` machinery for `Option` values. + * + * **When to use** + * + * Use when marking generic code so `Option` unification should be ignored. + * + * **Details** + * + * This supports type-level unification behavior for `Option`. Users normally + * do not need to reference this interface directly. + * + * @category models + * @since 2.0.0 + */ +export interface OptionUnifyIgnore {} + +/** + * Type lambda interface for higher-kinded type encodings with `Option`. + * + * **When to use** + * + * Use when defining higher-kinded abstractions that must accept optional-value + * types as one of their type-lambda inputs. + * + * @category utility types + * @since 2.0.0 + */ +export interface OptionTypeLambda extends TypeLambda { + readonly type: Option +} + +/** + * Creates an `Option` representing the absence of a value. + * + * **When to use** + * + * Use to represent a missing or uninitialized value, such as returning "no + * result" from a function. + * + * **Details** + * + * - Returns `Option`, which is a subtype of `Option` for any `A` + * - Always returns the same singleton instance + * + * **Example** (Creating an empty Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * // ┌─── Option + * // ▼ + * const noValue = Option.none() // => Option.none() + * ``` + * + * @see {@link some} for the opposite operation. + * + * @category constructors + * @since 2.0.0 + */ +export const none = (): Option => option.none + +/** + * Wraps the given value into an `Option` to represent its presence. + * + * **When to use** + * + * Use to wrap a known present value as `Option` + * - Returning a successful result from a partial function + * + * **Details** + * + * - Always returns `Some` + * - Does not filter `null` or `undefined`; use {@link fromNullishOr} for that + * + * **Example** (Wrapping a value) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * // ┌─── Option + * // ▼ + * const value = Option.some(1) // => Option.some(1) + * ``` + * + * @see {@link none} for the opposite operation. + * + * @category constructors + * @since 2.0.0 + */ +export const some: (value: A) => Option = option.some + +/** + * Determines whether the given value is an `Option`. + * + * **When to use** + * + * Use to validate unknown values at runtime boundaries, such as type-narrowing + * in union types. + * + * **Details** + * + * - Returns `true` for both `Some` and `None` instances + * - Acts as a type guard, narrowing the input to `Option` + * + * **Example** (Checking if a value is an Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.isOption(Option.some(1)) // => true + * Option.isOption(Option.none()) // => true + * Option.isOption({}) // => false + * ``` + * + * @see {@link isNone} to check for `None` specifically + * @see {@link isSome} to check for `Some` specifically + * + * @category guards + * @since 2.0.0 + */ +export const isOption: (input: unknown) => input is Option = option.isOption + +/** + * Checks whether an `Option` is `None` (absent). + * + * **When to use** + * + * Use when you need to branch on an absent `Option` before accessing `.value`. + * + * **Details** + * + * - Acts as a type guard, narrowing to `None` + * + * **Example** (Checking for None) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.isNone(Option.some(1)) // => false + * Option.isNone(Option.none()) // => true + * ``` + * + * @see {@link isSome} for the opposite check. + * + * @category guards + * @since 2.0.0 + */ +export const isNone: (self: Option) => self is None = option.isNone + +/** + * Checks whether an `Option` contains a value (`Some`). + * + * **When to use** + * + * Use when you need to branch on a present `Option` before accessing `.value`. + * + * **Details** + * + * - Acts as a type guard, narrowing to `Some` + * + * **Example** (Checking for Some) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.isSome(Option.some(1)) // => true + * Option.isSome(Option.none()) // => false + * ``` + * + * @see {@link isNone} for the opposite check. + * + * @category guards + * @since 2.0.0 + */ +export const isSome: (self: Option) => self is Some = option.isSome + +/** + * Pattern-matches on an `Option`, handling both `None` and `Some` cases. + * + * **When to use** + * + * Use when you need to handle both `Some` and `None` in one expression and + * transform an `Option` into a plain value. + * + * **Details** + * + * - If `None`, calls `onNone` and returns its result + * - If `Some`, calls `onSome` with the value and returns its result + * - Supports the `dual` API (data-last and data-first) + * + * **Example** (Matching on an Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.match(Option.some(1), { + * onNone: () => "Option is empty", + * onSome: (value) => `Option has a value: ${value}` + * }) // => "Option has a value: 1" + * ``` + * + * @see {@link getOrElse} for unwrapping with a default + * + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + (options: { + readonly onNone: LazyArg + readonly onSome: (a: A) => C + }): (self: Option) => B | C + (self: Option, options: { + readonly onNone: LazyArg + readonly onSome: (a: A) => C + }): B | C +} = dual( + 2, + (self: Option, { onNone, onSome }: { + readonly onNone: LazyArg + readonly onSome: (a: A) => C + }): B | C => isNone(self) ? onNone() : onSome(self.value) +) + +/** + * Converts an `Option`-returning function into a type guard (refinement). + * + * **When to use** + * + * Use when you need to turn an `Option`-returning parser into a type-narrowing + * predicate, such as for `Array.prototype.filter`. + * + * **Details** + * + * - Returns `true` when the original function returns `Some` + * - Returns `false` when the original function returns `None` + * - Narrows the input type to `B` on success + * + * **Example** (Converting a parser to a type guard) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * type MyData = string | number + * + * const parseString = (data: MyData): Option.Option => + * typeof data === "string" ? Option.some(data) : Option.none() + * + * // ┌─── (a: MyData) => a is string + * // ▼ + * const isString = Option.toRefinement(parseString) + * + * isString("a") // => true + * isString(1) // => false + * ``` + * + * @see {@link liftPredicate} for the reverse direction + * + * @category converting + * @since 2.0.0 + */ +export const toRefinement = (f: (a: A) => Option): (a: A) => a is B => (a: A): a is B => isSome(f(a)) + +/** + * Wraps the first element of an `Iterable` in a `Some`, or returns `None` if + * the iterable is empty. + * + * **When to use** + * + * Use when you need to safely extract the head of a collection, including + * generators or lazy iterables. + * + * **Details** + * + * - Only consumes the first element; does not iterate the rest + * - Returns `None` for empty iterables + * + * **Example** (Getting the first element) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.fromIterable([1, 2, 3]) // => Option.some(1) + * Option.fromIterable([]) // => Option.none() + * ``` + * + * @see {@link toArray} for the inverse direction + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = (collection: Iterable): Option => { + for (const a of collection) { + return some(a) + } + return none() +} + +/** + * Converts a `Result` into an `Option`, keeping only the success value. + * + * **When to use** + * + * Use when you need to discard a `Result` failure and keep only the success + * value as an `Option`. + * + * **Details** + * + * - `Success` becomes `Some` with the success value + * - `Failure` becomes `None` and the failure value is discarded + * + * **Example** (Extracting the success side) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Option.getSuccess(Result.succeed("ok")) // => Option.some("ok") + * Option.getSuccess(Result.fail("err")) // => Option.none() + * ``` + * + * @see {@link getFailure} for the opposite operation. + * + * @category converting + * @since 4.0.0 + */ +export const getSuccess: (self: Result) => Option = result.getSuccess + +/** + * Converts a `Result` into an `Option`, keeping only the failure value. + * + * **When to use** + * + * Use when you need to discard a `Result` success and keep only the failure + * value as an `Option`. + * + * **Details** + * + * - `Failure` becomes `Some` with the failure value + * - `Success` becomes `None` and the success value is discarded + * + * **Example** (Extracting the failure side) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Option.getFailure(Result.succeed("ok")) // => Option.none() + * Option.getFailure(Result.fail("err")) // => Option.some("err") + * ``` + * + * @see {@link getSuccess} for the opposite operation. + * + * @category converting + * @since 4.0.0 + */ +export const getFailure: (self: Result) => Option = result.getFailure + +/** + * Extracts the value from a `Some`, or evaluates a fallback thunk on `None`. + * + * **When to use** + * + * Use when providing a default value for an absent `Option` + * - Unwrapping with lazy evaluation of the fallback + * + * **Details** + * + * - `Some` → returns the inner value + * - `None` → calls `onNone()` and returns its result + * - `onNone` is only called when needed (lazy) + * + * **Example** (Unwrapping with a fallback) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.some(1).pipe(Option.getOrElse(() => 0)) // => 1 + * Option.none().pipe(Option.getOrElse(() => 0)) // => 0 + * ``` + * + * @see {@link getOrNull} to fall back to `null` + * @see {@link getOrUndefined} to fall back to `undefined` + * @see {@link getOrThrow} to throw on `None` + * + * @category getters + * @since 2.0.0 + */ +export const getOrElse: { + (onNone: LazyArg): (self: Option) => B | A + (self: Option, onNone: LazyArg): A | B +} = dual( + 2, + (self: Option, onNone: LazyArg): A | B => isNone(self) ? onNone() : self.value +) + +/** + * Returns the fallback `Option` if `self` is `None`; otherwise returns `self`. + * + * **When to use** + * + * Use when you need a lazy fallback `Option`, such as when building priority + * chains of optional values. + * + * **Details** + * + * - `Some` → returns `self` unchanged + * - `None` → evaluates and returns `that()` + * - `that` is lazily evaluated + * + * **Example** (Providing a fallback Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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 + * @see {@link firstSomeOf} to pick the first `Some` from a collection + * + * @category error handling + * @since 2.0.0 + */ +export const orElse: { + (that: LazyArg>): (self: Option) => Option + (self: Option, that: LazyArg>): Option +} = dual( + 2, + (self: Option, that: LazyArg>): Option => isNone(self) ? that() : self +) + +/** + * Returns `Some` of the fallback value if `self` is `None`; otherwise returns + * `self`. + * + * **When to use** + * + * Use when providing a default plain value (not an `Option`) as fallback + * + * **Details** + * + * - `Some` → returns `self` unchanged + * - `None` → calls `onNone()`, wraps result in `Some`, and returns it + * + * **Example** (Providing a fallback value) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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` + * + * @category error handling + * @since 2.0.0 + */ +export const orElseSome: { + (onNone: LazyArg): (self: Option) => Option + (self: Option, onNone: LazyArg): Option +} = dual( + 2, + (self: Option, onNone: LazyArg): Option => isNone(self) ? some(onNone()) : self +) + +/** + * Returns the first available value and marks whether it came from the fallback. + * + * **When to use** + * + * Use when you need to know whether a present value came from the primary or + * fallback `Option`. + * + * **Details** + * + * - `self` is `Some` → `Some(Result.fail(value))` (value from primary) + * - `self` is `None`, `that()` is `Some` → `Some(Result.succeed(value))` (value from fallback) + * - Both `None` → `None` + * + * **Example** (Tracking value source) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * const fallback = () => Option.some("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 + * + * @category error handling + * @since 4.0.0 + */ +export const orElseResult: { + (that: LazyArg>): (self: Option) => Option> + (self: Option, that: LazyArg>): Option> +} = dual( + 2, + (self: Option, that: LazyArg>): Option> => + isNone(self) ? map(that(), result.succeed) : map(self, result.fail) +) + +/** + * Returns the first `Some` found in an iterable of `Option`s, or `None` if + * all are `None`. + * + * **When to use** + * + * Use when you need the first available `Some` value from a priority list. + * + * **Details** + * + * - Short-circuits on the first `Some` + * - Returns `None` only when every element is `None` + * + * **Example** (Finding the first Some) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.firstSomeOf([ + * Option.none(), + * Option.some(1), + * Option.some(2) + * ]) // => Option.some(1) + * ``` + * + * @see {@link orElse} for a two-option fallback + * + * @category error handling + * @since 2.0.0 + */ +export const firstSomeOf = > = Iterable>>( + collection: C +): [C] extends [Iterable>] ? Option : never => { + let out: Option = none() + for (out of collection) { + if (isSome(out)) { + return out as any + } + } + return out as any +} + +/** + * Converts a nullable value (`null` or `undefined`) into an `Option`. + * + * **When to use** + * + * Use when you need JavaScript nullish values to become absence at an API + * boundary while all other values, including falsy ones, remain present. + * + * **Details** + * + * - `null` or `undefined` → `None` + * - Any other value → `Some` (typed as `NonNullable`) + * + * **Example** (Converting nullable values to an Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.fromNullishOr(undefined) // => Option.none() + * Option.fromNullishOr(null) // => Option.none() + * Option.fromNullishOr(1) // => Option.some(1) + * ``` + * + * @see {@link fromNullOr} to only treat `null` as absent + * @see {@link fromUndefinedOr} to only treat `undefined` as absent + * @see {@link liftNullishOr} to lift a nullable-returning function + * + * @category converting + * @since 4.0.0 + */ +export const fromNullishOr = ( + a: A +): Option> => (a == null ? none() : some(a as NonNullable)) + +/** + * Converts a possibly `undefined` value into an `Option`, leaving `null` + * as a valid `Some`. + * + * **When to use** + * + * Use when you want to treat only `undefined` as absent while preserving `null` + * as a meaningful value. + * + * **Details** + * + * - `undefined` → `None` + * - Any other value (including `null`) → `Some` + * + * **Example** (Converting possibly undefined values to an Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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 + * @see {@link fromNullOr} to only treat `null` as absent + * + * @category converting + * @since 4.0.0 + */ +export const fromUndefinedOr = ( + a: A +): Option> => (a === undefined ? none() : some(a as Exclude)) + +/** + * Converts a possibly `null` value into an `Option`, leaving `undefined` + * as a valid `Some`. + * + * **When to use** + * + * Use when you want to treat only `null` as absent while preserving + * `undefined` as a meaningful value. + * + * **Details** + * + * - `null` → `None` + * - Any other value (including `undefined`) → `Some` + * + * **Example** (Converting possibly null values to an Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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 + * @see {@link fromUndefinedOr} to only treat `undefined` as absent + * + * @category converting + * @since 4.0.0 + */ +export const fromNullOr = ( + a: A +): Option> => (a === null ? none() : some(a as Exclude)) + +/** + * Lifts a function that may return `null` or `undefined` into one that returns + * an `Option`. + * + * **When to use** + * + * Use to wrap existing nullable-returning functions for use in `Option` pipelines + * + * **Details** + * + * - Calls the original function with the given arguments + * - Wraps the result via {@link fromNullishOr} + * + * **Example** (Lifting a parser) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const parse = (s: string): number | undefined => { + * const n = parseFloat(s) + * return isNaN(n) ? undefined : n + * } + * + * const parseOption = Option.liftNullishOr(parse) + * + * parseOption("1") // => Option.some(1) + * parseOption("not a number") // => Option.none() + * ``` + * + * @see {@link fromNullishOr} for converting a single value + * @see {@link liftThrowable} for functions that throw instead + * + * @category converting + * @since 4.0.0 + */ +export const liftNullishOr = , B>( + f: (...a: A) => B +): (...a: A) => Option> => +(...a) => fromNullishOr(f(...a)) + +/** + * Extracts the value from a `Some`, or returns `null` for `None`. + * + * **When to use** + * + * Use when you need to pass absent `Option` values to APIs that expect `null`. + * + * **Details** + * + * - `Some` → the inner value + * - `None` → `null` + * + * **Example** (Unwrapping to null) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.getOrNull(Option.some(1)) // => 1 + * Option.getOrNull(Option.none()) // => null + * ``` + * + * @see {@link getOrUndefined} to return `undefined` instead + * @see {@link getOrElse} for a custom fallback + * + * @category getters + * @since 2.0.0 + */ +export const getOrNull: (self: Option) => A | null = getOrElse(constNull) + +/** + * Extracts the value from a `Some`, or returns `undefined` for `None`. + * + * **When to use** + * + * Use when you need to pass absent `Option` values to APIs that expect + * `undefined`. + * + * **Details** + * + * - `Some` → the inner value + * - `None` → `undefined` + * + * **Example** (Unwrapping to undefined) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.getOrUndefined(Option.some(1)) // => 1 + * Option.getOrUndefined(Option.none()) // => undefined + * ``` + * + * @see {@link getOrNull} to return `null` instead + * @see {@link getOrElse} for a custom fallback + * + * @category getters + * @since 2.0.0 + */ +export const getOrUndefined: (self: Option) => A | undefined = getOrElse(constUndefined) + +/** + * Lifts a function that may throw into one that returns an `Option`. + * + * **When to use** + * + * Use to wrap exception-throwing APIs (e.g. `JSON.parse`) for safe usage + * + * **Details** + * + * - If the function returns normally → `Some` with the result + * - If the function throws → `None` (exception is swallowed) + * + * **Example** (Lifting JSON.parse) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const parse = Option.liftThrowable(JSON.parse) + * + * parse("1") // => Option.some(1) + * parse("") // => Option.none() + * ``` + * + * @see {@link liftNullishOr} for nullable-returning functions + * + * @category converting + * @since 2.0.0 + */ +export const liftThrowable = , B>( + f: (...a: A) => B +): (...a: A) => Option => +(...a) => { + try { + return some(f(...a)) + } catch { + return none() + } +} + +/** + * Extracts the value from a `Some`, or throws a custom error for `None`. + * + * **When to use** + * + * Use when you need fail-fast unwrapping of an `Option` for unexpected absence + * and want to provide a descriptive debugging error. + * + * **Details** + * + * - `Some` → returns the inner value + * - `None` → throws the value returned by `onNone()` + * + * **Example** (Throwing a custom error) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Option.getOrThrowWith(Option.some(1), () => new Error("missing")) // => 1 + * + * 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 + * @see {@link getOrElse} for a non-throwing alternative + * + * @category converting + * @since 2.0.0 + */ +export const getOrThrowWith: { + (onNone: () => unknown): (self: Option) => A + (self: Option, onNone: () => unknown): A +} = dual(2, (self: Option, onNone: () => unknown): A => { + if (isSome(self)) { + return self.value + } + throw onNone() +}) + +/** + * Extracts the value from a `Some`, or throws a default `Error` for `None`. + * + * **When to use** + * + * Use when you need quick fail-fast unwrapping of an `Option` and a generic + * error is acceptable. + * + * **Details** + * + * - `Some` → returns the inner value + * - `None` → throws `new Error("getOrThrow called on a None")` + * + * **Example** (Throwing a default error) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Option.getOrThrow(Option.some(1)) // => 1 + * + * 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 + * @see {@link getOrElse} for a non-throwing alternative + * + * @category converting + * @since 2.0.0 + */ +export const getOrThrow: (self: Option) => A = getOrThrowWith(() => new Error("getOrThrow called on a None")) + +/** + * Transforms the value inside a `Some` using the provided function, leaving + * `None` unchanged. + * + * **When to use** + * + * Use to apply a pure transformation to an `Option`'s present value, especially + * when chaining transformations in a pipeline. + * + * **Details** + * + * - `Some` → applies `f` and wraps the result in a new `Some` + * - `None` → returns `None` unchanged + * + * **Example** (Mapping over an Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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` + * @see {@link as} to replace the value with a constant + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (a: A) => B): (self: Option) => Option + (self: Option, f: (a: A) => B): Option +} = dual( + 2, + (self: Option, f: (a: A) => B): Option => isNone(self) ? none() : some(f(self.value)) +) + +/** + * Replaces the value inside a `Some` with a constant, leaving `None` unchanged. + * + * **When to use** + * + * Use when you need to replace a present `Option` value while preserving + * whether it was `Some` or `None`. + * + * **Example** (Replacing a value) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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` + * @see {@link map} for a general transformation + * + * @category mapping + * @since 2.0.0 + */ +export const as: { + (b: B): (self: Option) => Option + (self: Option, b: B): Option +} = dual(2, (self: Option, b: B): Option => map(self, () => b)) + +/** + * Replaces the value inside a `Some` with `void` (`undefined`), leaving `None` + * unchanged. + * + * **When to use** + * + * Use when you need to discard a present `Option` value while preserving + * whether it was `Some` or `None`. + * + * **Example** (Voiding the value) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.asVoid(Option.some(42)) // => Option.some(undefined) + * Option.asVoid(Option.none()) // => Option.none() + * ``` + * + * @see {@link as} to replace with a specific constant + * + * @category mapping + * @since 2.0.0 + */ +export const asVoid: <_>(self: Option<_>) => Option = as(undefined) + +const void_: Option = some(undefined) +export { + /** + * Provides a pre-built `Some(undefined)` constant. + * + * **When to use** + * + * Use to return a "success with no meaningful value" from an `Option`-returning function + * + * **Example** (Referencing Option.void) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.void // => Option.some(undefined) + * ``` + * + * @see {@link asVoid} to convert an existing `Option` to `Option` + * + * @category constructors + * @since 2.0.0 + */ + void_ as void +} + +/** + * Applies a function that returns an `Option` to the value of a `Some`, + * flattening the result. Returns `None` if the input is `None`. + * + * **When to use** + * + * Use when you need to chain dependent `Option` computations where each step + * may return `None`. + * + * **Details** + * + * - `Some` → applies `f` to the value and returns its `Option` result + * - `None` → returns `None` without calling `f` + * - Equivalent to `map` followed by {@link flatten} + * + * **Example** (Chaining optional lookups) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * interface User { + * readonly name: string + * readonly address: Option.Option<{ readonly street: Option.Option }> + * } + * + * const user: User = { + * name: "John", + * address: Option.some({ street: Option.some("123 Main St") }) + * } + * + * user.address.pipe( + * Option.flatMap((addr) => addr.street) + * ) // => Option.some("123 Main St") + * ``` + * + * @see {@link map} when `f` returns a plain value + * @see {@link andThen} for a more flexible variant + * @see {@link flatten} to unwrap a nested `Option>` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + (f: (a: A) => Option): (self: Option) => Option + (self: Option, f: (a: A) => Option): Option +} = dual( + 2, + (self: Option, f: (a: A) => Option): Option => isNone(self) ? none() : f(self.value) +) + +/** + * Chains a second computation onto an `Option`. The second value can be a + * plain value, an `Option`, or a function returning either. + * + * **When to use** + * + * Use when you need to chain an `Option` with a next step that may be another + * `Option`, a plain value, or a function. + * + * **Details** + * + * - If `self` is `None`, returns `None` immediately + * - If `f` is a function, calls it with the `Some` value + * - If `f` returns an `Option`, returns it as-is; if a plain value, wraps in `Some` + * - If `f` is not a function, uses it directly (same wrapping rules) + * + * **Example** (Chaining with andThen) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * // Chain with a function returning Option + * Option.andThen(Option.some(5), (x) => Option.some(x * 2)) // => Option.some(10) + * + * // Chain with a static value + * Option.andThen(Option.some(5), "hello") // => Option.some("hello") + * + * // Chain with None - skips + * Option.andThen(Option.none(), (x) => Option.some(x * 2)) // => Option.none() + * ``` + * + * @see {@link flatMap} for the standard monadic bind + * @see {@link map} when you always return a plain value + * + * @category sequencing + * @since 2.0.0 + */ +export const andThen: { + (f: (a: A) => Option): (self: Option) => Option + (f: Option): (self: Option) => Option + (f: (a: A) => B): (self: Option) => Option + (f: NotFunction): (self: Option) => Option + (self: Option, f: (a: A) => Option): Option + (self: Option, f: Option): Option + (self: Option, f: (a: A) => B): Option + (self: Option, f: NotFunction): Option +} = dual( + 2, + (self: Option, f: (a: A) => Option | Option): Option => + flatMap(self, (a) => { + const b = isFunction(f) ? f(a) : f + return isOption(b) ? b : some(b) + }) +) + +/** + * Combines {@link flatMap} with {@link fromNullishOr}: applies a function that + * may return `null`/`undefined` to the value of a `Some`. + * + * **When to use** + * + * Use when you need to chain optional computations that use `null` or + * `undefined` instead of `Option`, such as nested property access. + * + * **Details** + * + * - `None` → `None` + * - `Some` → applies `f`, then wraps via {@link fromNullishOr} + * + * **Example** (Navigating optional properties) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * interface Employee { + * company?: { address?: { street?: { name?: string } } } + * } + * + * const emp: Employee = { + * company: { address: { street: { name: "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` + * @see {@link fromNullishOr} for single-value conversion + * + * @category sequencing + * @since 4.0.0 + */ +export const flatMapNullishOr: { + (f: (a: A) => B): (self: Option) => Option> + (self: Option, f: (a: A) => B): Option> +} = dual( + 2, + (self: Option, f: (a: A) => B): Option> => + isNone(self) ? none() : fromNullishOr(f(self.value)) +) + +/** + * Flattens a nested `Option>` into `Option`. + * + * **When to use** + * + * Use when you need to remove one layer of nested `Option`. + * + * **Details** + * + * - `Some(Some(value))` → `Some(value)` + * - `Some(None)` → `None` + * - `None` → `None` + * + * **Example** (Flattening nested Options) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatten: (self: Option>) => Option = flatMap(identity) + +/** + * Sequences two `Option`s, keeping the value from the second if both are `Some`. + * + * **When to use** + * + * Use when you need two `Option` values to both be `Some`, then keep only the + * second value. + * + * **Details** + * + * - Both `Some` → returns `that` + * - Either `None` → returns `None` + * + * **Example** (Keeping the second value) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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 + * @see {@link zipWith} to combine both values + * + * @category zipping + * @since 2.0.0 + */ +export const zipRight: { + (that: Option): <_>(self: Option<_>) => Option + (self: Option, that: Option): Option +} = dual(2, (self: Option, that: Option): Option => flatMap(self, () => that)) + +/** + * Sequences two `Option`s, keeping the value from the first if both are `Some`. + * + * **When to use** + * + * Use when you need two `Option` values to both be `Some`, then keep only the + * first value. + * + * **Details** + * + * - Both `Some` → returns `self` + * - Either `None` → returns `None` + * + * **Example** (Keeping the first value) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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 + * @see {@link zipWith} to combine both values + * + * @category zipping + * @since 2.0.0 + */ +export const zipLeft: { + <_>(that: Option<_>): (self: Option) => Option + (self: Option, that: Option): Option +} = dual(2, (self: Option, that: Option): Option => tap(self, () => that)) + +/** + * Composes two `Option`-returning functions into a single function that chains + * them together. + * + * **When to use** + * + * Use when you need to compose two functions that each return an `Option`, so + * `None` short-circuits without calling the next function. + * + * **Details** + * + * - Calls `afb(a)`, then if `Some`, calls `bfc` with its value + * - Short-circuits to `None` if either function returns `None` + * + * **Example** (Composing parsers) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const parse = (s: string): Option.Option => + * isNaN(Number(s)) ? Option.none() : Option.some(Number(s)) + * + * const double = (n: number): Option.Option => + * n > 0 ? Option.some(n * 2) : Option.none() + * + * const parseAndDouble = Option.composeK(parse, double) + * + * parseAndDouble("42") // => Option.some(84) + * parseAndDouble("not a number") // => Option.none() + * ``` + * + * @see {@link flatMap} for single-step chaining + * + * @category sequencing + * @since 2.0.0 + */ +export const composeK: { + (bfc: (b: B) => Option): (afb: (a: A) => Option) => (a: A) => Option + (afb: (a: A) => Option, bfc: (b: B) => Option): (a: A) => Option +} = dual(2, (afb: (a: A) => Option, bfc: (b: B) => Option) => (a: A): Option => flatMap(afb(a), bfc)) + +/** + * Runs a side-effecting `Option`-returning function on the value of a `Some`, + * returning the original `Option` if the function returns `Some`, or `None` + * if it returns `None`. + * + * **When to use** + * + * Use to validate an `Option`'s present value without transforming it, such as + * adding a side-condition check in a pipeline. + * + * **Details** + * + * - `None` → `None` + * - `Some` → calls `f(value)`; if result is `Some`, returns original `self`; if `None`, returns `None` + * + * **Example** (Validating without transforming) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const getInteger = (n: number) => + * Number.isInteger(n) ? Option.some(n) : Option.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 + * @see {@link filter} for predicate-based filtering + * + * @category sequencing + * @since 2.0.0 + */ +export const tap: { + (f: (a: A) => Option): (self: Option) => Option + (self: Option, f: (a: A) => Option): Option +} = dual(2, (self: Option, f: (a: A) => Option): Option => flatMap(self, (a) => map(f(a), () => a))) + +/** + * Combines two `Option`s into a `Some` containing a tuple `[A, B]` if both + * are `Some`. + * + * **When to use** + * + * Use when you need to require two `Option` values to both be `Some` and keep + * both values as a tuple. + * + * **Details** + * + * - Both `Some` → `Some([a, b])` + * - Either `None` → `None` + * + * **Example** (Pairing two Options) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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 + * @see {@link all} to combine many `Option`s + * + * @category combining + * @since 2.0.0 + */ +export const product = (self: Option, that: Option): Option<[A, B]> => + isSome(self) && isSome(that) ? some([self.value, that.value]) : none() + +/** + * Combines a primary `Option` with an iterable of `Option`s into a tuple if + * all are `Some`. + * + * **When to use** + * + * Use when you need several `Option` values of the same type to all be `Some` + * and return them as a non-empty tuple. + * + * **Details** + * + * - All `Some` → `Some([self.value, ...rest])` + * - Any `None` → `None` + * + * **Example** (Combining many Options) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const first = Option.some(1) + * const rest = [Option.some(2), Option.some(3)] + * + * 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 + * @see {@link all} for tuples, structs, and iterables + * + * @category combining + * @since 2.0.0 + */ +export const productMany = ( + self: Option, + collection: Iterable> +): Option<[A, ...Array]> => { + if (isNone(self)) { + return none() + } + const out: [A, ...Array] = [self.value] + for (const o of collection) { + if (isNone(o)) { + return none() + } + out.push(o.value) + } + return some(out) +} + +/** + * Combines a structure of `Option`s (tuple, struct, or iterable) into a single + * `Option` containing the unwrapped structure. + * + * **When to use** + * + * Use when you need to combine multiple `Option` values into one while + * preserving the input shape, with any `None` making the result `None`. + * + * **Details** + * + * - Tuple input → `Option` of a tuple with the same length + * - Struct input → `Option` of a struct with the same keys + * - Iterable input → `Option` of an `Array` + * - Any `None` in the input → entire result is `None` + * + * **Example** (Combining a tuple and a struct) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const maybeName: Option.Option = Option.some("John") + * const maybeAge: Option.Option = Option.some(25) + * + * // ┌─── Option<[string, number]> + * // ▼ + * const tuple = Option.all([maybeName, maybeAge]) // => Option.some(["John", 25]) + * + * // ┌─── Option<{ name: string; age: number; }> + * // ▼ + * const struct = Option.all({ name: maybeName, age: maybeAge }) // => Option.some({ name: "John", age: 25 }) + * ``` + * + * @see {@link product} for combining exactly two + * @see {@link productMany} for a homogeneous collection + * + * @category combining + * @since 2.0.0 + */ +// @ts-expect-error +export const all: > | Record>>( + input: I +) => [I] extends [ReadonlyArray>] ? Option< + { -readonly [K in keyof I]: [I[K]] extends [Option] ? A : never } + > + : [I] extends [Iterable>] ? Option> + : Option<{ -readonly [K in keyof I]: [I[K]] extends [Option] ? A : never }> = ( + input: Iterable> | Record> + ): Option => { + if (Symbol.iterator in input) { + const out: Array> = [] + for (const o of (input as Iterable>)) { + if (isNone(o)) { + return none() + } + out.push(o.value) + } + return some(out) + } + + const out: Record = {} + for (const key of Object.keys(input)) { + const o = input[key] + if (isNone(o)) { + return none() + } + InternalRecord.assignProperty(out, key, o.value) + } + return some(out) + } + +/** + * Combines two `Option`s using a provided function. + * + * **When to use** + * + * Use when you need to combine two present `Option` values into a computed + * result. + * + * **Details** + * + * - Both `Some` → applies `f(a, b)` and wraps in `Some` + * - Either `None` → `None` + * + * **Example** (Combining with a function) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.zipWith( + * Option.some("John"), + * Option.some(25), + * (name, age) => ({ name: name.toUpperCase(), age }) + * ) // => Option.some({ name: "JOHN", age: 25 }) + * ``` + * + * @see {@link product} to combine into a tuple instead + * @see {@link lift2} to lift a binary function + * + * @category zipping + * @since 2.0.0 + */ +export const zipWith: { + (that: Option, f: (a: A, b: B) => C): (self: Option) => Option + (self: Option, that: Option, f: (a: A, b: B) => C): Option +} = dual( + 3, + (self: Option, that: Option, f: (a: A, b: B) => C): Option => + map(product(self, that), ([a, b]) => f(a, b)) +) + +/** + * Reduces an iterable of `Option`s to a single value, skipping `None` entries. + * + * **When to use** + * + * Use when you need to aggregate values from a collection where some may be + * absent. + * + * **Details** + * + * - Iterates through the collection, applying `f` only to `Some` values + * - `None` values are skipped entirely + * - Returns the accumulated result + * + * **Example** (Summing present values) + * + * ```ts import.meta.vitest + * import { Option, pipe } from "effect" + * + * const items = [Option.some(1), Option.none(), Option.some(2), Option.none()] + * + * pipe(items, Option.reduceCompact(0, (b, a) => b + a)) // => 3 + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduceCompact: { + (b: B, f: (b: B, a: A) => B): (self: Iterable>) => B + (self: Iterable>, b: B, f: (b: B, a: A) => B): B +} = dual( + 3, + (self: Iterable>, b: B, f: (b: B, a: A) => B): B => { + let out: B = b + for (const oa of self) { + if (isSome(oa)) { + out = f(out, oa.value) + } + } + return out + } +) + +/** + * Converts an `Option` into an `Array`. + * + * **When to use** + * + * Use when you need to pass an `Option` to array-based APIs or spread optional + * values into collections. + * + * **Details** + * + * - `Some` → single-element array `[value]` + * - `None` → empty array `[]` + * + * **Example** (Converting to an array) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * Option.toArray(Option.some(1)) // => [1] + * Option.toArray(Option.none()) // => [] + * ``` + * + * @see {@link fromIterable} for the inverse direction + * + * @category converting + * @since 2.0.0 + */ +export const toArray = (self: Option): Array => isNone(self) ? [] : [self.value] + +/** + * Splits an `Option` into two `Option`s using a function that returns a `Result`. + * + * **When to use** + * + * Use when you need to split an optional value into "left" and "right" + * channels using a `Result`-returning function. + * + * **Details** + * + * - `None` → `[None, None]` + * - `Some` where `f` returns `Err` → `[Some(error), None]` + * - `Some` where `f` returns `Ok` → `[None, Some(value)]` + * + * **Example** (Partitioning by Result) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * const parseNumber = (s: string): Result.Result => { + * const n = Number(s) + * return isNaN(n) ? Result.fail("Not a number") : Result.succeed(n) + * } + * + * 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 + * + * @category filtering + * @since 2.0.0 + */ +export const partitionMap: { + (f: (a: A) => Result): (self: Option) => [left: Option, right: Option] + (self: Option, f: (a: A) => Result): [left: Option, right: Option] +} = dual(2, ( + self: Option, + f: (a: A) => Result +): [excluded: Option, satisfying: Option] => { + if (isNone(self)) { + return [none(), none()] + } + const e = f(self.value) + return result.isFailure(e) ? [some(e.failure), none()] : [none(), some(e.success)] +}) + +/** + * Transforms and filters an `Option` using a `Filter` callback. + * + * **When to use** + * + * Use to transform an `Option`'s present value and discard it when the `Filter` + * fails. + * + * **Details** + * + * The callback returns a `Result`: `Result.succeed` keeps and transforms the + * value, while `Result.fail` discards it. + * + * **Example** (Filtering and transforming) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Option.filterMap( + * Option.some(2), + * (n) => (n % 2 === 0 ? Result.succeed(`Even: ${n}`) : Result.failVoid) + * ) // => Option.some("Even: 2") + * ``` + * + * @see {@link filter} for predicate-based filtering + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + (f: Filter.Filter): (self: Option) => Option + (self: Option, f: Filter.Filter): Option +} = dual(2, (self: Option, f: Filter.Filter): Option => { + if (isNone(self)) { + return none() + } + const next = f(self.value) + return result.isSuccess(next) ? some(next.success) : none() +}) + +/** + * Filters an `Option` using a predicate. Returns `None` if the predicate is + * not satisfied or the input is `None`. + * + * **When to use** + * + * Use when you need to discard an `Option`'s present value when it does not + * meet a condition, while narrowing the type via a refinement predicate. + * + * **Details** + * + * - `None` → `None` + * - `Some` where `predicate(value)` is `true` → `Some(value)` + * - `Some` where `predicate(value)` is `false` → `None` + * - Supports refinements for type narrowing + * + * **Example** (Filtering with a predicate) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const removeEmpty = (input: Option.Option) => + * Option.filter(input, (value) => value !== "") + * + * 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 + * @see {@link exists} to test without filtering + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (refinement: Refinement): (self: Option) => Option + (predicate: Predicate): (self: Option) => Option + (self: Option, refinement: Refinement): Option + (self: Option, predicate: Predicate): Option +} = dual( + 2, + (self: Option, predicate: Predicate): Option => + isNone(self) ? none() : predicate(self.value) ? some(self.value) : none() +) + +/** + * Creates an `Equivalence` for `Option` from an `Equivalence` for `A`. + * + * **When to use** + * + * Use when you need equality to treat two `None` values as equal and compare + * two `Some` values with a supplied equality rule. + * + * **Details** + * + * - `None` vs `None` → `true` + * - `Some` vs `None` (or vice versa) → `false` + * - `Some(a)` vs `Some(b)` → delegates to the provided `Equivalence` + * + * **Example** (Comparing Options) + * + * ```ts import.meta.vitest + * import { Equivalence, Option } from "effect" + * + * const eq = Option.makeEquivalence(Equivalence.strictEqual()) + * + * eq(Option.some(1), Option.some(1)) // => true + * eq(Option.some(1), Option.some(2)) // => false + * eq(Option.none(), Option.none()) // => true + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence = (isEquivalent: Equivalence.Equivalence): Equivalence.Equivalence> => + Equivalence.make((x, y) => isNone(x) ? isNone(y) : isNone(y) ? false : isEquivalent(x.value, y.value)) + +/** + * Creates an `Order` for `Option` from an `Order` for `A`. + * + * **When to use** + * + * Use when you need to sort `Some` and `None` values, with `None` ordered + * before present values and present values compared by a supplied ordering + * rule. + * + * **Details** + * + * - `None` is considered less than any `Some` + * - Two `Some` values are compared using the provided `Order` + * - Two `None` values are equal (returns `0`) + * + * **Example** (Ordering Options) + * + * ```ts import.meta.vitest + * import { Number as N, Option } from "effect" + * + * const ord = Option.makeOrder(N.Order) + * + * ord(Option.none(), Option.some(1)) // => -1 + * ord(Option.some(1), Option.none()) // => 1 + * ord(Option.some(1), Option.some(2)) // => -1 + * ``` + * + * @category sorting + * @since 4.0.0 + */ +export const makeOrder = (O: Order): Order> => + order.make((self, that) => isSome(self) ? (isSome(that) ? O(self.value, that.value) : 1) : -1) + +/** + * Lifts a binary function to operate on two `Option` values. + * + * **When to use** + * + * Use when you need to reuse an existing binary function with two `Option` + * values. + * + * **Details** + * + * - Both `Some` → applies `f` and wraps in `Some` + * - Either `None` → `None` + * + * **Example** (Lifting addition) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const addOptions = Option.lift2((a: number, b: number) => a + b) + * + * 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 + * + * @category lifting + * @since 2.0.0 + */ +export const lift2 = (f: (a: A, b: B) => C): { + (that: Option): (self: Option) => Option + (self: Option, that: Option): Option +} => dual(2, (self: Option, that: Option): Option => zipWith(self, that, f)) + +/** + * Lifts a `Predicate` or `Refinement` into the `Option` context: returns + * `Some(value)` when the predicate holds, `None` otherwise. + * + * **When to use** + * + * Use to convert a boolean check into an `Option`-returning function + * - Validating input and wrapping it in `Option` + * + * **Details** + * + * - `predicate(value)` is `true` → `Some(value)` + * - `predicate(value)` is `false` → `None` + * - Supports refinements for type narrowing + * + * **Example** (Validating positive numbers) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const parsePositive = Option.liftPredicate((n: number) => n > 0) + * + * parsePositive(1) // => Option.some(1) + * parsePositive(-1) // => Option.none() + * ``` + * + * @see {@link filter} to apply a predicate to an existing `Option` + * @see {@link toRefinement} for the inverse direction + * + * @category lifting + * @since 2.0.0 + */ +export const liftPredicate: { // Note: I intentionally avoid using the NoInfer pattern here. + (refinement: Refinement): (a: A) => Option + (predicate: Predicate): (b: B) => Option + ( + self: A, + refinement: Refinement + ): Option + ( + self: B, + predicate: Predicate + ): Option +} = dual( + 2, + (b: B, predicate: Predicate): Option => predicate(b) ? some(b) : none() +) + +/** + * Checks whether an `Option` contains a value equivalent to the given one, using a + * custom `Equivalence`. + * + * **When to use** + * + * Use when you need to test whether an `Option` contains a value using a + * custom equality check. + * + * **Details** + * + * - `Some` where `isEquivalent(value, a)` is `true` → `true` + * - `Some` where not equivalent, or `None` → `false` + * + * **Example** (Checking with custom equivalence) + * + * ```ts import.meta.vitest + * import { Equivalence, Option } from "effect" + * + * const check = Option.containsWith(Equivalence.strictEqual()) + * + * 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 predicates + * @since 2.0.0 + */ +export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { + (a: A): (self: Option) => boolean + (self: Option, a: A): boolean +} => dual(2, (self: Option, a: A): boolean => isNone(self) ? false : isEquivalent(self.value, a)) + +/** + * Checks whether an `Option` contains a value equal to the given one, using default + * structural equality. + * + * **When to use** + * + * Use when you need a quick membership test for an `Option` value using + * standard equality. + * + * **Details** + * + * - `Some` where `Equal.equals(value, a)` is `true` → `true` + * - `Some` where not equal, or `None` → `false` + * + * **Example** (Checking containment) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * 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 predicates + * @since 2.0.0 + */ +export const contains: { + (a: A): (self: Option) => boolean + (self: Option, a: A): boolean +} = containsWith(Equal.asEquivalence()) + +/** + * Checks whether the value in a `Some` satisfies a predicate or refinement. + * + * **When to use** + * + * Use to check a condition on an optional value without unwrapping + * + * **Details** + * + * - `None` → `false` + * - `Some` where `predicate(value)` is `true` → `true` + * - `Some` where `predicate(value)` is `false` → `false` + * - With a refinement, narrows the `Option` type on `true` + * + * **Example** (Testing a condition) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const isEven = (n: number) => n % 2 === 0 + * + * 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 predicates + * @since 2.0.0 + */ +export const exists: { + (refinement: Refinement, B>): (self: Option) => self is Option + (predicate: Predicate>): (self: Option) => boolean + (self: Option, refinement: Refinement): self is Option + (self: Option, predicate: Predicate): boolean +} = dual( + 2, + (self: Option, refinement: Refinement): self is Option => + isNone(self) ? false : refinement(self.value) +) + +// ------------------------------------------------------------------------------------- +// do notation +// ------------------------------------------------------------------------------------- + +/** + * Gives a name to the value of an `Option`, creating a single-key record + * inside `Some`. Starting point for the do notation pipeline. + * + * **When to use** + * + * Use when you need to start an `Option` do notation chain by naming the first + * value. + * + * **Example** (Starting do notation) + * + * ```ts import.meta.vitest + * import { Option, pipe } from "effect" + * + * pipe( + * Option.some(2), + * Option.bindTo("x"), + * Option.bind("y", () => Option.some(3)), + * Option.let("sum", ({ x, y }) => x + y) + * ) // => 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 mapping + * @since 2.0.0 + */ +export const bindTo: { + (name: N): (self: Option) => Option<{ [K in N]: A }> + (self: Option, name: N): Option<{ [K in N]: A }> +} = doNotation.bindTo(map) + +const let_: { + ( + name: Exclude, + f: (a: NoInfer) => B + ): (self: Option) => Option<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> + ( + self: Option, + name: Exclude, + f: (a: NoInfer) => B + ): Option<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> +} = doNotation.let_(map) + +export { + /** + * Adds a computed plain value to the do notation record. + * + * **When to use** + * + * Use when you need to bind a derived non-`Option` value in an `Option` do + * notation pipeline. + * + * **Example** (Adding a computed value) + * + * ```ts import.meta.vitest + * import { Option, pipe } from "effect" + * + * pipe( + * Option.Do, + * Option.bind("x", () => Option.some(2)), + * Option.bind("y", () => Option.some(3)), + * Option.let("sum", ({ x, y }) => x + y) + * ) // => 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 mapping + * @since 2.0.0 + */ + let_ as let +} + +/** + * Adds an `Option` value to the do notation record under a given name. If the + * `Option` is `None`, the whole pipeline short-circuits to `None`. + * + * **When to use** + * + * Use when you need to sequence `Option` computations in do notation. + * + * **Example** (Binding Option values) + * + * ```ts import.meta.vitest + * import { Option, pipe } from "effect" + * + * 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) + * ) // => 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 sequencing + * @since 2.0.0 + */ +export const bind: { + ( + name: Exclude, + f: (a: NoInfer) => Option + ): (self: Option) => Option<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> + ( + self: Option, + name: Exclude, + f: (a: NoInfer) => Option + ): Option<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }> +} = doNotation.bind(map, flatMap) + +/** + * Provides an `Option` containing an empty record `{}`, used as the starting point for + * do notation chains. + * + * **When to use** + * + * Use when you need to start an `Option` do notation pipeline before adding + * bindings. + * + * **Example** (Building Option pipelines with do notation) + * + * ```ts import.meta.vitest + * import { Option, pipe } from "effect" + * + * 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) + * ) // => 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 constructors + * @since 2.0.0 + */ +export const Do: Option<{}> = some({}) + +/** + * Provides generator-based syntax for `Option`, similar to `async`/`await` but for + * optional values. Yielding a `None` short-circuits the generator to `None`. + * + * **When to use** + * + * Use when you need generator syntax for a sequence of `Option` steps that + * should short-circuit on `None`. + * + * **Details** + * + * - Each `yield*` unwraps a `Some` value or short-circuits to `None` + * - The return value is wrapped in `Some` + * - No `Effect` runtime is needed + * + * **Example** (Sequencing Option computations with generator syntax) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * + * const maybeName: Option.Option = Option.some("John") + * const maybeAge: Option.Option = Option.some(25) + * + * Option.gen(function*() { + * const name = (yield* maybeName).toUpperCase() + * const age = yield* maybeAge + * return { name, age } + * }) // => Option.some({ name: "JOHN", age: 25 }) + * ``` + * + * @see {@link Do} / {@link bind} for the do notation alternative + * + * @category generators + * @since 2.0.0 + */ +export const gen: Gen.Gen = (...args) => { + const f = args.length === 1 ? args[0] : args[1].bind(args[0]) + const iterator = f() + let state: IteratorResult = iterator.next() + while (!state.done) { + const current = state.value + if (isNone(current)) { + return current + } + state = iterator.next(current.value as never) + } + return some(state.value) +} + +/** + * Creates a `Reducer` for `Option` that prioritizes the first non-`None` + * value and combines values when both are `Some`. + * + * **When to use** + * + * Use to build an `Option` reducer that falls back to the first available value + * when either side may be absent. + * + * **Details** + * + * - `None` + `None` → `None` + * - `Some(a)` + `None` → `Some(a)` + * - `None` + `Some(b)` → `Some(b)` + * - `Some(a)` + `Some(b)` → `Some(combine(a, b))` + * - Initial value is `None` + * + * **Example** (Reducing with first-wins semantics) + * + * ```ts import.meta.vitest + * import { Number, Option } from "effect" + * + * const reducer = Option.makeReducer(Number.ReducerSum) + * reducer.combineAll([Option.some(1), Option.none(), Option.some(2)]) // => Option.some(3) + * ``` + * + * @see {@link makeReducerFailFast} for fail-fast semantics + * + * @category constructors + * @since 4.0.0 + */ +export function makeReducer(combiner: Combiner.Combiner): Reducer.Reducer> { + return Reducer.make((self, that) => { + if (isNone(self)) return that + if (isNone(that)) return self + return some(combiner.combine(self.value, that.value)) + }, none()) +} + +/** + * Creates a `Combiner` for `Option` with fail-fast semantics: returns `None` + * if either operand is `None`. + * + * **When to use** + * + * Use when you need an `Option` combiner that returns `None` unless both + * operands are `Some`. + * + * **Details** + * + * - `None` + anything → `None` + * - anything + `None` → `None` + * - `Some(a)` + `Some(b)` → `Some(combine(a, b))` + * + * **Example** (Fail-fast combining) + * + * ```ts import.meta.vitest + * import { Number, Option } from "effect" + * + * const combiner = Option.makeCombinerFailFast(Number.ReducerSum) + * 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 constructors + * @since 4.0.0 + */ +export function makeCombinerFailFast(combiner: Combiner.Combiner): Combiner.Combiner> { + return Combiner.make((self, that) => { + if (isNone(self) || isNone(that)) return none() + return some(combiner.combine(self.value, that.value)) + }) +} + +/** + * Creates a `Reducer` for `Option` by lifting an existing `Reducer` with + * fail-fast semantics. + * + * **When to use** + * + * Use when you need to reduce `Option` values with fail-fast semantics, where + * any `None` aborts the entire result instead of being skipped. + * + * **Details** + * + * - Initial value is `Some(reducer.initialValue)` + * - Combines only when both operands are `Some` + * - Any `None` causes the result to become `None` immediately + * + * **Example** (Fail-fast reducing) + * + * ```ts import.meta.vitest + * import { Number, Option } from "effect" + * + * const reducer = Option.makeReducerFailFast(Number.ReducerSum) + * 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 constructors + * @since 4.0.0 + */ +export function makeReducerFailFast(reducer: Reducer.Reducer): Reducer.Reducer> { + const combine = makeCombinerFailFast(reducer).combine + const initialValue = some(reducer.initialValue) + return Reducer.make(combine, initialValue, (collection) => { + let out = initialValue + for (const value of collection) { + out = combine(out, value) + if (isNone(out)) return out + } + return out + }) +} diff --git a/.repos/effect/packages/effect/src/Order.ts b/.repos/effect/packages/effect/src/Order.ts new file mode 100644 index 000000000..c52ea3604 --- /dev/null +++ b/.repos/effect/packages/effect/src/Order.ts @@ -0,0 +1,982 @@ +/** + * Defines comparison functions for ordered values. + * + * An `Order` compares two `A` values and returns whether the first is less + * than, equal to, or greater than the second. Orders are used for sorting, + * choosing minimum or maximum values, checking ranges, and building ordered data + * structures. This module includes built-in orders, constructors for custom + * orders, tools for reversing and combining comparisons, tuple and struct + * helpers, comparison predicates, clamping, and reducer support. + * + * @since 2.0.0 + */ +import { dual } from "./Function.ts" +import type { TypeLambda } from "./HKT.ts" +import type { Ordering } from "./Ordering.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Represents a total ordering for values of type `A`. + * + * **When to use** + * + * Use when you need to define how values of a type are compared. + * + * **Details** + * + * An order returns `-1` when the first value is less than the second, `0` when + * the values are equal according to this ordering, and `1` when the first value + * is greater than the second. It must satisfy total ordering laws: totality, + * antisymmetry, and transitivity. + * + * **Example** (Defining a custom Order) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const byAge: Order.Order<{ name: string; age: number }> = (self, that) => { + * if (self.age < that.age) return -1 + * if (self.age > that.age) return 1 + * return 0 + * } + * + * const person1 = { name: "Alice", age: 30 } + * const person2 = { name: "Bob", age: 25 } + * 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 models + * @since 2.0.0 + */ +export interface Order { + (self: A, that: A): Ordering +} + +/** + * Type lambda for the `Order` type class, used internally for higher-kinded type operations. + * + * **When to use** + * + * Use when you need to abstract over `Order` in higher-kinded type code. + * + * **Details** + * + * This is type-level only, has no runtime representation, and is used + * internally by the Effect type system. + * + * @category utility types + * @since 2.0.0 + */ +export interface OrderTypeLambda extends TypeLambda { + readonly type: Order +} + +/** + * Creates a new `Order` instance from a comparison function. + * + * **When to use** + * + * Use when you need a sorting rule not covered by the built-in orders or input + * mapping helpers, and you can provide a total comparison. + * + * **Details** + * + * Uses reference equality (`===`) as a shortcut: if `self === that`, it returns + * `0` without calling the comparison function. The comparison function should + * return `-1`, `0`, or `1`, and the returned order satisfies total ordering + * laws when the comparison function does. + * + * **Example** (Creating an Order) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const byAge = Order.make<{ name: string; age: number }>((self, that) => { + * if (self.age < that.age) return -1 + * if (self.age > that.age) return 1 + * return 0 + * }) + * + * 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 + * @see {@link combine} to combine multiple orders + * @category constructors + * @since 2.0.0 + */ +export function make( + compare: (self: A, that: A) => -1 | 0 | 1 +): Order { + return (self, that) => self === that ? 0 : compare(self, that) +} + +/** + * Order instance for strings that compares them lexicographically using JavaScript's `<` operator. + * + * **When to use** + * + * Use when you need lexicographic string ordering. + * + * **Details** + * + * Uses lexicographic dictionary ordering. The empty string is less than any + * non-empty string, and comparisons are case-sensitive. + * + * **Example** (Ordering strings) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * 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 + * @see {@link Struct} to combine with other orders for struct comparison + * @category instances + * @since 4.0.0 + */ +export const String: Order = make((self, that) => self < that ? -1 : 1) + +/** + * Order instance for numbers that compares them numerically. + * + * **When to use** + * + * Use when you need numeric ordering for numbers. + * + * **Details** + * + * `0` is considered equal to `-0`. All `NaN` values are considered equal to + * each other, and any `NaN` is considered less than any non-`NaN` number. All + * other values use standard numeric comparison. + * + * **Example** (Ordering numbers) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * Order.Number(1, 1) // => 0 + * Order.Number(1, 2) // => -1 + * Order.Number(2, 1) // => 1 + * + * Order.Number(0, -0) // => 0 + * Order.Number(NaN, 1) // => -1 + * ``` + * + * @see {@link mapInput} to compare objects by a number property + * @see {@link BigInt} for bigint comparisons + * @category instances + * @since 4.0.0 + */ +export const Number: Order = make((self, that) => { + if (globalThis.Number.isNaN(self) && globalThis.Number.isNaN(that)) return 0 + if (globalThis.Number.isNaN(self)) return -1 // NaN < any number + if (globalThis.Number.isNaN(that)) return 1 // any number > NaN + return self < that ? -1 : 1 +}) + +/** + * Order instance for booleans where `false` is considered less than `true`. + * + * **When to use** + * + * Use when you need boolean ordering where `false` comes before `true`. + * + * **Details** + * + * `false` is less than `true`, and equal values return `0`. + * + * **Example** (Ordering booleans) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * 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 + * @category instances + * @since 4.0.0 + */ +export const Boolean: Order = make((self, that) => self < that ? -1 : 1) + +/** + * Order instance for bigints that compares them numerically. + * + * **When to use** + * + * Use when you need numeric ordering for `bigint` values. + * + * **Details** + * + * Uses standard numeric comparison for bigint values and handles arbitrarily + * large integers. + * + * **Example** (Ordering BigInts) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * Order.BigInt(1n, 2n) // => -1 + * Order.BigInt(2n, 1n) // => 1 + * Order.BigInt(1n, 1n) // => 0 + * ``` + * + * @see {@link Number} for regular number comparisons + * @see {@link mapInput} to compare objects by a bigint property + * @category instances + * @since 4.0.0 + */ +export const BigInt: Order = make((self, that) => self < that ? -1 : 1) + +/** + * Creates a new `Order` that reverses the comparison order of the input `Order`. + * + * **When to use** + * + * Use when you need the reverse of an existing order. + * + * **Details** + * + * Returns a new order that swaps the arguments before comparison. If the + * original order returns `-1`, the flipped order returns `1`, and vice versa. + * Equal comparisons remain `0`. + * + * **Example** (Reversing an Order) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const flip = Order.flip(Order.Number) + * + * flip(1, 2) // => 1 + * flip(2, 1) // => -1 + * flip(1, 1) // => 0 + * ``` + * + * @see {@link combine} to combine orders for multi-criteria comparison + * @category combinators + * @since 4.0.0 + */ +export function flip(O: Order): Order { + return make((self, that) => O(that, self)) +} + +/** + * Combines two `Order` instances to create a new `Order` that first compares using the first `Order`, + * and if the values are equal, then compares using the second `Order`. + * + * **When to use** + * + * Use when you need tie-breaking with exactly two orders. + * + * **Details** + * + * First applies the first order. If the result is non-zero, that result is + * returned; otherwise, the second order is applied. The result is the first + * non-zero comparison result, or `0` if both orders return `0`. + * + * **Example** (Combining two Orders) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const byAge = Order.mapInput( + * Order.Number, + * (person: { name: string; age: number }) => person.age + * ) + * const byName = Order.mapInput( + * Order.String, + * (person: { name: string; age: number }) => person.name + * ) + * const byAgeAndName = Order.combine(byAge, byName) + * + * const person1 = { name: "Alice", age: 30 } + * const person2 = { name: "Bob", age: 30 } + * const person3 = { name: "Charlie", age: 25 } + * + * byAgeAndName(person1, person2) // => -1 + * byAgeAndName(person1, person3) // => 1 + * ``` + * + * @see {@link combineAll} to combine multiple orders from a collection + * @see {@link mapInput} to transform orders to work with different types + * @category combining + * @since 2.0.0 + */ +export const combine: { + (that: Order): (self: Order) => Order + (self: Order, that: Order): Order +} = dual(2, (self: Order, that: Order): Order => + make((a1, a2) => { + const out = self(a1, a2) + if (out !== 0) { + return out + } + return that(a1, a2) + })) + +/** + * Creates an `Order` that considers all values as equal. + * + * **When to use** + * + * Use when you need an order that treats all values as equal. + * + * **Details** + * + * Always returns `0` regardless of input values, making it useful as a neutral + * element in order composition. + * + * **Example** (Ordering with an always-equal Order) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const alwaysEqualOrder = Order.alwaysEqual() + * + * alwaysEqualOrder(1, 2) // => 0 + * alwaysEqualOrder(2, 1) // => 0 + * alwaysEqualOrder(1, 1) // => 0 + * ``` + * + * @see {@link combine} to combine with other orders + * @category constructors + * @since 4.0.0 + */ +export function alwaysEqual(): Order { + return make(() => 0) +} + +/** + * Combines all `Order` instances in the provided collection into a single `Order`. + * The resulting `Order` compares using each `Order` in sequence until a non-zero result is found. + * + * **When to use** + * + * Use when you need tie-breaking across a variable number of orders. + * + * **Details** + * + * Applies orders in iteration order and short-circuits on the first non-zero + * result. It returns `0` only if all orders return `0`. + * + * **Example** (Combining multiple Orders) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const byAge = Order.mapInput( + * Order.Number, + * (person: { name: string; age: number }) => person.age + * ) + * const byName = Order.mapInput( + * Order.String, + * (person: { name: string; age: number }) => person.name + * ) + * + * const combinedOrder = Order.combineAll([byAge, byName]) + * + * const person1 = { name: "Alice", age: 30 } + * const person2 = { name: "Bob", age: 30 } + * + * combinedOrder(person1, person2) // => -1 + * ``` + * + * @see {@link combine} to combine two orders + * @see {@link makeReducer} to create a reducer for combining orders + * @category combining + * @since 2.0.0 + */ +export function combineAll(collection: Iterable>): Order { + return make((a1, a2) => { + let out: Ordering = 0 + for (const O of collection) { + out = O(a1, a2) + if (out !== 0) { + return out + } + } + return out + }) +} + +/** + * Transforms an `Order` on type `A` into an `Order` on type `B` by providing a function that + * maps values of type `B` to values of type `A`. + * + * **When to use** + * + * Use when you need to adapt an `Order` to compare a larger value by one + * derived property. + * + * **Details** + * + * Applies the mapping function to both values before comparison. The mapping + * function should be pure and not have side effects so the ordering properties + * of the original order are preserved. + * + * **Example** (Mapping Input) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const byLength = Order.mapInput(Order.Number, (s: string) => s.length) + * + * byLength("a", "bb") // => -1 + * byLength("bb", "a") // => 1 + * byLength("aa", "bb") // => 0 + * ``` + * + * @see {@link combine} to combine mapped orders for multi-criteria comparison + * @see {@link Struct} to create orders for structs with multiple fields + * @category mapping + * @since 2.0.0 + */ +export const mapInput: { + (f: (b: B) => A): (self: Order) => Order + (self: Order, f: (b: B) => A): Order +} = dual( + 2, + (self: Order, f: (b: B) => A): Order => make((b1, b2) => self(f(b1), f(b2))) +) + +/** + * Order instance for `Date` objects that compares them chronologically by their timestamp. + * + * **When to use** + * + * Use when you need chronological ordering for JavaScript date values. + * + * **Details** + * + * Compares dates by their underlying timestamp in milliseconds since the epoch. + * Earlier dates are less than later dates. Invalid dates are compared through + * their `getTime()` result. + * + * **Example** (Ordering Dates) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const date1 = new Date("2023-01-01") + * const date2 = new Date("2023-01-02") + * + * 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 + * @category instances + * @since 2.0.0 + */ +export const Date: Order = mapInput(Number, (date) => date.getTime()) + +/** + * Creates an `Order` for a tuple type based on orders for each element. + * + * **When to use** + * + * Use when you need fixed-length tuple ordering with per-position orders. + * + * **Details** + * + * Compares tuples element-by-element using the corresponding order and stops at + * the first non-zero comparison result. Tuples must have the same length as the + * order collection, and the result is `0` only if all elements are equal. + * + * **Example** (Ordering tuples) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const tupleOrder = Order.Tuple([Order.Number, Order.String]) + * + * 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 + * @category combinators + * @since 4.0.0 + */ +export function Tuple>>( + elements: Elements +): Order<{ readonly [I in keyof Elements]: [Elements[I]] extends [Order] ? A : never }> { + return make((self, that) => { + const len = elements.length + for (let i = 0; i < len; i++) { + const o = elements[i](self[i], that[i]) + if (o !== 0) { + return o + } + } + return 0 + }) +} + +/** + * @since 4.0.0 + */ +function Array_(O: Order): Order> { + return make((self, that) => { + const aLen = self.length + const bLen = that.length + const len = Math.min(aLen, bLen) + for (let i = 0; i < len; i++) { + const o = O(self[i], that[i]) + if (o !== 0) { + return o + } + } + return Number(aLen, bLen) + }) +} + +export { + /** + * Creates an `Order` for arrays by applying the given `Order` to each element, then comparing by length if all elements are equal. + * + * **When to use** + * + * Use when you need lexicographic ordering for arrays of one element type. + * + * **Details** + * + * Compares arrays element-by-element using the provided order and stops at the + * first non-zero comparison result. If all elements are equal, shorter arrays + * are less than longer arrays. The result is `0` only if arrays have the same + * length and all elements are equal. + * + * **Example** (Ordering array elements) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const arrayOrder = Order.Array(Order.Number) + * + * 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 + * @category combinators + * @since 4.0.0 + */ + Array_ as Array +} + +/** + * Creates an `Order` for structs by applying the given `Order`s to each property in sequence. + * + * **When to use** + * + * Use when you need multi-field ordering for objects with known properties. + * + * **Details** + * + * Compares structs field-by-field in the key order of the fields object and + * stops at the first non-zero comparison result. Field order matters: earlier + * fields take precedence. The result is `0` only if all fields are equal. + * + * **Example** (Ordering structs) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const personOrder = Order.Struct({ + * name: Order.String, + * age: Order.Number + * }) + * + * const person1 = { name: "Alice", age: 30 } + * const person2 = { name: "Bob", age: 25 } + * const person3 = { name: "Alice", age: 25 } + * + * personOrder(person1, person2) // => -1 + * personOrder(person1, person3) // => 1 + * personOrder(person1, person1) // => 0 + * ``` + * + * @see {@link combine} to combine orders manually + * @see {@link mapInput} to extract and compare by a single property + * @category combinators + * @since 4.0.0 + */ +export function Struct }>( + fields: R +): Order<{ [K in keyof R]: [R[K]] extends [Order] ? A : never }> { + const keys = Object.keys(fields) + return make((self, that) => { + for (const key of keys) { + const o = fields[key](self[key], that[key]) + if (o !== 0) { + return o + } + } + return 0 + }) +} + +/** + * Checks whether one value is strictly less than another according to the given order. + * + * **When to use** + * + * Use when you need a boolean less-than predicate using an `Order`. + * + * **Details** + * + * Returns `true` if the order returns `-1`, meaning the first value is less + * than the second. Equal or greater values return `false`. + * + * **Example** (Checking less-than comparisons) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const isLessThanNumber = Order.isLessThan(Order.Number) + * + * isLessThanNumber(1, 2) // => true + * isLessThanNumber(2, 1) // => false + * isLessThanNumber(1, 1) // => false + * ``` + * + * @see {@link isLessThanOrEqualTo} for non-strict less than or equal + * @see {@link isGreaterThan} for strict greater than + * @category predicates + * @since 4.0.0 + */ +export const isLessThan = (O: Order): { + (that: A): (self: A) => boolean + (self: A, that: A): boolean +} => dual(2, (self: A, that: A) => O(self, that) === -1) + +/** + * Checks whether one value is strictly greater than another according to the given order. + * + * **When to use** + * + * Use when you need a boolean greater-than predicate using an `Order`. + * + * **Details** + * + * Returns `true` if the order returns `1`, meaning the first value is greater + * than the second. Equal or lesser values return `false`. + * + * **Example** (Checking greater-than comparisons) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const isGreaterThanNumber = Order.isGreaterThan(Order.Number) + * + * isGreaterThanNumber(2, 1) // => true + * isGreaterThanNumber(1, 2) // => false + * isGreaterThanNumber(1, 1) // => false + * ``` + * + * @see {@link isGreaterThanOrEqualTo} for non-strict greater than or equal + * @see {@link isLessThan} for strict less than + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThan = (O: Order): { + (that: A): (self: A) => boolean + (self: A, that: A): boolean +} => dual(2, (self: A, that: A) => O(self, that) === 1) + +/** + * Checks whether one value is less than or equal to another according to the given order. + * + * **When to use** + * + * Use when you need a boolean less-than-or-equal predicate using an `Order`. + * + * **Details** + * + * Returns `true` if the order returns `-1` or `0`, and returns `false` only if + * the order returns `1`. + * + * **Example** (Checking less-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const isLessThanOrEqualToNumber = Order.isLessThanOrEqualTo(Order.Number) + * + * isLessThanOrEqualToNumber(1, 2) // => true + * isLessThanOrEqualToNumber(1, 1) // => true + * isLessThanOrEqualToNumber(2, 1) // => false + * ``` + * + * @see {@link isLessThan} for strict less than + * @see {@link isGreaterThan} for strict greater than + * @category predicates + * @since 4.0.0 + */ +export const isLessThanOrEqualTo = (O: Order): { + (that: A): (self: A) => boolean + (self: A, that: A): boolean +} => dual(2, (self: A, that: A) => O(self, that) !== 1) + +/** + * Checks whether one value is greater than or equal to another according to the given order. + * + * **When to use** + * + * Use when you need a boolean greater-than-or-equal predicate using an + * `Order`. + * + * **Details** + * + * Returns `true` if the order returns `1` or `0`, and returns `false` only if + * the order returns `-1`. + * + * **Example** (Checking greater-than-or-equal comparisons) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const isGreaterThanOrEqualToNumber = Order.isGreaterThanOrEqualTo(Order.Number) + * + * isGreaterThanOrEqualToNumber(2, 1) // => true + * isGreaterThanOrEqualToNumber(1, 1) // => true + * isGreaterThanOrEqualToNumber(1, 2) // => false + * ``` + * + * @see {@link isGreaterThan} for strict greater than + * @see {@link isLessThanOrEqualTo} for less than or equal + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo = (O: Order): { + (that: A): (self: A) => boolean + (self: A, that: A): boolean +} => dual(2, (self: A, that: A) => O(self, that) !== -1) + +/** + * Returns the minimum of two values according to the given order. If they are equal, returns the first argument. + * + * **When to use** + * + * Use when you need to select the smaller of two values according to an + * `Order`. + * + * **Details** + * + * Returns the value that compares as less than or equal to the other value. If + * values are equal, the first argument is returned. + * + * **Example** (Selecting the minimum value) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const minNumber = Order.min(Order.Number) + * + * minNumber(1, 2) // => 1 + * minNumber(2, 1) // => 1 + * minNumber(1, 1) // => 1 + * ``` + * + * @see {@link max} for the maximum of two values + * @see {@link clamp} to clamp a value between min and max + * @category comparisons + * @since 2.0.0 + */ +export const min = (O: Order): { + (that: A): (self: A) => A + (self: A, that: A): A +} => dual(2, (self: A, that: A) => self === that || O(self, that) < 1 ? self : that) + +/** + * Returns the maximum of two values according to the given order. If they are equal, returns the first argument. + * + * **When to use** + * + * Use when you need to select the larger of two values according to an + * `Order`. + * + * **Details** + * + * Returns the value that compares as greater than or equal to the other value. + * If values are equal, the first argument is returned. + * + * **Example** (Selecting the maximum value) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const maxNumber = Order.max(Order.Number) + * + * maxNumber(1, 2) // => 2 + * maxNumber(2, 1) // => 2 + * maxNumber(1, 1) // => 1 + * ``` + * + * @see {@link min} for the minimum of two values + * @see {@link clamp} to clamp a value between min and max + * @category comparisons + * @since 2.0.0 + */ +export const max = (O: Order): { + (that: A): (self: A) => A + (self: A, that: A): A +} => dual(2, (self: A, that: A) => self === that || O(self, that) > -1 ? self : that) + +/** + * Restricts a value between a minimum and a maximum according to the given order. + * + * **When to use** + * + * Use when you need to clamp a value to an inclusive range according to an + * `Order`. + * + * **Details** + * + * Returns the value itself when it is between minimum and maximum, inclusive. + * Values below the range return minimum, and values above the range return + * maximum. The minimum must be less than or equal to the maximum according to + * the order. + * + * **Example** (Clamping values) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const clamp = Order.clamp(Order.Number)({ minimum: 1, maximum: 5 }) + * + * clamp(3) // => 3 + * clamp(0) // => 1 + * clamp(6) // => 5 + * ``` + * + * @see {@link min} for the minimum of two values + * @see {@link max} for the maximum of two values + * @see {@link isBetween} to check if a value is within a range + * @category comparisons + * @since 2.0.0 + */ +export const clamp = (O: Order): { + (options: { + minimum: A + maximum: A + }): (self: A) => A + (self: A, options: { + minimum: A + maximum: A + }): A +} => + dual( + 2, + (self: A, options: { + minimum: A + maximum: A + }): A => min(O)(options.maximum, max(O)(options.minimum, self)) + ) + +/** + * Checks whether a value is between a minimum and a maximum (inclusive) according to the given order. + * + * **When to use** + * + * Use when you need range checks that respect domain-specific ordering, such as + * dates, versions, or custom priorities, instead of JavaScript numeric + * comparison. + * + * **Details** + * + * Returns `true` when the value is greater than or equal to minimum and less + * than or equal to maximum. Values outside the range return `false`. Both + * bounds are inclusive. + * + * **Example** (Checking ranges) + * + * ```ts import.meta.vitest + * import { Order } from "effect" + * + * const betweenNumber = Order.isBetween(Order.Number) + * + * 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 + * @see {@link isLessThanOrEqualTo} for less than or equal check + * @see {@link isGreaterThanOrEqualTo} for greater than or equal check + * @category predicates + * @since 4.0.0 + */ +export const isBetween = (O: Order): { + (options: { + minimum: A + maximum: A + }): (self: A) => boolean + (self: A, options: { + minimum: A + maximum: A + }): boolean +} => + dual( + 2, + (self: A, options: { + minimum: A + maximum: A + }): boolean => !isLessThan(O)(self, options.minimum) && !isGreaterThan(O)(self, options.maximum) + ) + +/** + * Creates a `Reducer` for combining `Order` instances, useful for aggregating orders in collections. + * + * **When to use** + * + * Use when you need a reducer that combines orders. + * + * **Details** + * + * Returns a reducer that combines orders using `combine`, uses `alwaysEqual` as + * the identity element for empty collections, and uses `combineAll` for + * combining collections of orders. The reducer can be used with fold operations + * on collections. + * + * **Example** (Creating a Reducer) + * + * ```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) + * combined(1, 2) // => -1 + * ``` + * + * @see {@link combine} to combine two orders + * @see {@link combineAll} to combine multiple orders + * @see {@link Reducer} for reducing orders as a collection operation + * @category constructors + * @since 4.0.0 + */ +export function makeReducer() { + return Reducer.make>( + combine, + () => 0, + combineAll + ) +} diff --git a/.repos/effect/packages/effect/src/Ordering.ts b/.repos/effect/packages/effect/src/Ordering.ts new file mode 100644 index 000000000..ff93ffca7 --- /dev/null +++ b/.repos/effect/packages/effect/src/Ordering.ts @@ -0,0 +1,181 @@ +/** + * The standard result of comparing two values. An `Ordering` is `-1` when the + * first value is less than the second, `0` when both values compare as equal, + * and `1` when the first value is greater than the second. This module also + * provides helpers for reversing an ordering, matching on the three cases, and + * combining ordered comparison results with a reducer. + * + * @since 2.0.0 + */ +import type { LazyArg } from "./Function.ts" +import { dual } from "./Function.ts" +import * as Reducer_ from "./Reducer.ts" + +/** + * Represents the result of comparing two values. + * + * **When to use** + * + * Use to model a normalized comparison result that is exactly less than, + * equal to, or greater than. + * + * **Details** + * + * - `-1` indicates the first value is less than the second + * - `0` indicates the values are equal + * - `1` indicates the first value is greater than the second + * + * **Example** (Defining comparison results) + * + * ```ts import.meta.vitest + * import type { Ordering } from "effect" + * + * // Custom comparison function + * const compareNumbers = (a: number, b: number): Ordering.Ordering => { + * if (a < b) return -1 + * if (a > b) return 1 + * return 0 + * } + * + * compareNumbers(5, 10) // => -1 + * compareNumbers(10, 5) // => 1 + * compareNumbers(5, 5) // => 0 + * + * // Using with string comparison + * const compareStrings = (a: string, b: string): Ordering.Ordering => { + * return a.localeCompare(b) as Ordering.Ordering + * } + * ``` + * + * @category models + * @since 2.0.0 + */ +export type Ordering = -1 | 0 | 1 + +/** + * Reverses the ordering of the input Ordering. + * This is useful for creating descending sort orders from ascending ones. + * + * **When to use** + * + * Use to flip an ordering result when reversing sort direction or comparison + * priority. + * + * **Example** (Reversing comparison order) + * + * ```ts import.meta.vitest + * import { Ordering } from "effect" + * + * // Basic reversal + * 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 => + * a < b ? -1 : a > b ? 1 : 0 + * + * const compareDescending = (a: number, b: number): Ordering.Ordering => + * Ordering.reverse(compareNumbers(a, b)) + * + * const numbers = [3, 1, 4, 1, 5] + * numbers.sort(compareNumbers) // [1, 1, 3, 4, 5] (ascending) + * numbers.sort(compareDescending) // [5, 4, 3, 1, 1] (descending) + * + * // Useful for toggling sort direction + * const createSorter = (ascending: boolean) => (a: number, b: number) => { + * const ordering = compareNumbers(a, b) + * return ascending ? ordering : Ordering.reverse(ordering) + * } + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const reverse = (o: Ordering): Ordering => (o === -1 ? 1 : o === 1 ? -1 : 0) + +/** + * Matches an `Ordering` value and returns the branch selected by that ordering. + * + * **When to use** + * + * Use to branch on the three possible comparison outcomes in one expression. + * + * **Example** (Pattern matching on orderings) + * + * ```ts import.meta.vitest + * import { Function, Ordering } from "effect" + * + * const toMessage = Ordering.match({ + * onLessThan: Function.constant("less than"), + * onEqual: Function.constant("equal"), + * onGreaterThan: Function.constant("greater than") + * }) + * + * toMessage(-1) // => "less than" + * toMessage(0) // => "equal" + * toMessage(1) // => "greater than" + * ``` + * + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + ( + options: { + readonly onLessThan: LazyArg + readonly onEqual: LazyArg + readonly onGreaterThan: LazyArg + } + ): (self: Ordering) => A | B | C + ( + o: Ordering, + options: { + readonly onLessThan: LazyArg + readonly onEqual: LazyArg + readonly onGreaterThan: LazyArg + } + ): A | B | C +} = dual(2, ( + self: Ordering, + { onEqual, onGreaterThan, onLessThan }: { + readonly onLessThan: LazyArg + readonly onEqual: LazyArg + readonly onGreaterThan: LazyArg + } +): A | B | C => self === -1 ? onLessThan() : self === 0 ? onEqual() : onGreaterThan()) + +/** + * Reducer for combining `Ordering`s. + * + * **When to use** + * + * Use to combine multiple comparison results in priority order, such as + * checking secondary criteria only when earlier criteria compare as equal. + * + * **Details** + * + * If any of the `Ordering`s is non-zero, the result is the first non-zero `Ordering`. + * If all the `Ordering`s are zero, the result is zero. + * + * **Gotchas** + * + * `combineAll` stops consuming the iterable as soon as it finds a non-zero + * `Ordering`. + * + * @category ordering + * @since 4.0.0 + */ +export const Reducer: Reducer_.Reducer = Reducer_.make( + (self, that) => self !== 0 ? self : that, + 0, + (collection) => { + let ordering: Ordering = 0 + for (ordering of collection) { + if (ordering !== 0) { + return ordering + } + } + return ordering + } +) diff --git a/.repos/effect/packages/effect/src/PartitionedSemaphore.ts b/.repos/effect/packages/effect/src/PartitionedSemaphore.ts new file mode 100644 index 000000000..d700e46ee --- /dev/null +++ b/.repos/effect/packages/effect/src/PartitionedSemaphore.ts @@ -0,0 +1,558 @@ +/** + * The `PartitionedSemaphore` module provides a semaphore for limiting + * concurrency across a shared permit pool while keeping waiters grouped by + * partition key. A `PartitionedSemaphore` is useful when many independent + * groups of work compete for the same bounded resource and each group should + * make progress without one busy group monopolizing released permits. + * + * @since 4.0.0 + */ +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import * as MutableHashMap from "./MutableHashMap.ts" +import * as Option from "./Option.ts" + +/** + * Runtime type identifier used to mark values that implement + * `PartitionedSemaphore`. + * + * **Details** + * + * This marker is part of the runtime representation of partitioned semaphore + * values. + * + * @category type IDs + * @since 4.0.0 + */ +export const PartitionedTypeId: PartitionedTypeId = "~effect/PartitionedSemaphore" + +/** + * Literal type of the `PartitionedSemaphore` runtime type identifier. + * + * **When to use** + * + * Use to type fields that store the exact `PartitionedSemaphore` runtime marker. + * + * **Details** + * + * Use this type when declaring fields that must contain the exact + * `PartitionedTypeId` marker value. + * + * @category type IDs + * @since 4.0.0 + */ +export type PartitionedTypeId = "~effect/PartitionedSemaphore" + +/** + * A `PartitionedSemaphore` controls access to a shared permit pool while + * tracking waiters by partition key. + * + * **When to use** + * + * Use to coordinate shared permits across partition keys so waiting groups make + * progress without one group monopolizing the pool. + * + * **Details** + * + * Waiting permits are distributed across partitions in round-robin order. + * + * @category models + * @since 3.19.4 + */ +export interface PartitionedSemaphore { + readonly [PartitionedTypeId]: PartitionedTypeId + readonly capacity: number + readonly available: Effect.Effect + readonly take: (key: K, permits: number) => Effect.Effect + readonly release: (permits: number) => Effect.Effect + readonly withPermits: ( + key: K, + permits: number + ) => (effect: Effect.Effect) => Effect.Effect + readonly withPermit: (key: K) => (effect: Effect.Effect) => Effect.Effect + readonly withPermitsIfAvailable: ( + permits: number + ) => (effect: Effect.Effect) => Effect.Effect, E, R> +} + +/** + * Alias interface for a `PartitionedSemaphore` keyed by values of type `K`. + * + * **When to use** + * + * Use as an alternate exported name for a partitioned permit pool keyed by `K`. + * + * **Details** + * + * This interface does not add members beyond `PartitionedSemaphore`; it + * provides an alternate exported name for APIs that refer to a partitioned + * permit pool. + * + * @category models + * @since 4.0.0 + */ +export interface Partitioned extends PartitionedSemaphore {} + +/** + * Constructs a `PartitionedSemaphore` synchronously, outside of `Effect`. + * + * **When to use** + * + * Use when you need to construct a partitioned semaphore synchronously outside + * an `Effect` workflow. + * + * **Details** + * + * Negative permit counts are clamped to `0`. Non-finite permit counts create + * an unbounded semaphore whose acquire and release operations complete + * immediately. + * + * @see {@link make} for creating a partitioned semaphore inside `Effect` + * + * @category constructors + * @since 3.19.4 + */ +export const makeUnsafe = (options: { + readonly permits: number +}): PartitionedSemaphore => { + const maxPermits = Math.max(0, options.permits) + + if (!Number.isFinite(maxPermits)) { + return { + [PartitionedTypeId]: PartitionedTypeId, + capacity: maxPermits, + available: Effect.succeed(maxPermits), + take: () => Effect.void, + release: () => Effect.succeed(maxPermits), + withPermits: () => (effect) => effect, + withPermit: () => (effect) => effect, + withPermitsIfAvailable: () => (effect) => Effect.asSome(effect) + } + } + + let totalPermits = maxPermits + let waitingPermits = 0 + + type Waiter = { + permits: number + readonly resume: () => void + } + + const partitions = MutableHashMap.empty>() + let iterator = partitions[Symbol.iterator]() + + const releaseUnsafe = (permits: number): number => { + while (permits > 0) { + if (waitingPermits === 0) { + totalPermits = Math.min(maxPermits, totalPermits + permits) + return totalPermits + } + + let state = iterator.next() + if (state.done) { + iterator = partitions[Symbol.iterator]() + state = iterator.next() + if (state.done) { + return totalPermits + } + } + + const waiter = state.value[1].values().next().value + if (waiter === undefined) { + continue + } + + waiter.permits -= 1 + waitingPermits -= 1 + + if (waiter.permits === 0) { + waiter.resume() + } + + permits -= 1 + } + + return totalPermits + } + + const take = (key: K, permits: number): Effect.Effect => { + if (permits <= 0) { + return Effect.void + } + + return Effect.callback((resume) => { + if (maxPermits < permits) { + resume(Effect.never) + return + } + + if (totalPermits >= permits) { + totalPermits -= permits + resume(Effect.void) + return + } + + const needed = permits - totalPermits + if (totalPermits > 0) { + totalPermits = 0 + } + waitingPermits += needed + + const waiters = Option.getOrElse( + MutableHashMap.get(partitions, key), + () => { + const set = new Set() + MutableHashMap.set(partitions, key, set) + return set + } + ) + + const entry: Waiter = { + permits: needed, + resume: () => { + cleanup() + resume(Effect.void) + } + } + + const cleanup = () => { + waiters.delete(entry) + if (waiters.size === 0) { + MutableHashMap.remove(partitions, key) + } + } + + waiters.add(entry) + + return Effect.sync(() => { + cleanup() + waitingPermits -= entry.permits + releaseUnsafe(permits - entry.permits) + }) + }) + } + + const withPermits = + (key: K, permits: number) => (effect: Effect.Effect): Effect.Effect => { + if (permits <= 0) { + return effect + } + + const takePermits = take(key, permits) + return Effect.uninterruptibleMask((restore) => + Effect.flatMap( + restore(takePermits), + () => + Effect.ensuring( + restore(effect), + Effect.sync(() => { + releaseUnsafe(permits) + }) + ) + ) + ) + } + + const tryTake = (permits: number): boolean => { + if (permits <= 0) { + return true + } + + if (maxPermits < permits || totalPermits < permits) { + return false + } + + totalPermits -= permits + return true + } + + return { + [PartitionedTypeId]: PartitionedTypeId, + capacity: maxPermits, + available: Effect.sync(() => totalPermits), + take, + release: (permits) => Effect.sync(() => releaseUnsafe(permits)), + withPermits, + withPermit: (key) => withPermits(key, 1), + withPermitsIfAvailable: + (permits) => (effect: Effect.Effect): Effect.Effect, E, R> => { + if (permits <= 0) { + return Effect.asSome(effect) + } + + return Effect.suspend(() => { + if (!tryTake(permits)) { + return Effect.succeed(Option.none()) + } + + return Effect.ensuring( + Effect.asSome(effect), + Effect.sync(() => { + releaseUnsafe(permits) + }) + ) + }) + } + } +} + +/** + * Creates a `PartitionedSemaphore` inside an `Effect`. + * + * **When to use** + * + * Use when semaphore construction should stay inside an `Effect` workflow. + * + * **Details** + * + * The `permits` option sets the shared permit capacity. The resulting + * semaphore tracks waiters by partition key and distributes released permits + * across waiting partitions in round-robin order. + * + * **Gotchas** + * + * Negative permit counts are clamped to `0`. Non-finite permit counts create + * an unbounded semaphore. + * + * @see {@link makeUnsafe} for synchronous construction + * + * @category constructors + * @since 3.19.4 + */ +export const make = (options: { + readonly permits: number +}): Effect.Effect> => Effect.sync(() => makeUnsafe(options)) + +/** + * Gets the current number of available permits. + * + * **When to use** + * + * Use to inspect a snapshot of how many permits are currently free. + * + * **Details** + * + * Running the returned effect reads the semaphore's current availability. + * Taking permits decreases availability, and releasing permits can increase it + * up to the semaphore capacity. + * + * **Gotchas** + * + * Reading availability does not reserve permits. + * + * @see {@link capacity} for the fixed total permit capacity + * @see {@link release} for returning permits to the shared pool + * @see {@link withPermitsIfAvailable} for running only when permits are immediately available + * + * @category combinators + * @since 4.0.0 + */ +export const available = (self: PartitionedSemaphore): Effect.Effect => self.available + +/** + * Gets the total capacity. + * + * **When to use** + * + * Use to inspect the fixed number of permits configured for the semaphore. + * + * **Details** + * + * Capacity is stored when the semaphore is created and does not change as + * permits are acquired or released. + * + * @see {@link available} for the current number of free permits + * + * @category getters + * @since 4.0.0 + */ +export const capacity = (self: PartitionedSemaphore): number => self.capacity + +/** + * Returns an effect that acquires the requested number of permits for the + * given partition key. + * + * **When to use** + * + * Use when you need manual permit acquisition for a partition and want to + * control acquisition and release as separate effects. + * + * **Details** + * + * If enough permits are available, the effect completes immediately. Otherwise + * it waits until released permits are assigned to this partition. + * + * **Gotchas** + * + * Requests for more permits than the semaphore capacity never complete. + * Requests for zero or a negative number of permits complete without acquiring + * anything. + * + * @see {@link release} for manually returning permits to the shared pool + * @see {@link withPermits} for automatic acquire and release around an effect + * @see {@link withPermit} for acquiring exactly one permit around an effect + * + * @category combinators + * @since 4.0.0 + */ +export const take: { + (key: K, permits: number): (self: PartitionedSemaphore) => Effect.Effect + (self: PartitionedSemaphore, key: K, permits: number): Effect.Effect +} = dual(3, (self: PartitionedSemaphore, key: K, permits: number): Effect.Effect => self.take(key, permits)) + +/** + * Returns an effect that releases permits back to the shared pool and returns + * the current available permit count. + * + * **When to use** + * + * Use when you need to return permits acquired with `take` in a lower-level + * partitioned permit protocol with explicit release control. + * + * **Details** + * + * Released permits are first assigned to waiting partitions in round-robin + * order. Only permits not needed by waiters increase the available count, + * which is capped at the semaphore capacity. + * + * @see {@link take} for manual acquisition + * @see {@link withPermits} for automatic acquire and release around an effect + * @see {@link available} for reading the permit count without releasing + * + * @category combinators + * @since 4.0.0 + */ +export const release: { + (permits: number): (self: PartitionedSemaphore) => Effect.Effect + (self: PartitionedSemaphore, permits: number): Effect.Effect +} = dual(2, (self: PartitionedSemaphore, permits: number): Effect.Effect => self.release(permits)) + +/** + * Runs an effect after acquiring permits for a partition, then releases those + * permits when the effect exits. + * + * **When to use** + * + * Use to guard weighted partitioned work with automatic permit acquisition and + * release around an effect. + * + * **Details** + * + * Permit acquisition may wait according to `take` semantics. Once acquired, + * the permits are released even if the wrapped effect fails or is interrupted. + * + * **Gotchas** + * + * Requests for more permits than the semaphore capacity never complete. + * Requests for zero or a negative number of permits run the effect without + * acquiring anything. + * + * @see {@link withPermit} for the single-permit variant + * @see {@link withPermitsIfAvailable} for running only when permits are + * immediately available + * @see {@link take} for manual acquisition + * @see {@link release} for manual release + * + * @category combinators + * @since 4.0.0 + */ +export const withPermits: { + ( + self: PartitionedSemaphore, + key: K, + permits: number + ): (effect: Effect.Effect) => Effect.Effect + ( + self: PartitionedSemaphore, + key: K, + permits: number, + effect: Effect.Effect + ): Effect.Effect +} = ((...args: Array) => { + if (args.length === 3) { + const [self, key, permits] = args + return (effect: Effect.Effect) => self.withPermits(key, permits)(effect) + } + const [self, key, permits, effect] = args + return self.withPermits(key, permits)(effect) +}) as any + +/** + * Runs an effect after acquiring one permit for a partition, then releases the + * permit when the effect exits. + * + * **When to use** + * + * Use to guard partitioned work with exactly one permit and automatic release + * when the effect exits. + * + * **Details** + * + * This is the single-permit variant of `withPermits`. The permit is released + * even if the wrapped effect fails or is interrupted. + * + * @see {@link withPermits} for acquiring a weighted number of permits + * @see {@link withPermitsIfAvailable} for running only when permits are + * immediately available + * @see {@link take} for manual acquisition + * @see {@link release} for manual release + * + * @category combinators + * @since 4.0.0 + */ +export const withPermit: { + (self: PartitionedSemaphore, key: K): (effect: Effect.Effect) => Effect.Effect + ( + self: PartitionedSemaphore, + key: K, + effect: Effect.Effect + ): Effect.Effect +} = ((...args: Array) => { + if (args.length === 2) { + const [self, key] = args + return (effect: Effect.Effect) => self.withPermit(key)(effect) + } + const [self, key, effect] = args + return self.withPermit(key)(effect) +}) as any + +/** + * Runs an effect only when the requested permits can be acquired immediately, + * returning the result in `Some`. + * + * **When to use** + * + * Use when guarded work should run only if the shared permit pool can provide + * the requested permits immediately. + * + * **Details** + * + * If the permits are not available, the effect is not run and the result is + * `None`. When permits are acquired, they are released after the wrapped + * effect completes, fails, or is interrupted. Requests for zero or a negative + * number of permits run the effect and return `Some`. + * + * @see {@link withPermits} for the keyed variant that waits until permits are + * available for a partition + * + * @category combinators + * @since 4.0.0 + */ +export const withPermitsIfAvailable: { + ( + self: PartitionedSemaphore, + permits: number + ): (effect: Effect.Effect) => Effect.Effect, E, R> + ( + self: PartitionedSemaphore, + permits: number, + effect: Effect.Effect + ): Effect.Effect, E, R> +} = ((...args: Array) => { + if (args.length === 2) { + const [self, permits] = args + return (effect: Effect.Effect) => self.withPermitsIfAvailable(permits)(effect) + } + const [self, permits, effect] = args + return self.withPermitsIfAvailable(permits)(effect) +}) as any diff --git a/.repos/effect/packages/effect/src/Path.ts b/.repos/effect/packages/effect/src/Path.ts new file mode 100644 index 000000000..13195b6c2 --- /dev/null +++ b/.repos/effect/packages/effect/src/Path.ts @@ -0,0 +1,867 @@ +/** + * Provides path operations through the Effect environment. + * + * The `Path` service works with file system paths without tying code to one + * concrete platform module. It exposes common operations such as joining, + * normalizing, parsing, formatting, resolving, and converting paths to or from + * file URLs. This module includes the service interface, parsed path type, + * service tag, runtime marker, and built-in POSIX path layer. + * + * @since 4.0.0 + */ +import * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import { identity } from "./Function.ts" +import * as Layer from "./Layer.ts" +import { BadArgument } from "./PlatformError.ts" + +/** + * Runtime type identifier used to mark implementations of the `Path` service. + * + * **Details** + * + * The marker is the exact string stored on `Path` service implementations. + * Most code should depend on the `Path` service instead of inspecting this + * value directly. + * + * @see {@link layer} for the built-in POSIX `Path` service layer + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId = "~effect/platform/Path" + +/** + * Defines the service interface for platform-specific path manipulation. + * + * **When to use** + * + * Use to depend on path operations through the Effect environment instead of a + * concrete host path module. + * + * **Details** + * + * The service exposes operations for joining, normalizing, parsing, + * formatting, and converting file system paths. URL conversion methods return + * `Effect`s because invalid file URLs or paths can fail with `BadArgument`. + * + * **Example** (Using path operations) + * + * ```ts import.meta.vitest + * import { Effect, Path } from "effect" + * + * const program = Effect.gen(function*() { + * const path = yield* Path.Path + * + * 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 services + * @since 4.0.0 + */ +export interface Path { + readonly [TypeId]: typeof TypeId + readonly sep: string + readonly basename: (path: string, suffix?: string) => string + readonly dirname: (path: string) => string + readonly extname: (path: string) => string + readonly format: (pathObject: Partial) => string + readonly fromFileUrl: (url: URL) => Effect.Effect + readonly isAbsolute: (path: string) => boolean + readonly join: (...paths: ReadonlyArray) => string + readonly normalize: (path: string) => string + readonly parse: (path: string) => Path.Parsed + readonly relative: (from: string, to: string) => string + readonly resolve: (...pathSegments: ReadonlyArray) => string + readonly toFileUrl: (path: string) => Effect.Effect + readonly toNamespacedPath: (path: string) => string +} + +/** + * Namespace containing types associated with the `Path` service. + * + * **When to use** + * + * Use to reference types associated with path parsing and formatting. + * + * **Example** (Working with parsed paths) + * + * ```ts import.meta.vitest + * import { Effect, Path } from "effect" + * + * // Access types and utilities in the Path namespace + * const program = Effect.gen(function*() { + * const path = yield* Path.Path + * + * // Parse a path and get a Path.Parsed object + * const parsed = path.parse("/home/user/file.txt") + * + * // The parsed object conforms to the Path.Parsed interface + * const exampleParsed = { + * root: "/", + * dir: "/home/user", + * base: "file.txt", + * ext: ".txt", + * name: "file" + * } + * + * return [parsed.base, exampleParsed.base] + * }) + * + * Effect.runSync(Effect.provide(program, Path.layer)) // => ["file.txt", "file.txt"] + * ``` + * + * @since 4.0.0 + */ +export declare namespace Path { + /** + * Structured representation of a parsed file system path. + * + * **When to use** + * + * Use to model the object form produced by `Path.parse` and consumed by + * `Path.format`. + * + * **Details** + * + * The fields correspond to the path root, directory, base filename, + * extension, and filename without extension, matching the shape consumed by + * `Path.format`. + * + * **Example** (Parsing and formatting paths) + * + * ```ts import.meta.vitest + * import { Effect, Path } from "effect" + * + * const program = Effect.gen(function*() { + * const path = yield* Path.Path + * + * // Parse a path into its components + * const parsed = path.parse("/home/user/documents/file.txt") + * // Format a path from its components + * const formatted = path.format({ + * dir: "/home/user", + * name: "newfile", + * ext: ".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 + * @since 4.0.0 + */ + export interface Parsed { + readonly root: string + readonly dir: string + readonly base: string + readonly ext: string + readonly name: string + } +} + +/** + * Service tag for accessing the current `Path` implementation. + * + * **When to use** + * + * Use when you need path operations supplied by an effect's environment. + * + * **Example** (Providing a custom Path service) + * + * ```ts import.meta.vitest + * import { Effect, Layer, Path } from "effect" + * + * // Create a custom path implementation + * const customPath: Path.Path = { + * [Path.TypeId]: Path.TypeId, + * sep: "/", + * basename: (path: string, suffix?: string) => { + * const base = path.split("/").pop() || "" + * return suffix && base.endsWith(suffix) + * ? base.slice(0, -suffix.length) + * : base + * }, + * dirname: (path: string) => path.split("/").slice(0, -1).join("/") || "/", + * extname: (path: string) => { + * const match = path.match(/\.[^.]*$/) + * return match ? match[0] : "" + * }, + * format: (pathObject) => { + * const dir = pathObject.dir || "" + * const name = pathObject.name || "" + * const ext = pathObject.ext || "" + * return dir ? `${dir}/${name}${ext}` : `${name}${ext}` + * }, + * fromFileUrl: (url: URL) => Effect.succeed(url.pathname), + * isAbsolute: (path: string) => path.startsWith("/"), + * join: (...paths: ReadonlyArray) => paths.join("/"), + * normalize: (path: string) => path.replace(/\/+/g, "/"), + * parse: (path: string) => ({ + * root: path.startsWith("/") ? "/" : "", + * dir: path.split("/").slice(0, -1).join("/") || "/", + * base: path.split("/").pop() || "", + * ext: path.match(/\.[^.]*$/)?.[0] || "", + * name: path.split("/").pop()?.replace(/\.[^.]*$/, "") || "" + * }), + * relative: (from: string, to: string) => to.replace(from, ""), + * resolve: (...pathSegments: ReadonlyArray) => pathSegments.join("/"), + * toFileUrl: (path: string) => Effect.succeed(new URL(`file://${path}`)), + * toNamespacedPath: (path: string) => path + * } + * + * // Provide the path service + * const customPathLayer = Layer.succeed(Path.Path)(customPath) + * + * const program = Effect.gen(function*() { + * const path = yield* Path.Path + * return path.join("home", "user", "file.txt") + * }) + * + * // Run with custom path implementation + * Effect.runSync(Effect.provide(program, customPathLayer)) // => "home/user/file.txt" + * ``` + * + * @category services + * @since 4.0.0 + */ +export const Path: Context.Service = Context.Service("effect/Path") + +/** + * The following functions are adapted from the Node.js source code: + * https://github.com/nodejs/node/blob/main/lib/internal/url.js + * + * The following license applies to these functions: + * - MIT + */ + +// Resolves . and .. elements in a path with directory names +function normalizeStringPosix(path: string, allowAboveRoot: boolean) { + let res = "" + let lastSegmentLength = 0 + let lastSlash = -1 + let dots = 0 + let code + for (let i = 0; i <= path.length; ++i) { + if (i < path.length) { + code = path.charCodeAt(i) + } else if (code === 47 /*/*/) { + break + } else { + code = 47 /*/*/ + } + if (code === 47 /*/*/) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (lastSlash !== i - 1 && dots === 2) { + if ( + res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 /*.*/ || + res.charCodeAt(res.length - 2) !== 46 /*.*/ + ) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf("/") + if (lastSlashIndex !== res.length - 1) { + if (lastSlashIndex === -1) { + res = "" + lastSegmentLength = 0 + } else { + res = res.slice(0, lastSlashIndex) + lastSegmentLength = res.length - 1 - res.lastIndexOf("/") + } + lastSlash = i + dots = 0 + continue + } + } else if (res.length === 2 || res.length === 1) { + res = "" + lastSegmentLength = 0 + lastSlash = i + dots = 0 + continue + } + } + if (allowAboveRoot) { + if (res.length > 0) { + res += "/.." + } else { + res = ".." + } + lastSegmentLength = 2 + } + } else { + if (res.length > 0) { + res += "/" + path.slice(lastSlash + 1, i) + } else { + res = path.slice(lastSlash + 1, i) + } + lastSegmentLength = i - lastSlash - 1 + } + lastSlash = i + dots = 0 + } else if (code === 46 /*.*/ && dots !== -1) { + ++dots + } else { + dots = -1 + } + } + return res +} + +function _format(sep: string, pathObject: Partial) { + const dir = pathObject.dir || pathObject.root + const base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "") + if (!dir) { + return base + } + if (dir === pathObject.root) { + return dir + base + } + return dir + sep + base +} + +function fromFileUrl(url: URL): Effect.Effect { + if (url.protocol !== "file:") { + return Effect.fail( + new BadArgument({ + module: "Path", + method: "fromFileUrl", + description: "URL must be of scheme file" + }) + ) + } else if (url.hostname !== "") { + return Effect.fail( + new BadArgument({ + module: "Path", + method: "fromFileUrl", + description: "Invalid file URL host" + }) + ) + } + const pathname = url.pathname + for (let n = 0; n < pathname.length; n++) { + if (pathname[n] === "%") { + const third = pathname.codePointAt(n + 2)! | 0x20 + if (pathname[n + 1] === "2" && third === 102) { + return Effect.fail( + new BadArgument({ + module: "Path", + method: "fromFileUrl", + description: "must not include encoded / characters" + }) + ) + } + } + } + return Effect.succeed(decodeURIComponent(pathname)) +} + +const resolve: Path["resolve"] = function resolve() { + let resolvedPath = "" + let resolvedAbsolute = false + let cwd: string | undefined = undefined + + for (let i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + let path: string + if (i >= 0) { + path = arguments[i] + } else { + const process = (globalThis as any).process + if ( + cwd === undefined && "process" in globalThis && + typeof process === "object" && + process !== null && + typeof process.cwd === "function" + ) { + cwd = process.cwd() + } + path = cwd! + } + + // Skip empty entries + if (path.length === 0) { + continue + } + + resolvedPath = path + "/" + resolvedPath + resolvedAbsolute = path.charCodeAt(0) === 47 /*/*/ + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute) + + if (resolvedAbsolute) { + if (resolvedPath.length > 0) { + return "/" + resolvedPath + } else { + return "/" + } + } else if (resolvedPath.length > 0) { + return resolvedPath + } else { + return "." + } +} + +const CHAR_FORWARD_SLASH = 47 + +function toFileUrl(filepath: string) { + const outURL = new URL("file://") + let resolved = resolve(filepath) + // path.resolve strips trailing slashes so we must add them back + const filePathLast = filepath.charCodeAt(filepath.length - 1) + if ( + (filePathLast === CHAR_FORWARD_SLASH) && + resolved[resolved.length - 1] !== "/" + ) { + resolved += "/" + } + outURL.pathname = encodePathChars(resolved) + return Effect.succeed(outURL) +} + +const percentRegExp = /%/g +const backslashRegExp = /\\/g +const newlineRegExp = /\n/g +const carriageReturnRegExp = /\r/g +const tabRegExp = /\t/g + +function encodePathChars(filepath: string) { + if (filepath.includes("%")) { + filepath = filepath.replace(percentRegExp, "%25") + } + if (filepath.includes("\\")) { + filepath = filepath.replace(backslashRegExp, "%5C") + } + if (filepath.includes("\n")) { + filepath = filepath.replace(newlineRegExp, "%0A") + } + if (filepath.includes("\r")) { + filepath = filepath.replace(carriageReturnRegExp, "%0D") + } + if (filepath.includes("\t")) { + filepath = filepath.replace(tabRegExp, "%09") + } + return filepath +} + +const posixImpl = Path.of({ + [TypeId]: TypeId, + resolve, + normalize(path) { + if (path.length === 0) return "." + + const isAbsolute = path.charCodeAt(0) === 47 /*/*/ + const trailingSeparator = path.charCodeAt(path.length - 1) === 47 /*/*/ + + // Normalize the path + path = normalizeStringPosix(path, !isAbsolute) + + if (path.length === 0 && !isAbsolute) path = "." + if (path.length > 0 && trailingSeparator) path += "/" + + if (isAbsolute) return "/" + path + return path + }, + + isAbsolute(path) { + return path.length > 0 && path.charCodeAt(0) === 47 /*/*/ + }, + + join() { + if (arguments.length === 0) { + return "." + } + let joined + for (let i = 0; i < arguments.length; ++i) { + const arg = arguments[i] + if (arg.length > 0) { + if (joined === undefined) { + joined = arg + } else { + joined += "/" + arg + } + } + } + if (joined === undefined) { + return "." + } + return posixImpl.normalize(joined) + }, + + relative(from, to) { + if (from === to) return "" + + from = posixImpl.resolve(from) + to = posixImpl.resolve(to) + + if (from === to) return "" + + // Trim any leading backslashes + let fromStart = 1 + for (; fromStart < from.length; ++fromStart) { + if (from.charCodeAt(fromStart) !== 47 /*/*/) { + break + } + } + const fromEnd = from.length + const fromLen = fromEnd - fromStart + + // Trim any leading backslashes + let toStart = 1 + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== 47 /*/*/) { + break + } + } + const toEnd = to.length + const toLen = toEnd - toStart + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen + let lastCommonSep = -1 + let i = 0 + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === 47 /*/*/) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1) + } else if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i) + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === 47 /*/*/) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo'; to='/' + lastCommonSep = 0 + } + } + break + } + const fromCode = from.charCodeAt(fromStart + i) + const toCode = to.charCodeAt(toStart + i) + if (fromCode !== toCode) { + break + } else if (fromCode === 47 /*/*/) { + lastCommonSep = i + } + } + + let out = "" + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === 47 /*/*/) { + if (out.length === 0) { + out += ".." + } else { + out += "/.." + } + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return out + to.slice(toStart + lastCommonSep) + } else { + toStart += lastCommonSep + if (to.charCodeAt(toStart) === 47 /*/*/) { + ++toStart + } + return to.slice(toStart) + } + }, + + dirname(path) { + if (path.length === 0) return "." + let code = path.charCodeAt(0) + const hasRoot = code === 47 /*/*/ + let end = -1 + let matchedSlash = true + for (let i = path.length - 1; i >= 1; --i) { + code = path.charCodeAt(i) + if (code === 47 /*/*/) { + if (!matchedSlash) { + end = i + break + } + } else { + // We saw the first non-path separator + matchedSlash = false + } + } + + if (end === -1) return hasRoot ? "/" : "." + if (hasRoot && end === 1) return "//" + return path.slice(0, end) + }, + + basename(path, ext) { + let start = 0 + let end = -1 + let matchedSlash = true + let i + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) return "" + let extIdx = ext.length - 1 + let firstNonSlashEnd = -1 + for (i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i) + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1 + break + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false + firstNonSlashEnd = i + 1 + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1 + end = firstNonSlashEnd + } + } + } + } + + if (start === end) end = firstNonSlashEnd + else if (end === -1) end = path.length + return path.slice(start, end) + } else { + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1 + break + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false + end = i + 1 + } + } + + if (end === -1) return "" + return path.slice(start, end) + } + }, + + extname(path) { + let startDot = -1 + let startPart = 0 + let end = -1 + let matchedSlash = true + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0 + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i) + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1 + break + } + continue + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false + end = i + 1 + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i + } else if (preDotState !== 1) { + preDotState = 1 + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1 + } + } + + if ( + startDot === -1 || end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1 + ) { + return "" + } + return path.slice(startDot, end) + }, + + format: function format(pathObject) { + if (pathObject === null || typeof pathObject !== "object") { + throw new TypeError("The \"pathObject\" argument must be of type Object. Received type " + typeof pathObject) + } + return _format("/", pathObject) + }, + + parse(path) { + const ret = { root: "", dir: "", base: "", ext: "", name: "" } + if (path.length === 0) return ret + let code = path.charCodeAt(0) + const isAbsolute = code === 47 /*/*/ + let start + if (isAbsolute) { + ret.root = "/" + start = 1 + } else { + start = 0 + } + let startDot = -1 + let startPart = 0 + let end = -1 + let matchedSlash = true + let i = path.length - 1 + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0 + + // Get non-dir info + for (; i >= start; --i) { + code = path.charCodeAt(i) + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1 + break + } + continue + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false + end = i + 1 + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i + else if (preDotState !== 1) preDotState = 1 + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1 + } + } + + if ( + startDot === -1 || end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1 + ) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(1, end) + else ret.base = ret.name = path.slice(startPart, end) + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot) + ret.base = path.slice(1, end) + } else { + ret.name = path.slice(startPart, startDot) + ret.base = path.slice(startPart, end) + } + ret.ext = path.slice(startDot, end) + } + + if (startPart > 0) ret.dir = path.slice(0, startPart - 1) + else if (isAbsolute) ret.dir = "/" + + return ret + }, + + sep: "/", + fromFileUrl, + toFileUrl, + toNamespacedPath: identity +}) + +/** + * Layer that provides the built-in POSIX `Path` implementation. + * + * **When to use** + * + * Use when you need an effect that requires the `Path` service to run with the + * built-in POSIX path implementation. + * + * **Details** + * + * The layer provides a static service whose separator is `/` and whose + * operations use POSIX path semantics. + * + * @see {@link Path} for accessing the `Path` service from an effect + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.succeed(Path)(posixImpl) diff --git a/.repos/effect/packages/effect/src/Pipeable.ts b/.repos/effect/packages/effect/src/Pipeable.ts new file mode 100644 index 000000000..6f2697e74 --- /dev/null +++ b/.repos/effect/packages/effect/src/Pipeable.ts @@ -0,0 +1,675 @@ +/** + * The `Pipeable` module defines the shared interface and implementation helpers + * for values that support Effect-style method chaining with `.pipe(...)`. + * + * A `Pipeable` value can pass itself through a sequence of unary functions from + * left to right, so code can be written as `value.pipe(f, g, h)` instead of + * deeply nesting calls. This is the method form used by many Effect data types + * to compose transformations, validations, and effectful operations while + * keeping the original value as the starting point of the pipeline. + * + * @since 2.0.0 + */ + +/** + * Interface for values that support method-style `pipe` composition. + * + * **When to use** + * + * Use to type values that expose an Effect-style `.pipe(...)` method. + * + * **Details** + * + * Calling `value.pipe(f, g, h)` passes the value through each function from + * left to right, returning the final result. Many Effect data types implement + * this so operations can be chained without nesting function calls. + * + * **Example** (Chaining operations with pipe) + * + * ```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.runSync(program) // => 4 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Pipeable { + pipe(this: A): A + pipe(this: A, ab: (_: A) => B): B + pipe(this: A, ab: (_: A) => B, bc: (_: B) => C): C + pipe(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D + pipe( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E + ): E + pipe( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F + ): F + pipe( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G + ): G + pipe( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H + ): H + pipe( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I + ): I + pipe( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J + ): J + pipe( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K + ): K + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L + ): L + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M + ): M + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N + ): N + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O + ): O + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O, + op: (_: O) => P + ): P + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O, + op: (_: O) => P, + pq: (_: P) => Q + ): Q + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O, + op: (_: O) => P, + pq: (_: P) => Q, + qr: (_: Q) => R + ): R + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never, + S = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O, + op: (_: O) => P, + pq: (_: P) => Q, + qr: (_: Q) => R, + rs: (_: R) => S + ): S + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never, + S = never, + T = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O, + op: (_: O) => P, + pq: (_: P) => Q, + qr: (_: Q) => R, + rs: (_: R) => S, + st: (_: S) => T + ): T + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never, + S = never, + T = never, + U = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O, + op: (_: O) => P, + pq: (_: P) => Q, + qr: (_: Q) => R, + rs: (_: R) => S, + st: (_: S) => T, + tu: (_: T) => U + ): U + pipe< + A, + B = never, + C = never, + D = never, + E = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never, + M = never, + N = never, + O = never, + P = never, + Q = never, + R = never, + S = never, + T = never, + U = never + >( + this: A, + ab: (_: A) => B, + bc: (_: B) => C, + cd: (_: C) => D, + de: (_: D) => E, + ef: (_: E) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => L, + lm: (_: L) => M, + mn: (_: M) => N, + no: (_: N) => O, + op: (_: O) => P, + pq: (_: P) => Q, + qr: (_: Q) => R, + rs: (_: R) => S, + st: (_: S) => T, + tu: (_: T) => U + ): U +} + +/** + * Applies a `pipe` method's variadic arguments to an initial value from left + * to right. + * + * **When to use** + * + * Use to implement a custom `.pipe(...)` method from JavaScript's `arguments` + * object. + * + * **Details** + * + * This helper is intended for implementing `Pipeable.pipe` methods that + * receive JavaScript's `arguments` object. With no functions it returns the + * original value; otherwise it feeds each result into the next function. + * + * **Example** (Implementing a pipe method) + * + * ```ts import.meta.vitest + * import { Pipeable } from "effect" + * + * class NumberBox { + * constructor(readonly value: number) {} + * + * pipe(..._fns: ReadonlyArray<(value: number) => number>): number { + * return Pipeable.pipeArguments(this.value, arguments) as number + * } + * } + * + * const result = new NumberBox(5).pipe( + * (n) => n + 2, + * (n) => n * 3 + * ) + * result // => 21 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const pipeArguments = (self: A, args: IArguments): unknown => { + switch (args.length) { + case 0: + return self + case 1: + return args[0](self) + case 2: + return args[1](args[0](self)) + case 3: + return args[2](args[1](args[0](self))) + case 4: + return args[3](args[2](args[1](args[0](self)))) + case 5: + return args[4](args[3](args[2](args[1](args[0](self))))) + case 6: + return args[5](args[4](args[3](args[2](args[1](args[0](self)))))) + case 7: + return args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))) + case 8: + return args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))))) + case 9: + return args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))))) + default: { + let ret = self + for (let i = 0, len = args.length; i < len; i++) { + ret = args[i](ret) + } + return ret + } + } +} + +/** + * Reusable prototype that implements `Pipeable.pipe`. + * + * **When to use** + * + * Use when classes or object prototypes can reuse this value when they need the + * standard pipe implementation backed by `pipeArguments`. + * + * @category prototypes + * @since 3.15.0 + */ +export const Prototype: Pipeable = { + pipe() { + return pipeArguments(this, arguments) + } +} + +/** + * Provides a base constructor whose instances implement the standard `Pipeable.pipe` + * method. + * + * **When to use** + * + * Use when you need to define a class that supports Effect-style method + * chaining through `.pipe(...)`. + * + * @category constructors + * @since 3.15.0 + */ +export const Class: new() => Pipeable = (function() { + function PipeableBase() {} + PipeableBase.prototype = Prototype + return PipeableBase as any +})() + +/** + * Constructor type for classes whose instances implement `Pipeable`. + * + * **When to use** + * + * Use as the constructor-side type when a class value should be known to create + * instances that support Effect-style method chaining with `.pipe(...)`. + * + * @see {@link Pipeable} for the instance-side contract + * @see {@link Class} for the base constructor + * @see {@link Mixin} for wrapping an existing class constructor + * + * @category models + * @since 3.15.0 + */ +export interface PipeableConstructor { + new(...args: ReadonlyArray): Pipeable +} + +/** + * Returns a subclass of the provided class that adds the standard `pipe` + * method. + * + * **When to use** + * + * Use to add pipe support to an existing class without extending a base class + * or modifying its prototype. + * + * **Details** + * + * The original constructor and instance members are preserved, and the added + * method delegates to `pipeArguments`. + * + * @see {@link Prototype} for a reusable prototype object + * @see {@link Class} for a base constructor to extend + * @category constructors + * @since 4.0.0 + */ +export const Mixin = ) => any>( + klass: TBase +): TBase & PipeableConstructor => (class extends klass { + pipe() { + return pipeArguments(this, arguments) + } +}) diff --git a/.repos/effect/packages/effect/src/PlatformError.ts b/.repos/effect/packages/effect/src/PlatformError.ts new file mode 100644 index 000000000..25ae0dc9e --- /dev/null +++ b/.repos/effect/packages/effect/src/PlatformError.ts @@ -0,0 +1,221 @@ +/** + * Normalized errors for platform APIs. + * + * Platform services such as file systems, terminals, and sockets use + * `PlatformError` to report host-level failures in a consistent shape. The + * wrapper records whether the problem came from an invalid argument or from the + * operating system, while preserving useful details such as the module, method, + * path, descriptor, description, and original cause when available. + * + * @since 4.0.0 + */ +import * as Data from "./Data.ts" + +const TypeId = "~effect/platform/PlatformError" + +/** + * Error data for an invalid argument passed to a platform API. + * + * **When to use** + * + * Use when you need to model caller input rejected before a platform operation + * runs, including invalid-argument reason data. + * + * **Details** + * + * The error records the module and method that rejected the argument, with an + * optional description and cause. It is usually wrapped in `PlatformError`. + * + * @see {@link badArgument} for creating a wrapped `PlatformError` whose reason is `BadArgument` + * @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 errors + * @since 4.0.0 + */ +export class BadArgument extends Data.TaggedError("BadArgument")<{ + module: string + method: string + description?: string | undefined + cause?: unknown +}> { + /** + * Formats the module, method, and optional description that rejected the argument. + * + * **When to use** + * + * Use to read the formatted error message for a rejected platform argument. + * + * @since 4.0.0 + */ + override get message(): string { + return `${this.module}.${this.method}${this.description ? `: ${this.description}` : ""}` + } +} + +/** + * Normalized category for failures reported by platform or system operations. + * + * **When to use** + * + * Use to type or match the normalized `_tag` on `SystemError` values reported + * by platform operations. + * + * **Details** + * + * The tags group lower-level platform errors into a stable set such as + * `NotFound`, `PermissionDenied`, `TimedOut`, and `Unknown`. + * + * @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 errors + * @since 4.0.0 + */ +export type SystemErrorTag = + | "AlreadyExists" + | "BadResource" + | "Busy" + | "InvalidData" + | "NotFound" + | "PermissionDenied" + | "TimedOut" + | "UnexpectedEof" + | "Unknown" + | "WouldBlock" + | "WriteZero" + +/** + * Error data for a platform or system operation failure. + * + * **When to use** + * + * Use when you need normalized reason data for a platform or system operation + * failure, including the operation details. + * + * **Details** + * + * The error records a normalized `_tag`, the module and method that failed, + * and optional details such as the syscall, path or descriptor, description, + * and original cause. It is usually wrapped in `PlatformError`. + * + * @see {@link systemError} for creating the usual `PlatformError` wrapper from this reason data + * @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 errors + * @since 4.0.0 + */ +export class SystemError extends Data.Error<{ + _tag: SystemErrorTag + module: string + method: string + description?: string | undefined + syscall?: string | undefined + pathOrDescriptor?: string | number | undefined + cause?: unknown +}> { + /** + * Formats the normalized system error tag with operation and path details. + * + * **When to use** + * + * Use to read the formatted error message for a normalized system failure. + * + * @since 4.0.0 + */ + override get message(): string { + return `${this._tag}: ${this.module}.${this.method}${ + this.pathOrDescriptor !== undefined ? ` (${this.pathOrDescriptor})` : "" + }${this.description ? `: ${this.description}` : ""}` + } +} + +/** + * Tagged error used by platform APIs to report either invalid arguments or + * system-level failures. + * + * **When to use** + * + * Use as the shared error type for platform APIs that expose invalid arguments + * and host or operating-system failures through a single `Effect` error + * channel. + * + * **Details** + * + * The `reason` field contains the underlying `BadArgument` or `SystemError`. + * When that reason has a cause, the cause is preserved on the wrapper. + * + * @see {@link BadArgument} for invalid inputs rejected before an operation runs + * @see {@link SystemError} for failures reported by the host platform or operating system + * @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 errors + * @since 4.0.0 + */ +export class PlatformError extends Data.TaggedError("PlatformError")<{ + reason: BadArgument | SystemError +}> { + constructor(reason: BadArgument | SystemError) { + if ("cause" in reason) { + super({ reason, cause: reason.cause } as any) + } else { + super({ reason }) + } + } + + /** + * Marks this value as a platform error wrapper for runtime guards. + * + * **When to use** + * + * Use to identify `PlatformError` values through their runtime type marker. + * + * @since 4.0.0 + */ + readonly [TypeId]: typeof TypeId = TypeId + + override get message(): string { + return this.reason.message + } +} + +/** + * Creates a `PlatformError` whose reason is a `SystemError`. + * + * **When to use** + * + * Use to adapt an operating-system or platform failure into the normalized + * platform error model. + * + * @category constructors + * @since 4.0.0 + */ +export const systemError = (options: { + readonly _tag: SystemErrorTag + readonly module: string + readonly method: string + readonly description?: string | undefined + readonly syscall?: string | undefined + readonly pathOrDescriptor?: string | number | undefined + readonly cause?: unknown +}): PlatformError => new PlatformError(new SystemError(options)) + +/** + * Creates a `PlatformError` whose reason is a `BadArgument`. + * + * **When to use** + * + * Use to report a platform API rejecting caller input before performing the + * underlying operation. + * + * @category constructors + * @since 4.0.0 + */ +export const badArgument = (options: { + readonly module: string + readonly method: string + readonly description?: string | undefined + readonly cause?: unknown +}): PlatformError => new PlatformError(new BadArgument(options)) diff --git a/.repos/effect/packages/effect/src/Pool.ts b/.repos/effect/packages/effect/src/Pool.ts new file mode 100644 index 000000000..49294db98 --- /dev/null +++ b/.repos/effect/packages/effect/src/Pool.ts @@ -0,0 +1,934 @@ +/** + * Shares scoped resources across fibers. + * + * A `Pool` acquires resource-backed values with a scoped effect, lets + * fibers borrow them with `get`, can invalidate broken values, and releases all + * acquired values when the pool scope closes. This module includes fixed-size + * pools, pools that resize with a time-to-live policy, custom strategy pools, + * per-item concurrency limits, and runtime state types used by pool strategies. + * + * @since 2.0.0 + */ +import type * as Cause from "./Cause.ts" +import { Clock } from "./Clock.ts" +import * as Context from "./Context.ts" +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 { 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 { type Pipeable, pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import * as Queue from "./Queue.ts" +import { UnhandledLogLevel } from "./References.ts" +import * as Scope from "./Scope.ts" +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 + * an item `A` from a pool may fail with an error of type `E`. + * + * **When to use** + * + * Use when you need to share a bounded set of scoped resources across fibers + * while the pool manages acquisition, reuse, and release. + * + * @see {@link make} for creating a pool with size bounds + * @see {@link makeWithTTL} for creating a pool with idle item expiration + * @see {@link makeWithStrategy} for creating a pool with a custom strategy + * @see {@link get} for acquiring an item from a pool + * @see {@link invalidate} for removing a broken item from the pool + * + * @category models + * @since 2.0.0 + */ +export interface Pool extends Pipeable { + readonly [TypeId]: typeof TypeId + readonly config: Config + readonly state: State +} + +/** + * Normalized configuration used by a `Pool`. + * + * **When to use** + * + * Use as the normalized, read-only description of how a pool acquires, sizes, + * shares, and resizes its items after construction. + * + * **Details** + * + * The config stores the acquire effect, size bounds, per-item concurrency, + * target utilization, and resizing strategy used by the pool implementation. + * + * @see {@link Pool} for the value exposing this configuration + * @see {@link State} for mutable runtime state instead of static configuration + * @see {@link Strategy} for the resizing and reclamation contract stored on the config + * + * @category models + * @since 4.0.0 + */ +export interface Config { + readonly acquire: Effect.Effect + readonly concurrency: number + readonly isFixed: boolean + readonly minSize: number + readonly maxSize: number + readonly strategy: Strategy + readonly targetUtilization: number +} + +/** + * Mutable runtime state maintained by a `Pool`. + * + * **When to use** + * + * Use when you need to inspect or support the runtime state backing a `Pool`, + * including its scope, item sets, semaphores, waiters, invalidation tracking, + * and shutdown flag. + * + * **Details** + * + * 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 + * @see {@link get} for acquiring items through the high-level API + * @see {@link invalidate} for invalidating items through the high-level API + * + * @category models + * @since 4.0.0 + */ +export interface State { + readonly scope: Scope.Scope + isShuttingDown: boolean + usage: number + readonly resizeSemaphore: Semaphore.Semaphore + readonly items: Set> + availableHead: PoolItem | undefined + availableTail: PoolItem | undefined + readonly invalidated: Set> + readonly waiters: Set<() => void> +} + +/** + * Internal record for a value managed by a `Pool`. + * + * **When to use** + * + * Use when implementing a custom pool `Strategy` that needs to inspect + * acquired items, track reference counts, or return reclaimable items to the + * pool. + * + * **Details** + * + * Each item stores the acquisition `Exit`, its finalizer, the current + * reference count, and whether automatic reclaiming has been disabled because + * the item was invalidated. + * + * @see {@link Strategy} for the custom strategy callbacks that receive and return pool items + * @see {@link State} for the runtime sets that store active, available, and invalidated pool items + * + * @category models + * @since 4.0.0 + */ +export interface PoolItem { + readonly exit: Exit.Exit + finalizer: Effect.Effect + refCount: number + disableReclaim: boolean + isAvailable: boolean + availablePrevious: PoolItem | undefined + availableNext: PoolItem | undefined + release: (exit: Exit.Exit) => Effect.Effect +} + +/** + * Strategy used by a `Pool` to manage background resizing and item + * reclamation. + * + * **When to use** + * + * Use when defining a custom pool lifecycle policy that needs to run background + * work, observe acquired items, or choose items for reclamation. + * + * **Details** + * + * `run` starts any strategy-specific background work, `onAcquire` is invoked + * when an item is acquired, and `reclaim` selects an item that can be removed + * or replaced. + * + * @see {@link makeWithStrategy} for constructing a pool from a custom `Strategy` + * + * @category models + * @since 4.0.0 + */ +export interface Strategy { + readonly run: (pool: Pool) => Effect.Effect + readonly onAcquire: (item: PoolItem) => Effect.Effect + readonly reclaim: (pool: Pool) => Effect.Effect | undefined> +} + +/** + * Returns `true` if the specified value is a `Pool`, `false` otherwise. + * + * **When to use** + * + * Use to validate unknown values at runtime boundaries before treating them as + * `Pool` values. + * + * **Details** + * + * This predicate narrows the input to `Pool`. + * + * @category guards + * @since 2.0.0 + */ +export const isPool = (u: unknown): u is Pool => hasProperty(u, TypeId) + +/** + * Makes a new pool of the specified fixed size. + * + * **When to use** + * + * Use when you need a fixed-size pool with no growth or shrinkage. + * + * **Details** + * + * The pool is returned in a `Scope`, which governs the lifetime of the pool. + * When the pool is shutdown because the `Scope` is closed, the individual + * items allocated by the pool will be released in some unspecified order. + * + * By setting the `concurrency` parameter, you can control the level of concurrent + * access per pool item. By default, the number of permits is set to `1`. + * + * `targetUtilization` determines when to create new pool items. It is a value + * between 0 and 1, where 1 means only create new pool items when all the existing + * items are fully utilized. + * + * A `targetUtilization` of 0.5 will create new pool items when the existing items are + * 50% utilized. + * + * @see {@link makeWithTTL} for pools with min/max sizes and a TTL-based shrinking policy + * @see {@link makeWithStrategy} for pools with a custom resizing and reclamation strategy + * @category constructors + * @since 2.0.0 + */ +export const make = (options: { + readonly acquire: Effect.Effect + readonly size: number + readonly concurrency?: number | undefined + readonly targetUtilization?: number | undefined +}): Effect.Effect, never, R | Scope.Scope> => + makeWithStrategy({ ...options, min: options.size, max: options.size, strategy: strategyNoop }) + +/** + * Creates a scoped pool with minimum and maximum sizes and a time-to-live + * policy for shrinking unused excess items. + * + * **When to use** + * + * Use to create an elastic scoped pool that can grow up to a maximum size and + * later reclaim unused excess items. + * + * **Details** + * + * The returned pool requires `Scope`; when that scope is closed, allocated + * items are released in an unspecified order. `concurrency` controls how many + * fibers may use each pool item at once and defaults to `1`. + * + * `targetUtilization` controls when new items are created and is clamped by the + * pool implementation. A value of `1` waits until existing items are fully + * utilized before creating more items. + * + * `timeToLiveStrategy` controls when excess items expire: `"creation"` measures + * from item creation, while `"usage"` measures from pool usage. The default is + * `"usage"`. + * + * **Example** (Creating a connection pool) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Pool } from "effect" + * + * interface Connection { + * readonly execute: (sql: string) => Effect.Effect> + * readonly close: Effect.Effect + * } + * + * const acquireDBConnection = Effect.acquireRelease( + * Effect.succeed({ + * execute: (sql) => Effect.succeed([`executed: ${sql}`]), + * close: Effect.void + * } satisfies Connection), + * (connection) => connection.close + * ) + * + * const program = Effect.scoped( + * Effect.flatMap( + * Pool.makeWithTTL({ + * acquire: acquireDBConnection, + * min: 10, + * max: 20, + * timeToLive: Duration.seconds(60) + * }), + * (pool) => Effect.flatMap(Pool.get(pool), (connection) => connection.execute("select 1")) + * ) + * ) + * + * await Effect.runPromise(program) // => ["executed: select 1"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const makeWithTTL = (options: { + readonly acquire: Effect.Effect + readonly min: number + readonly max: number + readonly concurrency?: number | undefined + readonly targetUtilization?: number | undefined + readonly timeToLive: Duration.Input + readonly timeToLiveStrategy?: "creation" | "usage" | undefined +}): Effect.Effect, never, R | Scope.Scope> => + Effect.flatMap( + options.timeToLiveStrategy === "creation" ? + strategyCreationTTL(options.timeToLive) : + strategyUsageTTL(options.timeToLive), + (strategy) => makeWithStrategy({ ...options, strategy }) + ) + +/** + * Creates a scoped pool using a custom resizing and reclamation strategy. + * + * **When to use** + * + * Use to build a pool whose item lifecycle is controlled by an explicit + * `Strategy`, such as custom background resizing, replacement, or reclamation. + * + * **Details** + * + * The returned pool requires `Scope`; closing the scope shuts down the pool and + * releases allocated items. + * + * @see {@link make} for fixed-size pools without custom resizing or reclamation + * @see {@link makeWithTTL} for min/max pools that shrink excess items with a TTL policy + * @see {@link Strategy} for the custom strategy contract consumed by this constructor + * + * @category constructors + * @since 4.0.0 + */ +export const makeWithStrategy = (options: { + readonly acquire: Effect.Effect + readonly min: number + readonly max: number + readonly concurrency?: number | undefined + readonly targetUtilization?: number | undefined + readonly strategy: Strategy +}): Effect.Effect, never, Scope.Scope | R> => + Effect.uninterruptibleMask(Effect.fnUntraced(function*(restore) { + const services = yield* Effect.context() + const scope = Context.get(services, Scope.Scope) + const acquire = Effect.updateContext( + options.acquire, + (input) => Context.merge(services, input) + ) as Effect.Effect + const concurrency = options.concurrency ?? 1 + + const config: Config = { + acquire, + concurrency, + isFixed: options.min === options.max, + minSize: options.min, + maxSize: options.max, + strategy: options.strategy, + targetUtilization: Math.min(Math.max(options.targetUtilization ?? 1, 0.1), 1) + } + const state: State = { + scope, + isShuttingDown: false, + usage: 0, + resizeSemaphore: Semaphore.makeUnsafe(1), + items: new Set(), + availableHead: undefined, + availableTail: undefined, + invalidated: new Set(), + waiters: new Set() + } + const self: PoolImpl = { + [TypeId]: TypeId, + [Acquire]: options.acquire as Effect.Effect, + [AcquireContext]: services as Context.Context, + config, + state, + pipe() { + return pipeArguments(this, arguments) + } + } + yield* Scope.addFinalizer(scope, shutdown(self)) + 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 + })) + +const shutdown = Effect.fnUntraced(function*(self: Pool) { + if (self.state.isShuttingDown) return + self.state.isShuttingDown = true + const size = self.state.items.size + const semaphore = Semaphore.makeUnsafe(size) + for (const item of self.state.items) { + if (item.refCount > 0) { + item.finalizer = Effect.tap(item.finalizer, semaphore.release(1)) + self.state.invalidated.add(item) + yield* semaphore.take(1) + } else { + self.state.items.delete(item) + removeAvailable(self, item) + self.state.invalidated.delete(item) + yield* item.finalizer + } + } + yield* semaphore.releaseAll + 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) +}) + +/** + * Retrieves an item from the pool in a scoped effect. + * + * **When to use** + * + * Use to borrow a pooled resource for the lifetime of the current scope so it + * is automatically returned when that scope closes. + * + * **Details** + * + * The returned effect waits for an available item when the pool is at capacity. + * If acquiring a new item fails, the effect fails with the acquisition error. + * + * **Gotchas** + * + * Retrying a failed `get` can repeat the acquisition attempt. + * + * @see {@link invalidate} for removing an unhealthy item from future reuse + * + * @category getters + * @since 2.0.0 + */ +export const get = (self: Pool): Effect.Effect => + 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 getSlowWith(self, leaseItemWith) + }) + +/** + * 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 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 + } + 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) + } + if (item.refCount === self.config.concurrency - 1) { + addAvailable(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 + }) + +const addAvailable = (self: Pool, item: PoolItem): void => { + if (item.isAvailable) 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 + } + self.state.availableTail = 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 + * item, lazily if needed. + * + * **When to use** + * + * Use to prevent a pooled item from being reused after it becomes unsuitable, + * such as a stale connection or a resource that failed a health check. + * + * **Gotchas** + * + * The item is matched with strict equality. Passing an equivalent but different + * object instance does nothing. + * + * @see {@link get} for retrieving scoped items from the pool + * + * @category combinators + * @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 => + Effect.suspend(() => { + if (self.state.isShuttingDown) return Effect.void + for (const poolItem of self.state.items) { + if (poolItem.exit._tag === "Success" && poolItem.exit.value === item) { + poolItem.disableReclaim = true + return Effect.uninterruptible(invalidatePoolItem(self, poolItem)) + } + } + return Effect.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) + removeAvailable(self, poolItem) + self.state.invalidated.delete(poolItem) + return Effect.asVoid(Effect.flatMap( + poolItem.finalizer, + () => Effect.forkIn(Effect.interruptible(resize(self)), self.state.scope, { startImmediately: true }) + )) + } + self.state.invalidated.add(poolItem) + removeAvailable(self, poolItem) + return Effect.void + }) + +const resize = (self: Pool): Effect.Effect => + self.state.resizeSemaphore.withPermits(1)(resizeLoop(self)) + +const resizeLoop = (self: Pool): Effect.Effect => + Effect.suspend(() => { + const active = activeSize(self) + const target = targetSize(self) + if (active >= target) { + return Effect.void + } + const toAcquire = target - active + 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(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> => + 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 targetSize = (self: Pool) => { + if (self.state.isShuttingDown) return 0 + 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) +} + +const activeSize = (self: Pool) => { + return self.state.items.size - self.state.invalidated.size +} + +// ----------------------------------------------------------------------------- +// 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 + const queue = yield* Queue.unbounded>() + const ttlMillis = Duration.toMillis(Duration.fromInputUnsafe(ttl)) + const creationTimes = new WeakMap, number>() + return identity>({ + run: (pool) => { + const process = (item: PoolItem): Effect.Effect => + Effect.suspend(() => { + if (!pool.state.items.has(item) || pool.state.invalidated.has(item)) { + return Effect.void + } + const now = clock.currentTimeMillisUnsafe() + const created = creationTimes.get(item)! + const remaining = ttlMillis - (now - created) + return remaining > 0 + ? Effect.delay(process(item), remaining) + : invalidatePoolItem(pool, item) + }) + return Queue.take(queue).pipe( + Effect.tap(process), + Effect.forever({ disableYield: true }) + ) + }, + onAcquire: (item) => + Effect.suspend(() => { + creationTimes.set(item, clock.currentTimeMillisUnsafe()) + return Queue.offer(queue, item) + }), + reclaim: (_) => Effect.undefined + }) +}) + +const strategyUsageTTL = Effect.fnUntraced(function*(ttl: Duration.Input) { + const queue = yield* Queue.unbounded>() + return identity>({ + run: (pool) => { + const process: Effect.Effect = Effect.suspend(() => { + const excess = activeSize(pool) - targetSize(pool) + if (excess <= 0) return Effect.void + return Queue.take(queue).pipe( + Effect.tap((item) => invalidatePoolItem(pool, item)), + Effect.flatMap(() => process) + ) + }) + return process.pipe( + Effect.delay(ttl), + Effect.forever({ disableYield: true }) + ) + }, + onAcquire: (item) => Queue.offer(queue, item), + reclaim(pool) { + return Effect.suspend((): Effect.Effect | undefined> => { + if (pool.state.invalidated.size === 0) { + return Effect.undefined + } + const item = Iterable.head( + Iterable.filter(pool.state.invalidated, (item) => !item.disableReclaim) + ) + if (item._tag === "None") { + return Effect.undefined + } + pool.state.invalidated.delete(item.value) + if (item.value.refCount < pool.config.concurrency) { + addAvailable(pool, item.value) + } + return Effect.as(Queue.offer(queue, item.value), item.value) + }) + } + }) +}) + +const reportUnhandledError = (cause: Cause.Cause) => + Effect.withFiber((fiber) => { + const unhandledLogLevel = fiber.getRef(UnhandledLogLevel) + if (unhandledLogLevel) { + return Effect.logWithLevel(unhandledLogLevel)( + "Unhandled error in pool finalizer", + cause + ) + } + return Effect.void + }) diff --git a/.repos/effect/packages/effect/src/Predicate.ts b/.repos/effect/packages/effect/src/Predicate.ts new file mode 100644 index 000000000..f66458aaf --- /dev/null +++ b/.repos/effect/packages/effect/src/Predicate.ts @@ -0,0 +1,1883 @@ +/** + * Defines runtime checks for values. + * + * A `Predicate` returns `true` or `false` for an `A`. A + * `Refinement` is a predicate that also narrows the TypeScript type when + * it succeeds. This module includes guards for common JavaScript values, + * property and tag checks, tuple and struct checks, boolean combinators, and + * helpers for composing predicates and refinements. + * + * @since 2.0.0 + */ +import { dual } from "./Function.ts" +import type { TypeLambda } from "./HKT.ts" +import type { TupleOf, TupleOfAtLeast } from "./Types.ts" + +/** + * A function that decides whether a value of type `A` satisfies a condition. + * + * **When to use** + * + * Use when you want a reusable boolean check for `A`, especially when you plan + * to combine checks with {@link and}/{@link or} or pass a predicate to arrays + * and iterables. + * + * **Details** + * + * A predicate returns `true` or `false` and never throws by itself. It does not + * narrow types unless you use `Refinement`. + * + * **Example** (Defining a predicate) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isPositive: Predicate.Predicate = (n) => n > 0 + * + * isPositive(1) // => true + * ``` + * + * @see {@link Refinement} + * @see {@link mapInput} + * @see {@link and} + * @category models + * @since 2.0.0 + */ +export interface Predicate { + (a: A): boolean +} + +/** + * Type-level lambda for higher-kinded usage of {@link Predicate}. + * + * **When to use** + * + * Use when you are defining APIs that abstract over predicates with HKTs and + * need a `TypeLambda` instance for predicate-based type classes. + * + * **Details** + * + * This is type-only, creates no runtime value, and does not affect emitted + * JavaScript. + * + * **Example** (Type-level usage) + * + * ```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 utility types + * @since 2.0.0 + */ +export interface PredicateTypeLambda extends TypeLambda { + readonly type: Predicate +} + +/** + * A predicate that also narrows the input type when it returns `true`. + * + * **When to use** + * + * Use when you want a runtime check that refines `A` to `B` for TypeScript, + * especially when composing type guards with {@link compose} or safely + * checking `unknown` values. + * + * **Details** + * + * A refinement returns a type predicate (`a is B`). Use it with `if` or + * `filter` to narrow types. + * + * **Example** (Narrowing unknown values) + * + * ```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)) { + * data.toUpperCase() // => "HELLO" + * } + * ``` + * + * @see {@link Predicate} + * @see {@link compose} + * @see {@link isString} + * @category models + * @since 2.0.0 + */ +export interface Refinement { + (a: A): a is B +} + +/** + * Type-level utilities for working with {@link Predicate} types. + * + * **When to use** + * + * Use when you need to extract input types from predicate signatures while + * writing generic helpers over predicate types. + * + * **Details** + * + * These utilities are type-only, create no runtime values, and the namespace is + * erased at runtime. + * + * **Example** (Extracting predicate input) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * type IsString = Predicate.Predicate + * type Input = Predicate.Predicate.In + * + * const input: Input = "value" + * ``` + * + * @see {@link Predicate} + * @see {@link Refinement} + * @since 3.6.0 + */ +export declare namespace Predicate { + /** + * Extracts the input type `A` from a `Predicate`. + * + * **When to use** + * + * Use when you want to infer the input type from a predicate type while + * defining generic utilities over predicates. + * + * **Details** + * + * This is type-only and creates no runtime value. It resolves to `never` if + * the type does not match `Predicate`. + * + * **Example** (Inferring the input type) + * + * ```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} + * @see {@link Refinement.In} + * @category utility types + * @since 3.6.0 + */ + export type In = [T] extends [Predicate] ? _A : never + + /** + * A utility type representing any predicate type. + * + * **When to use** + * + * Use when you need a constraint for "any predicate" in generic code. + * + * **Details** + * + * This is type-only and creates no runtime value. + * + * **Example** (Using generic constraints) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * type AnyPredicate = Predicate.Predicate.Any + * + * const witness: AnyPredicate = () => true + * witness("value") // => true + * ``` + * + * @see {@link Predicate.In} + * @category utility types + * @since 3.6.0 + */ + export type Any = Predicate +} + +/** + * Type-level utilities for working with {@link Refinement} types. + * + * **When to use** + * + * Use when you need to extract input and output types from refinement + * signatures while writing generic helpers over refinements. + * + * **Details** + * + * These utilities are type-only, create no runtime values, and the namespace is + * erased at runtime. + * + * **Example** (Extracting refinement types) + * + * ```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} + * @see {@link Predicate} + * @since 3.6.0 + */ +export declare namespace Refinement { + /** + * Extracts the input type `A` from a `Refinement`. + * + * **When to use** + * + * Use when you want to infer the input type from a refinement type. + * + * **Details** + * + * This is type-only and creates no runtime value. It resolves to `never` if + * the type does not match `Refinement`. + * + * **Example** (Inferring the input type) + * + * ```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} + * @see {@link Predicate.In} + * @category utility types + * @since 3.6.0 + */ + + export type In = [T] extends [Refinement] ? _A : never + + /** + * Extracts the output type `B` from a `Refinement`. + * + * **When to use** + * + * Use when you want to infer the narrowed type from a refinement type. + * + * **Details** + * + * This is type-only and creates no runtime value. It resolves to `never` if + * the type does not match `Refinement`. + * + * **Example** (Inferring the output type) + * + * ```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} + * @category utility types + * @since 3.6.0 + */ + export type Out = [T] extends [Refinement] ? _B : never + + /** + * A utility type representing any refinement type. + * + * **When to use** + * + * Use when you need a constraint for "any refinement" in generic code. + * + * **Details** + * + * This is type-only and creates no runtime value. + * + * **Example** (Using generic constraints) + * + * ```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} + * @see {@link Refinement.Out} + * @category utility types + * @since 3.6.0 + */ + export type Any = Refinement +} + +/** + * Transforms the input of a predicate using a mapping function. + * + * **When to use** + * + * Use when you have a predicate on `A` and want to check `B` values by mapping + * each `B` to an `A`, such as checking lengths or projections. + * + * **Details** + * + * Returns a new predicate that applies `f` before `self`. There is no + * additional short-circuiting beyond what `self` does. + * + * **Example** (Checking string length) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isLongerThan2 = Predicate.mapInput((s: string) => s.length)( + * (n: number) => n > 2 + * ) + * + * isLongerThan2("hello") // => true + * ``` + * + * @see {@link Predicate} + * @see {@link and} + * @see {@link not} + * @category combinators + * @since 2.0.0 + */ +export const mapInput: { + (f: (b: B) => A): (self: Predicate) => Predicate + (self: Predicate, f: (b: B) => A): Predicate +} = dual(2, (self: Predicate, f: (b: B) => A): Predicate => (b) => self(f(b))) + +/** + * Checks whether a readonly array has exactly `n` elements. + * + * **When to use** + * + * Use when you need a `Predicate` guard for exact tuple length that narrows + * `ReadonlyArray` to `TupleOf`. + * + * **Details** + * + * This only checks length, not element types, and returns a refinement on the + * array type. + * + * **Example** (Checking exact length) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isPair = Predicate.isTupleOf(2) + * + * isPair([1, 2]) // => true + * ``` + * + * @see {@link isTupleOfAtLeast} + * @see {@link Tuple} + * @category guards + * @since 3.3.0 + */ +export const isTupleOf: { + (n: N): (self: ReadonlyArray) => self is TupleOf + (self: ReadonlyArray, n: N): self is TupleOf +} = dual(2, (self: ReadonlyArray, n: N): self is TupleOf => self.length === n) + +/** + * Checks whether a readonly array has at least `n` elements. + * + * **When to use** + * + * Use when you need a `Predicate` guard for tuple-like minimum length that + * narrows `ReadonlyArray` to `TupleOfAtLeast`. + * + * **Details** + * + * This only checks length, not element types, and returns a refinement on the + * array type. + * + * **Example** (Checking minimum length) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const hasAtLeast2 = Predicate.isTupleOfAtLeast(2) + * + * hasAtLeast2([1, 2, 3]) // => true + * ``` + * + * @see {@link isTupleOf} + * @see {@link Tuple} + * @category guards + * @since 3.3.0 + */ +export const isTupleOfAtLeast: { + (n: N): (self: ReadonlyArray) => self is TupleOfAtLeast + (self: ReadonlyArray, n: N): self is TupleOfAtLeast +} = dual(2, (self: ReadonlyArray, n: N): self is TupleOfAtLeast => self.length >= n) + +/** + * Checks whether a value is truthy. + * + * **When to use** + * + * Use when you want a predicate that mirrors JavaScript truthiness and filters + * out falsy values like `0`, `""`, and `false`. + * + * **Details** + * + * This uses `!!input` and treats `0`, `""`, `false`, `null`, and `undefined` + * as false. + * + * **Example** (Filtering truthy values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const values = [0, 1, "", "ok", false] + * const truthy = values.filter(Predicate.isTruthy) // => [1, "ok"] + * ``` + * + * @see {@link isNullish} + * @see {@link isNotNullish} + * @category predicates + * @since 2.0.0 + */ +export function isTruthy(input: unknown): boolean { + return !!input +} + +/** + * Checks whether a value is a `Set`. + * + * **When to use** + * + * Use when you need a `Predicate` runtime guard for `Set` values. + * + * **Details** + * + * Uses `instanceof Set`. + * + * **Example** (Guarding a Set) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = new Set([1, 2]) + * + * if (Predicate.isSet(data)) { + * data.size // => 2 + * } + * ``` + * + * @see {@link isMap} + * @see {@link isIterable} + * @category guards + * @since 2.0.0 + */ +export function isSet(input: unknown): input is Set { + return input instanceof Set +} + +/** + * Checks whether a value is a `Map`. + * + * **When to use** + * + * Use when you need a `Predicate` runtime guard for `Map` values. + * + * **Details** + * + * Uses `instanceof Map`. + * + * **Example** (Guarding a Map) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = new Map([["a", 1]]) + * + * if (Predicate.isMap(data)) { + * data.size // => 1 + * } + * ``` + * + * @see {@link isSet} + * @see {@link isIterable} + * @category guards + * @since 2.0.0 + */ +export function isMap(input: unknown): input is Map { + return input instanceof Map +} + +/** + * Checks whether a value is a `string`. + * + * **When to use** + * + * Use when you need a `Predicate` guard to narrow an `unknown` value to a + * string. + * + * **Details** + * + * Uses `typeof input === "string"`. + * + * **Example** (Guarding strings) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = "hi" + * + * if (Predicate.isString(data)) { + * data.toUpperCase() // => "HI" + * } + * ``` + * + * @see {@link isNumber} + * @see {@link isBoolean} + * @see {@link Refinement} + * @category guards + * @since 2.0.0 + */ +export function isString(input: unknown): input is string { + return typeof input === "string" +} + +/** + * Checks whether a value is a `number`. + * + * **When to use** + * + * Use when you need a `Predicate` guard to narrow an `unknown` value to a + * number. + * + * **Details** + * + * Uses `typeof input === "number"` and does not exclude `NaN` or `Infinity`. + * + * **Example** (Guarding numbers) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = 42 + * + * if (Predicate.isNumber(data)) { + * data + 1 // => 43 + * } + * ``` + * + * @see {@link isBigInt} + * @see {@link isString} + * @category guards + * @since 2.0.0 + */ +export function isNumber(input: unknown): input is number { + return typeof input === "number" +} + +/** + * Checks whether a value is a `boolean`. + * + * **When to use** + * + * Use when you need a `Predicate` guard to narrow an `unknown` value to a + * boolean. + * + * **Details** + * + * Uses `typeof input === "boolean"`. + * + * **Example** (Guarding booleans) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = true + * + * if (Predicate.isBoolean(data)) { + * data ? "yes" : "no" // => "yes" + * } + * ``` + * + * @see {@link isString} + * @see {@link isNumber} + * @category guards + * @since 2.0.0 + */ +export function isBoolean(input: unknown): input is boolean { + return typeof input === "boolean" +} + +/** + * Checks whether a value is a `bigint`. + * + * **When to use** + * + * Use when you need a `Predicate` guard to narrow an `unknown` value to a + * bigint. + * + * **Details** + * + * Uses `typeof input === "bigint"`. + * + * **Example** (Guarding bigints) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = 1n + * + * if (Predicate.isBigInt(data)) { + * data + 2n // => 3n + * } + * ``` + * + * @see {@link isNumber} + * @category guards + * @since 2.0.0 + */ +export function isBigInt(input: unknown): input is bigint { + return typeof input === "bigint" +} + +/** + * Checks whether a value is a `symbol`. + * + * **When to use** + * + * Use when you need a `Predicate` guard to narrow an `unknown` value to a + * symbol. + * + * **Details** + * + * Uses `typeof input === "symbol"`. + * + * **Example** (Guarding symbols) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = Symbol.for("id") + * + * if (Predicate.isSymbol(data)) { + * data.description // => "id" + * } + * ``` + * + * @see {@link isPropertyKey} + * @category guards + * @since 2.0.0 + */ +export function isSymbol(input: unknown): input is symbol { + return typeof input === "symbol" +} + +/** + * Checks whether a value is a valid `PropertyKey` (string, number, or symbol). + * + * **When to use** + * + * Use when you need a `Predicate` guard for unknown property keys before + * indexing. + * + * **Details** + * + * Uses `isString`, `isNumber`, and `isSymbol`. + * + * **Example** (Guarding property keys) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const key: unknown = "name" + * const obj: Record = { name: "Ada" } + * + * if (Predicate.isPropertyKey(key) && key in obj) { + * obj[key] // => "Ada" + * } + * ``` + * + * @see {@link isString} + * @see {@link isNumber} + * @see {@link isSymbol} + * @category guards + * @since 4.0.0 + */ +export function isPropertyKey(u: unknown): u is PropertyKey { + return isString(u) || isNumber(u) || isSymbol(u) +} + +/** + * Checks whether a value is a `function`. + * + * **When to use** + * + * Use when you need a `Predicate` guard to narrow an `unknown` value to a + * callable function. + * + * **Details** + * + * Uses `typeof input === "function"`. + * + * **Example** (Guarding functions) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = () => 1 + * + * if (Predicate.isFunction(data)) { + * data() // => 1 + * } + * ``` + * + * @see {@link isObjectKeyword} + * @category guards + * @since 2.0.0 + */ +export function isFunction(input: unknown): input is Function { + return typeof input === "function" +} + +/** + * Checks whether a value is `undefined`. + * + * **When to use** + * + * Use when you need a `Predicate` guard for values that are exactly + * `undefined`. + * + * **Details** + * + * Uses `input === undefined`. + * + * **Example** (Guarding undefined values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = undefined + * + * Predicate.isUndefined(data) // => true + * ``` + * + * @see {@link isNotUndefined} + * @see {@link isNullish} + * @category guards + * @since 2.0.0 + */ +export function isUndefined(input: unknown): input is undefined { + return input === undefined +} + +/** + * Checks whether a value is not `undefined`. + * + * **When to use** + * + * Use when you need a `Predicate` refinement that filters out `undefined` + * while preserving other falsy values. + * + * **Details** + * + * Returns a refinement that excludes `undefined`. + * + * **Example** (Filtering undefined values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const values = [1, undefined, 2] + * const defined = values.filter(Predicate.isNotUndefined) // => [1, 2] + * ``` + * + * @see {@link isUndefined} + * @see {@link isNotNullish} + * @category guards + * @since 2.0.0 + */ +export function isNotUndefined(input: A): input is Exclude { + return input !== undefined +} + +/** + * Checks whether a value is `null`. + * + * **When to use** + * + * Use when you need a `Predicate` guard for nullable values. + * + * **Details** + * + * Uses `input === null`. + * + * **Example** (Guarding null values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = null + * + * Predicate.isNull(data) // => true + * ``` + * + * @see {@link isNotNull} + * @see {@link isNullish} + * @category guards + * @since 2.0.0 + */ +export function isNull(input: unknown): input is null { + return input === null +} + +/** + * Checks whether a value is not `null`. + * + * **When to use** + * + * Use when you need a `Predicate` refinement that filters out `null` while + * preserving other falsy values. + * + * **Details** + * + * Returns a refinement that excludes `null`. + * + * **Example** (Filtering null values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const values = [1, null, 2] + * const nonNull = values.filter(Predicate.isNotNull) // => [1, 2] + * ``` + * + * @see {@link isNull} + * @see {@link isNotNullish} + * @category guards + * @since 2.0.0 + */ +export function isNotNull(input: A): input is Exclude { + return input !== null +} + +/** + * Checks whether a value is `null` or `undefined`. + * + * **When to use** + * + * Use when you need a `Predicate` guard for nullish values. + * + * **Details** + * + * Uses `input === null || input === undefined`. + * + * **Example** (Guarding nullish values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const values = [0, null, "", undefined] + * const nullish = values.filter(Predicate.isNullish) // => [null, undefined] + * ``` + * + * @see {@link isNotNullish} + * @see {@link isUndefined} + * @see {@link isNull} + * @category guards + * @since 4.0.0 + */ +export function isNullish(input: A): input is A & (null | undefined) { + return input === null || input === undefined +} + +/** + * Checks whether a value is not `null` and not `undefined`. + * + * **When to use** + * + * Use when you need a `Predicate` refinement that filters out nullish values + * but keeps other falsy ones. + * + * **Details** + * + * Uses `input != null`. + * + * **Example** (Filtering non-nullish values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const values = [0, null, "", undefined] + * const present = values.filter(Predicate.isNotNullish) // => [0, ""] + * ``` + * + * @see {@link isNullish} + * @see {@link isNotNull} + * @see {@link isNotUndefined} + * @category guards + * @since 4.0.0 + */ +export function isNotNullish(input: A): input is NonNullable { + return input != null +} + +/** + * Type guard that always returns `false`. + * + * **When to use** + * + * Use when you need a `Predicate` that never accepts, e.g. in default branches. + * + * **Example** (Matching no values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * Predicate.isNever("anything") // => false + * ``` + * + * @see {@link isUnknown} + * @category guards + * @since 2.0.0 + */ +export function isNever(_: unknown): _ is never { + return false +} + +/** + * Type guard that always returns `true`. + * + * **When to use** + * + * Use when you need a `Predicate` that always accepts, e.g. as a placeholder. + * + * **Example** (Matching every value) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * Predicate.isUnknown(123) // => true + * ``` + * + * @see {@link isNever} + * @category guards + * @since 2.0.0 + */ +export function isUnknown(_: unknown): _ is unknown { + return true +} + +/** + * Checks whether a value is an object or an array (non-null object). + * + * **When to use** + * + * Use when you need a `Predicate` guard that accepts plain objects and arrays, + * but not `null`. + * + * **Details** + * + * Uses `typeof input === "object" && input !== null` and includes arrays. + * + * **Example** (Checking objects or arrays) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * Predicate.isObjectOrArray([]) // => true + * ``` + * + * @see {@link isObject} + * @see {@link isObjectKeyword} + * @category guards + * @since 4.0.0 + */ +export function isObjectOrArray(input: unknown): input is { [x: PropertyKey]: unknown } | Array { + return typeof input === "object" && input !== null +} + +/** + * Checks whether a value is a non-null object value that is not an array. + * + * **When to use** + * + * Use to narrow unknown input to a non-null, non-array object with a + * `Predicate` guard. + * + * **Details** + * + * This is a structural runtime check using `typeof input === "object"`, so it + * also accepts object instances such as `Date`, `Map`, class instances, and + * typed arrays. It excludes `null` and arrays. + * + * **Example** (Guarding objects) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * Predicate.isObject({ a: 1 }) // => true + * Predicate.isObject([1, 2]) // => false + * ``` + * + * @see {@link isObjectOrArray} + * @see {@link isReadonlyObject} + * @category guards + * @since 2.0.0 + */ +export function isObject(input: unknown): input is { [x: PropertyKey]: unknown } { + return typeof input === "object" && input !== null && !Array.isArray(input) +} + +/** + * Checks whether a value is a non-null, non-array object and narrows it to a + * readonly indexable object type. + * + * **When to use** + * + * Use to narrow unknown input to a readonly view of a non-null, non-array + * object with a `Predicate` guard. + * + * **Details** + * + * Readonly-ness is a TypeScript type-level view; it is not observable at + * runtime. This delegates to `isObject`, so class instances and built-in object + * instances are accepted. + * + * **Example** (Checking readonly objects) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = { a: 1 } + * + * Predicate.isReadonlyObject(data) // => true + * ``` + * + * @see {@link isObject} + * @category guards + * @since 4.0.0 + */ +export function isReadonlyObject(input: unknown): input is { readonly [x: PropertyKey]: unknown } { + return isObject(input) +} + +/** + * Checks whether a value is an `object` in the JavaScript sense (objects, arrays, functions). + * + * **When to use** + * + * Use when you need a `Predicate` guard that accepts arrays and functions as + * well as objects. + * + * **Details** + * + * Returns `true` for arrays and functions, and `false` for `null`. + * + * **Example** (Checking object keywords) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * Predicate.isObjectKeyword(() => 1) // => true + * Predicate.isObjectKeyword(null) // => false + * ``` + * + * @see {@link isObject} + * @see {@link isObjectOrArray} + * @category guards + * @since 4.0.0 + */ +export function isObjectKeyword(input: unknown): input is object { + return (typeof input === "object" && input !== null) || isFunction(input) +} + +/** + * Checks whether a value has a given property key. + * + * **When to use** + * + * Use when you need a `Predicate` guard for property access on `unknown` + * values with a simple structural object check. + * + * **Details** + * + * Uses the `in` operator and `isObjectKeyword`. This does not check property + * value types. + * + * **Example** (Guarding object properties) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const hasName = Predicate.hasProperty("name") + * const data: unknown = { name: "Ada" } + * + * if (hasName(data)) { + * data.name // => "Ada" + * } + * ``` + * + * @see {@link isTagged} + * @see {@link isObjectKeyword} + * @category guards + * @since 2.0.0 + */ +export const hasProperty: { +

(property: P): (self: unknown) => self is { [K in P]: unknown } +

(self: unknown, property: P): self is { [K in P]: unknown } +} = dual( + 2, +

(self: unknown, property: P): self is { [K in P]: unknown } => + isObjectKeyword(self) && (property in self) +) + +/** + * Checks whether a value has a `_tag` property equal to the given tag. + * + * **When to use** + * + * Use when you model tagged unions with a `_tag` field and want a quick + * `Predicate` guard for tagged values. + * + * **Details** + * + * Uses `hasProperty` and strict equality on `_tag`. + * + * **Example** (Guarding tagged values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isOk = Predicate.isTagged("Ok") + * + * isOk({ _tag: "Ok", value: 1 }) // => true + * ``` + * + * @see {@link hasProperty} + * @category guards + * @since 2.0.0 + */ +export const isTagged: { + (tag: K): (self: unknown) => self is { _tag: K } + (self: unknown, tag: K): self is { _tag: K } +} = dual( + 2, + (self: unknown, tag: K): self is { _tag: K } => hasProperty(self, "_tag") && self["_tag"] === tag +) + +/** + * Checks whether a value is an `Error`. + * + * **When to use** + * + * Use when you need a `Predicate` guard for errors caught from unknown sources. + * + * **Details** + * + * Uses `instanceof Error`. + * + * **Example** (Guarding errors) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = new Error("boom") + * + * Predicate.isError(data) // => true + * ``` + * + * @see {@link isUnknown} + * @category guards + * @since 2.0.0 + */ +export function isError(input: unknown): input is Error { + return input instanceof Error +} + +/** + * Checks whether a value is a `Uint8Array`. + * + * **When to use** + * + * Use when you need a `Predicate` runtime guard for binary data. + * + * **Details** + * + * Uses `instanceof Uint8Array`. + * + * **Example** (Guarding Uint8Array values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = new Uint8Array([1, 2]) + * + * Predicate.isUint8Array(data) // => true + * ``` + * + * @see {@link isIterable} + * @see {@link isSet} + * @category guards + * @since 2.0.0 + */ +export function isUint8Array(input: unknown): input is Uint8Array { + return input instanceof Uint8Array +} + +/** + * Checks whether a value is a `Date`. + * + * **When to use** + * + * Use when you need a `Predicate` runtime guard for dates. + * + * **Details** + * + * Uses `instanceof Date`. + * + * **Example** (Guarding Date values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = new Date() + * + * Predicate.isDate(data) // => true + * ``` + * + * @see {@link isRegExp} + * @category guards + * @since 2.0.0 + */ +export function isDate(input: unknown): input is Date { + return input instanceof Date +} + +/** + * Checks whether a value is iterable. + * + * **When to use** + * + * Use when you need a `Predicate` guard before iterating an unknown value. + * + * **Details** + * + * Accepts strings as iterable and uses `hasProperty` for `Symbol.iterator`. + * + * **Example** (Guarding iterables) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = [1, 2, 3] + * + * Predicate.isIterable(data) // => true + * ``` + * + * @see {@link isSet} + * @see {@link isMap} + * @category guards + * @since 2.0.0 + */ +export function isIterable(input: unknown): input is Iterable { + return hasProperty(input, Symbol.iterator) || isString(input) +} + +/** + * Checks whether a value is a `Promise`-like object with `then` and `catch`. + * + * **When to use** + * + * Use when you need a `Predicate` guard for promise instances across realms. + * + * **Details** + * + * Performs a structural check for `then` and `catch` functions. + * + * **Example** (Guarding promises) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = Promise.resolve(1) + * + * Predicate.isPromise(data) // => true + * ``` + * + * @see {@link isPromiseLike} + * @category guards + * @since 2.0.0 + */ +export function isPromise(input: unknown): input is Promise { + return hasProperty(input, "then") && "catch" in input && isFunction(input.then) && isFunction(input.catch) +} + +/** + * Checks whether a value is `PromiseLike` (has a `then` method). + * + * **When to use** + * + * Use when you need a `Predicate` guard for promise-like values with a + * callable `then` method. + * + * **Details** + * + * Performs a structural check for a callable `then`. + * + * **Example** (Guarding promise-like values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = { then: () => {} } + * + * Predicate.isPromiseLike(data) // => true + * ``` + * + * @see {@link isPromise} + * @category guards + * @since 2.0.0 + */ +export function isPromiseLike(input: unknown): input is PromiseLike { + return hasProperty(input, "then") && isFunction(input.then) +} + +/** + * Checks whether a value is a `RegExp`. + * + * **When to use** + * + * Use when you need a `Predicate` runtime guard for regular expressions. + * + * **Details** + * + * Uses `instanceof RegExp`. + * + * **Example** (Guarding RegExp values) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const data: unknown = /abc/ + * + * Predicate.isRegExp(data) // => true + * ``` + * + * @see {@link isDate} + * @category guards + * @since 3.9.0 + */ +export function isRegExp(input: unknown): input is RegExp { + return input instanceof RegExp +} + +/** + * Composes two predicates or refinements into one. + * + * **When to use** + * + * Use when you want to compose two `Predicate` checks in sequence, especially + * when chaining refinements for progressive narrowing. + * + * **Details** + * + * For refinements, the output type is narrowed by both checks. Evaluation + * short-circuits on the first `false`. + * + * **Example** (Composing refinements) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isNumber: Predicate.Refinement = (u): u is number => typeof u === "number" + * const isInteger: Predicate.Refinement = (n): n is number => Number.isInteger(n) + * + * const isIntegerNumber = Predicate.compose(isNumber, isInteger) + * + * isIntegerNumber(1) // => true + * ``` + * + * @see {@link and} + * @see {@link Refinement} + * @category combinators + * @since 2.0.0 + */ +export const compose: { + (bc: Refinement): (ab: Refinement) => Refinement + (bc: Predicate>): (ab: Refinement) => Refinement + (ab: Refinement, bc: Refinement): Refinement + (ab: Refinement, bc: Predicate>): Refinement +} = dual( + 2, + (ab: Refinement, bc: Refinement): Refinement => (a): a is C => + ab(a) && bc(a) +) + +/** + * Creates a predicate for tuples by applying predicates to each element. + * + * **When to use** + * + * Use when you want to validate tuple positions independently by lifting + * element predicates into a tuple predicate. + * + * **Details** + * + * Returns a refinement if any element predicate is a refinement. Evaluation + * stops at the first failing element. + * + * **Example** (Checking tuples) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const tupleCheck = Predicate.Tuple([(n: number) => n > 0, Predicate.isString]) + * + * tupleCheck([1, "ok"]) // => true + * ``` + * + * @see {@link Struct} + * @see {@link isTupleOf} + * @category combinators + * @since 4.0.0 + */ +export function Tuple>( + elements: T +): [Extract] extends [never] ? Predicate<{ readonly [I in keyof T]: Predicate.In }> + : Refinement< + { readonly [I in keyof T]: T[I] extends Refinement.Any ? Refinement.In : Predicate.In }, + { readonly [I in keyof T]: T[I] extends Refinement.Any ? Refinement.Out : Predicate.In } + > +{ + return ((as: Array) => { + for (let i = 0; i < elements.length; i++) { + if (elements[i](as[i]) === false) { + return false + } + } + return true + }) as any +} + +/** + * Creates a predicate for objects by applying predicates to named properties. + * + * **When to use** + * + * Use when you want to validate a record shape at runtime by lifting property + * predicates into an object predicate. + * + * **Details** + * + * Returns a refinement if any field predicate is a refinement. Only the + * specified keys are checked, and extra keys are ignored. + * + * **Example** (Checking structs) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const userCheck = Predicate.Struct({ + * id: Predicate.isNumber, + * name: Predicate.isString + * }) + * + * userCheck({ id: 1, name: "Ada" }) // => true + * ``` + * + * @see {@link Tuple} + * @see {@link hasProperty} + * @category combinators + * @since 4.0.0 + */ +export function Struct>( + fields: R +): [Extract] extends [never] ? Predicate<{ readonly [K in keyof R]: Predicate.In }> : + Refinement< + { readonly [K in keyof R]: R[K] extends Refinement.Any ? Refinement.In : Predicate.In }, + { readonly [K in keyof R]: R[K] extends Refinement.Any ? Refinement.Out : Predicate.In } + > +{ + const keys = Object.keys(fields) + return ((a: Record) => { + for (const key of keys) { + if (!fields[key](a[key] as never)) { + return false + } + } + return true + }) as any +} + +/** + * Negates a predicate. + * + * **When to use** + * + * Use when you want the inverse of an existing predicate. + * + * **Details** + * + * Returns a new predicate that flips the boolean result. + * + * **Example** (Negating a predicate) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isNotString = Predicate.not(Predicate.isString) + * + * isNotString(1) // => true + * ``` + * + * @see {@link and} + * @see {@link or} + * @see {@link xor} + * @category combinators + * @since 2.0.0 + */ +export function not(self: Predicate): Predicate { + return (a) => !self(a) +} + +/** + * Creates a predicate that returns `true` if either predicate is `true`. + * + * **When to use** + * + * Use when you want to combine `Predicate`s with OR, accepting values that + * satisfy at least one condition, including refinements that narrow to a union. + * + * **Details** + * + * Evaluation short-circuits on the first `true`. For refinements, the output + * type is a union. + * + * **Example** (Checking either condition) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isStringOrNumber = Predicate.or(Predicate.isString, Predicate.isNumber) + * + * isStringOrNumber("a") // => true + * ``` + * + * @see {@link and} + * @see {@link xor} + * @category combinators + * @since 2.0.0 + */ +export const or: { + (that: Refinement): (self: Refinement) => Refinement + (self: Refinement, that: Refinement): Refinement + (that: Predicate): (self: Predicate) => Predicate + (self: Predicate, that: Predicate): Predicate +} = dual(2, (self: Predicate, that: Predicate): Predicate => (a) => self(a) || that(a)) + +/** + * Creates a predicate that returns `true` only if both predicates are `true`. + * + * **When to use** + * + * Use when you want to combine `Predicate`s with AND, accepting values that + * satisfy multiple conditions, including refinements that narrow to an + * intersection. + * + * **Details** + * + * Evaluation short-circuits on the first `false`. For refinements, the output + * type is an intersection. + * + * **Example** (Checking both conditions) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const hasAAndB = Predicate.and( + * Predicate.hasProperty("a"), + * Predicate.hasProperty("b") + * ) + * + * const input: unknown = JSON.parse(`{"a":1,"b":"ok"}`) + * if (hasAAndB(input)) { + * // input has both properties at this point + * const a = input.a + * const b = input.b + * + * const values = [a, b] // => [1, "ok"] + * } + * ``` + * + * @see {@link or} + * @see {@link not} + * @category combinators + * @since 2.0.0 + */ +export const and: { + (that: Refinement): (self: Refinement) => Refinement + (self: Refinement, that: Refinement): Refinement + (that: Predicate): (self: Predicate) => Predicate + (self: Predicate, that: Predicate): Predicate +} = dual(2, (self: Predicate, that: Predicate): Predicate => (a) => self(a) && that(a)) + +/** + * Creates a predicate that returns `true` if exactly one predicate is `true`. + * + * **When to use** + * + * Use when you want to combine two `Predicate`s with exclusive-or semantics. + * + * **Details** + * + * Returns `true` when results differ. + * + * **Example** (Checking exclusive-or conditions) + * + * ```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) + * + * either(-2) // => true + * ``` + * + * @see {@link or} + * @see {@link and} + * @category combinators + * @since 2.0.0 + */ +export const xor: { + (that: Predicate): (self: Predicate) => Predicate + (self: Predicate, that: Predicate): Predicate +} = dual(2, (self: Predicate, that: Predicate): Predicate => (a) => self(a) !== that(a)) + +/** + * Creates a predicate that returns `true` when both predicates agree. + * + * **When to use** + * + * Use when you want to check equivalence of two `Predicate`s. + * + * **Details** + * + * Returns `true` when both results are equal. + * + * **Example** (Defining equivalence) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const isEven = (n: number) => n % 2 === 0 + * const same = Predicate.eqv(isEven, isEven) + * + * same(3) // => true + * ``` + * + * @see {@link xor} + * @category combinators + * @since 2.0.0 + */ +export const eqv: { + (that: Predicate): (self: Predicate) => Predicate + (self: Predicate, that: Predicate): Predicate +} = dual(2, (self: Predicate, that: Predicate): Predicate => (a) => self(a) === that(a)) + +/** + * Creates a predicate representing logical implication: if `antecedent`, then `consequent`. + * + * **When to use** + * + * Use when you need to encode logical implication between `Predicate` rules, + * where one rule only applies when a precondition holds. + * + * **Details** + * + * Models constraints like "if A then B" and returns `true` when the antecedent + * is `false`. + * + * **Example** (Checking implication) + * + * ```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) + * + * implies(16) // => true + * ``` + * + * @see {@link and} + * @see {@link or} + * @category combinators + * @since 2.0.0 + */ +export const implies: { + (consequent: Predicate): (antecedent: Predicate) => Predicate + (antecedent: Predicate, consequent: Predicate): Predicate +} = dual( + 2, + (antecedent: Predicate, consequent: Predicate): Predicate => (a) => antecedent(a) ? consequent(a) : true +) + +/** + * Creates a predicate that returns `true` when neither predicate is `true`. + * + * **When to use** + * + * Use when you want to combine two `Predicate`s with logical NOR semantics. + * + * **Details** + * + * Returns the negation of `or`. + * + * **Example** (Checking NOR conditions) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const neither = Predicate.nor(Predicate.isString, Predicate.isNumber) + * + * neither(true) // => true + * ``` + * + * @see {@link or} + * @see {@link not} + * @category combinators + * @since 2.0.0 + */ +export const nor: { + (that: Predicate): (self: Predicate) => Predicate + (self: Predicate, that: Predicate): Predicate +} = dual( + 2, + (self: Predicate, that: Predicate): Predicate => (a) => !(self(a) || that(a)) +) + +/** + * Creates a predicate that returns `true` unless both predicates are `true`. + * + * **When to use** + * + * Use when you want to combine two `Predicate`s with logical NAND semantics. + * + * **Details** + * + * Returns the negation of `and`. + * + * **Example** (Checking NAND conditions) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const notBoth = Predicate.nand(Predicate.isString, Predicate.isNumber) + * + * notBoth("a") // => true + * ``` + * + * @see {@link and} + * @see {@link not} + * @category combinators + * @since 2.0.0 + */ +export const nand: { + (that: Predicate): (self: Predicate) => Predicate + (self: Predicate, that: Predicate): Predicate +} = dual( + 2, + (self: Predicate, that: Predicate): Predicate => (a) => !(self(a) && that(a)) +) + +/** + * Creates a predicate that returns `true` if all predicates in the collection return `true`. + * + * **When to use** + * + * Use when you have a dynamic list of predicates to apply. + * + * **Details** + * + * Evaluation short-circuits on the first `false`. The collection is iterated + * each time the predicate is called. + * + * **Example** (Checking all predicates) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const allChecks = Predicate.every([Predicate.isNumber, (n: number) => n > 0]) + * + * allChecks(2) // => true + * ``` + * + * @see {@link some} + * @see {@link and} + * @category combining + * @since 2.0.0 + */ +export function every(collection: Iterable>): Predicate { + return (a) => { + for (const p of collection) { + if (!p(a)) { + return false + } + } + return true + } +} + +/** + * Creates a predicate that returns `true` if any predicate in the collection returns `true`. + * + * **When to use** + * + * Use when you have a dynamic list of predicates and only need one to pass. + * + * **Details** + * + * Evaluation short-circuits on the first `true`. The collection is iterated + * each time the predicate is called. + * + * **Example** (Checking any predicate) + * + * ```ts import.meta.vitest + * import { Predicate } from "effect" + * + * const anyCheck = Predicate.some([Predicate.isString, Predicate.isNumber]) + * + * anyCheck("ok") // => true + * ``` + * + * @see {@link every} + * @see {@link or} + * @category combining + * @since 2.0.0 + */ +export function some(collection: Iterable>): Predicate { + return (a) => { + for (const p of collection) { + if (p(a)) { + return true + } + } + return false + } +} diff --git a/.repos/effect/packages/effect/src/PrimaryKey.ts b/.repos/effect/packages/effect/src/PrimaryKey.ts new file mode 100644 index 000000000..ed76a3bc2 --- /dev/null +++ b/.repos/effect/packages/effect/src/PrimaryKey.ts @@ -0,0 +1,123 @@ +/** + * The `PrimaryKey` module defines a small protocol for values that can expose + * a stable, string-based identifier. A value participates by implementing a + * method at {@link symbol}; consumers can check unknown values with + * {@link isPrimaryKey} and read the key with {@link value}. + * + * @since 2.0.0 + */ + +import { hasProperty } from "./Predicate.ts" + +/** + * Defines the unique identifier used to identify objects that implement the `PrimaryKey` interface. + * + * **When to use** + * + * Use to implement the `PrimaryKey` protocol as a computed property key on + * classes or object literals that expose a stable string identifier. + * + * @see {@link PrimaryKey} for the protocol interface that declares the method keyed by this symbol + * @see {@link value} for reading the string key from a `PrimaryKey` value + * @see {@link isPrimaryKey} for checking whether an unknown value carries this method + * + * @category symbols + * @since 2.0.0 + */ +export const symbol = "~effect/interfaces/PrimaryKey" + +/** + * An interface for objects that can provide a string-based primary key. + * + * **When to use** + * + * Use to define values that expose a stable string identifier for equality, + * hashing, caching, or persistence. + * + * **Details** + * + * Objects implementing this interface must provide a method that returns + * a unique string identifier. + * + * **Example** (Implementing a primary key) + * + * ```ts import.meta.vitest + * import { PrimaryKey } from "effect" + * + * class ProductId implements PrimaryKey.PrimaryKey { + * constructor(private category: string, private id: number) {} + * + * [PrimaryKey.symbol](): string { + * return `${this.category}-${this.id}` + * } + * } + * + * const productId = new ProductId("electronics", 42) + * PrimaryKey.value(productId) // => "electronics-42" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface PrimaryKey { + [symbol](): string +} + +/** + * Checks whether a value implements the `PrimaryKey` protocol. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a `PrimaryKey`. + * + * **Details** + * + * This is a structural guard for the `PrimaryKey.symbol` property. + * + * **Gotchas** + * + * This guard does not call the method or verify that it returns a string. + * + * @see {@link PrimaryKey} for the protocol being checked + * @see {@link value} for extracting the string value after narrowing + * + * @category guards + * @since 4.0.0 + */ +export const isPrimaryKey = (u: unknown): u is PrimaryKey => hasProperty(u, symbol) + +/** + * Extracts the string value from a `PrimaryKey`. + * + * **When to use** + * + * Use to read the stable string identifier from a value that implements + * `PrimaryKey`. + * + * **Example** (Reading primary key values) + * + * ```ts import.meta.vitest + * import { PrimaryKey } from "effect" + * + * class OrderId implements PrimaryKey.PrimaryKey { + * constructor(private timestamp: number, private sequence: number) {} + * + * [PrimaryKey.symbol](): string { + * return `order_${this.timestamp}_${this.sequence}` + * } + * } + * + * const orderId = new OrderId(1640995200000, 1) + * PrimaryKey.value(orderId) // => "order_1640995200000_1" + * + * // Can also be used with simple string-based implementations + * const simpleKey = { + * [PrimaryKey.symbol]: () => "simple-key-123" + * } + * PrimaryKey.value(simpleKey) // => "simple-key-123" + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const value = (self: PrimaryKey): string => self[symbol]() diff --git a/.repos/effect/packages/effect/src/PubSub.ts b/.repos/effect/packages/effect/src/PubSub.ts new file mode 100644 index 000000000..3127101cb --- /dev/null +++ b/.repos/effect/packages/effect/src/PubSub.ts @@ -0,0 +1,2812 @@ +/** + * Broadcasts values from publishers to many subscribers. + * + * Publishers add messages with `publish` or `publishAll`, and each active + * `Subscription` receives its own copy of every accepted message. Unlike a + * queue, subscribers do not compete for messages. This module includes bounded, + * dropping, sliding, and unbounded hubs, optional replay buffers for late + * subscribers, message-taking helpers, capacity and shutdown operations, and + * low-level types for custom hub strategies. + * + * @since 2.0.0 + */ +import * as Arr from "./Array.ts" +import * as Context from "./Context.ts" +import * as Deferred from "./Deferred.ts" +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 Latch from "./Latch.ts" +import * as MutableList from "./MutableList.ts" +import * as MutableRef from "./MutableRef.ts" +import { nextPow2 } from "./Number.ts" +import * as Option from "./Option.ts" +import { type Pipeable, pipeArguments } from "./Pipeable.ts" +import * as Scope from "./Scope.ts" +import type { Covariant, Invariant } from "./Types.ts" + +const TypeId = "~effect/PubSub" + +/** + * A `PubSub` is an asynchronous message hub into which publishers can publish + * messages of type `A` and subscribers can subscribe to take messages of type + * `A`. + * + * **Example** (Publishing and subscribing to messages) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * // Create a bounded PubSub with capacity 10 + * const pubsub = yield* PubSub.bounded(10) + * + * // Subscribe and consume messages + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish messages + * yield* PubSub.publish(pubsub, "Hello") + * yield* PubSub.publish(pubsub, "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 + * @since 2.0.0 + */ +export interface PubSub extends Pipeable { + readonly [TypeId]: { + readonly _A: Invariant + } + readonly pubsub: PubSub.Atomic + readonly subscribers: PubSub.Subscribers + readonly scope: Scope.Closeable + readonly shutdownHook: Latch.Latch + readonly shutdownFlag: MutableRef.MutableRef + readonly strategy: PubSub.Strategy +} + +/** + * Companion namespace containing the low-level building blocks used by + * `PubSub`, including atomic implementations, backing subscriptions, replay + * windows, and delivery strategies. + * + * @since 2.0.0 + */ +export declare namespace PubSub { + /** + * Low-level atomic PubSub interface that handles the core message storage and retrieval. + * + * @category models + * @since 4.0.0 + */ + export interface Atomic { + readonly capacity: number + isEmpty(): boolean + isFull(): boolean + size(): number + publish(value: A): boolean + publishAll(elements: Iterable): Array + slide(): void + subscribe(): BackingSubscription + replayWindow(): ReplayWindow + } + + /** + * Low-level subscription interface that handles message polling for individual subscribers. + * + * @category models + * @since 4.0.0 + */ + export interface BackingSubscription { + isEmpty(): boolean + size(): number + poll(): A | MutableList.Empty + pollUpTo(n: number): Array + unsubscribe(): void + } + + /** + * Tracks the pollers currently waiting on each backing subscription. + * + * **Details** + * + * This type is part of the low-level `PubSub.Strategy` contract. Most + * application code should use `subscribe`, `take`, and the other `PubSub` + * operations instead of manipulating subscriber maps directly. + * + * @category models + * @since 4.0.0 + */ + export type Subscribers = Map< + BackingSubscription, + Set>> + > + + /** + * Interface for accessing replay buffer contents for late subscribers. + * + * @category models + * @since 4.0.0 + */ + export interface ReplayWindow { + take(): A | undefined + takeN(n: number): Array + takeAll(): Array + close(): void + readonly remaining: number + } + + /** + * Strategy interface defining how PubSub handles backpressure and message distribution. + * + * @category models + * @since 4.0.0 + */ + export interface Strategy { + /** + * Describes any finalization logic associated with this strategy. + */ + readonly shutdown: Effect.Effect + + /** + * Describes how publishers should signal to subscribers that they are + * waiting for space to become available in the `PubSub`. + */ + handleSurplus( + pubsub: Atomic, + subscribers: Subscribers, + elements: Iterable, + isShutdown: MutableRef.MutableRef + ): Effect.Effect + + /** + * Describes how subscribers should signal to publishers waiting for space + * to become available in the `PubSub` that space may be available. + */ + onPubSubEmptySpaceUnsafe( + pubsub: Atomic, + subscribers: Subscribers + ): void + + /** + * Describes how subscribers waiting for additional values from the `PubSub` + * should take those values and signal to publishers that they are no + * longer waiting for additional values. + */ + completePollersUnsafe( + pubsub: Atomic, + subscribers: Subscribers, + subscription: BackingSubscription, + pollers: MutableList.MutableList> + ): void + + /** + * Describes how publishers should signal to subscribers waiting for + * additional values from the `PubSub` that new values are available. + */ + completeSubscribersUnsafe( + pubsub: Atomic, + subscribers: Subscribers + ): void + } +} + +const SubscriptionTypeId = "~effect/PubSub/Subscription" + +/** + * A subscription represents a consumer's connection to a PubSub, allowing them to take messages. + * + * **Example** (Taking messages from a subscription) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * // Subscribe within a scope for automatic cleanup + * 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 + * @since 4.0.0 + */ +export interface Subscription extends Pipeable { + readonly [SubscriptionTypeId]: { + readonly _A: Covariant + } + readonly pubsub: PubSub.Atomic + readonly subscribers: PubSub.Subscribers + readonly subscription: PubSub.BackingSubscription + readonly pollers: MutableList.MutableList> + readonly shutdownHook: Latch.Latch + readonly shutdownFlag: MutableRef.MutableRef + readonly strategy: PubSub.Strategy + readonly replayWindow: PubSub.ReplayWindow +} + +/** + * Creates a PubSub with a custom atomic implementation and strategy. + * + * **Example** (Creating a PubSub with a custom strategy) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * // Create custom PubSub with specific atomic implementation and strategy + * const pubsub = yield* PubSub.make({ + * atomicPubSub: () => PubSub.makeAtomicBounded(100), + * strategy: () => new PubSub.BackPressureStrategy() + * }) + * + * // Use the created PubSub + * const published = yield* PubSub.publish(pubsub, "Hello") + * yield* PubSub.shutdown(pubsub) + * return published + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => true + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + options: { + readonly atomicPubSub: LazyArg> + readonly strategy: LazyArg> + } +): Effect.Effect> => + Effect.sync(() => + makePubSubUnsafe( + options.atomicPubSub(), + new Map(), + Scope.makeUnsafe(), + Latch.makeUnsafe(false), + MutableRef.make(false), + options.strategy() + ) + ) + +/** + * Creates a bounded `PubSub` that applies backpressure when it reaches + * capacity. + * + * **Details** + * + * Published messages are retained until all current subscribers have taken + * them. When the capacity is full, publishers suspend until space is available. + * Pass an options object to configure both `capacity` and an optional replay + * buffer for late subscribers. + * + * **Example** (Creating a bounded PubSub) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * // Create bounded PubSub with capacity 100 + * const pubsub = yield* PubSub.bounded(100) + * + * // Create with replay buffer for late subscribers + * const pubsubWithReplay = yield* PubSub.bounded({ + * 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 + * @since 2.0.0 + */ +export const bounded = ( + capacity: number | { + readonly capacity: number + readonly replay?: number | undefined + } +): Effect.Effect> => + make({ + atomicPubSub: () => makeAtomicBounded(capacity), + strategy: () => new BackPressureStrategy() + }) + +/** + * Creates a bounded `PubSub` with the dropping strategy. The `PubSub` will drop new + * messages if the `PubSub` is at capacity. + * + * **Details** + * + * For best performance use capacities that are powers of two. + * + * **Example** (Dropping messages when full) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * // Create dropping PubSub that drops new messages when full + * const pubsub = yield* PubSub.dropping(3) + * + * 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) + * + * const messages = yield* PubSub.takeAll(subscription) + * return { dropped: !dropped, messages } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { dropped: true, messages: ["msg1", "msg2", "msg3"] } + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const dropping = ( + capacity: number | { + readonly capacity: number + readonly replay?: number | undefined + } +): Effect.Effect> => + make({ + atomicPubSub: () => makeAtomicBounded(capacity), + strategy: () => new DroppingStrategy() + }) + +/** + * Creates a bounded `PubSub` with the sliding strategy. The `PubSub` will add new + * messages and drop old messages if the `PubSub` is at capacity. + * + * **Details** + * + * For best performance use capacities that are powers of two. + * + * **Example** (Sliding old messages when full) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * // Create sliding PubSub that evicts old messages when full + * const pubsub = yield* PubSub.sliding(3) + * + * 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 + * + * return yield* PubSub.takeAll(subscription) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => ["msg2", "msg3", "msg4"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const sliding = ( + capacity: number | { + readonly capacity: number + readonly replay?: number | undefined + } +): Effect.Effect> => + make({ + atomicPubSub: () => makeAtomicBounded(capacity), + strategy: () => new SlidingStrategy() + }) + +/** + * Creates an unbounded `PubSub`. + * + * **Example** (Creating an unbounded PubSub) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * // Create unbounded PubSub + * const pubsub = yield* PubSub.unbounded() + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Can publish unlimited messages + * for (let i = 0; i < 3; i++) { + * yield* PubSub.publish(pubsub, `message-${i}`) + * } + * + * return yield* PubSub.takeAll(subscription) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => ["message-0", "message-1", "message-2"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unbounded = (options?: { + readonly replay?: number | undefined +}): Effect.Effect> => + make({ + atomicPubSub: () => makeAtomicUnbounded(options), + strategy: () => new DroppingStrategy() + }) + +/** + * Creates a bounded atomic PubSub implementation with optional replay buffer. + * + * **When to use** + * + * Use to provide bounded message storage when building a custom `PubSub` with + * `make` and an explicit delivery strategy. + * + * **Details** + * + * Pass either a capacity number or an options object with `capacity` and + * optional `replay`. A positive `replay` value enables a replay buffer for late + * subscribers, and fractional replay sizes are rounded up. + * + * **Gotchas** + * + * The capacity must be greater than zero; invalid capacities throw + * synchronously before an atomic implementation is created. + * + * @see {@link make} for constructing a `PubSub` from an atomic implementation and delivery strategy + * @see {@link makeAtomicUnbounded} for an atomic implementation without a bounded capacity + * @see {@link bounded} for the higher-level backpressure constructor + * @see {@link dropping} for the higher-level dropping constructor + * @see {@link sliding} for the higher-level sliding constructor + * + * @category constructors + * @since 4.0.0 + */ +export const makeAtomicBounded = ( + capacity: number | { + readonly capacity: number + readonly replay?: number | undefined + } +): PubSub.Atomic => { + const options = typeof capacity === "number" ? { capacity } : capacity + ensureCapacity(options.capacity) + const replayBuffer = options.replay && options.replay > 0 ? new ReplayBuffer(Math.ceil(options.replay)) : undefined + if (options.capacity === 1) { + return new BoundedPubSubSingle(replayBuffer) + } else if (nextPow2(options.capacity) === options.capacity) { + return new BoundedPubSubPow2(options.capacity, replayBuffer) + } else { + return new BoundedPubSubArb(options.capacity, replayBuffer) + } +} + +/** + * Creates an unbounded atomic PubSub implementation with optional replay buffer. + * + * **When to use** + * + * Use to create the low-level storage layer for a custom `PubSub` whose active + * subscribers may retain an unbounded number of pending messages. + * + * **Gotchas** + * + * Messages published while subscribers are active can be retained without a + * capacity limit until those subscribers take them or unsubscribe. + * + * @see {@link makeAtomicBounded} for a bounded atomic implementation that enforces capacity + * @see {@link make} for wrapping an atomic implementation with a delivery strategy + * @see {@link unbounded} for the high-level effectful constructor for unbounded `PubSub` values + * + * @category constructors + * @since 4.0.0 + */ +export const makeAtomicUnbounded = (options?: { + readonly replay?: number | 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 import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(100) + * const unboundedPubsub = yield* PubSub.unbounded() + * return [PubSub.capacity(pubsub), PubSub.capacity(unboundedPubsub)] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [100, Number.MAX_SAFE_INTEGER] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const capacity = (self: PubSub): number => self.pubsub.capacity + +/** + * Returns the current number of messages retained by the `PubSub` for active + * subscribers. + * + * **Details** + * + * If the `PubSub` has been shut down, the returned effect succeeds with `0`. + * The size is not a count of waiting subscribers or suspended publishers. + * + * **Example** (Getting PubSub size) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * // Initially empty + * const initialSize = yield* PubSub.size(pubsub) + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish some messages for the active subscription + * yield* PubSub.publish(pubsub, "msg1") + * yield* PubSub.publish(pubsub, "msg2") + * + * const afterPublish = yield* PubSub.size(pubsub) + * const messages = yield* PubSub.takeAll(subscription) + * return { initialSize, afterPublish, messages } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { initialSize: 0, afterPublish: 2, messages: ["msg1", "msg2"] } + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: PubSub): Effect.Effect => Effect.sync(() => sizeUnsafe(self)) +/** + * Returns the current number of messages retained by the `PubSub` for active + * subscribers synchronously. + * + * **When to use** + * + * Use when an immediate `PubSub` size snapshot is needed outside effectful code + * and concurrent changes between the check and later use are acceptable. + * + * **Details** + * + * Returns `0` after shutdown. Because this is an unsafe synchronous snapshot, + * prefer `size` in effectful code. + * + * **Example** (Reading size synchronously) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * return PubSub.sizeUnsafe(pubsub) + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 0 + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const sizeUnsafe = (self: PubSub): number => { + if (MutableRef.get(self.shutdownFlag)) { + return 0 + } + return self.pubsub.size() +} + +/** + * Returns `true` when the `PubSub` has reached its configured capacity. + * + * **Details** + * + * For unbounded PubSubs this is normally `false`. + * + * **Example** (Checking whether a PubSub is full) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * + * // Initially not full + * const initiallyFull = yield* PubSub.isFull(pubsub) + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Fill the PubSub for the active subscription + * yield* PubSub.publish(pubsub, "msg1") + * yield* PubSub.publish(pubsub, "msg2") + * + * const nowFull = yield* PubSub.isFull(pubsub) + * const messages = yield* PubSub.takeAll(subscription) + * return { initiallyFull, nowFull, messages } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { initiallyFull: false, nowFull: true, messages: ["msg1", "msg2"] } + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isFull = (self: PubSub): Effect.Effect => + Effect.map(size(self), (size) => size === self.pubsub.capacity) + +/** + * Returns `true` if the `Pubsub` contains zero elements, `false` otherwise. + * + * **Example** (Checking whether a PubSub is empty) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * // Initially empty + * const initiallyEmpty = yield* PubSub.isEmpty(pubsub) + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish a message for the active subscription + * yield* PubSub.publish(pubsub, "Hello") + * + * const nowEmpty = yield* PubSub.isEmpty(pubsub) + * const message = yield* PubSub.take(subscription) + * return { initiallyEmpty, nowEmpty, message } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { initiallyEmpty: true, nowEmpty: false, message: "Hello" } + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: PubSub): Effect.Effect => Effect.map(size(self), (size) => size === 0) + +/** + * Shuts down the `PubSub`, interrupting suspended publishers and subscribers + * and finalizing active subscriptions. + * + * **Details** + * + * After shutdown, `publish` and `publishAll` succeed with `false`, + * `publishUnsafe` returns `false`, and subscription operations such as `take` + * interrupt. + * + * **Example** (Shutting down a PubSub) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(1) + * + * // Shutdown the PubSub + * yield* PubSub.shutdown(pubsub) + * + * const isShutdown = yield* PubSub.isShutdown(pubsub) + * + * // Publishing after shutdown returns false + * const published = yield* PubSub.publish(pubsub, "msg1") + * return { isShutdown, published } + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { isShutdown: true, published: false } + * ``` + * + * @category lifecycle + * @since 2.0.0 + */ +export const shutdown = (self: PubSub): Effect.Effect => + Effect.uninterruptible(Effect.withFiber((fiber) => { + MutableRef.set(self.shutdownFlag, true) + return Scope.close(self.scope, Exit.interrupt(fiber.id)).pipe( + Effect.andThen(self.strategy.shutdown), + Effect.when(self.shutdownHook.open), + Effect.asVoid + ) + })) + +/** + * Checks effectfully whether `shutdown` has been called, returning `true` + * after shutdown and `false` otherwise. + * + * **Example** (Checking whether a PubSub is shut down) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * // Initially not shutdown + * const initiallyShutdown = yield* PubSub.isShutdown(pubsub) + * + * // Shutdown the PubSub + * yield* PubSub.shutdown(pubsub) + * + * const nowShutdown = yield* PubSub.isShutdown(pubsub) + * return [initiallyShutdown, nowShutdown] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [false, true] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isShutdown = (self: PubSub): Effect.Effect => Effect.sync(() => isShutdownUnsafe(self)) + +/** + * Checks synchronously whether `shutdown` has been called, returning `true` + * after shutdown and `false` otherwise. + * + * **When to use** + * + * Use when an immediate `PubSub` shutdown-state snapshot is needed outside + * effectful code and racing shutdown changes are acceptable. + * + * **Example** (Checking shutdown synchronously) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * 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)] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [false, true] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isShutdownUnsafe = (self: PubSub): boolean => self.shutdownFlag.current + +/** + * Waits until the queue is shutdown. The `Effect` returned by this method will + * not resume until the queue has been shutdown. If the queue is already + * shutdown, the `Effect` will resume right away. + * + * **Example** (Waiting for shutdown) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * // Start a fiber that waits for shutdown + * const waiterFiber = yield* Effect.forkChild( + * Effect.gen(function*() { + * yield* PubSub.awaitShutdown(pubsub) + * return "PubSub has been shutdown!" + * }) + * ) + * + * // Shutdown the PubSub + * yield* PubSub.shutdown(pubsub) + * + * // The waiter will now complete + * return yield* Fiber.join(waiterFiber) + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => "PubSub has been shutdown!" + * ``` + * + * @category lifecycle + * @since 2.0.0 + */ +export const awaitShutdown = (self: PubSub): Effect.Effect => self.shutdownHook.await + +/** + * Publishes a message to the `PubSub` as an `Effect`, returning whether the + * message was accepted. + * + * **When to use** + * + * Use when you need to publish from effectful code and let the configured + * PubSub strategy handle surplus messages. + * + * **Details** + * + * The effect succeeds with `false` if the `PubSub` is shut down. If the message + * cannot be accepted immediately, the configured strategy decides how surplus + * messages are handled. + * + * **Example** (Publishing a message) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * // Publish a message + * const published = yield* PubSub.publish(pubsub, "Hello World") + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * 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 + * + * @category publishing + * @since 2.0.0 + */ +export const publish: { + (value: A): (self: PubSub) => Effect.Effect + (self: PubSub, value: A): Effect.Effect +} = dual(2, (self: PubSub, value: A): Effect.Effect => + Effect.suspend(() => { + if (self.shutdownFlag.current) { + return Effect.succeed(false) + } + + if (self.pubsub.publish(value)) { + self.strategy.completeSubscribersUnsafe(self.pubsub, self.subscribers) + return Effect.succeed(true) + } + + return self.strategy.handleSurplus( + self.pubsub, + self.subscribers, + [value], + self.shutdownFlag + ) + })) + +/** + * Attempts to publish a message synchronously without applying the PubSub + * strategy's effectful surplus handling. + * + * **When to use** + * + * Use when you need a non-blocking synchronous publish attempt where `false` + * is an acceptable result when the message cannot be accepted immediately. + * + * **Details** + * + * Returns `false` if the `PubSub` is shut down or the message cannot be + * accepted immediately, for example when a bounded PubSub is full. Prefer + * `publish` when backpressure or sliding behavior should be honored. + * + * **Example** (Publishing without suspending) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * return PubSub.publishUnsafe(pubsub, "Hello") + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => true + * ``` + * + * @see {@link publish} for effectful publishing that honors the configured surplus strategy + * + * @category publishing + * @since 4.0.0 + */ +export const publishUnsafe: { + (value: A): (self: PubSub) => boolean + (self: PubSub, value: A): boolean +} = dual(2, (self: PubSub, value: A): boolean => { + if (self.shutdownFlag.current) return false + if (self.pubsub.publish(value)) { + self.strategy.completeSubscribersUnsafe(self.pubsub, self.subscribers) + return true + } + return false +}) + +/** + * Publishes all of the specified messages to the `PubSub`, returning whether they + * were published to the `PubSub`. + * + * **Example** (Publishing multiple messages) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * // Publish multiple messages at once + * 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 subscription = yield* PubSub.subscribe(smallPubsub) + * + * // 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) + * const result = yield* Fiber.join(fiber) + * const secondBatch = yield* PubSub.takeAll(subscription) + * return { allPublished, firstBatch, result, secondBatch } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { allPublished: true, firstBatch: ["msg1", "msg2"], result: true, secondBatch: ["msg3", "msg4"] } + * ``` + * + * @category publishing + * @since 2.0.0 + */ +export const publishAll: { + (elements: Iterable): (self: PubSub) => Effect.Effect + (self: PubSub, elements: Iterable): Effect.Effect +} = dual(2, (self: PubSub, elements: Iterable): Effect.Effect => + Effect.suspend(() => { + if (self.shutdownFlag.current) { + return Effect.succeed(false) + } + const surplus = self.pubsub.publishAll(elements) + self.strategy.completeSubscribersUnsafe(self.pubsub, self.subscribers) + if (surplus.length === 0) { + return Effect.succeed(true) + } + return self.strategy.handleSurplus( + self.pubsub, + self.subscribers, + surplus, + self.shutdownFlag + ) + })) + +/** + * Subscribes to receive messages from the `PubSub`. The resulting subscription can + * be evaluated multiple times within the scope to take a message from the `PubSub` + * each time. + * + * **Example** (Subscribing to messages) + * + * ```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 + * const first = yield* Effect.scoped(Effect.gen(function*() { + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish some messages + * yield* PubSub.publish(pubsub, "Hello") + * yield* PubSub.publish(pubsub, "World") + * + * // Take messages one by one + * const msg1 = yield* PubSub.take(subscription) + * const msg2 = yield* PubSub.take(subscription) + * + * // Subscription is automatically cleaned up when scope exits + * return [msg1, msg2] + * })) + * + * 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") + * + * return yield* Effect.all([ + * PubSub.take(sub1), + * PubSub.take(sub2) + * ]) + * })) + * return [first, second] + * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [["Hello", "World"], ["Broadcast", "Broadcast"]] + * ``` + * + * @category subscriptions + * @since 2.0.0 + */ +export const subscribe = (self: PubSub): Effect.Effect, never, Scope.Scope> => + Effect.uninterruptible( + Effect.contextWith((services) => { + const localScope = Context.get(services, Scope.Scope) + const scope = Scope.forkUnsafe(self.scope) + const subscription = makeSubscriptionUnsafe(self.pubsub, self.subscribers, self.strategy) + return Scope.addFinalizer(scope, unsubscribe(subscription)).pipe( + Effect.andThen(Scope.addFinalizerExit(localScope, (exit) => Scope.close(scope, exit))), + Effect.as(subscription) + ) + }) + ) + +const unsubscribe = (self: Subscription): Effect.Effect => + Effect.uninterruptible( + Effect.withFiber((state) => { + MutableRef.set(self.shutdownFlag, true) + return Effect.forEach( + MutableList.takeAll(self.pollers), + (d) => Deferred.interruptWith(d, state.id), + { discard: true, concurrency: "unbounded" } + ).pipe( + Effect.tap(() => + Effect.sync(() => { + self.subscribers.delete(self.subscription) + self.subscription.unsubscribe() + self.replayWindow.close() + self.strategy.onPubSubEmptySpaceUnsafe(self.pubsub, self.subscribers) + }) + ), + Effect.when(self.shutdownHook.open), + Effect.asVoid + ) + }) + ) + +/** + * Takes a single message from the subscription. If no messages are available, + * this will suspend until a message becomes available. + * + * **Example** (Taking a message) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // 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") + * + * // The take will now complete + * return yield* Fiber.join(takeFiber) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => "Hello" + * ``` + * + * @category subscriptions + * @since 4.0.0 + */ +export const take = (self: Subscription): Effect.Effect => + Effect.suspend(() => { + if (self.shutdownFlag.current) { + return Effect.interrupt + } + if (self.replayWindow.remaining > 0) { + const message = self.replayWindow.take()! + return Effect.succeed(message) + } + const message = self.pollers.length === 0 + ? self.subscription.poll() + : MutableList.Empty + if (message === MutableList.Empty) { + return pollForItem(self) + } else { + self.strategy.onPubSubEmptySpaceUnsafe(self.pubsub, self.subscribers) + return Effect.succeed(message) + } + }) + +/** + * Takes all available messages from the subscription, suspending if no items + * are available. + * + * **Example** (Taking all available messages) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish multiple messages + * yield* PubSub.publishAll(pubsub, ["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 + * @since 4.0.0 + */ +export const takeAll = (self: Subscription): Effect.Effect> => + Effect.suspend(function loop(value?: [A]): Effect.Effect> { + if (self.shutdownFlag.current) { + return Effect.interrupt + } + let as = self.pollers.length === 0 + ? self.subscription.pollUpTo(Number.POSITIVE_INFINITY) + : [] + if (value) { + as = value.concat(as) + } + self.strategy.onPubSubEmptySpaceUnsafe(self.pubsub, self.subscribers) + if (self.replayWindow.remaining > 0) { + return Effect.succeed(self.replayWindow.takeAll().concat(as) as Arr.NonEmptyArray) + } else if (!Arr.isArrayNonEmpty(as)) { + return Effect.flatMap(pollForItem(self), (item) => loop([item])) + } + return Effect.succeed(as) + }) + +const pollForItem = (self: Subscription) => { + const deferred = Deferred.makeUnsafe() + let set = self.subscribers.get(self.subscription) + if (!set) { + set = new Set() + self.subscribers.set(self.subscription, set) + } + set.add(self.pollers) + MutableList.append(self.pollers, deferred) + self.strategy.completePollersUnsafe( + self.pubsub, + self.subscribers, + self.subscription, + self.pollers + ) + return Effect.onInterrupt( + Deferred.await(deferred), + () => { + MutableList.remove(self.pollers, deferred) + return Effect.void + } + ) +} + +/** + * Takes up to the specified number of messages from the subscription without suspending. + * + * **Example** (Taking up to a maximum number of messages) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish multiple messages + * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3", "msg4", "msg5"]) + * + * // 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) + * + * // 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 + * @since 4.0.0 + */ +export const takeUpTo: { + (max: number): (self: Subscription) => Effect.Effect> + (self: Subscription, max: number): Effect.Effect> +} = dual(2, (self: Subscription, max: number): Effect.Effect> => + Effect.suspend(() => { + if (self.shutdownFlag.current) return Effect.interrupt + let replay: Array | undefined = undefined + if (self.replayWindow.remaining >= max) { + return Effect.succeed(self.replayWindow.takeN(max)) + } else if (self.replayWindow.remaining > 0) { + replay = self.replayWindow.takeAll() + max = max - replay.length + } + const as = self.pollers.length === 0 + ? self.subscription.pollUpTo(max) + : [] + self.strategy.onPubSubEmptySpaceUnsafe(self.pubsub, self.subscribers) + return replay ? Effect.succeed(replay.concat(as)) : Effect.succeed(as) + })) + +/** + * Takes between the specified minimum and maximum number of messages from the subscription. + * Will suspend if the minimum number is not immediately available. + * + * **Example** (Taking between a minimum and maximum) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * 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)) + * + * // Publish 3 messages + * yield* PubSub.publishAll(pubsub, ["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 + * @since 4.0.0 + */ +export const takeBetween: { + (min: number, max: number): (self: Subscription) => Effect.Effect> + (self: Subscription, min: number, max: number): Effect.Effect> +} = dual( + 3, + (self: Subscription, min: number, max: number): Effect.Effect> => + Effect.suspend(() => takeRemainderLoop(self, min, max, [])) +) + +const takeRemainderLoop = ( + self: Subscription, + min: number, + max: number, + acc: Array +): Effect.Effect> => { + if (max < min) { + return Effect.succeed(acc) + } + return Effect.flatMap(takeUpTo(self, max), (bs) => { + acc.push(...bs) + const remaining = min - bs.length + if (remaining === 1) { + return Effect.map(take(self), (b) => { + acc.push(b) + return acc + }) + } + if (remaining > 1) { + return Effect.flatMap(take(self), (b) => { + acc.push(b) + return takeRemainderLoop( + self, + remaining - 1, + max - bs.length - 1, + acc + ) + }) + } + return Effect.succeed(acc) + }) +} + +/** + * Returns the number of messages currently available in the subscription as an + * `Effect`. + * + * **When to use** + * + * Use when checking a subscription from effectful code and shutdown should + * interrupt the effect. + * + * **Details** + * + * The count includes replay-buffered messages. If the subscription has been + * shut down, the effect interrupts. + * + * **Example** (Checking remaining messages) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(10) + * + * const subscription = yield* PubSub.subscribe(pubsub) + * + * // Publish some messages + * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) + * + * // Check how many messages are available + * const count = yield* PubSub.remaining(subscription) + * + * // Take one message + * const message = yield* PubSub.take(subscription) + * + * 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()` + * + * @category getters + * @since 4.0.0 + */ +export const remaining = (self: Subscription): Effect.Effect => + Effect.suspend(() => + self.shutdownFlag.current + ? Effect.interrupt + : Effect.succeed(self.subscription.size() + self.replayWindow.remaining) + ) + +/** + * Synchronously returns the number of messages currently available in the + * subscription, or `Option.none()` when it is shut down. + * + * **When to use** + * + * Use when you need synchronous polling outside a managed workflow and want + * shutdown observed as data instead of interruption. + * + * **Example** (Checking remaining messages synchronously) + * + * ```ts import.meta.vitest + * import { Effect, Option, PubSub } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * const subscription = yield* PubSub.subscribe(pubsub) + * return PubSub.remainingUnsafe(subscription) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => Option.some(0) + * ``` + * + * @see {@link remaining} for the effectful variant that interrupts on shutdown + * + * @category getters + * @since 4.0.0 + */ +export const remainingUnsafe = (self: Subscription): Option.Option => { + if (self.shutdownFlag.current) { + return Option.none() + } + return Option.some(self.subscription.size() + self.replayWindow.remaining) +} + +// ----------------------------------------------------------------------------- +// internal +// ----------------------------------------------------------------------------- + +const AbsentValue = Symbol.for("effect/PubSub/AbsentValue") +type AbsentValue = typeof AbsentValue + +const addSubscribers = ( + subscribers: PubSub.Subscribers, + subscription: PubSub.BackingSubscription, + pollers: MutableList.MutableList> +) => { + if (!subscribers.has(subscription)) { + subscribers.set(subscription, new Set()) + } + const set = subscribers.get(subscription)! + set.add(pollers) +} + +const removeSubscribers = ( + subscribers: PubSub.Subscribers, + subscription: PubSub.BackingSubscription, + pollers: MutableList.MutableList> +) => { + if (!subscribers.has(subscription)) { + return + } + const set = subscribers.get(subscription)! + set.delete(pollers) + if (set.size === 0) { + subscribers.delete(subscription) + } +} + +const makeSubscriptionUnsafe = ( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + strategy: PubSub.Strategy +): Subscription => + new SubscriptionImpl( + pubsub, + subscribers, + pubsub.subscribe(), + MutableList.make>(), + Latch.makeUnsafe(false), + MutableRef.make(false), + strategy, + pubsub.replayWindow() + ) + +class BoundedPubSubArb implements PubSub.Atomic { + array: Array + replayIndices: Array + publisherIndex = 0 + subscribers: Array + subscriberCount = 0 + subscribersIndex = 0 + + readonly capacity: number + readonly replayBuffer: ReplayBuffer | undefined + + constructor(capacity: number, replayBuffer: ReplayBuffer | undefined) { + 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 }) + } + + replayWindow(): PubSub.ReplayWindow { + return this.replayBuffer ? new ReplayWindowImpl(this.replayBuffer) : emptyReplayWindow + } + + isEmpty(): boolean { + return this.publisherIndex === this.subscribersIndex + } + + isFull(): boolean { + return this.publisherIndex === this.subscribersIndex + this.capacity + } + + size(): number { + return this.publisherIndex - this.subscribersIndex + } + + publish(value: A): boolean { + 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 + } + return true + } + + publishAll(elements: Iterable): Array { + if (this.subscriberCount === 0) { + if (this.replayBuffer) { + this.replayBuffer.offerAll(elements) + } + return [] + } + const chunk = Arr.fromIterable(elements) + const n = chunk.length + const size = this.publisherIndex - this.subscribersIndex + const available = this.capacity - size + const forPubSub = Math.min(n, available) + if (forPubSub === 0) { + return chunk + } + let iteratorIndex = 0 + const publishAllIndex = this.publisherIndex + forPubSub + while (this.publisherIndex !== publishAllIndex) { + 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 + } + return chunk.slice(iteratorIndex) + } + + 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 + this.replayBuffer?.slide(value, this.replayIndices[index]) + } + } + + subscribe(): PubSub.BackingSubscription { + this.subscriberCount += 1 + return new BoundedPubSubArbSubscription(this, this.publisherIndex, false) + } +} + +class BoundedPubSubArbSubscription implements PubSub.BackingSubscription { + private self: BoundedPubSubArb + private subscriberIndex: number + private unsubscribed: boolean + + constructor( + self: BoundedPubSubArb, + subscriberIndex: number, + unsubscribed: boolean + ) { + this.self = self + this.subscriberIndex = subscriberIndex + this.unsubscribed = unsubscribed + } + + isEmpty(): boolean { + return ( + this.unsubscribed || + this.self.publisherIndex === this.subscriberIndex || + this.self.publisherIndex === this.self.subscribersIndex + ) + } + + size() { + if (this.unsubscribed) { + return 0 + } + return this.self.publisherIndex - Math.max(this.subscriberIndex, this.self.subscribersIndex) + } + + poll(): A | MutableList.Empty { + if (this.unsubscribed) { + return MutableList.Empty + } + this.subscriberIndex = Math.max(this.subscriberIndex, this.self.subscribersIndex) + if (this.subscriberIndex !== this.self.publisherIndex) { + const index = this.subscriberIndex % this.self.capacity + const elem = this.self.array[index]! + this.self.subscribers[index] -= 1 + if (this.self.subscribers[index] === 0) { + this.self.array[index] = AbsentValue as unknown as A + this.self.subscribersIndex += 1 + } + this.subscriberIndex += 1 + return elem + } + return MutableList.Empty + } + + pollUpTo(n: number): Array { + if (this.unsubscribed) { + return [] + } + this.subscriberIndex = Math.max(this.subscriberIndex, this.self.subscribersIndex) + const size = this.self.publisherIndex - this.subscriberIndex + const toPoll = Math.min(n, size) + if (toPoll <= 0) { + return [] + } + const builder: Array = [] + const pollUpToIndex = this.subscriberIndex + toPoll + while (this.subscriberIndex !== pollUpToIndex) { + const index = this.subscriberIndex % this.self.capacity + const a = this.self.array[index] as A + this.self.subscribers[index] -= 1 + if (this.self.subscribers[index] === 0) { + this.self.array[index] = AbsentValue as unknown as A + this.self.subscribersIndex += 1 + } + builder.push(a) + this.subscriberIndex += 1 + } + + return builder + } + + unsubscribe(): void { + if (!this.unsubscribed) { + this.unsubscribed = true + this.self.subscriberCount -= 1 + this.subscriberIndex = Math.max(this.subscriberIndex, this.self.subscribersIndex) + while (this.subscriberIndex !== this.self.publisherIndex) { + const index = this.subscriberIndex % this.self.capacity + this.self.subscribers[index] -= 1 + if (this.self.subscribers[index] === 0) { + this.self.array[index] = AbsentValue as unknown as A + this.self.subscribersIndex += 1 + } + this.subscriberIndex += 1 + } + } + } +} + +class BoundedPubSubPow2 implements PubSub.Atomic { + array: Array + replayIndices: Array + mask: number + publisherIndex = 0 + subscribers: Array + subscriberCount = 0 + subscribersIndex = 0 + + readonly capacity: number + readonly replayBuffer: ReplayBuffer | undefined + + constructor(capacity: number, replayBuffer: ReplayBuffer | undefined) { + 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 }) + } + + replayWindow(): PubSub.ReplayWindow { + return this.replayBuffer ? new ReplayWindowImpl(this.replayBuffer) : emptyReplayWindow + } + + isEmpty(): boolean { + return this.publisherIndex === this.subscribersIndex + } + + isFull(): boolean { + return this.publisherIndex === this.subscribersIndex + this.capacity + } + + size(): number { + return this.publisherIndex - this.subscribersIndex + } + + publish(value: A): boolean { + 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 + } + return true + } + + publishAll(elements: Iterable): Array { + if (this.subscriberCount === 0) { + if (this.replayBuffer) { + this.replayBuffer.offerAll(elements) + } + return [] + } + const chunk = Arr.fromIterable(elements) + const n = chunk.length + const size = this.publisherIndex - this.subscribersIndex + const available = this.capacity - size + const forPubSub = Math.min(n, available) + if (forPubSub === 0) { + return chunk + } + let iteratorIndex = 0 + const publishAllIndex = this.publisherIndex + forPubSub + while (this.publisherIndex !== publishAllIndex) { + 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 + } + return chunk.slice(iteratorIndex) + } + + 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 + this.replayBuffer?.slide(value, this.replayIndices[index]) + } + } + + subscribe(): PubSub.BackingSubscription { + this.subscriberCount += 1 + return new BoundedPubSubPow2Subscription(this, this.publisherIndex, false) + } +} + +class BoundedPubSubPow2Subscription implements PubSub.BackingSubscription { + private self: BoundedPubSubPow2 + private subscriberIndex: number + private unsubscribed: boolean + + constructor( + self: BoundedPubSubPow2, + subscriberIndex: number, + unsubscribed: boolean + ) { + this.self = self + this.subscriberIndex = subscriberIndex + this.unsubscribed = unsubscribed + } + + isEmpty(): boolean { + return ( + this.unsubscribed || + this.self.publisherIndex === this.subscriberIndex || + this.self.publisherIndex === this.self.subscribersIndex + ) + } + + size() { + if (this.unsubscribed) { + return 0 + } + return this.self.publisherIndex - Math.max(this.subscriberIndex, this.self.subscribersIndex) + } + + poll(): A | MutableList.Empty { + if (this.unsubscribed) { + return MutableList.Empty + } + this.subscriberIndex = Math.max(this.subscriberIndex, this.self.subscribersIndex) + if (this.subscriberIndex !== this.self.publisherIndex) { + const index = this.subscriberIndex & this.self.mask + const elem = this.self.array[index]! + this.self.subscribers[index] -= 1 + if (this.self.subscribers[index] === 0) { + this.self.array[index] = AbsentValue as unknown as A + this.self.subscribersIndex += 1 + } + this.subscriberIndex += 1 + return elem + } + return MutableList.Empty + } + + pollUpTo(n: number): Array { + if (this.unsubscribed) { + return [] + } + this.subscriberIndex = Math.max(this.subscriberIndex, this.self.subscribersIndex) + const size = this.self.publisherIndex - this.subscriberIndex + const toPoll = Math.min(n, size) + if (toPoll <= 0) { + return [] + } + const builder: Array = [] + const pollUpToIndex = this.subscriberIndex + toPoll + while (this.subscriberIndex !== pollUpToIndex) { + const index = this.subscriberIndex & this.self.mask + const elem = this.self.array[index] as A + this.self.subscribers[index] -= 1 + if (this.self.subscribers[index] === 0) { + this.self.array[index] = AbsentValue as unknown as A + this.self.subscribersIndex += 1 + } + builder.push(elem) + this.subscriberIndex += 1 + } + return builder + } + + unsubscribe(): void { + if (!this.unsubscribed) { + this.unsubscribed = true + this.self.subscriberCount -= 1 + this.subscriberIndex = Math.max(this.subscriberIndex, this.self.subscribersIndex) + while (this.subscriberIndex !== this.self.publisherIndex) { + const index = this.subscriberIndex & this.self.mask + this.self.subscribers[index] -= 1 + if (this.self.subscribers[index] === 0) { + this.self.array[index] = AbsentValue as unknown as A + this.self.subscribersIndex += 1 + } + this.subscriberIndex += 1 + } + } + } +} + +class BoundedPubSubSingle implements PubSub.Atomic { + publisherIndex = 0 + subscriberCount = 0 + subscribers = 0 + value: A = AbsentValue as unknown as A + replayIndex = 0 + + readonly capacity = 1 + readonly replayBuffer: ReplayBuffer | undefined + + constructor(replayBuffer: ReplayBuffer | undefined) { + this.replayBuffer = replayBuffer + } + + replayWindow(): PubSub.ReplayWindow { + return this.replayBuffer ? new ReplayWindowImpl(this.replayBuffer) : emptyReplayWindow + } + + pipe() { + return pipeArguments(this, arguments) + } + + isEmpty(): boolean { + return this.subscribers === 0 + } + + isFull(): boolean { + return !this.isEmpty() + } + + size(): number { + return this.isEmpty() ? 0 : 1 + } + + publish(value: A): boolean { + 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 + } + return true + } + + publishAll(elements: Iterable): Array { + if (this.subscriberCount === 0) { + if (this.replayBuffer) { + this.replayBuffer.offerAll(elements) + } + return [] + } + const chunk = Arr.fromIterable(elements) + if (chunk.length === 0) { + return chunk + } + if (this.publish(chunk[0])) { + return chunk.slice(1) + } else { + return chunk + } + } + + slide(): void { + if (this.isFull()) { + const value = this.value + this.subscribers = 0 + this.value = AbsentValue as unknown as A + this.replayBuffer?.slide(value, this.replayIndex) + } + } + + subscribe(): PubSub.BackingSubscription { + this.subscriberCount += 1 + return new BoundedPubSubSingleSubscription(this, this.publisherIndex, false) + } +} + +class BoundedPubSubSingleSubscription implements PubSub.BackingSubscription { + private self: BoundedPubSubSingle + private subscriberIndex: number + private unsubscribed: boolean + + constructor( + self: BoundedPubSubSingle, + subscriberIndex: number, + unsubscribed: boolean + ) { + this.self = self + this.subscriberIndex = subscriberIndex + this.unsubscribed = unsubscribed + } + + isEmpty(): boolean { + return ( + this.unsubscribed || + this.self.subscribers === 0 || + this.subscriberIndex === this.self.publisherIndex + ) + } + + size() { + return this.isEmpty() ? 0 : 1 + } + + poll(): A | MutableList.Empty { + if (this.isEmpty()) { + return MutableList.Empty + } + const elem = this.self.value + this.self.subscribers -= 1 + if (this.self.subscribers === 0) { + this.self.value = AbsentValue as unknown as A + } + this.subscriberIndex += 1 + return elem + } + + pollUpTo(n: number): Array { + if (this.isEmpty() || n < 1) { + 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] + } + + unsubscribe(): void { + if (!this.unsubscribed) { + this.unsubscribed = true + this.self.subscriberCount -= 1 + if (this.subscriberIndex !== this.self.publisherIndex) { + this.self.subscribers -= 1 + if (this.self.subscribers === 0) { + this.self.value = AbsentValue as unknown as A + } + } + } + } +} + +interface Node { + value: A | AbsentValue + replayIndex: number | undefined + subscribers: number + next: Node | null +} + +class UnboundedPubSub implements PubSub.Atomic { + publisherHead: Node = { + value: AbsentValue, + replayIndex: undefined, + subscribers: 0, + next: null + } + publisherTail = this.publisherHead + publisherIndex = 0 + subscribersIndex = 0 + + readonly capacity = Number.MAX_SAFE_INTEGER + readonly replayBuffer: ReplayBuffer | undefined + + constructor(replayBuffer: ReplayBuffer | undefined) { + this.replayBuffer = replayBuffer + } + + replayWindow(): PubSub.ReplayWindow { + return this.replayBuffer ? new ReplayWindowImpl(this.replayBuffer) : emptyReplayWindow + } + + isEmpty(): boolean { + return this.publisherHead === this.publisherTail + } + + isFull(): boolean { + return false + } + + size(): number { + return this.publisherIndex - this.subscribersIndex + } + + publish(value: A): boolean { + const replayIndex = this.replayBuffer?.offer(value) + const subscribers = this.publisherTail.subscribers + if (subscribers !== 0) { + const node: Node = { + value, + replayIndex, + subscribers, + next: null + } + this.publisherTail.next = node + this.publisherTail = this.publisherTail.next + this.publisherIndex += 1 + } + return true + } + + publishAll(elements: Iterable): Array { + if (this.publisherTail.subscribers !== 0) { + for (const a of elements) { + this.publish(a) + } + } else if (this.replayBuffer) { + this.replayBuffer.offerAll(elements) + } + return [] + } + + 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 + this.replayBuffer?.slide(value, node.replayIndex!) + } + } + + subscribe(): PubSub.BackingSubscription { + this.publisherTail.subscribers += 1 + return new UnboundedPubSubSubscription( + this, + this.publisherTail, + this.publisherIndex, + false + ) + } +} + +class UnboundedPubSubSubscription implements PubSub.BackingSubscription { + private self: UnboundedPubSub + private subscriberHead: Node + private subscriberIndex: number + private unsubscribed: boolean + + constructor( + self: UnboundedPubSub, + subscriberHead: Node, + subscriberIndex: number, + unsubscribed: boolean + ) { + this.self = self + this.subscriberHead = subscriberHead + this.subscriberIndex = subscriberIndex + this.unsubscribed = unsubscribed + } + + isEmpty(): boolean { + if (this.unsubscribed) { + return true + } + let empty = true + let loop = true + while (loop) { + if (this.subscriberHead === this.self.publisherTail) { + loop = false + } else { + if (this.subscriberHead.next!.value !== AbsentValue) { + empty = false + loop = false + } else { + this.subscriberHead = this.subscriberHead.next! + this.subscriberIndex += 1 + } + } + } + return empty + } + + size() { + if (this.unsubscribed) { + return 0 + } + return this.self.publisherIndex - Math.max(this.subscriberIndex, this.self.subscribersIndex) + } + + poll(): A | MutableList.Empty { + if (this.unsubscribed) { + return MutableList.Empty + } + let loop = true + let polled: A | MutableList.Empty = MutableList.Empty + while (loop) { + if (this.subscriberHead === this.self.publisherTail) { + loop = false + } else { + const elem = this.subscriberHead.next!.value + if (elem !== AbsentValue) { + polled = elem + this.subscriberHead.subscribers -= 1 + if (this.subscriberHead.subscribers === 0) { + this.self.publisherHead = this.self.publisherHead.next! + this.self.publisherHead.value = AbsentValue + this.self.subscribersIndex += 1 + } + loop = false + } + this.subscriberHead = this.subscriberHead.next! + this.subscriberIndex += 1 + } + } + return polled + } + + pollUpTo(n: number): Array { + const builder: Array = [] + let i = 0 + while (i !== n) { + const a = this.poll() + if (a === MutableList.Empty) { + i = n + } else { + builder.push(a) + i += 1 + } + } + return builder + } + + unsubscribe(): void { + if (!this.unsubscribed) { + this.unsubscribed = true + this.self.publisherTail.subscribers -= 1 + while (this.subscriberHead !== this.self.publisherTail) { + if (this.subscriberHead.next!.value !== AbsentValue) { + this.subscriberHead.subscribers -= 1 + if (this.subscriberHead.subscribers === 0) { + this.self.publisherHead = this.self.publisherHead.next! + this.self.publisherHead.value = AbsentValue + this.self.subscribersIndex += 1 + } + } + this.subscriberHead = this.subscriberHead.next! + } + } + } +} + +class SubscriptionImpl implements Subscription { + readonly [SubscriptionTypeId] = { + _A: identity + } + + readonly pubsub: PubSub.Atomic + readonly subscribers: PubSub.Subscribers + readonly subscription: PubSub.BackingSubscription + readonly pollers: MutableList.MutableList> + readonly shutdownHook: Latch.Latch + readonly shutdownFlag: MutableRef.MutableRef + readonly strategy: PubSub.Strategy + readonly replayWindow: PubSub.ReplayWindow + + constructor( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + subscription: PubSub.BackingSubscription, + pollers: MutableList.MutableList>, + shutdownHook: Latch.Latch, + shutdownFlag: MutableRef.MutableRef, + strategy: PubSub.Strategy, + replayWindow: PubSub.ReplayWindow + ) { + this.pubsub = pubsub + this.subscribers = subscribers + this.subscription = subscription + this.pollers = pollers + this.shutdownHook = shutdownHook + this.shutdownFlag = shutdownFlag + this.strategy = strategy + this.replayWindow = replayWindow + } + + pipe() { + return pipeArguments(this, arguments) + } +} + +class PubSubImpl implements PubSub { + readonly [TypeId] = { + _A: identity + } + + readonly pubsub: PubSub.Atomic + readonly subscribers: PubSub.Subscribers + readonly scope: Scope.Closeable + readonly shutdownHook: Latch.Latch + readonly shutdownFlag: MutableRef.MutableRef + readonly strategy: PubSub.Strategy + + constructor( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + scope: Scope.Closeable, + shutdownHook: Latch.Latch, + shutdownFlag: MutableRef.MutableRef, + strategy: PubSub.Strategy + ) { + this.pubsub = pubsub + this.subscribers = subscribers + this.scope = scope + this.shutdownHook = shutdownHook + this.shutdownFlag = shutdownFlag + this.strategy = strategy + } + + pipe() { + return pipeArguments(this, arguments) + } +} + +const makePubSubUnsafe = ( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + scope: Scope.Closeable, + shutdownHook: Latch.Latch, + shutdownFlag: MutableRef.MutableRef, + strategy: PubSub.Strategy +): PubSub => new PubSubImpl(pubsub, subscribers, scope, shutdownHook, shutdownFlag, strategy) + +const ensureCapacity = (capacity: number): void => { + if (capacity <= 0) { + throw new Error(`Cannot construct PubSub with capacity of ${capacity}`) + } +} + +// ----------------------------------------------------------------------------- +// PubSub.Strategy +// ----------------------------------------------------------------------------- + +/** + * Represents the back-pressure strategy for bounded `PubSub` values. + * + * **When to use** + * + * Use to preserve every message for current subscribers when a bounded custom + * `PubSub` should make publishers wait for capacity instead of dropping or + * evicting messages. + * + * **Details** + * + * Publishers wait when the `PubSub` is at capacity, so all current subscribers + * can receive every published message. + * + * **Gotchas** + * + * A slow subscriber can slow down publishers and other subscribers. + * + * @see {@link bounded} for creating bounded PubSubs with back pressure by default + * @see {@link DroppingStrategy} for dropping new messages when capacity is full + * @see {@link SlidingStrategy} for evicting old messages when capacity is full + * + * @category models + * @since 4.0.0 + */ +export class BackPressureStrategy implements PubSub.Strategy { + publishers: MutableList.MutableList< + readonly [A, Deferred.Deferred, boolean] + > = MutableList.make() + + get shutdown(): Effect.Effect { + return Effect.withFiber((fiber) => + Effect.forEach( + MutableList.takeAll(this.publishers), + ([_, deferred, last]) => last ? Deferred.interruptWith(deferred, fiber.id) : Effect.void, + { concurrency: "unbounded", discard: true } + ) + ) + } + + handleSurplus( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + elements: Iterable, + isShutdown: MutableRef.MutableRef + ): Effect.Effect { + return Effect.suspend(() => { + const deferred = Deferred.makeUnsafe() + this.offerUnsafe(elements, deferred) + this.onPubSubEmptySpaceUnsafe(pubsub, subscribers) + this.completeSubscribersUnsafe(pubsub, subscribers) + return (MutableRef.get(isShutdown) ? Effect.interrupt : Deferred.await(deferred)).pipe( + Effect.onInterrupt(() => { + this.removeUnsafe(deferred) + return Effect.void + }) + ) + }) + } + + onPubSubEmptySpaceUnsafe( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers + ): void { + let keepPolling = true + while (keepPolling && !pubsub.isFull()) { + const publisher = MutableList.take(this.publishers) + if (publisher === MutableList.Empty) { + keepPolling = false + } else { + const [value, deferred] = publisher + const published = pubsub.publish(value) + if (published && publisher[2]) { + Deferred.doneUnsafe(deferred, Exit.succeed(true)) + } else if (!published) { + MutableList.prepend(this.publishers, publisher) + } + this.completeSubscribersUnsafe(pubsub, subscribers) + } + } + } + + completePollersUnsafe( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + subscription: PubSub.BackingSubscription, + pollers: MutableList.MutableList> + ): void { + return strategyCompletePollersUnsafe(this, pubsub, subscribers, subscription, pollers) + } + + completeSubscribersUnsafe(pubsub: PubSub.Atomic, subscribers: PubSub.Subscribers): void { + return strategyCompleteSubscribersUnsafe(this, pubsub, subscribers) + } + + private offerUnsafe(elements: Iterable, deferred: Deferred.Deferred): void { + const iterator = elements[Symbol.iterator]() + let next: IteratorResult = iterator.next() + if (!next.done) { + // oxlint-disable-next-line no-constant-condition + while (1) { + const value = next.value + next = iterator.next() + if (next.done) { + MutableList.append(this.publishers, [value, deferred, true]) + break + } + MutableList.append(this.publishers, [value, deferred, false]) + } + } + } + + removeUnsafe(deferred: Deferred.Deferred): void { + MutableList.filter(this.publishers, ([_, d]) => d !== deferred) + } +} + +/** + * Represents the dropping strategy for bounded `PubSub` values. + * + * **When to use** + * + * Use to keep publishers fast by dropping new messages when the `PubSub` is at + * capacity. + * + * **Details** + * + * A publish that arrives while the `PubSub` is full is dropped instead of + * waiting for capacity. + * + * **Gotchas** + * + * Subscribers may miss messages published while they are subscribed. + * + * **Example** (Applying a dropping strategy) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * 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() + * }) + * + * 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) + * + * // Subscribers will only see the first two messages + * const messages = yield* PubSub.takeAll(subscription) + * return { published: [pub1, pub2, pub3], messages } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { published: [true, true, false], messages: ["msg1", "msg2"] } + * ``` + * + * @category models + * @since 4.0.0 + */ +export class DroppingStrategy implements PubSub.Strategy { + get shutdown(): Effect.Effect { + return Effect.void + } + + handleSurplus( + _pubsub: PubSub.Atomic, + _subscribers: PubSub.Subscribers, + _elements: Iterable, + _isShutdown: MutableRef.MutableRef + ): Effect.Effect { + return Effect.succeed(false) + } + + onPubSubEmptySpaceUnsafe( + _pubsub: PubSub.Atomic, + _subscribers: PubSub.Subscribers + ): void { + // + } + + completePollersUnsafe( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + subscription: PubSub.BackingSubscription, + pollers: MutableList.MutableList> + ): void { + return strategyCompletePollersUnsafe(this, pubsub, subscribers, subscription, pollers) + } + + completeSubscribersUnsafe(pubsub: PubSub.Atomic, subscribers: PubSub.Subscribers): void { + return strategyCompleteSubscribersUnsafe(this, pubsub, subscribers) + } +} + +/** + * Represents the sliding strategy for bounded `PubSub` values. + * + * **When to use** + * + * Use to keep the most recent messages when the `PubSub` is at capacity. + * + * **Details** + * + * New messages are accepted by evicting older messages from the bounded + * `PubSub`. + * + * **Gotchas** + * + * Slow subscribers may miss older messages that are evicted before they are + * consumed. + * + * **Example** (Applying a sliding strategy) + * + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" + * + * 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() + * }) + * + * 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 + * + * // Subscribers will see the most recent messages + * return yield* PubSub.takeAll(subscription) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => ["msg3", "msg4"] + * ``` + * + * @category models + * @since 4.0.0 + */ +export class SlidingStrategy implements PubSub.Strategy { + get shutdown(): Effect.Effect { + return Effect.void + } + + handleSurplus( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + elements: Iterable, + _isShutdown: MutableRef.MutableRef + ): Effect.Effect { + return Effect.sync(() => { + this.slidingPublishUnsafe(pubsub, elements) + this.completeSubscribersUnsafe(pubsub, subscribers) + return true + }) + } + + onPubSubEmptySpaceUnsafe( + _pubsub: PubSub.Atomic, + _subscribers: PubSub.Subscribers + ): void { + // + } + + completePollersUnsafe( + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + subscription: PubSub.BackingSubscription, + pollers: MutableList.MutableList> + ): void { + return strategyCompletePollersUnsafe(this, pubsub, subscribers, subscription, pollers) + } + + completeSubscribersUnsafe(pubsub: PubSub.Atomic, subscribers: PubSub.Subscribers): void { + return strategyCompleteSubscribersUnsafe(this, pubsub, subscribers) + } + + slidingPublishUnsafe(pubsub: PubSub.Atomic, elements: Iterable): void { + const it = elements[Symbol.iterator]() + let next = it.next() + if (!next.done && pubsub.capacity > 0) { + let a = next.value + let loop = true + while (loop) { + pubsub.slide() + const pub = pubsub.publish(a) + if (pub && (next = it.next()) && !next.done) { + a = next.value + } else if (pub) { + loop = false + } + } + } + } +} + +const strategyCompletePollersUnsafe = ( + strategy: PubSub.Strategy, + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers, + subscription: PubSub.BackingSubscription, + pollers: MutableList.MutableList> +): void => { + let keepPolling = true + while (keepPolling && !subscription.isEmpty()) { + const poller = MutableList.take(pollers) + if (poller === MutableList.Empty) { + removeSubscribers(subscribers, subscription, pollers) + if (pollers.length === 0) { + keepPolling = false + } else { + addSubscribers(subscribers, subscription, pollers) + } + } else { + const pollResult = subscription.poll() + if (pollResult === MutableList.Empty) { + MutableList.prepend(pollers, poller) + } else { + Deferred.doneUnsafe(poller, Exit.succeed(pollResult)) + strategy.onPubSubEmptySpaceUnsafe(pubsub, subscribers) + } + } + } +} + +const strategyCompleteSubscribersUnsafe = ( + strategy: PubSub.Strategy, + pubsub: PubSub.Atomic, + subscribers: PubSub.Subscribers +): void => { + for ( + const [subscription, pollersSet] of subscribers + ) { + for (const pollers of pollersSet) { + strategy.completePollersUnsafe(pubsub, subscribers, subscription, pollers) + } + } +} + +interface ReplayNode { + value: A | AbsentValue + index: number + next: ReplayNode | null +} + +class ReplayBuffer { + readonly capacity: number + 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(value: A, publisherIndex: number): void { + this.slideValues[this.index % this.capacity] = { + value, + index: publisherIndex + } + this.index++ + } + 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 + if (this.size === this.capacity) { + this.head = this.head.next! + } else { + this.size += 1 + } + return index + } + offerAll(as: Iterable): void { + for (const a of as) { + this.offer(a) + } + } +} + +class ReplayWindowImpl implements PubSub.ReplayWindow { + readonly buffer: ReplayBuffer + readonly values: Array + index = 0 + remaining: number + slideIndex: number + newestIndex = -1 + + constructor(buffer: ReplayBuffer) { + this.buffer = buffer + this.remaining = buffer.size + 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 + } + 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-- + if (this.remaining === 0) { + this.close() + } + return value as A + } + takeN(n: number): Array { + const len = Math.min(n, this.remaining) + const items = new Array(len) + for (let i = 0; i < len; i++) { + items[i] = this.take()! + } + return items + } + takeAll(): Array { + return this.takeN(this.remaining) + } +} + +const emptyReplayWindow: PubSub.ReplayWindow = { + remaining: 0, + take: () => undefined, + takeN: () => [], + takeAll: () => [], + close: () => void 0 +} diff --git a/.repos/effect/packages/effect/src/Pull.ts b/.repos/effect/packages/effect/src/Pull.ts new file mode 100644 index 000000000..514b66e02 --- /dev/null +++ b/.repos/effect/packages/effect/src/Pull.ts @@ -0,0 +1,392 @@ +/** + * Models one low-level pull step for stream-like consumers. + * + * A `Pull` is an `Effect` that can produce one `A`, fail with an + * ordinary error `E`, or signal end-of-input with `Cause.Done`. The + * separate done signal lets low-level consumers distinguish normal completion + * from failure. This module includes type extractors and helpers for detecting, + * filtering, catching, converting, and matching done signals separately from + * ordinary failures. + * + * @since 4.0.0 + */ +import * as Cause from "./Cause.ts" +import type { Effect } from "./Effect.ts" +import * as Exit from "./Exit.ts" +import * as Filter from "./Filter.ts" +import { dual } from "./Function.ts" +import * as internalEffect from "./internal/effect.ts" +import * as Result from "./Result.ts" + +/** + * An effectful pull step that either produces a value, fails with `E`, or + * signals completion with `Cause.Done`. + * + * **When to use** + * + * Use to model one low-level pull step when a consumer repeatedly evaluates an + * effect that may emit a value, fail normally, or signal normal completion + * through `Cause.Done`. + * + * **Details** + * + * `Pull` represents completion in the error channel so low-level stream + * consumers can distinguish ordinary failures from end-of-input and carry a + * leftover value when needed. + * + * @category models + * @since 4.0.0 + */ +export interface Pull + extends Effect, R> +{} + +/** + * Extracts the success type from a Pull type. + * + * **When to use** + * + * Use to derive the value produced by an existing `Pull` when declaring + * reusable type aliases, low-level stream helpers, or function signatures. + * + * @see {@link Error} for extracting the ordinary failure type + * @see {@link Leftover} for extracting the completion leftover type + * @see {@link Services} for extracting the required services type instead + * + * @category utility types + * @since 4.0.0 + */ +export type Success

= P extends Effect ? _A : never + +/** + * Extracts the error type from a Pull type, excluding Done errors. + * + * **When to use** + * + * Use to derive only the ordinary failure type from a `Pull` when declaring + * wrappers or APIs that handle completion separately. + * + * @see {@link Success} for extracting the pulled value type instead + * @see {@link Leftover} for extracting the completion leftover type + * @see {@link Services} for extracting the required services type instead + * @see {@link ExcludeDone} for excluding `Cause.Done` from an error union + * + * @category utility types + * @since 4.0.0 + */ +export type Error

= P extends Effect ? _E extends Cause.Done ? never : _E + : never + +/** + * Extracts the leftover type from a Pull type. + * + * **When to use** + * + * Use to derive the completion leftover type from an existing `Pull` when + * declaring reusable type aliases or helper signatures that preserve a pull's + * done value. + * + * @see {@link Success} for extracting the pulled value type instead + * @see {@link Error} for extracting the ordinary failure type, excluding `Cause.Done` + * @see {@link Services} for extracting the required services type instead + * + * @category utility types + * @since 4.0.0 + */ +export type Leftover

= P extends Effect ? _E extends Cause.Done ? _L : never + : never + +/** + * Extracts the service requirements (context) type from a Pull type. + * + * **When to use** + * + * Use to derive the context requirements of a generic or inferred `Pull` + * without restating its `R` type parameter. + * + * @see {@link Success} for extracting the pulled value type instead + * @see {@link Error} for extracting the ordinary failure type + * @see {@link Leftover} for extracting the completion leftover type + * + * @category utility types + * @since 4.0.0 + */ +export type Services

= P extends Effect ? _R : never + +/** + * Excludes `Cause.Done` completion signals from an error type union. + * + * **When to use** + * + * Use to describe the ordinary error type that remains after `Cause.Done` + * completion signals have been handled or filtered out of an error union. + * + * @see {@link Error} for extracting ordinary failures from a `Pull` + * @see {@link Leftover} for extracting the completion leftover type + * + * @category utility types + * @since 4.0.0 + */ +export type ExcludeDone = Exclude> + +// ----------------------------------------------------------------------------- +// Done +// ----------------------------------------------------------------------------- + +/** + * Handles `Cause.Done` failures in an effect while leaving ordinary failures + * in the error channel. + * + * **When to use** + * + * Use to recover from a `Cause.Done` completion signal in an effect, such as + * turning a pull leftover value into a successful recovery effect while + * preserving ordinary failures. + * + * **Details** + * + * The handler receives the done leftover value and may recover with a new + * effect. Non-done errors are preserved. + * + * @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 error handling + * @since 4.0.0 + */ +export const catchDone: { + (f: (leftover: Cause.Done.Extract) => Effect): ( + self: Effect + ) => Effect | E2, R | R2> + ( + self: Effect, + f: (leftover: Cause.Done.Extract) => Effect + ): Effect | E2, R | R2> +} = dual(2, ( + effect: Effect, + f: (leftover: Cause.Done.Extract) => Effect +): Effect | E2, R | R2> => + internalEffect.catchCauseFilter(effect, filterDoneLeftover as any, (l: any) => f(l)) as any) + +/** + * Checks whether a Cause contains any done errors. + * + * **When to use** + * + * Use when you need to test whether a pull failure cause represents normal + * completion and only need a boolean result. + * + * @see {@link isDoneFailure} for checking a single `Cause.Reason` + * @see {@link filterDone} for extracting the `Cause.Done` value from a `Cause` + * @see {@link filterNoDone} for selecting causes with no done failures + * + * @category predicates + * @since 4.0.0 + */ +export const isDoneCause = (cause: Cause.Cause): boolean => cause.reasons.some(isDoneFailure) + +/** + * Checks whether a `Cause.Reason` is a `Fail` reason whose error is a + * `Cause.Done` signal. + * + * **When to use** + * + * Use when you need to identify done completion reasons while traversing + * `cause.reasons`, before handling ordinary failures. + * + * @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 guards + * @since 4.0.0 + */ +export const isDoneFailure = ( + failure: Cause.Reason +): failure is Cause.Fail> => failure._tag === "Fail" && Cause.isDone(failure.error) + +/** + * Finds a `Cause.Done` failure in a `Cause`. + * + * **When to use** + * + * Use to separate `Cause.Done` completion from ordinary causes while preserving + * the typed done value. + * + * **Details** + * + * 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 filtering + * @since 4.0.0 + */ +export const filterDone: ( + input: Cause.Cause +) => 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. + * + * **When to use** + * + * Use to detect `Cause.Done` completion in a `Cause` when the completion value + * is not part of the downstream logic. + * + * **Details** + * + * 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 filtering + * @since 4.0.0 + */ +export const filterDoneVoid: ( + input: Cause.Cause +) => Result.Result>> = filterDone as any + +/** + * Keeps a `Cause` only when it contains no `Cause.Done` failures. + * + * **When to use** + * + * Use to select ordinary failure causes for handling while leaving `Cause.Done` + * completion causes outside that handler. + * + * **Details** + * + * Returns a successful `Result` with the cause when every failure is non-done; + * otherwise returns a failed `Result` with the original cause. + * + * @see {@link filterDone} for the inverse typed done filter + * @see {@link filterDoneVoid} for done detection when the payload is not needed + * + * @category filtering + * @since 4.0.0 + */ +export const filterNoDone: ( + input: Cause.Cause +) => Result.Result< + Cause.Cause>, + Cause.Cause +> = Filter.fromPredicate((cause: Cause.Cause) => + cause.reasons.every((failure) => !isDoneFailure(failure)) +) as any + +/** + * Filters a Cause to extract the leftover value from done errors. + * + * **When to use** + * + * Use to extract only the leftover value carried by a `Cause.Done` completion + * signal. + * + * @category filtering + * @since 4.0.0 + */ +export const filterDoneLeftover: ( + cause: Cause.Cause +) => 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 + * completion. + * + * **When to use** + * + * Use to produce an `Exit` for finalizing a low-level pull workflow when a + * `Cause.Done` signal should be treated as success and any remaining cause + * should fail. + * + * **Details** + * + * 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 converting + * @since 4.0.0 + */ +export const doneExitFromCause = (cause: Cause.Cause): Exit.Exit, ExcludeDone> => { + const halt = filterDone(cause) + return !Result.isFailure(halt) ? Exit.succeed(halt.success.value as any) : Exit.failCause(halt.failure) +} + +/** + * Pattern matches on a Pull, handling success, failure, and done cases. + * + * **When to use** + * + * Use to handle all three `Pull` outcomes with effectful handlers. + * + * **Example** (Matching Pull outcomes) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Pull } from "effect" + * + * const pull = Cause.done("stream ended") + * + * const result = Pull.matchEffect(pull, { + * onSuccess: (value) => Effect.succeed(`Got value: ${value}`), + * onFailure: (cause) => 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 + * @since 4.0.0 + */ +export const matchEffect: { + (options: { + readonly onSuccess: (value: A) => Effect + readonly onFailure: (failure: Cause.Cause) => Effect + readonly onDone: (leftover: L) => Effect + }): (self: Pull) => Effect + (self: Pull, options: { + readonly onSuccess: (value: A) => Effect + readonly onFailure: (failure: Cause.Cause) => Effect + readonly onDone: (leftover: L) => Effect + }): Effect +} = dual(2, (self: Pull, options: { + readonly onSuccess: (value: A) => Effect + readonly onFailure: (failure: Cause.Cause) => Effect + readonly onDone: (leftover: L) => Effect +}): Effect => + internalEffect.matchCauseEffect(self, { + onSuccess: options.onSuccess, + onFailure: (cause): Effect => { + const halt = filterDone(cause) + return !Result.isFailure(halt) ? options.onDone(halt.success.value as L) : options.onFailure(halt.failure) + } + })) diff --git a/.repos/effect/packages/effect/src/Queue.ts b/.repos/effect/packages/effect/src/Queue.ts new file mode 100644 index 000000000..cad92efc3 --- /dev/null +++ b/.repos/effect/packages/effect/src/Queue.ts @@ -0,0 +1,1997 @@ +/** + * Passes values asynchronously between fibers. + * + * A `Queue` accepts values, hands each value to one consumer in offer + * order, and can complete, fail, interrupt, or shut down. Queues can be bounded + * or unbounded, and bounded queues can suspend, drop, or slide values when + * producers are faster than consumers. + * + * @since 3.8.0 + */ +import * as Arr from "./Array.ts" +import type { Cause, Done } from "./Cause.ts" +import type { Effect } from "./Effect.ts" +import type { Exit, Failure } from "./Exit.ts" +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 internalEffect from "./internal/effect.ts" +import * as MutableList from "./MutableList.ts" +import * as Option from "./Option.ts" +import { hasProperty } from "./Predicate.ts" +import * as Pull from "./Pull.ts" +import type { SchedulerDispatcher } from "./Scheduler.ts" +import type * as Types from "./Types.ts" + +const TypeId = "~effect/Queue" +const EnqueueTypeId = "~effect/Queue/Enqueue" +const DequeueTypeId = "~effect/Queue/Dequeue" + +/** + * Type guard to check if a value is a Queue. + * + * **When to use** + * + * Use to narrow an unknown value to a full `Queue` before passing it to APIs + * that need both offering and taking capabilities. + * + * @see {@link isEnqueue} for checking values that only need write access + * @see {@link isDequeue} for checking values that only need read access + * + * @category guards + * @since 2.0.0 + */ +export const isQueue = ( + u: unknown +): u is Queue => hasProperty(u, TypeId) + +/** + * Type guard to check if a value is an Enqueue. + * + * **When to use** + * + * Use to narrow an unknown value before calling queue operations that require + * write-side access. + * + * **Gotchas** + * + * A full `Queue` also satisfies this guard because every queue includes the + * enqueue side. + * + * @see {@link isQueue} for checking for a full read-write queue handle + * @see {@link isDequeue} for checking for the read side of a queue + * @see {@link asEnqueue} for narrowing an existing `Queue` to its write-only interface + * + * @category guards + * @since 2.0.0 + */ +export const isEnqueue = ( + u: unknown +): u is Enqueue => hasProperty(u, EnqueueTypeId) + +/** + * Type guard to check if a value is a Dequeue. + * + * **When to use** + * + * Use to narrow an unknown value before passing it to read-side queue + * operations. + * + * @see {@link Dequeue} for the read-side queue handle checked by this guard + * @see {@link isQueue} for checking for a full read-write queue handle + * @see {@link isEnqueue} for checking for the write side of a queue + * @see {@link asDequeue} for narrowing an existing `Queue` to its read-only interface + * + * @category guards + * @since 2.0.0 + */ +export const isDequeue = ( + u: unknown +): u is Dequeue => hasProperty(u, DequeueTypeId) + +/** + * Converts a `Queue` to its write-only `Enqueue` interface. + * + * **When to use** + * + * Use to expose only the producer side of a `Queue` to code that should offer + * values or signal queue lifecycle. + * + * **Gotchas** + * + * This is a type-level capability restriction. It returns the same queue + * object, so it does not hide read operations at runtime. + * + * @see {@link asDequeue} for exposing only the read side of a `Queue` + * @see {@link Enqueue} for the write-only queue handle returned by this conversion + * + * @category converting + * @since 4.0.0 + */ +export const asEnqueue = (self: Queue): Enqueue => self + +/** + * Narrows a `Queue` to a `Dequeue`, exposing the consumer side of the queue. + * + * **When to use** + * + * Use to pass a queue to code that should consume values while keeping + * producer-side operations out of that code's TypeScript type. + * + * **Gotchas** + * + * This is a type-level narrowing operation. It returns the same queue object + * and does not create a runtime wrapper. + * + * @see {@link asEnqueue} for narrowing a queue to its producer side + * @see {@link Dequeue} for the consumer-side queue handle returned by this function + * + * @category converting + * @since 4.0.0 + */ +export const asDequeue: (self: Queue) => Dequeue = identity + +/** + * An `Enqueue` is a queue that can be offered to. + * + * **Details** + * + * This interface represents the write-only part of a Queue, allowing you to offer + * elements to the queue but not take elements from it. + * + * **Example** (Offering through enqueue handles) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * // Function that only needs write access to a queue + * const producer = (enqueue: Queue.Enqueue) => + * Effect.gen(function*() { + * yield* Queue.offer(enqueue, "hello") + * yield* Queue.offerAll(enqueue, ["world", "!"]) + * }) + * + * 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 + * @since 2.0.0 + */ +export interface Enqueue extends Inspectable { + readonly [EnqueueTypeId]: Enqueue.Variance + readonly strategy: "suspend" | "dropping" | "sliding" + readonly dispatcher: SchedulerDispatcher + capacity: number + messages: MutableList.MutableList + state: Queue.State + scheduleRunning: boolean +} + +/** + * Companion namespace containing type-level metadata for the `Enqueue` + * write-only queue interface. + * + * @since 2.0.0 + */ +export declare namespace Enqueue { + /** + * Type-level variance marker for `Enqueue`. + * + * **Details** + * + * `Enqueue` is contravariant in both its offered value type `A` and failure + * type `E`, because values and failures flow into the queue through this + * handle. + * + * @category models + * @since 4.0.0 + */ + export interface Variance { + _A: Types.Contravariant + _E: Types.Contravariant + } +} + +/** + * A `Dequeue` is a queue that can be taken from. + * + * **Details** + * + * This interface represents the read-only part of a Queue, allowing you to take + * elements from the queue but not offer elements to it. + * + * **Example** (Taking through dequeue handles) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // A Dequeue can only take elements + * const dequeue: Queue.Dequeue = queue + * + * // Pre-populate the queue + * yield* Queue.offerAll(queue, ["a", "b", "c"]) + * + * // Take elements using dequeue interface + * const item = yield* Queue.take(dequeue) + * return item + * }) + * + * await Effect.runPromise(program) // => "a" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Dequeue extends Inspectable { + readonly [DequeueTypeId]: Dequeue.Variance + readonly strategy: "suspend" | "dropping" | "sliding" + readonly dispatcher: SchedulerDispatcher + capacity: number + messages: MutableList.MutableList + state: Queue.State + scheduleRunning: boolean +} + +/** + * Companion namespace containing type-level metadata for the `Dequeue` + * read-only queue interface. + * + * @since 2.0.0 + */ +export declare namespace Dequeue { + /** + * Type-level variance marker for `Dequeue`. + * + * **Details** + * + * `Dequeue` is covariant in both the taken value type `A` and failure type + * `E`, because values and failures are observed through this handle. + * + * @category models + * @since 4.0.0 + */ + export interface Variance { + _A: Types.Covariant + _E: Types.Covariant + } +} + +/** + * A `Queue` is an asynchronous queue that can be offered to and taken from. + * + * **Details** + * + * It also supports signaling that it is done or failed. + * + * **Example** (Offering and taking queue values) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a bounded queue + * const queue = yield* Queue.bounded(10) + * + * // Producer: offer items to the queue + * yield* Queue.offer(queue, "hello") + * yield* Queue.offerAll(queue, ["world", "!"]) + * + * // Consumer: take items from the queue + * const item1 = yield* Queue.take(queue) + * const item2 = yield* Queue.take(queue) + * const item3 = yield* Queue.take(queue) + * + * return [item1, item2, item3] + * }) + * + * await Effect.runPromise(program) // => ["hello", "world", "!"] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Queue extends Enqueue, Dequeue { + readonly [TypeId]: Queue.Variance +} + +/** + * Companion namespace containing type-level metadata and low-level state types + * for `Queue`. + * + * @since 2.0.0 + */ +export declare namespace Queue { + /** + * Type-level variance marker for `Queue`. + * + * **Details** + * + * A full `Queue` is invariant in both `A` and `E` because the same handle can + * both produce and consume values and failures. + * + * @category models + * @since 4.0.0 + */ + export interface Variance { + _A: Types.Invariant + _E: Types.Invariant + } + + /** + * Tagged state of a `Queue`. + * + * **Details** + * + * `Open` queues can accept offers and takers, `Closing` queues are + * completing with a stored failure exit, and `Done` queues have finished. + * This is low-level metadata exposed by the queue model; most users should + * inspect queues through the public operations. + * + * @category models + * @since 4.0.0 + */ + export type State = + | { + readonly _tag: "Open" + readonly takers: Set<(_: Effect) => void> + readonly offers: Set> + readonly awaiters: Set<(_: Effect) => void> + } + | { + readonly _tag: "Closing" + readonly takers: Set<(_: Effect) => void> + readonly offers: Set> + readonly awaiters: Set<(_: Effect) => void> + readonly exit: Failure + } + | { + readonly _tag: "Done" + readonly exit: Failure + } + + /** + * Represents a suspended offer waiting to be admitted to a bounded queue. + * + * **Details** + * + * An entry is either a single message or a batch with an offset into its + * remaining messages, plus a resume callback that completes the suspended + * offer when the queue can accept more input. + * + * @category models + * @since 4.0.0 + */ + export type OfferEntry = + | { + readonly _tag: "Array" + readonly remaining: Array + offset: number + readonly resume: (_: Effect>) => void + } + | { + readonly _tag: "Single" + readonly message: A + readonly resume: (_: Effect) => void + } +} + +const variance = { + _A: identity, + _E: identity +} +const QueueProto = { + [TypeId]: variance, + [EnqueueTypeId]: variance, + [DequeueTypeId]: variance, + ...PipeInspectableProto, + toJSON(this: Queue) { + return { + _id: "effect/Queue", + state: this.state._tag, + size: sizeUnsafe(this) + } + } +} + +/** + * Creates a `Queue` with optional capacity and overflow strategy. + * + * **Details** + * + * By default the queue is unbounded and uses the `"suspend"` strategy. Provide + * `capacity` for a bounded queue and choose `"suspend"`, `"dropping"`, or + * `"sliding"` to control what happens when the queue is full. The returned + * queue can be offered to, taken from, failed, ended, interrupted, or shut down. + * + * **Example** (Creating queues) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.make() + * + * // add messages to the queue + * yield* Queue.offer(queue, 1) + * yield* Queue.offer(queue, 2) + * yield* Queue.offerAll(queue, [3, 4, 5]) + * + * // take messages from the queue + * const messages = yield* Queue.takeAll(queue) + * + * // signal that the queue is done + * yield* Queue.end(queue) + * const done = yield* Effect.flip(Queue.take(queue)) + * + * // signal that another queue has failed + * const failedQueue = yield* Queue.make() + * const failed = yield* Queue.fail(failedQueue, "boom") + * return { messages, done, failed } + * }) + * + * await Effect.runPromise(program) // => { messages: [1, 2, 3, 4, 5], done: Cause.Done(), failed: true } + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + options?: { + readonly capacity?: number | undefined + readonly strategy?: "suspend" | "dropping" | "sliding" | undefined + } | undefined +): Effect> => + core.withFiber((fiber) => { + const self = Object.create(QueueProto) + self.dispatcher = fiber.currentDispatcher + self.capacity = options?.capacity ?? Number.POSITIVE_INFINITY + self.strategy = options?.strategy ?? "suspend" + self.messages = MutableList.make() + self.scheduleRunning = false + self.state = { + _tag: "Open", + takers: new Set(), + offers: new Set(), + awaiters: new Set() + } + return internalEffect.succeed(self) + }) + +/** + * Creates a bounded queue with the specified capacity that uses backpressure strategy. + * + * **Details** + * + * When the queue reaches capacity, producers will be suspended until space becomes available. + * This ensures all messages are processed but may slow down producers. + * + * **Example** (Creating bounded queues) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(5) + * + * // This will succeed as queue has capacity + * yield* Queue.offer(queue, "first") + * yield* Queue.offer(queue, "second") + * + * const size = yield* Queue.size(queue) + * return size + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const bounded = (capacity: number): Effect> => make({ capacity }) + +/** + * Creates a bounded queue with sliding strategy. When the queue reaches capacity, + * new elements are added and the oldest elements are dropped. + * + * **When to use** + * + * Use when you need producer offers not to block and can accept dropping the + * oldest messages, such as when maintaining a rolling window of recent values. + * + * **Example** (Creating sliding queues) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.sliding(3) + * + * // Fill the queue to capacity + * yield* Queue.offer(queue, 1) + * yield* Queue.offer(queue, 2) + * yield* Queue.offer(queue, 3) + * + * // This will succeed, dropping the oldest element (1) + * yield* Queue.offer(queue, 4) + * + * const all = yield* Queue.takeAll(queue) + * return all + * }) + * + * await Effect.runPromise(program) // => [2, 3, 4] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const sliding = (capacity: number): Effect> => make({ capacity, strategy: "sliding" }) + +/** + * Creates a bounded queue with dropping strategy. When the queue reaches capacity, + * new elements are dropped and the offer operation returns false. + * + * **When to use** + * + * Use when you need producer offers not to block while preserving existing + * queued messages, even if new messages may be dropped when the queue is full. + * + * **Example** (Creating dropping queues) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.dropping(2) + * + * // Fill the queue to capacity + * const success1 = yield* Queue.offer(queue, 1) + * const success2 = yield* Queue.offer(queue, 2) + * + * // This will be dropped + * const success3 = yield* Queue.offer(queue, 3) + * + * const all = yield* Queue.takeAll(queue) + * return [success1, success2, success3, all] + * }) + * + * await Effect.runPromise(program) // => [true, true, false, [1, 2]] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const dropping = (capacity: number): Effect> => + make({ capacity, strategy: "dropping" }) + +/** + * Creates an unbounded queue that can grow to any size without blocking producers. + * + * **When to use** + * + * Use when you need producers to add messages without backpressure and accept + * unbounded memory growth. + * + * **Example** (Creating unbounded queues) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.unbounded() + * + * // Producers can always add messages without blocking + * yield* Queue.offer(queue, "message1") + * yield* Queue.offer(queue, "message2") + * yield* Queue.offerAll(queue, ["message3", "message4", "message5"]) + * + * // Check current size + * const size = yield* Queue.size(queue) + * + * // Take all messages + * const messages = yield* Queue.takeAll(queue) + * return { size, messages } + * }) + * + * await Effect.runPromise(program) // => { size: 5, messages: ["message1", "message2", "message3", "message4", "message5"] } + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unbounded = (): Effect> => make() + +/** + * Adds a message to the queue. Returns `false` if the queue is done. + * + * **Details** + * + * For bounded queues, this operation may suspend if the queue is at capacity, + * depending on the backpressure strategy. For dropping/sliding queues, it may + * return false or succeed immediately by dropping/sliding existing messages. + * + * **Example** (Offering a value) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(3) + * + * // Successfully add messages to queue + * const success1 = yield* Queue.offer(queue, 1) + * const success2 = yield* Queue.offer(queue, 2) + * + * // Queue state + * const size = yield* Queue.size(queue) + * return { offered: [success1, success2], size } + * }) + * + * await Effect.runPromise(program) // => { offered: [true, true], size: 2 } + * ``` + * + * @category offering + * @since 2.0.0 + */ +export const offer = (self: Enqueue, message: Types.NoInfer): Effect => + internalEffect.suspend(() => { + if (self.state._tag !== "Open") { + return exitFalse + } else if (self.messages.length >= self.capacity) { + switch (self.strategy) { + case "dropping": + return exitFalse + case "suspend": + if (self.capacity <= 0 && self.state.takers.size > 0) { + MutableList.append(self.messages, message) + releaseTakers(self as Queue) + return exitTrue + } + return offerRemainingSingle(self as Queue, message) + case "sliding": + MutableList.take(self.messages) + MutableList.append(self.messages, message) + return exitTrue + } + } + MutableList.append(self.messages, message) + scheduleReleaseTaker(self as Queue) + return exitTrue + }) + +/** + * Adds a message to the queue synchronously. Returns `false` if the queue is done. + * + * **When to use** + * + * Use when you are already in synchronous queue internals or a performance + * boundary where wrapping the mutation in `Effect` is intentionally avoided. + * + * **Gotchas** + * + * This is an unsafe operation that directly modifies the queue without Effect wrapping. + * Use this only when you're certain about the synchronous nature of the operation. + * + * **Example** (Offering a value synchronously) + * + * ```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*() { + * const queue = yield* Queue.bounded(3) + * + * // Add messages synchronously using unsafe API + * const success1 = Queue.offerUnsafe(queue, 1) + * const success2 = Queue.offerUnsafe(queue, 2) + * + * // Check current size + * const size = Queue.sizeUnsafe(queue) + * return { offered: [success1, success2], size } + * }) + * + * await Effect.runPromise(program) // => { offered: [true, true], size: 2 } + * ``` + * + * @category offering + * @since 4.0.0 + */ +export const offerUnsafe = (self: Enqueue, message: Types.NoInfer): boolean => { + if (self.state._tag !== "Open") { + return false + } else if (self.messages.length >= self.capacity) { + if (self.strategy === "sliding") { + MutableList.take(self.messages) + MutableList.append(self.messages, message) + return true + } else if (self.capacity <= 0 && self.state.takers.size > 0) { + MutableList.append(self.messages, message) + releaseTakers(self as Queue) + return true + } + return false + } + MutableList.append(self.messages, message) + scheduleReleaseTaker(self as Queue) + return true +} + +/** + * Adds multiple messages to the queue. Returns the remaining messages that + * were not added. + * + * **When to use** + * + * Use when producers can submit a batch at once and need to know which messages + * did not fit under the queue's capacity strategy. + * + * **Details** + * + * For bounded queues, this operation may suspend if the queue doesn't have + * enough capacity. The operation returns an array of messages that couldn't + * be added (empty array means all messages were successfully added). + * + * **Example** (Offering multiple values) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.dropping(3) + * + * // Try to add more messages than capacity without suspending + * const remaining1 = yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) + * return remaining1 + * }) + * + * await Effect.runPromise(program) // => [4, 5] + * ``` + * + * @category offering + * @since 2.0.0 + */ +export const offerAll = (self: Enqueue, messages: Iterable): Effect> => + internalEffect.suspend(() => { + if (self.state._tag !== "Open") { + return internalEffect.succeed(Arr.fromIterable(messages)) + } + const remaining = offerAllUnsafe(self as Queue, messages) + if (remaining.length === 0) { + return core.exitSucceed([]) + } else if (self.strategy === "dropping") { + return internalEffect.succeed(remaining) + } + return offerRemainingArray(self as Queue, remaining) + }) + +/** + * Adds multiple messages to the queue synchronously. Returns the remaining messages that + * were not added. + * + * **When to use** + * + * Use when queue internals or a performance boundary need a synchronous batch + * offer and can handle any messages that do not fit. + * + * **Gotchas** + * + * This is an unsafe operation that directly modifies the queue without Effect wrapping. + * + * **Example** (Offering multiple values synchronously) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * // Create a bounded queue and use unsafe API + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(3) + * + * // Try to add 5 messages to capacity-3 queue using unsafe API + * const remaining = Queue.offerAllUnsafe(queue, [1, 2, 3, 4, 5]) + * + * // Check what's in the queue + * const size = Queue.sizeUnsafe(queue) + * return { remaining, size } + * }) + * + * await Effect.runPromise(program) // => { remaining: [4, 5], size: 3 } + * ``` + * + * @category offering + * @since 4.0.0 + */ +export const offerAllUnsafe = (self: Enqueue, messages: Iterable): Array => { + if (self.state._tag !== "Open") { + return Arr.fromIterable(messages) + } else if ( + self.capacity === Number.POSITIVE_INFINITY || + self.strategy === "sliding" + ) { + MutableList.appendAll(self.messages, messages) + if (self.strategy === "sliding") { + MutableList.takeN(self.messages, self.messages.length - self.capacity) + } + scheduleReleaseTaker(self as Queue) + return [] + } + const free = self.capacity <= 0 + ? self.state.takers.size + : self.capacity - self.messages.length + if (free === 0) { + return Arr.fromIterable(messages) + } + const remaining: Array = [] + let i = 0 + for (const message of messages) { + if (i < free) { + MutableList.append(self.messages, message) + } else { + remaining.push(message) + } + i++ + } + scheduleReleaseTaker(self as Queue) + return remaining +} + +/** + * Fails the queue with an error. If the queue is already done, `false` is + * returned. + * + * **Example** (Failing queues with an error) + * + * ```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") + * + * // Taking from the failed queue fails with the error + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [failed, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Something went wrong")] + * ``` + * + * @category completion + * @since 4.0.0 + */ +export const fail = (self: Enqueue, error: E) => failCause(self, core.causeFail(error)) + +/** + * Fails the queue with a cause. If the queue is already done, `false` is + * returned. + * + * **Example** (Failing queues with a cause) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Create a cause and fail the queue + * const cause = Cause.fail("Queue processing failed") + * const failed = yield* Queue.failCause(queue, cause) + * + * // The queue is now done with the specified failure cause + * 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 + * @since 4.0.0 + */ +export const failCause: { + (cause: Cause): (self: Enqueue) => Effect + (self: Enqueue, cause: Cause): Effect +} = dual( + 2, + (self: Enqueue, cause: Cause): Effect => + internalEffect.sync(() => failCauseUnsafe(self, cause)) +) + +/** + * Fails the queue with a cause synchronously. If the queue is already done, `false` is + * returned. + * + * **When to use** + * + * Use when queue completion must be driven from synchronous internals while + * preserving the full failure `Cause`. + * + * **Gotchas** + * + * This is an unsafe operation that directly modifies the queue without Effect wrapping. + * + * **Example** (Failing queues with a cause synchronously) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Create a cause and fail the queue synchronously + * const cause = Cause.fail("Processing error") + * const failed = Queue.failCauseUnsafe(queue, cause) + * + * // The queue is now done with the specified failure cause + * const exit = Queue.takeUnsafe(queue) + * return [failed, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Processing error"))] + * ``` + * + * @category completion + * @since 4.0.0 + */ +export const failCauseUnsafe = (self: Enqueue, cause: Cause): boolean => { + if (self.state._tag !== "Open") { + return false + } + const exit = core.exitFailCause(cause) + const fail = internalEffect.exitZipRight(exit, exitFailDone) as Failure + if ( + self.state.offers.size === 0 && + self.messages.length === 0 + ) { + finalize(self, fail) + return true + } + self.state = { ...self.state, _tag: "Closing", exit: fail } + return true +} + +/** + * Signals queue completion. + * + * **When to use** + * + * Use to stop accepting new offers while allowing already queued messages to be + * consumed. + * + * **Details** + * + * Returns `false` if the queue is already done. + * + * **Example** (Ending queues) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Add some messages + * yield* Queue.offer(queue, 1) + * yield* Queue.offer(queue, 2) + * + * // Signal completion - no more messages will be accepted + * const ended = yield* Queue.end(queue) + * + * // Trying to offer more messages will return false + * const offerResult = yield* Queue.offer(queue, 3) + * + * // But we can still take existing messages + * const message = yield* Queue.take(queue) + * return [ended, offerResult, message] + * }) + * + * await Effect.runPromise(program) // => [true, false, 1] + * ``` + * + * @category completion + * @since 4.0.0 + */ +export const end = (self: Enqueue): Effect => failCause(self, core.causeFail(core.Done())) + +/** + * Signals queue completion synchronously. + * + * **When to use** + * + * Use when implementing low-level queue integrations that must complete a queue + * without wrapping the operation in `Effect`. + * + * **Details** + * + * Returns `false` if the queue is already done. + * + * **Gotchas** + * + * This is an unsafe operation that directly modifies the queue without Effect wrapping. + * + * **Example** (Ending queues synchronously) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * // Create a queue and use unsafe operations + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Add some messages + * Queue.offerUnsafe(queue, 1) + * Queue.offerUnsafe(queue, 2) + * + * // End the queue synchronously + * const ended = Queue.endUnsafe(queue) + * + * // Existing messages can still be consumed while the queue is closing + * const states = [queue.state._tag] + * + * Queue.takeUnsafe(queue) + * Queue.takeUnsafe(queue) + * + * // After buffered messages are consumed, the queue is done + * states.push(queue.state._tag) + * return { ended, states } + * }) + * + * await Effect.runPromise(program) // => { ended: true, states: ["Closing", "Done"] } + * ``` + * + * @category completion + * @since 4.0.0 + */ +export const endUnsafe = (self: Enqueue) => failCauseUnsafe(self, core.causeFail(core.Done())) + +/** + * Interrupts the queue gracefully, transitioning it to a closing state. + * + * **Details** + * + * This operation stops accepting new offers but allows existing messages to be consumed. + * Once all messages are drained, the queue transitions to the Done state with an interrupt cause. + * + * **Example** (Interrupting queues gracefully) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Add some messages + * yield* Queue.offer(queue, 1) + * yield* Queue.offer(queue, 2) + * + * // Interrupt gracefully - no more offers accepted, but messages can be consumed + * const interrupted = yield* Queue.interrupt(queue) + * + * // Trying to offer more messages will return false + * const offerResult = yield* Queue.offer(queue, 3) + * + * // But we can still take existing messages + * const message1 = yield* Queue.take(queue) + * + * const message2 = yield* Queue.take(queue) + * + * // After all messages are consumed, queue is done + * const isDone = queue.state._tag === "Done" + * return { interrupted, offerResult, messages: [message1, message2], isDone } + * }) + * + * await Effect.runPromise(program) // => { interrupted: true, offerResult: false, messages: [1, 2], isDone: true } + * ``` + * + * @category completion + * @since 4.0.0 + */ +export const interrupt = (self: Enqueue): Effect => + core.withFiber((fiber) => failCause(self, internalEffect.causeInterrupt(fiber.id))) + +/** + * Shuts down the queue immediately, discarding buffered messages and resuming + * pending operations. + * + * **Details** + * + * The operation is idempotent and returns `true`, including when the queue has + * already been shut down or completed. + * + * **Example** (Shutting down queues) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(2) + * + * // Add messages + * yield* Queue.offer(queue, 1) + * yield* Queue.offer(queue, 2) + * + * // Shutdown clears buffered messages and prevents further offers + * const wasShutdown = yield* Queue.shutdown(queue) + * + * // Queue is now done and cleared + * const size = yield* Queue.size(queue) + * return { wasShutdown, size } + * }) + * + * await Effect.runPromise(program) // => { wasShutdown: true, size: 0 } + * ``` + * + * @category completion + * @since 2.0.0 + */ +export const shutdown = (self: Enqueue): Effect => + internalEffect.sync(() => { + if (self.state._tag === "Done") { + return true + } + MutableList.clear(self.messages) + const offers = self.state.offers + finalize(self, self.state._tag === "Open" ? exitInterrupt : self.state.exit) + if (offers.size > 0) { + for (const entry of offers) { + if (entry._tag === "Single") { + entry.resume(exitFalse) + } else { + entry.resume(core.exitSucceed(entry.remaining.slice(entry.offset))) + } + } + offers.clear() + } + return true + }) + +/** + * Takes and returns all currently buffered messages without waiting for more. + * + * **Details** + * + * Returns an empty array when the queue is empty or has completed normally. If + * the queue has failed, the effect fails with the queue's error. + * + * **Example** (Clearing queued values) + * + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Add several messages + * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) + * + * // Clear all messages from the queue + * const messages = yield* Queue.clear(queue) + * + * // Queue is now empty + * const size = yield* Queue.size(queue) + * + * // Clearing empty queue returns empty array + * const empty = yield* Queue.clear(queue) + * return { messages, size, empty } + * }) + * + * await Effect.runPromise(program) // => { messages: [1, 2, 3, 4, 5], size: 0, empty: [] } + * ``` + * + * @category taking + * @since 4.0.0 + */ +export const clear = (self: Dequeue): Effect, Pull.ExcludeDone> => + internalEffect.suspend(() => { + if (self.state._tag === "Done") { + if (Pull.isDoneCause(self.state.exit.cause)) { + return internalEffect.succeed([]) + } + return self.state.exit + } + const messages = takeAllUnsafe(self) + releaseCapacity(self) + return internalEffect.succeed(messages) + }) + +/** + * Takes all currently available messages, waiting until at least one message + * is available when the queue is empty. + * + * **When to use** + * + * Use when consumers should process the next non-empty batch of buffered + * messages instead of repeatedly taking one message at a time. + * + * **Details** + * + * Returns a non-empty array. If the queue completes or fails before a message + * can be taken, the effect fails with the queue's terminal error. + * + * **Example** (Taking all available values) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(5) + * + * // Add several messages + * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) + * + * // Take all available messages + * const messages1 = yield* Queue.takeAll(queue) + * return messages1 + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] + * ``` + * + * @category taking + * @since 2.0.0 + */ +export const takeAll = (self: Dequeue): Effect, E> => + takeBetween(self, 1, Number.POSITIVE_INFINITY) as any + +/** + * Takes all messages from the queue, until the queue has errored or is done. + * + * **Example** (Collecting values until completion) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(5) + * + * // Add several messages + * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) + * yield* Queue.end(queue) + * + * // Collect all available messages + * return yield* Queue.collect(queue) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] + * ``` + * + * @category taking + * @since 4.0.0 + */ +export const collect = (self: Dequeue): Effect, Pull.ExcludeDone> => + internalEffect.suspend(() => { + const out = Arr.empty() + return internalEffect.as( + Pull.catchDone( + internalEffect.whileLoop({ + while: constTrue, + body: constant(takeAll(self)), + step(items: Arr.NonEmptyArray) { + for (let i = 0; i < items.length; i++) { + out.push(items[i]) + } + } + }), + () => internalEffect.void + ), + out + ) + }) as any + +/** + * Takes up to `n` messages from the queue. + * + * **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. + * + * **Example** (Taking a fixed number of values) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Add several messages + * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5, 6, 7]) + * + * // Take exactly 3 messages + * const first3 = yield* Queue.takeN(queue, 3) + * + * // Take exactly 2 more messages + * const next2 = yield* Queue.takeN(queue, 2) + * + * // Take remaining messages + * const remaining = yield* Queue.takeN(queue, 2) + * return [first3, next2, remaining] + * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3], [4, 5], [6, 7]] + * ``` + * + * @category taking + * @since 2.0.0 + */ +export const takeN = ( + self: Dequeue, + n: number +): Effect, E> => takeBetween(self, n, n) + +/** + * Takes between `min` and `max` messages from the queue. + * + * **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. + * + * **Example** (Taking a bounded batch of values) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Add several messages + * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5, 6, 7, 8]) + * + * // Take between 2 and 5 messages + * const batch1 = yield* Queue.takeBetween(queue, 2, 5) + * + * // Take between 1 and 10 messages (but only 3 remain) + * const batch2 = yield* Queue.takeBetween(queue, 1, 10) + * + * // 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 + * @since 2.0.0 + */ +export const takeBetween = ( + self: Dequeue, + min: number, + max: number +): Effect, E> => + 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 + * available. + * + * **Details** + * + * If the queue is done, it will fail with `Done`. If the + * queue fails, the Effect will fail with the error. + * + * **Example** (Taking one value) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(3) + * + * // Add some messages + * yield* Queue.offer(queue, "first") + * yield* Queue.offer(queue, "second") + * + * // Take messages one by one + * const msg1 = yield* Queue.take(queue) + * const msg2 = yield* Queue.take(queue) + * + * // End the queue + * yield* Queue.end(queue) + * + * // Taking from an ended queue fails with Done + * const result = yield* Effect.exit(Queue.take(queue)) + * return [[msg1, msg2], result] + * }) + * + * await Effect.runPromise(program) // => [["first", "second"], Exit.fail(Cause.Done())] + * ``` + * + * @category taking + * @since 2.0.0 + */ +export const take = (self: Dequeue): Effect => + internalEffect.suspend( + () => takeUnsafe(self) ?? internalEffect.andThen(awaitTake(self), take(self)) + ) + +/** + * Attempts to take one item from the queue without waiting. + * + * **Details** + * + * Returns `Option.some` when an item is immediately available. Returns + * `Option.none` when no item is available, when the queue is done, or when the + * immediate take observes a queue failure. + * + * **Example** (Polling without blocking) + * + * ```ts import.meta.vitest + * import { Effect, Option, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Poll returns Option.none if empty + * const maybe1 = yield* Queue.poll(queue) + * + * // Add an item + * yield* Queue.offer(queue, 42) + * + * // Poll returns Option.some with the item + * const maybe2 = yield* Queue.poll(queue) + * return [maybe1, maybe2] + * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] + * ``` + * + * @category taking + * @since 2.0.0 + */ +export const poll = (self: Dequeue): Effect> => + internalEffect.suspend(() => { + const result = takeUnsafe(self) + if (result === undefined) { + return internalEffect.succeed(Option.none()) + } + if (result._tag === "Success") { + return internalEffect.succeed(Option.some(result.value)) + } + return internalEffect.succeed(Option.none()) + }) + +/** + * Peeks at the next item without removing it. + * + * **Details** + * + * Blocks until an item is available. If the queue is done or fails, the error is propagated. + * + * **Example** (Peeking at the next value) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * yield* Queue.offer(queue, 42) + * + * // Peek at the next item without removing it + * const item = yield* Queue.peek(queue) + * return item + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category taking + * @since 4.0.0 + */ +export const peek = (self: Dequeue): Effect => + internalEffect.suspend(() => { + if (self.state._tag === "Done") { + return self.state.exit + } + if (self.messages.length > 0 && self.messages.head) { + return internalEffect.succeed(self.messages.head.array[self.messages.head.offset]) + } + return internalEffect.andThen(awaitTake(self), peek(self)) + }) + +/** + * Attempts to take one message from the queue synchronously. + * + * **When to use** + * + * Use when polling queue internals must not suspend or register a waiting taker, + * and `undefined` is an acceptable result for an empty queue. + * + * **Details** + * + * Returns an `Exit` for an immediately available message or for the queue's + * terminal state. Returns `undefined` when no message is immediately available. + * This operation does not wait or register a taker. + * + * **Example** (Taking one value synchronously) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Queue } from "effect" + * + * // Create a queue and use unsafe operations + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(10) + * + * // Add some messages + * Queue.offerUnsafe(queue, 1) + * Queue.offerUnsafe(queue, 2) + * + * // Take a message synchronously + * const result1 = Queue.takeUnsafe(queue) + * + * const result2 = Queue.takeUnsafe(queue) + * + * // No more messages - returns undefined + * const result3 = Queue.takeUnsafe(queue) + * return [result1, result2, result3] + * }) + * + * await Effect.runPromise(program) // => [Exit.succeed(1), Exit.succeed(2), undefined] + * ``` + * + * @category taking + * @since 4.0.0 + */ +export const takeUnsafe = (self: Dequeue): Exit | undefined => { + if (self.state._tag === "Done") { + return self.state.exit + } + if (self.messages.length > 0) { + const message = MutableList.take(self.messages)! + 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)! + releaseCapacity(self) + return core.exitSucceed(message) + } + return undefined +} + +const await_ = (self: Dequeue): Effect> => + internalEffect.callback>((resume) => { + const awaiter = (effect: Effect) => resume(Pull.catchDone(effect, () => internalEffect.exitVoid)) + if (self.state._tag === "Done") { + return awaiter(self.state.exit) + } + self.state.awaiters.add(awaiter) + return internalEffect.sync(() => { + if (self.state._tag !== "Done") { + self.state.awaiters.delete(awaiter) + } + }) + }) + +export { + /** + * Waits until a queue reaches the `Done` state. + * + * **When to use** + * + * Use to suspend a fiber until no further values can be taken from the queue + * and its terminal outcome is known. + * + * **Details** + * + * The effect succeeds with `void` for normal `Done` completion. Other + * terminal causes are preserved, so failures and interruptions complete this + * effect with the same terminal outcome. + * + * **Gotchas** + * + * A queue can be closing before it is done. `await` resumes at `Done`, not at + * the first completion signal, so buffered messages may need to be drained + * first. + * + * @see {@link end} for signaling normal completion while preserving buffered messages for consumers + * @see {@link fail} for signaling an error while preserving buffered messages for consumers + * @see {@link interrupt} for graceful interruption after buffered messages are drained + * @see {@link shutdown} for immediately discarding buffered messages and resuming pending operations + * + * @category completion + * @since 4.0.0 + */ + await_ as await +} + +/** + * Returns the current number of buffered messages in the queue. + * + * **Details** + * + * 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.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) + * + * // Add some messages + * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) + * + * // Check size after adding messages + * const size2 = yield* Queue.size(queue) + * + * // End the queue + * yield* Queue.end(queue) + * + * // Ending retains the buffered size while the queue is Closing + * const size3 = yield* Queue.size(queue) + * return [size1, size2, size3] + * }) + * + * await Effect.runPromise(program) // => [0, 5, 5] + * ``` + * + * @category sizes + * @since 2.0.0 + */ +export const size = (self: Dequeue): Effect => internalEffect.sync(() => sizeUnsafe(self)) + +/** + * Checks whether the queue is full. + * + * **Example** (Checking if queues are full) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(3) + * + * const before = yield* Queue.isFull(queue) + * + * // Add some messages + * yield* Queue.offerAll(queue, [1, 2, 3]) + * + * const after = yield* Queue.isFull(queue) + * return [before, after] + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isFull = (self: Dequeue): Effect => internalEffect.sync(() => isFullUnsafe(self)) + +/** + * Returns the current number of buffered messages in the queue synchronously. + * + * **When to use** + * + * Use when you need an immediate `Queue` size snapshot for diagnostics or + * internals and do not need the read wrapped in `Effect`. + * + * **Details** + * + * 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.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) + * + * // Add some messages + * Queue.offerUnsafe(queue, 1) + * Queue.offerUnsafe(queue, 2) + * Queue.offerUnsafe(queue, 3) + * + * // Check size after adding messages + * const size2 = Queue.sizeUnsafe(queue) + * + * // End the queue + * Queue.endUnsafe(queue) + * + * // Ending retains the buffered size while the queue is Closing + * const size3 = Queue.sizeUnsafe(queue) + * return [size1, size2, size3] + * }) + * + * await Effect.runPromise(program) // => [0, 3, 3] + * ``` + * + * @category sizes + * @since 4.0.0 + */ +export const sizeUnsafe = (self: Dequeue): number => self.state._tag === "Done" ? 0 : self.messages.length + +/** + * Checks whether the queue is full synchronously. + * + * **When to use** + * + * Use when an immediate `Queue` capacity snapshot is needed outside effectful + * code and racing queue changes are acceptable. + * + * **Example** (Checking fullness synchronously) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(3) + * + * const before = Queue.isFullUnsafe(queue) + * + * // Add some messages + * yield* Queue.offerAll(queue, [1, 2, 3]) + * + * const after = Queue.isFullUnsafe(queue) + * return [before, after] + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isFullUnsafe = (self: Dequeue): boolean => sizeUnsafe(self) === self.capacity + +/** + * Runs an `Effect` into a `Queue`, where success ends the queue and failure + * fails the queue. + * + * **Example** (Running effects into queues) + * + * ```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.yieldNow + * return "Processing completed successfully" + * }) + * + * // Pipe the effect into the queue + * // If dataProcessing succeeds, queue ends successfully + * // If dataProcessing fails, queue fails with the error + * const effectIntoQueue = Queue.into(queue)(dataProcessing) + * + * const wasCompleted = yield* effectIntoQueue + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [wasCompleted, exit] + * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail(Cause.Done())] + * ``` + * + * @category completion + * @since 4.0.0 + */ +export const into: { + ( + self: Enqueue + ): ( + effect: Effect + ) => Effect + ( + effect: Effect, + self: Enqueue + ): Effect +} = dual( + 2, + ( + effect: Effect, + self: Enqueue + ): Effect => + internalEffect.uninterruptibleMask((restore) => + internalEffect.matchCauseEffect(restore(effect), { + onFailure: (cause) => failCause(self, cause), + onSuccess: (_) => end(self) + }) + ) +) + +// ----------------------------------------------------------------------------- +// internals +// ----------------------------------------------------------------------------- +// + +const exitFalse = core.exitSucceed(false) +const exitTrue = core.exitSucceed(true) +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 + } + for (const taker of self.state.takers) { + self.state.takers.delete(taker) + taker(internalEffect.exitVoid) + if (self.messages.length === 0) { + break + } + } +} + +const scheduleReleaseTaker = (self: Enqueue) => { + if (self.scheduleRunning || self.state._tag === "Done" || self.state.takers.size === 0) { + return + } + self.scheduleRunning = true + self.dispatcher.scheduleTask(() => releaseTakers(self), 0) +} + +const takeBetweenUnsafe = ( + self: Dequeue, + min: number, + max: number +): Exit, E> | undefined => { + if (self.state._tag === "Done") { + 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)!] + releaseCapacity(self) + return core.exitSucceed(messages) + } + min = Math.min(min, self.capacity || 1) + if (min <= self.messages.length) { + const messages = MutableList.takeN(self.messages, max) + releaseCapacity(self) + return core.exitSucceed(messages) + } +} + +const offerRemainingSingle = (self: Enqueue, message: A) => { + return internalEffect.callback((resume) => { + if (self.state._tag !== "Open") { + return resume(exitFalse) + } + const entry: Queue.OfferEntry = { _tag: "Single", message, resume } + self.state.offers.add(entry) + return internalEffect.sync(() => { + if (self.state._tag === "Open") { + self.state.offers.delete(entry) + } + }) + }) +} + +const offerRemainingArray = (self: Enqueue, remaining: Array) => { + return internalEffect.callback>((resume) => { + if (self.state._tag !== "Open") { + return resume(core.exitSucceed(remaining)) + } + const entry: Queue.OfferEntry = { + _tag: "Array", + remaining, + offset: 0, + resume + } + self.state.offers.add(entry) + return internalEffect.sync(() => { + if (self.state._tag === "Open") { + self.state.offers.delete(entry) + } + }) + }) +} + +const releaseCapacity = (self: Dequeue): boolean => { + if (self.state._tag === "Done") { + return Pull.isDoneCause(self.state.exit.cause) + } else if (self.state.offers.size === 0) { + if ( + self.state._tag === "Closing" && + self.messages.length === 0 + ) { + finalize(self, self.state.exit) + return Pull.isDoneCause(self.state.exit.cause) + } + return false + } + let n = self.capacity - self.messages.length + for (const entry of self.state.offers) { + if (n === 0) break + else if (entry._tag === "Single") { + MutableList.append(self.messages, entry.message) + n-- + entry.resume(exitTrue) + self.state.offers.delete(entry) + } 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) + } + } + return false +} + +const awaitTake = (self: Dequeue) => + internalEffect.callback((resume) => { + if (self.state._tag === "Done") { + return resume(self.state.exit) + } + self.state.takers.add(resume) + return internalEffect.sync(() => { + if (self.state._tag !== "Done") { + self.state.takers.delete(resume) + } + }) + }) + +const takeAllUnsafe = (self: Dequeue) => { + if (self.messages.length > 0) { + const messages = MutableList.takeAll(self.messages) + 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)!] + releaseCapacity(self) + return messages + } + return [] +} + +const finalize = (self: Enqueue | Dequeue, exit: Failure) => { + if (self.state._tag === "Done") { + return + } + const openState = self.state + self.state = { _tag: "Done", exit } + for (const taker of openState.takers) { + taker(exit) + } + openState.takers.clear() + for (const awaiter of openState.awaiters) { + awaiter(exit) + } + openState.awaiters.clear() +} diff --git a/.repos/effect/packages/effect/src/Random.ts b/.repos/effect/packages/effect/src/Random.ts new file mode 100644 index 000000000..2afe37ea1 --- /dev/null +++ b/.repos/effect/packages/effect/src/Random.ts @@ -0,0 +1,572 @@ +/** + * Provides pseudo-random generation through an Effect service. + * + * This module exposes effectful generators for booleans, doubles, safe + * integers, bounded numbers, shuffling, and deterministic seeded runs. Because + * random generation is a service, tests and applications can replace the + * generator used by Effect programs. + * + * @since 4.0.0 + */ +import type * as Arr from "./Array.ts" +import * as Cause from "./Cause.ts" +import type * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import * as random from "./internal/random.ts" +import type * as NonEmptyIterable from "./NonEmptyIterable.ts" +import * as Predicate from "./Predicate.ts" + +/** + * Represents a service for generating pseudo-random numbers. + * + * **When to use** + * + * Use to access or provide the random-number generator service used by Effect + * programs. + * + * **Gotchas** + * + * The default implementation is based on `Math.random` and is not + * cryptographically secure. Replace the service with a cryptographically secure + * implementation before using these generators for security-sensitive values. + * + * **Example** (Accessing the random service) + * + * ```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) + * return [float, integer, inRange] as const + * }) + * + * await Effect.runPromise(program.pipe(Random.withSeed("example"))) // => [0.1633802591287037, 3434461687501127, 1] + * ``` + * + * @category services + * @since 2.0.0 + */ +export const Random: Context.Reference<{ + nextIntUnsafe(): number + nextDoubleUnsafe(): number +}> = random.Random + +const randomWith = (f: (random: typeof Random["Service"]) => A): Effect.Effect => + Effect.withFiber((fiber) => Effect.succeed(f(fiber.getRef(Random)))) + +/** + * Generates a random number between 0 (inclusive) and 1 (exclusive). + * + * **When to use** + * + * Use to generate a pseudo-random floating-point number in the standard + * `[0, 1)` range. + * + * **Example** (Generating a random number) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * await Effect.runPromise(Random.next.pipe(Random.withSeed("example"))) // => 0.1633802591287037 + * ``` + * + * @category generators + * @since 2.0.0 + */ +export const next: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe()) + +/** + * Generates a random boolean value. + * + * **When to use** + * + * Use to make a pseudo-random true-or-false choice. + * + * **Example** (Generating a random boolean) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * await Effect.runPromise(Random.nextBoolean.pipe(Random.withSeed("example"))) // => false + * ``` + * + * @category generators + * @since 2.0.0 + */ +export const nextBoolean: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe() > 0.5) + +/** + * Generates a random integer between `Number.MIN_SAFE_INTEGER` (inclusive) + * and `Number.MAX_SAFE_INTEGER` (inclusive). + * + * **When to use** + * + * Use to generate a pseudo-random safe integer across the full safe-integer + * range. + * + * **Example** (Generating a random integer) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * await Effect.runPromise(Random.nextInt.pipe(Random.withSeed("example"))) // => -6064002158214091 + * ``` + * + * @category generators + * @since 2.0.0 + */ +export const nextInt: Effect.Effect = randomWith((r) => r.nextIntUnsafe()) + +/** + * Generates a random number between `min` (inclusive) and `max` (exclusive). + * + * **When to use** + * + * Use to generate a pseudo-random floating-point number within a numeric range. + * + * **Example** (Generating a bounded random number) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * await Effect.runPromise(Random.nextBetween(0, 1).pipe(Random.withSeed("example"))) // => 0.1633802591287037 + * ``` + * + * @category generators + * @since 4.0.0 + */ +export const nextBetween = (min: number, max: number): Effect.Effect => + randomWith((r) => r.nextDoubleUnsafe() * (max - min) + min) + +/** + * Generates a random integer between `min` and `max`. + * + * **When to use** + * + * Use to generate a pseudo-random integer within a rounded numeric range. + * + * **Details** + * + * The lower bound is rounded up with `Math.ceil` and the upper bound is + * rounded down with `Math.floor`. By default the range is inclusive; set + * `options.halfOpen: true` to exclude the upper bound. + * + * **Example** (Generating a bounded random integer) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * const program = Effect.gen(function*() { + * const diceRoll1 = yield* Random.nextIntBetween(1, 6) + * const diceRoll2 = yield* Random.nextIntBetween(1, 6, { + * 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 generators + * @since 2.0.0 + */ +export const nextIntBetween = (min: number, max: number, options?: { + readonly halfOpen?: boolean +}): Effect.Effect => { + const extra = options?.halfOpen === true ? 0 : 1 + return randomWith((r) => { + const minInt = Math.ceil(min) + const maxInt = Math.floor(max) + return Math.floor(r.nextDoubleUnsafe() * (maxInt - minInt + extra)) + minInt + }) +} + +/** + * Uses the pseudo-random number generator to shuffle the specified iterable. + * + * **When to use** + * + * Use to randomly reorder an iterable using the active `Random` service. + * + * **Example** (Shuffling values) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * await Effect.runPromise(Random.shuffle([1, 2, 3, 4, 5]).pipe(Random.withSeed("example"))) // => [4, 2, 5, 3, 1] + * ``` + * + * @category generators + * @since 2.0.0 + */ +export const shuffle = (elements: Iterable): Effect.Effect> => + randomWith((r) => { + const buffer = Array.from(elements) + for (let i = buffer.length - 1; i >= 1; i = i - 1) { + const index = Math.min(i, Math.floor(r.nextDoubleUnsafe() * (i + 1))) + const value = buffer[i]! + buffer[i] = buffer[index]! + buffer[index] = value + } + return buffer + }) + +/** + * Gets a random element from an iterable. + * + * **When to use** + * + * Use to select one value uniformly from a collection using the active `Random` + * service. + * + * **Details** + * + * If the input type is known to be non-empty, the returned effect cannot fail. + * Otherwise, empty iterables fail with `Cause.NoSuchElementError`. + * + * **Example** (Choosing a random value) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * await Effect.runPromise(Random.choice(["red", "green", "blue"] as const).pipe(Random.withSeed("example"))) // => "red" + * ``` + * + * @category generators + * @since 3.6.0 + */ +export const choice: >( + elements: Self +) => Self extends NonEmptyIterable.NonEmptyIterable ? Effect.Effect + : Self extends Arr.NonEmptyReadonlyArray ? Effect.Effect + : Self extends Iterable ? Effect.Effect + : never = ((elements: Iterable) => { + const buffer = Array.from(elements) + return buffer.length === 0 + ? Effect.fail(new Cause.NoSuchElementError("Cannot select a random element from an empty array")) + : randomWith((r) => buffer[Math.min(buffer.length - 1, Math.floor(r.nextDoubleUnsafe() * buffer.length))]!) + }) as any + +/** + * Seeds the pseudo-random number generator with the specified value. + * + * **When to use** + * + * Use to run an effect with a deterministic pseudo-random sequence. + * + * **Details** + * + * Using the same seed produces the same random sequence, which is useful for + * tests and reproducible simulations. + * + * **Gotchas** + * + * Use an unpredictable seed when uniqueness or unpredictability matters. + * + * **Example** (Seeding random generation) + * + * ```ts import.meta.vitest + * import { Effect, Random } from "effect" + * + * const program = Effect.gen(function*() { + * const value1 = yield* Random.next + * const value2 = yield* Random.next + * return [value1, value2] + * }) + * + * 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 providing services + * @since 4.0.0 + */ +export const withSeed: { + (seed: string | number): (self: Effect.Effect) => Effect.Effect + (self: Effect.Effect, seed: string | number): Effect.Effect +} = dual(2, ( + self: Effect.Effect, + seed: string | number +) => Effect.provideService(self, Random, ISAAC_CSPRNG(seed))) + +/*/////////////////////////////////////////////////////////////////////////////////////////////////// +This is a derivative work copyright (c) 2025 Effectful Technologies Inc, under MIT license. +This is a derivative work copyright (c) 2018, William P. "Mac" McMeans, under BSD license. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of isaacCSPRNG nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Original work copyright (c) 2012 Yves-Marie K. Rinquin, under MIT license. +https://github.com/rubycon/isaac.js +///////////////////////////////////////////////////////////////////////////////////////////////////*/ +function ISAAC_CSPRNG(userSeed?: string | number) { + // Internal State + const memory = new Array(256) + const result = new Array(256) + let accumulator = 0 + let lastResult = 0 + let generation = 0 + let counter = 0 + + // Initial Seed + const internalSeed = Predicate.isUndefined(userSeed) ? getInitialSeed() : userSeed + seed(internalSeed) + + function getInitialSeed() { + const uint32a = new Uint32Array(2) + crypto.getRandomValues(uint32a) + return uint32a[0] + uint32a[1] + } + + function reset() { + accumulator = 0 + lastResult = 0 + counter = 0 + for (let i = 0; i < 256; ++i) { + memory[i] = 0 + result[i] = 0 + } + generation = 0 + } + + function seed(userSeed: string | number): void { + // The golden ratio ( 2654435769 ) + // See https://stackoverflow.com/questions/4948780/magic-number-in-boosthash-combine + const magicNumber = 0x9e3779b9 + let a = magicNumber + let b = magicNumber + let c = magicNumber + let d = magicNumber + let e = magicNumber + let f = magicNumber + let g = magicNumber + let h = magicNumber + let i = 0 + + let seed: Array + if (Predicate.isString(userSeed)) { + seed = toIntArray(userSeed) + } else { + seed = [userSeed] + } + + reset() + + for (i = 0; i < seed.length; i++) { + result[i & 0xff] += seed[i] + } + + function mix() { + a ^= b << 11 + d = add32(d, a) + b = add32(b, c) + + b ^= c >>> 2 + e = add32(e, b) + c = add32(c, d) + + c ^= d << 8 + f = add32(f, c) + d = add32(d, e) + + d ^= e >>> 16 + g = add32(g, d) + e = add32(e, f) + + e ^= f << 10 + h = add32(h, e) + f = add32(f, g) + + f ^= g >>> 4 + a = add32(a, f) + g = add32(g, h) + + g ^= h << 8 + b = add32(b, g) + h = add32(h, a) + + h ^= a >>> 9 + c = add32(c, h) + a = add32(a, b) + } + + // Scramble the seed + for (i = 0; i < 4; i++) { + mix() + } + + for (i = 0; i < 256; i += 8) { + // Use all the information in the seed + a = add32(a, result[i]) + b = add32(b, result[i + 1]) + c = add32(c, result[i + 2]) + d = add32(d, result[i + 3]) + e = add32(e, result[i + 4]) + f = add32(f, result[i + 5]) + g = add32(g, result[i + 6]) + h = add32(h, result[i + 7]) + + mix() + + // Fill in the memory with messy stuff + memory[i] = a + memory[i + 1] = b + memory[i + 2] = c + memory[i + 3] = d + memory[i + 4] = e + memory[i + 5] = f + memory[i + 6] = g + memory[i + 7] = h + } + + // Second pass to make sure seed affects memory + for (i = 0; i < 256; i += 8) { + a = add32(a, memory[i]) + b = add32(b, memory[i + 1]) + c = add32(c, memory[i + 2]) + d = add32(d, memory[i + 3]) + e = add32(e, memory[i + 4]) + f = add32(f, memory[i + 5]) + g = add32(g, memory[i + 6]) + h = add32(h, memory[i + 7]) + + mix() + + // Fill in the memory with messy stuff (again) + memory[i] = a + memory[i + 1] = b + memory[i + 2] = c + memory[i + 3] = d + memory[i + 4] = e + memory[i + 5] = f + memory[i + 6] = g + memory[i + 7] = h + } + + pnrg() + + generation = 256 + } + + function pnrg(n?: number): void { + let i = 0 + let x = 0 + let y = 0 + + n = Predicate.isUndefined(n) ? 1 : Math.abs(Math.floor(n)) + + while (n--) { + counter = add32(counter, 1) + lastResult = add32(lastResult, counter) + + for (i = 0; i < 256; i++) { + switch (i & 3) { + case 0: { + accumulator ^= accumulator << 13 + break + } + case 1: { + accumulator ^= accumulator >>> 6 + break + } + case 2: { + accumulator ^= accumulator << 2 + break + } + case 3: { + accumulator ^= accumulator >>> 16 + break + } + } + + accumulator = add32(memory[(i + 128) & 0xff], accumulator) + x = memory[i] + + memory[i] = add32(memory[(x >>> 2) & 0xff], add32(accumulator, lastResult)) + y = memory[i] + + result[i] = add32(memory[(y >>> 10) & 0xff], x) + lastResult = result[i] + } + } + } + + /** + * Returns a signed, random integer in the range [-2^31, 2^31). + */ + function nextInt32(): number { + if (!generation--) { + pnrg() + generation = 255 + } + return result[generation] + } + + function nextIntUnsafe(): number { + return Math.floor(nextDoubleUnsafe() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER + 1)) + + Number.MIN_SAFE_INTEGER + } + + /** + * Returns a 53-bit fraction in the range [0, 1). + */ + function nextDoubleUnsafe(): number { + const hi = (nextInt32() >>> 0) & 0x1FFFFF // take top 21 bits + const lo = nextInt32() >>> 0 // full 32 bits + + // 53-bit integer + const combined = hi * 4294967296 + lo + + return combined / 0x20000000000000 // [0, 1) + } + + return { nextIntUnsafe, nextDoubleUnsafe } +} + +/** + * 32-bit addition with overflow handling (JavaScript numbers are 53-bit). + * + * Example: add32(0xFFFFFFFF, 0x00000001) = 0x00000000 (wraps around) + */ +function add32(x: number, y: number): number { + // Add lower 16 bits separately to handle carry + // Example: x=0x12345678, y=0xABCDEF01 + // lsb = (0x5678 + 0xEF01) = 0x14579 + const lsb = (x & 0xffff) + (y & 0xffff) + + // Add upper 16 bits + carry from lower addition + // msb = (0x1234 + 0xABCD + (0x14579 >>> 16)) = (0x1234 + 0xABCD + 0x1) = 0xBE02 + const msb = (x >>> 16) + (y >>> 16) + (lsb >>> 16) + + // Combine: upper 16 bits | lower 16 bits (masked to prevent double carry) + // return (0xBE02 << 16) | (0x14579 & 0xffff) = 0xBE024579 + return (msb << 16) | (lsb & 0xffff) +} + +const seedEncoder = new TextEncoder() + +/** + * Convert a string to UTF-8 encoded 32-bit integers (little-endian). + */ +function toIntArray(seed: string): Array { + const bytes = seedEncoder.encode(seed) + const result: Array = [] + + for (let index = 0; index < bytes.length; index += 4) { + result.push( + ((bytes[index] ?? 0) << 0) | + ((bytes[index + 1] ?? 0) << 8) | + ((bytes[index + 2] ?? 0) << 16) | + ((bytes[index + 3] ?? 0) << 24) + ) + } + + return result +} diff --git a/.repos/effect/packages/effect/src/RcMap.ts b/.repos/effect/packages/effect/src/RcMap.ts new file mode 100644 index 000000000..1e00423de --- /dev/null +++ b/.repos/effect/packages/effect/src/RcMap.ts @@ -0,0 +1,700 @@ +/** + * Shares scoped resources by key and releases them when no one is using them. + * + * An `RcMap` runs a lookup effect the first time a key is requested, shares the + * in-progress or acquired resource with other callers for the same key, and + * tracks each caller through its current `Scope`. When the last scope for a key + * closes, the resource can be released, kept alive for an idle time, or removed + * by capacity limits or explicit invalidation. It is meant for resource + * lifecycles such as clients, sessions, and connections, not as a general + * mutable cache. + * + * @since 3.5.0 + */ +import * as Cause from "./Cause.ts" +import { Clock } from "./Clock.ts" +import * as Context from "./Context.ts" +import * as Deferred from "./Deferred.ts" +import * as Duration from "./Duration.ts" +import * as Effect from "./Effect.ts" +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" + +const TypeId = "~effect/RcMap" + +/** + * An `RcMap` is a reference-counted map data structure that manages the lifecycle + * of resources indexed by keys. Resources are lazily acquired and automatically + * released when no longer in use. + * + * **When to use** + * + * Use to share scoped resources by key while automatically releasing them after + * their last active reference is gone. + * + * **Example** (Inspecting a reference-counted map) + * + * ```ts import.meta.vitest + * import { Effect, RcMap } from "effect" + * + * 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}`), () => Effect.void), + * capacity: 10, + * idleTimeToLive: "5 minutes" + * }) + * + * // The RcMap interface provides access to: + * // - lookup: Function to acquire resources + * // - capacity: Maximum number of resources + * // - idleTimeToLive: Time before idle resources are released + * // - state: Current state of the map + * + * return dbConnectionMap.capacity + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => 10 + * ``` + * + * @see {@link make} for creating an `RcMap` + * @see {@link get} for acquiring or retaining a resource by key + * + * @category models + * @since 3.5.0 + */ +export interface RcMap extends Pipeable { + readonly [TypeId]: typeof TypeId + readonly lookup: (key: K) => Effect.Effect + readonly context: Context.Context + readonly scope: Scope.Scope + readonly idleTimeToLive: (key: K) => Duration.Duration + readonly capacity: number + state: State +} + +/** + * Represents the internal state of an RcMap, which can be either Open (active) + * or Closed (shutdown and no longer accepting operations). + * + * **When to use** + * + * Use when typing code that inspects an `RcMap`'s `state` field and narrows + * between open and closed lifecycle states. + * + * @see {@link RcMap} for the map value that exposes this state + * @see {@link State.Open} for the active state with entries + * @see {@link State.Closed} for the shutdown state + * + * @category models + * @since 4.0.0 + */ +export type State = State.Open | State.Closed + +/** + * Namespace containing the internal state types for RcMap. + * + * **When to use** + * + * Use when referring to the concrete open, closed, and entry state shapes used + * by `RcMap`. + * + * @since 4.0.0 + */ +export declare namespace State { + /** + * Represents the open/active state of an RcMap, containing the actual + * resource map that stores entries. + * + * **When to use** + * + * Use when handling an `RcMap` that can still accept operations and contains + * stored entries. + * + * @category models + * @since 4.0.0 + */ + export interface Open { + readonly _tag: "Open" + readonly map: MutableHashMap.MutableHashMap> + } + + /** + * Represents the closed state of an RcMap, indicating that the map has been + * shut down and will no longer accept new operations. + * + * **When to use** + * + * Use when handling an `RcMap` after its owning scope has closed. + * + * @category models + * @since 4.0.0 + */ + export interface Closed { + readonly _tag: "Closed" + } + + /** + * Represents an individual entry in the RcMap, containing the resource's + * metadata including reference count, expiration time, and lifecycle management. + * + * **When to use** + * + * Use when inspecting the stored resource, reference count, and idle lifecycle + * metadata for a single key. + * + * @category models + * @since 4.0.0 + */ + export interface Entry { + readonly deferred: Deferred.Deferred + readonly scope: Scope.Closeable + readonly finalizer: Effect.Effect + readonly idleTimeToLive: Duration.Duration + fiber: Fiber.Fiber | undefined + expiresAt: number + refCount: number + } +} + +const makeUnsafe = (options: { + readonly lookup: (key: K) => Effect.Effect + readonly context: Context.Context + readonly scope: Scope.Scope + readonly idleTimeToLive: (key: K) => Duration.Duration + readonly capacity: number +}): RcMap => ({ + [TypeId]: TypeId, + lookup: options.lookup, + context: options.context, + scope: options.scope, + idleTimeToLive: options.idleTimeToLive, + capacity: options.capacity, + state: { + _tag: "Open", + map: MutableHashMap.empty() + }, + pipe() { + return pipeArguments(this, arguments) + } +}) + +/** + * Creates an `RcMap` that can contain multiple reference counted resources that can be indexed + * by a key. The resources are lazily acquired on the first call to `get` and + * released when the last reference is released. + * + * **When to use** + * + * Use to create a scoped reference-counted map for resources that should be + * acquired once per key and shared while in use. + * + * **Details** + * + * Complex keys can extend `Equal` and `Hash` to allow lookups by value. + * + * - `capacity`: The maximum number of resources that can be held in the map. + * - `idleTimeToLive`: When the reference count reaches zero, the resource will be released after this duration. + * + * **Example** (Creating a reference-counted map) + * + * ```ts import.meta.vitest + * import { Effect, RcMap } from "effect" + * + * const events: Array = [] + * + * const program = Effect.gen(function*() { + * const map = yield* RcMap.make({ + * lookup: (key: string) => + * Effect.acquireRelease( + * Effect.succeed(`acquired ${key}`), + * () => Effect.sync(() => events.push(`released ${key}`)) + * ) + * }) + * + * // Get "foo" from the map twice, which will only acquire it once. + * // It will then be released once the scope closes. + * yield* RcMap.get(map, "foo").pipe( + * Effect.andThen(RcMap.get(map, "foo")), + * 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 constructors + * @since 3.5.0 + */ +export const make: { + (options: { + readonly lookup: (key: K) => Effect.Effect + readonly idleTimeToLive?: Duration.Input | ((key: K) => Duration.Input) | undefined + readonly capacity?: undefined + }): Effect.Effect, never, Scope.Scope | R> + (options: { + readonly lookup: (key: K) => Effect.Effect + readonly idleTimeToLive?: Duration.Input | ((key: K) => Duration.Input) | undefined + readonly capacity: number + }): Effect.Effect, never, Scope.Scope | R> +} = (options: { + readonly lookup: (key: K) => Effect.Effect + readonly idleTimeToLive?: Duration.Input | ((key: K) => Duration.Input) | undefined + readonly capacity?: number | undefined +}) => + Effect.withFiber, never, R | Scope.Scope>((fiber) => { + const context = fiber.context as Context.Context + const scope = Context.get(context, Scope.Scope) + const self = makeUnsafe({ + lookup: options.lookup as any, + context, + scope, + idleTimeToLive: typeof options.idleTimeToLive === "function" + ? flow(options.idleTimeToLive, Duration.fromInputUnsafe) + : constant(Duration.fromInputUnsafe(options.idleTimeToLive ?? Duration.zero)), + capacity: Math.max(options.capacity ?? Number.POSITIVE_INFINITY, 0) + }) + return Effect.as( + Scope.addFinalizerExit(scope, () => { + if (self.state._tag === "Closed") { + return Effect.void + } + const map = self.state.map + self.state = { _tag: "Closed" } + return Effect.forEach( + map, + ([, entry]) => Effect.exit(Scope.close(entry.scope, Exit.void)) + ).pipe( + Effect.tap(() => + Effect.sync(() => { + MutableHashMap.clear(map) + }) + ) + ) + }), + self + ) + }) + +/** + * Gets the resource for a key, acquiring it with the map's lookup function when + * the key is not already cached. + * + * **When to use** + * + * Use to acquire or retain the resource for a key within the current scope. + * + * **Details** + * + * The resource's reference count is incremented for the current `Scope`, and a + * release finalizer is added to that scope. When the current scope closes, the + * reference is released; the resource is closed when the last reference is + * released, subject to the map's idle time-to-live setting. + * + * **Example** (Acquiring a resource) + * + * ```ts import.meta.vitest + * import { Effect, RcMap } from "effect" + * + * const events: Array = [] + * + * const program = Effect.gen(function*() { + * const map = yield* RcMap.make({ + * lookup: (key: string) => + * Effect.acquireRelease( + * Effect.succeed(`Resource: ${key}`), + * () => Effect.sync(() => events.push(`released ${key}`)) + * ) + * }) + * + * // Get a resource - it will be acquired on first access + * const resource = yield* RcMap.get(map, "database") + * return [resource, events] as const + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => ["Resource: database", ["released database"]] + * ``` + * + * @see {@link make} for creating the reference-counted map + * @see {@link invalidate} for removing a resource by key + * + * @category combinators + * @since 3.5.0 + */ +export const get: { + (key: K): (self: RcMap) => Effect.Effect + (self: RcMap, key: K): Effect.Effect +} = dual( + 2, + (self: RcMap, key: K): Effect.Effect => + Effect.uninterruptibleMask((restore) => { + if (self.state._tag === "Closed") { + return Effect.interrupt + } + const state = self.state + const parent = Fiber.getCurrent()! + const o = MutableHashMap.get(state.map, key) + let entry: State.Entry + if (o._tag === "Some") { + entry = o.value + entry.refCount++ + } else if (Number.isFinite(self.capacity) && MutableHashMap.size(self.state.map) >= self.capacity) { + return Effect.fail( + new Cause.ExceededCapacityError(`RcMap attempted to exceed capacity of ${self.capacity}`) + ) as Effect.Effect + } else { + entry = { + deferred: Deferred.makeUnsafe(), + scope: Scope.makeUnsafe(), + idleTimeToLive: self.idleTimeToLive(key), + finalizer: undefined as any, + fiber: undefined, + expiresAt: 0, + refCount: 1 + } + ;(entry as any).finalizer = release(self, key, entry) + MutableHashMap.set(state.map, key, entry) + const context = new Map(self.context.mapUnsafe) + parent.context.mapUnsafe.forEach((value, key) => { + context.set(key, value) + }) + context.set(Scope.Scope.key, entry.scope) + self.lookup(key).pipe( + Effect.runForkWith(Context.makeUnsafe(context)), + Fiber.runIn(entry.scope) + ).addObserver((exit) => Deferred.doneUnsafe(entry.deferred, exit)) + } + const scope = Context.getUnsafe(parent.context, Scope.Scope) + return Scope.addFinalizer(scope, entry.finalizer).pipe( + Effect.andThen(restore(Deferred.await(entry.deferred))) + ) + }) +) + +/** + * 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 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 (!Duration.isFinite(entry.idleTimeToLive)) { + return Effect.void + } + + const clock = fiber.getRef(Clock) + entry.expiresAt = clock.currentTimeMillisUnsafe() + Duration.toMillis(entry.idleTimeToLive) + if (entry.fiber) return Effect.void + + entry.fiber = Effect.interruptibleMask(function loop(restore): Effect.Effect { + const now = clock.currentTimeMillisUnsafe() + const remaining = entry.expiresAt - now + if (remaining <= 0) { + if (self.state._tag === "Closed" || entry.refCount > 0) return Effect.void + MutableHashMap.remove(self.state.map, key) + return restore(Scope.close(entry.scope, Exit.void)) + } + return Effect.flatMap(clock.sleep(Duration.millis(remaining)), () => loop(restore)) + }).pipe( + Effect.ensuring(Effect.sync(() => { + entry.fiber = undefined + })), + Effect.runForkWith(fiber.context), + Fiber.runIn(self.scope) + ) + return Effect.void + }) + +/** + * Returns an iterable of all keys currently stored in the `RcMap`. + * + * **When to use** + * + * Use to inspect which keys currently have stored resources in an `RcMap`. + * + * **Details** + * + * If the `RcMap` has been closed, the effect is interrupted. + * + * **Example** (Listing keys) + * + * ```ts import.meta.vitest + * import { Effect, RcMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* RcMap.make({ + * lookup: (key: string) => Effect.succeed(`value-${key}`) + * }) + * + * // Add some resources to the map + * yield* RcMap.get(map, "foo") + * yield* RcMap.get(map, "bar") + * yield* RcMap.get(map, "baz") + * + * // Get all keys currently in the map + * const allKeys = yield* RcMap.keys(map) + * return Array.from(allKeys) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => ["foo", "bar", "baz"] + * ``` + * + * @see {@link has} for checking one key without enumerating all keys + * + * @category combinators + * @since 3.8.0 + */ +export const keys = (self: RcMap): Effect.Effect> => { + return Effect.suspend(() => + self.state._tag === "Closed" ? Effect.interrupt : Effect.succeed(MutableHashMap.keys(self.state.map)) + ) +} + +/** + * Invalidates and removes a specific key from the RcMap. If the resource is not + * currently in use (reference count is 0), it will be immediately released. + * + * **When to use** + * + * Use to remove a resource by key so the next access performs a fresh lookup. + * + * **Example** (Invalidating a resource) + * + * ```ts import.meta.vitest + * import { Effect, RcMap } from "effect" + * + * const events: Array = [] + * + * const program = Effect.gen(function*() { + * const map = yield* RcMap.make({ + * lookup: (key: string) => + * Effect.acquireRelease( + * Effect.succeed(`Resource: ${key}`), + * () => Effect.sync(() => events.push(`released ${key}`)) + * ) + * }) + * + * // Get a resource + * yield* RcMap.get(map, "cache") + * + * // Invalidate the resource - it will be removed from the map + * // and released if no longer in use + * yield* RcMap.invalidate(map, "cache") + * + * // Next access will create a new resource + * yield* RcMap.get(map, "cache") + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released cache", "released cache"] + * ``` + * + * @see {@link get} for acquiring or retaining the resource for a key + * @see {@link touch} for extending the idle lifetime without removing the entry + * + * @category combinators + * @since 3.13.0 + */ +export const invalidate: { + (key: K): (self: RcMap) => Effect.Effect + (self: RcMap, key: K): Effect.Effect +} = dual( + 2, + Effect.fnUntraced(function*(self: RcMap, key: K) { + if (self.state._tag === "Closed") return + const o = MutableHashMap.get(self.state.map, key) + if (o._tag === "None") return + 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) + }, Effect.uninterruptible) +) + +/** + * Returns whether the `RcMap` currently contains an entry for the specified + * key. + * + * **When to use** + * + * Use to check whether a key is already present in an `RcMap` without running + * the lookup function or acquiring a missing resource. + * + * **Details** + * + * This operation only checks the current map state. + * + * **Gotchas** + * + * Closed maps return `false`, so `false` does not distinguish a missing key + * from a closed map. + * + * @see {@link get} for acquiring or retaining the resource for a key + * @see {@link keys} for enumerating all currently stored keys + * + * @category combinators + * @since 3.17.7 + */ +export const has: { + (key: K): (self: RcMap) => Effect.Effect + (self: RcMap, key: K): Effect.Effect +} = dual( + 2, + (self: RcMap, key: K) => + Effect.sync(() => { + if (self.state._tag === "Closed") return false + return MutableHashMap.has(self.state.map, key) + }) +) + +/** + * Extends the idle time for a resource in the RcMap. If the RcMap has an + * `idleTimeToLive` configured, calling `touch` will reset the expiration + * timer for the specified key. + * + * **When to use** + * + * Use to keep an idle resource alive longer without acquiring a new reference. + * + * **Example** (Extending resource idle time) + * + * ```ts import.meta.vitest + * import { Effect, RcMap } from "effect" + * + * const events: Array = [] + * + * const program = Effect.gen(function*() { + * const map = yield* RcMap.make({ + * lookup: (key: string) => + * Effect.acquireRelease( + * Effect.succeed(`Resource: ${key}`), + * () => Effect.sync(() => events.push(`released ${key}`)) + * ), + * idleTimeToLive: "10 seconds" + * }) + * + * // Get a resource + * yield* RcMap.get(map, "session") + * + * // Touch the resource to extend its idle time + * // This resets the 10-second expiration timer + * yield* RcMap.touch(map, "session") + * + * // The resource will now live for another 10 seconds + * // from the time it was touched + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released session"] + * ``` + * + * @see {@link invalidate} for removing the resource instead of extending it + * + * @category combinators + * @since 3.13.0 + */ +export const touch: { + (key: K): (self: RcMap) => Effect.Effect + (self: RcMap, key: K): Effect.Effect +} = dual( + 2, + (self: RcMap, key: K) => + Effect.clockWith((clock) => { + if (self.state._tag === "Closed") { + return Effect.void + } + const o = MutableHashMap.get(self.state.map, key) + if (o._tag === "None" || Duration.isZero(o.value.idleTimeToLive)) { + return Effect.void + } + const entry = o.value + entry.expiresAt = clock.currentTimeMillisUnsafe() + Duration.toMillis(entry.idleTimeToLive) + return Effect.void + }) +) diff --git a/.repos/effect/packages/effect/src/RcRef.ts b/.repos/effect/packages/effect/src/RcRef.ts new file mode 100644 index 000000000..3d64d5890 --- /dev/null +++ b/.repos/effect/packages/effect/src/RcRef.ts @@ -0,0 +1,236 @@ +/** + * Reference-counted handles for sharing one scoped resource across many scoped + * users. An `RcRef` acquires the resource lazily the first time `get` + * needs it, reuses that value while it is borrowed or kept idle, and finalizes + * it when the final borrowing scope closes unless an idle timeout keeps it + * available. The module also provides `invalidate` for forcing the next `get` + * to acquire a fresh resource. + * + * @since 3.5.0 + */ +import type * as Duration from "./Duration.ts" +import type * as Effect from "./Effect.ts" +import * as internal from "./internal/rcRef.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { Scope } from "./Scope.ts" +import type * as Types from "./Types.ts" + +const TypeId = "~effect/RcRef" + +/** + * A reference counted reference that manages resource lifecycle. + * + * **When to use** + * + * Use to share a scoped resource across active users with reference-counted + * acquisition and release. + * + * **Details** + * + * An RcRef wraps a resource that can be acquired and released multiple times. + * The resource is lazily acquired on the first call to `get` and automatically + * released when the last reference is released. + * + * **Example** (Sharing a lazily acquired resource) + * + * ```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.sync(() => events.push(`closed ${connection}`)) + * ) + * }) + * + * // Use the RcRef in multiple operations + * const program = Effect.gen(function*() { + * const connectionRef = yield* createConnectionRef("postgres://localhost") + * + * // Multiple gets will share the same connection + * const connection1 = yield* RcRef.get(connectionRef) + * const connection2 = yield* RcRef.get(connectionRef) + * + * return [connection1 === connection2, events] as const + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => [true, ["closed Connected to postgres://localhost"]] + * ``` + * + * @category models + * @since 3.5.0 + */ +export interface RcRef extends Pipeable { + readonly [TypeId]: RcRef.Variance +} + +/** + * Namespace containing type-level members associated with `RcRef`. + * + * **Example** (Referencing namespace types) + * + * ```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 + */ +export declare namespace RcRef { + /** + * Type-level variance marker for `RcRef`. + * + * **When to use** + * + * Use to carry the value and error type parameters for `RcRef` in Effect's + * type machinery. + * + * **Details** + * + * This interface records the covariant value and error types carried by an + * `RcRef`. It is used by Effect's type machinery and is not normally + * referenced directly by users. + * + * @category models + * @since 3.5.0 + */ + export interface Variance { + readonly _A: Types.Covariant + readonly _E: Types.Covariant + } +} + +/** + * Creates an `RcRef` from an acquire effect. + * + * **When to use** + * + * Use to create a lazily acquired, reference-counted resource from an acquire + * effect. + * + * **Details** + * + * The resource is acquired lazily on the first `get` and shared by subsequent + * gets while it remains cached. Each `get` adds a reference to the current + * `Scope`. When the last reference is released, the resource is closed + * immediately by default, or after `idleTimeToLive` when that option is + * provided. + * + * **Example** (Creating a reference-counted resource) + * + * ```ts import.meta.vitest + * import { Effect, RcRef } from "effect" + * + * const events: Array = [] + * + * const program = Effect.gen(function*() { + * const ref = yield* RcRef.make({ + * acquire: Effect.acquireRelease( + * Effect.succeed("foo"), + * () => Effect.sync(() => events.push("released foo")) + * ) + * }) + * + * // will only acquire the resource once, and release it + * // when the scope is closed + * yield* RcRef.get(ref).pipe( + * Effect.andThen(RcRef.get(ref)), + * Effect.scoped + * ) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released foo"] + * ``` + * + * @category constructors + * @since 3.5.0 + */ +export const make: ( + options: { + readonly acquire: Effect.Effect + /** + * When the reference count reaches zero, the resource will be released + * after this duration. + */ + readonly idleTimeToLive?: Duration.Input | undefined + } +) => Effect.Effect, never, R | Scope> = internal.make + +/** + * Gets the value from an `RcRef`, acquiring it first if needed. + * + * **When to use** + * + * Use to borrow the current resource within a `Scope`, acquiring it first if + * necessary. + * + * **Details** + * + * The reference count is incremented for the current `Scope`, and a release + * finalizer is added to that scope. When the current scope closes, the + * reference is released; the resource is closed when the final reference is + * released, subject to any configured idle time-to-live. + * + * **Example** (Sharing one acquired value) + * + * ```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.sync(() => events.push(`released ${resource}`)) + * ) + * }) + * + * // Get the value from the RcRef + * const value1 = yield* RcRef.get(ref) + * const value2 = yield* RcRef.get(ref) + * + * return [value1 === value2, events] as const + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => [true, ["released shared resource"]] + * ``` + * + * @category combinators + * @since 3.5.0 + */ +export const get: (self: RcRef) => Effect.Effect = internal.get + +/** + * Invalidates the currently cached resource, if one has been acquired. + * + * **When to use** + * + * Use to force future `RcRef.get` calls to acquire a fresh resource when the + * currently cached resource should no longer be reused. + * + * **Details** + * + * After invalidation, the next `get` acquires a fresh resource. + * + * **Gotchas** + * + * Invalidation does not revoke resources already borrowed by active scopes; + * those remain usable until their scopes close. + * + * @see {@link get} for acquiring the current cached resource or the fresh resource after invalidation + * + * @category combinators + * @since 3.19.6 + */ +export const invalidate: (self: RcRef) => Effect.Effect = internal.invalidate diff --git a/.repos/effect/packages/effect/src/Record.ts b/.repos/effect/packages/effect/src/Record.ts new file mode 100644 index 000000000..ec47fe303 --- /dev/null +++ b/.repos/effect/packages/effect/src/Record.ts @@ -0,0 +1,1549 @@ +/** + * Works with plain JavaScript records as immutable key/value dictionaries. + * + * A record is an object whose keys are strings or symbols. This module includes + * helpers for construction, lookup, updates, mapping, filtering, folding, + * set-like combination, and typed conversions between records and iterable + * entries. Helpers that change values return new records instead of mutating the + * input. + * + * @since 2.0.0 + */ + +import type * as Combiner from "./Combiner.ts" +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" +import * as R from "./Result.ts" +import type { NoInfer } from "./Types.ts" + +/** + * Represents a readonly record with keys of type `K` and values of type `A`. + * This is the foundational type for immutable key-value mappings in Effect. + * + * **Example** (Defining a readonly record type) + * + * ```ts import.meta.vitest + * import type { Record } from "effect" + * + * // Creating a readonly record type + * type UserRecord = Record.ReadonlyRecord<"name" | "age", string | number> + * + * const user: UserRecord = { + * name: "John", + * age: 30 + * } + * user // => { name: "John", age: 30 } + * ``` + * + * @category models + * @since 2.0.0 + */ +export type ReadonlyRecord = { + readonly [P in K]: A +} + +/** + * Namespace containing utility types for working with readonly records. + * These types help with type-level operations on record keys and values. + * + * **Example** (Using readonly record helper types) + * + * ```ts import.meta.vitest + * import type { Record } from "effect" + * + * // Using NonLiteralKey to convert literal keys to generic types + * type GenericKey = Record.ReadonlyRecord.NonLiteralKey<"foo" | "bar"> // string + * + * // 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 + */ +export declare namespace ReadonlyRecord { + type IsFiniteString = T extends "" ? true : + [T] extends [`${infer Head}${infer Rest}`] + ? string extends Head ? false : `${number}` extends Head ? false : Rest extends "" ? true : IsFiniteString + : false + + /** + * Represents a type that converts literal string keys to generic string type and symbol keys to generic symbol type. + * This is useful for maintaining type safety while allowing flexible key types in record operations. + * + * **Example** (Converting literal keys to non-literal keys) + * + * ```ts import.meta.vitest + * import type { Record } from "effect" + * + * // For literal string keys, this becomes 'string' + * type Example1 = Record.ReadonlyRecord.NonLiteralKey<"foo" | "bar"> // string + * + * // For symbol keys, this becomes 'symbol' + * type Example2 = Record.ReadonlyRecord.NonLiteralKey // symbol + * + * const symbol: Example2 = Symbol.for("key") + * "key" satisfies Example1 + * symbol + * ``` + * + * @category models + * @since 2.0.0 + */ + export type NonLiteralKey = K extends string ? IsFiniteString extends true ? string : K + : symbol + + /** + * Represents the intersection of two key types, handling both literal and non-literal string keys. + * This type is used in record operations that need to compute overlapping keys. + * + * **Example** (Intersecting record keys) + * + * ```ts import.meta.vitest + * import type { Record } from "effect" + * + * // Intersection of literal keys + * type Example1 = Record.ReadonlyRecord.IntersectKeys<"a" | "b", "b" | "c"> // "b" + * + * // Intersection with generic string + * type Example2 = Record.ReadonlyRecord.IntersectKeys // string + * + * "b" satisfies Example1 + * "a" satisfies Example2 + * ``` + * + * @category models + * @since 2.0.0 + */ + 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> +} + +/** + * Type lambda for readonly records, used in higher-kinded type operations. + * This enables records to work with generic type constructors and functors. + * + * **Example** (Applying a readonly record type lambda) + * + * ```ts import.meta.vitest + * import type { HKT, Record } from "effect" + * + * type Settings = HKT.Kind< + * Record.ReadonlyRecordTypeLambda<"port" | "retries">, + * never, + * never, + * never, + * number + * > + * + * const defaults: Settings = { + * port: 3000, + * retries: 3 + * } + * defaults // => { port: 3000, retries: 3 } + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export interface ReadonlyRecordTypeLambda extends TypeLambda { + readonly type: ReadonlyRecord +} + +/** + * Creates a new, empty record. + * + * **Example** (Creating an empty record) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * // Create an empty record + * const emptyRecord = Record.empty() + * emptyRecord // => {} + * + * // The type ensures type safety for future operations + * Record.set(emptyRecord, "count", 42) // => { count: 42 } + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (): Record< + ReadonlyRecord.NonLiteralKey, + V +> => ({} as any) + +/** + * Determines if a mutable record is empty. + * + * **Example** (Checking for an empty record) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.isEmptyRecord({}) // => true + * Record.isEmptyRecord({ a: 3 }) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isEmptyRecord = (self: Record): self is Record => + Object.keys(self).length === 0 + +/** + * Determines if a readonly record is empty. + * + * **Example** (Checking for an empty readonly record) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.isEmptyReadonlyRecord({}) // => true + * Record.isEmptyReadonlyRecord({ a: 3 }) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isEmptyReadonlyRecord: ( + self: ReadonlyRecord +) => self is ReadonlyRecord = isEmptyRecord + +/** + * Takes an iterable and a projection function and returns a record. + * The projection function maps each value of the iterable to a tuple of a key and a value, which is then added to the resulting record. + * + * **Example** (Building a record from mapped iterable values) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.fromIterableWith([1, 2, 3, 4], (a) => [String(a), a * 2]) // => { "1": 2, "2": 4, "3": 6, "4": 8 } + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterableWith: { + ( + f: (a: A) => readonly [K, B] + ): (self: Iterable) => Record, B> + ( + self: Iterable, + f: (a: A) => readonly [K, B] + ): Record, B> +} = dual( + 2, + ( + self: Iterable, + f: (a: A) => readonly [K, B] + ): Record, B> => { + const out: Record = empty() + for (const a of self) { + const [k, b] = f(a) + InternalRecord.assignProperty(out, k, b) + } + return out + } +) + +/** + * Creates a new record from an iterable, utilizing the provided function to determine the key for each element. + * + * **Example** (Building a record keyed by iterable values) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * const users = [ + * { id: "2", name: "name2" }, + * { 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: { + ( + 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. + * + * **Details** + * + * If there are conflicting keys when using `fromEntries`, the last occurrence of the key/value pair will overwrite the + * previous ones. So the resulting record will only have the value of the last occurrence of each key. + * + * **Example** (Building a record from entries) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.fromEntries([["a", 1], ["b", 2]]) // => { a: 1, b: 2 } + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromEntries: ( + entries: Iterable +) => Record, Entry[1]> = Object.fromEntries + +/** + * Transforms the values of a record into an `Array` with a custom mapping function. + * + * **Example** (Collecting mapped record values) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * const x = { a: 1, b: 2, c: 3 } + * Record.collect(x, (key, n) => [key, n]) // => [["a", 1], ["b", 2], ["c", 3]] + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const collect: { + (f: (key: K, a: A) => B): (self: ReadonlyRecord) => Array + (self: ReadonlyRecord, f: (key: K, a: A) => B): Array +} = dual( + 2, + (self: ReadonlyRecord, f: (key: K, a: A) => B): Array => { + const out: Array = [] + for (const key of keys(self)) { + out.push(f(key, self[key])) + } + return out + } +) + +/** + * Takes a record and returns an array of tuples containing its keys and values. + * + * **Example** (Converting a record to entries) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * const x = { a: 1, b: 2, c: 3 } + * Record.toEntries(x) // => [["a", 1], ["b", 2], ["c", 3]] + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const toEntries: (self: ReadonlyRecord) => Array<[K, A]> = collect(( + key, + value +) => [key, value]) + +/** + * Returns the number of key/value pairs in a record. + * + * **Example** (Getting the record size) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.size({ a: "a", b: 1, c: true }) // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: ReadonlyRecord): number => keys(self).length + +/** + * Checks whether a given `key` exists in a record. + * + * **Example** (Checking key membership) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.has({ a: 1, b: 2 }, "a") // => true + * Record.has(Record.empty(), "c") // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const has: { + ( + key: NoInfer + ): (self: ReadonlyRecord) => boolean + ( + self: ReadonlyRecord, + key: NoInfer + ): boolean +} = dual( + 2, + ( + self: ReadonlyRecord, + key: NoInfer + ): boolean => Object.hasOwn(self, key) +) + +/** + * Retrieves a value at a particular key from a record safely, returning it wrapped in an `Option`. + * + * **Example** (Getting a value as an Option) + * + * ```ts import.meta.vitest + * import { Option, Record as R } from "effect" + * + * const person: Record = { name: "John Doe", age: 35 } + * + * R.get(person, "name") // => Option.some("John Doe") + * R.get(person, "email") // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const get: { + (key: NoInfer): (self: ReadonlyRecord) => Option.Option + (self: ReadonlyRecord, key: NoInfer): Option.Option +} = dual( + 2, + (self: ReadonlyRecord, key: NoInfer): Option.Option => + Object.hasOwn(self, key) ? Option.some(self[key]) : Option.none() +) + +/** + * Applies a function to the element at the specified key safely, creating a new record, + * or return `Option.none()` if the key doesn't exist. + * + * **Example** (Modifying a value at a key) + * + * ```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() + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const modify: { + ( + key: NoInfer, + f: (a: A) => B + ): (self: ReadonlyRecord) => Option.Option> + ( + self: ReadonlyRecord, + key: NoInfer, + f: (a: A) => B + ): Option.Option> +} = dual( + 3, + ( + self: ReadonlyRecord, + key: NoInfer, + f: (a: A) => B + ): Option.Option> => { + if (!has(self, key)) return Option.none() + return Option.some({ ...self, [key]: f(self[key]) }) + } +) + +/** + * Replaces the value at an existing key safely and returns the updated record in + * `Option.some`. + * + * **Details** + * + * If the key is not present, returns `Option.none()` and leaves the record + * unchanged. + * + * **Example** (Replacing a value at a key) + * + * ```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() + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const replace: { + ( + key: NoInfer, + b: B + ): (self: ReadonlyRecord) => Option.Option> + ( + self: ReadonlyRecord, + key: NoInfer, + b: B + ): Option.Option> +} = dual( + 3, + ( + self: ReadonlyRecord, + key: NoInfer, + b: B + ): Option.Option> => modify(self, key, () => b) +) + +/** + * Removes a key from a record. + * + * **When to use** + * + * Use to create a shallow copy of a record without one property. + * + * **Details** + * + * If the key is not present, the result is still a shallow copy of the original + * record. + * + * **Example** (Removing a key) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.remove({ a: 1, b: 2 }, "a") // => { b: 2 } + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const remove: { + (key: X): (self: ReadonlyRecord) => Record, A> + (self: ReadonlyRecord, key: X): Record, A> +} = dual( + 2, + (self: ReadonlyRecord, key: X): Record, A> => { + if (!has(self, key)) { + return { ...self } + } + const out = { ...self } + delete out[key] + return out + } +) + +/** + * Retrieves the value of the property with the given `key` from a record safely and returns an `Option` + * of a tuple with the value and the record with the removed property. + * If the key is not present, returns `Option.none()`. + * + * **Example** (Popping a value and removing its key) + * + * ```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() + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const pop: { + ( + key: X + ): (self: ReadonlyRecord) => Option.Option<[A, Record, A>]> + ( + self: ReadonlyRecord, + key: X + ): Option.Option<[A, Record, A>]> +} = dual(2, ( + self: ReadonlyRecord, + key: X +): Option.Option<[A, Record, A>]> => + has(self, key) ? Option.some([self[key], remove(self, key)]) : Option.none()) + +/** + * Maps a record into another record by applying a transformation function to each of its values. + * + * **Example** (Mapping record values) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * const f = (n: number) => `-${n}` + * + * Record.map({ a: 3, b: 5 }, f) // => { a: "-3", b: "-5" } + * + * const g = (n: number, key: string) => `${key.toUpperCase()}-${n}` + * + * Record.map({ a: 3, b: 5 }, g) // => { a: "A-3", b: "B-5" } + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (a: A, key: NoInfer) => B): (self: ReadonlyRecord) => Record + (self: ReadonlyRecord, f: (a: A, key: NoInfer) => B): Record +} = dual( + 2, + (self: ReadonlyRecord, f: (a: A, key: NoInfer) => B): Record => { + const out: Record = { ...self } as any + for (const key of keys(self)) { + InternalRecord.assignProperty(out, key, f(self[key], key)) + } + return out + } +) + +/** + * Maps the keys of a `ReadonlyRecord` while preserving the corresponding values. + * + * **Example** (Mapping record keys) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.mapKeys({ a: 3, b: 5 }, (key) => key.toUpperCase()) // => { A: 3, B: 5 } + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const mapKeys: { + ( + f: (key: K, a: A) => K2 + ): (self: ReadonlyRecord) => Record + ( + self: ReadonlyRecord, + f: (key: K, a: A) => K2 + ): Record +} = dual( + 2, + ( + self: ReadonlyRecord, + f: (key: K, a: A) => K2 + ): Record => { + const out: Record = {} as any + for (const key of keys(self)) { + const a = self[key] + InternalRecord.assignProperty(out, f(key, a), a) + } + return out + } +) + +/** + * Maps entries of a `ReadonlyRecord` using the provided function, allowing modification of both keys and corresponding values. + * + * **Example** (Mapping record entries) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.mapEntries({ a: 3, b: 5 }, (a, key) => [key.toUpperCase(), a + 1]) // => { A: 4, B: 6 } + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const mapEntries: { + ( + f: (a: A, key: K) => readonly [K2, B] + ): (self: ReadonlyRecord) => Record + ( + self: ReadonlyRecord, + f: (a: A, key: K) => [K2, B] + ): Record +} = dual( + 2, + ( + self: ReadonlyRecord, + f: (a: A, key: K) => [K2, B] + ): Record => { + const out = {} as Record + for (const key of keys(self)) { + const [k, b] = f(self[key], key) + InternalRecord.assignProperty(out, k, b) + } + return out + } +) + +/** + * Transforms a record by applying the function `f` to each key and value in the original record. + * If the function succeeds, the key-value pair is included in the output record. + * + * **Example** (Filtering and mapping with Result) + * + * ```ts import.meta.vitest + * import { Record, Result } from "effect" + * + * const x = { a: 1, b: 2, c: 3 } + * const f = (a: number, key: string) => a > 2 ? Result.succeed(a * 2) : Result.failVoid + * Record.filterMap(x, f) // => { c: 6 } + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + ( + f: (input: A, key: K) => Result + ): (self: ReadonlyRecord) => Record, B> + ( + self: ReadonlyRecord, + f: (input: A, key: K) => Result + ): Record, B> +} = dual( + 2, + ( + self: ReadonlyRecord, + f: (input: A, key: K) => Result + ): Record, B> => { + const out: Record = empty() + for (const key of keys(self)) { + const result = f(self[key], key) + if (R.isSuccess(result)) { + InternalRecord.assignProperty(out, key, result.success) + } + } + return out + } +) + +/** + * Selects properties from a record whose values match the given predicate. + * + * **Example** (Filtering record values) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * const x = { a: 1, b: 2, c: 3, d: 4 } + * Record.filter(x, (n) => n > 2) // => { c: 3, d: 4 } + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + ( + refinement: (a: NoInfer, key: K) => a is B + ): (self: ReadonlyRecord) => Record, B> + ( + predicate: (A: NoInfer, key: K) => boolean + ): (self: ReadonlyRecord) => Record, A> + ( + self: ReadonlyRecord, + refinement: (a: A, key: K) => a is B + ): Record, B> + ( + self: ReadonlyRecord, + predicate: (a: A, key: K) => boolean + ): Record, A> +} = dual( + 2, + ( + self: ReadonlyRecord, + predicate: (a: A, key: K) => boolean + ): Record, A> => { + const out: Record = empty() + for (const key of keys(self)) { + if (predicate(self[key], key)) { + InternalRecord.assignProperty(out, key, self[key]) + } + } + return out + } +) + +/** + * Returns a new record containing only the `Some` values from a record of + * `Option` values, preserving the original keys. + * + * **Example** (Extracting Some values) + * + * ```ts import.meta.vitest + * import { Option, Record } from "effect" + * + * Record.getSomes({ a: Option.some(1), b: Option.none(), c: Option.some(2) }) // => { a: 1, c: 2 } + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const getSomes: ( + self: ReadonlyRecord> +) => Record, A> = ( + self: ReadonlyRecord> +): Record, A> => { + const out: Record = empty() + for (const key of keys(self)) { + const option = self[key] + if (Option.isSome(option)) { + InternalRecord.assignProperty(out, key, option.value) + } + } + return out +} + +/** + * Returns a new record containing only the `Err` values from a record of + * `Result` values, preserving the original keys. + * + * **Example** (Extracting Result failures) + * + * ```ts import.meta.vitest + * import { Record, Result } from "effect" + * + * Record.getFailures({ + * a: Result.succeed(1), + * b: Result.fail("err"), + * c: Result.succeed(2) + * }) // => { b: "err" } + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const getFailures = ( + self: ReadonlyRecord> +): Record, E> => { + const out: Record = empty() + for (const key of keys(self)) { + const value = self[key] + if (R.isFailure(value)) { + InternalRecord.assignProperty(out, key, value.failure) + } + } + + return out +} + +/** + * Returns a new record containing only the `Ok` values from a record of + * `Result` values, preserving the original keys. + * + * **Example** (Extracting Result successes) + * + * ```ts import.meta.vitest + * import { Record, Result } from "effect" + * + * Record.getSuccesses({ + * a: Result.succeed(1), + * b: Result.fail("err"), + * c: Result.succeed(2) + * }) // => { a: 1, c: 2 } + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const getSuccesses = ( + self: ReadonlyRecord> +): Record => { + const out: Record = empty() + for (const key of keys(self)) { + const value = self[key] + if (R.isSuccess(value)) { + InternalRecord.assignProperty(out, key, value.success) + } + } + + return out +} + +/** + * Applies a function to each record entry and partitions the returned `Result` + * values into two records. + * + * **Details** + * + * Failure values are collected in the left record, and success values are + * collected in the right record, preserving the original keys. + * + * **Example** (Partitioning with Result) + * + * ```ts import.meta.vitest + * import { Record, Result } from "effect" + * + * const x = { a: 1, b: 2, c: 3 } + * const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) + * Record.partition(x, f) // => [{ a: 1, c: 3 }, { b: 2 }] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const partition: { + ( + f: (input: A, key: K) => Result + ): ( + self: ReadonlyRecord + ) => [left: Record, B>, right: Record, C>] + ( + self: ReadonlyRecord, + f: (input: A, key: K) => Result + ): [left: Record, B>, right: Record, C>] +} = dual( + 2, + ( + self: ReadonlyRecord, + f: (input: A, key: K) => Result + ): [left: Record, B>, right: Record, C>] => { + const left: Record = empty() + const right: Record = empty() + for (const key of keys(self)) { + const e = f(self[key], key) + if (R.isFailure(e)) { + InternalRecord.assignProperty(left, key, e.failure) + } else { + InternalRecord.assignProperty(right, key, e.success) + } + } + return [left, right] + } +) + +/** + * Partitions a record of `Result` values into two separate records, + * one with the `Err` values and one with the `Ok` values. + * + * **Example** (Separating Result values) + * + * ```ts import.meta.vitest + * import { Record, Result } from "effect" + * + * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }) // => [{ a: "e" }, { b: 1 }] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const separate: ( + self: ReadonlyRecord> +) => [Record, A>, Record, B>] = partition(identity) + +/** + * Retrieves the keys of a given record as an array. + * + * **Example** (Getting record keys) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.keys({ a: 1, b: 2, c: 3 }) // => ["a", "b", "c"] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const keys = (self: ReadonlyRecord): Array => + Object.keys(self) as Array + +/** + * Retrieves the values of a given record as an array. + * + * **Example** (Getting record values) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.values({ a: 1, b: 2, c: 3 }) // => [1, 2, 3] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const values = (self: ReadonlyRecord): Array => collect(self, (_, a) => a) + +/** + * Adds a new key-value pair or update an existing key's value in a record. + * + * **Example** (Setting a record value) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.set("a", 5)({ a: 1, b: 2 }) // => { a: 5, b: 2 } + * Record.set("c", 5)({ a: 1, b: 2 }) // => { a: 1, b: 2, c: 5 } + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const set: { + ( + key: K1, + value: B + ): (self: ReadonlyRecord) => Record + ( + self: ReadonlyRecord, + key: K1, + value: B + ): Record +} = dual( + 3, + ( + self: ReadonlyRecord, + key: K1, + value: B + ): Record => { + return { ...self, [key]: value } as any + } +) + +/** + * 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 import.meta.vitest + * import { Equivalence, Record } from "effect" + * + * const isSubrecord = Record.isSubrecordBy( + * Equivalence.make((self, that) => self.toLowerCase() === that.toLowerCase()) + * ) + * + * const required: Record.ReadonlyRecord = { role: "Admin" } + * const available: Record.ReadonlyRecord = { + * role: "admin", + * status: "active" + * } + * + * isSubrecord(required, available) // => true + * isSubrecord({ role: "Admin", status: "inactive" }, available) // => false + * isSubrecord(required, { role: "editor", status: "active" }) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isSubrecordBy = (equivalence: Equivalence): { + (that: ReadonlyRecord): (self: ReadonlyRecord) => boolean + (self: ReadonlyRecord, that: ReadonlyRecord): boolean +} => + dual(2, (self: ReadonlyRecord, that: ReadonlyRecord): boolean => { + for (const key of keys(self)) { + if (!has(that, key) || !equivalence(self[key], that[key])) { + return false + } + } + return true + }) + +/** + * Checks whether the first record is a subrecord of the second record. + * + * **Details** + * + * Returns `true` when every key and value in `self` is also present in `that`. + * Values are compared with Effect equality via `Equal.asEquivalence()`. + * + * **Example** (Checking subrecords) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.isSubrecord({ a: 1 } as Record, { a: 1, b: 2 }) // => true + * Record.isSubrecord({ a: 1, b: 2 }, { a: 1 } as Record) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isSubrecord: { + (that: ReadonlyRecord): (self: ReadonlyRecord) => boolean + (self: ReadonlyRecord, that: ReadonlyRecord): boolean +} = isSubrecordBy(Equal.asEquivalence()) + +/** + * Reduces a record to a single value by combining its entries with a specified function. + * + * **Example** (Reducing record values) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.reduce({ a: 1, b: 2, c: 3 }, 0, (acc, value) => acc + value) // => 6 + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + ( + zero: Z, + f: (accumulator: Z, value: V, key: K) => Z + ): (self: ReadonlyRecord) => Z + (self: ReadonlyRecord, zero: Z, f: (accumulator: Z, value: V, key: K) => Z): Z +} = dual( + 3, + ( + self: ReadonlyRecord, + zero: Z, + f: (accumulator: Z, value: V, key: K) => Z + ): Z => { + let out: Z = zero + for (const key of keys(self)) { + out = f(out, self[key], key) + } + return out + } +) + +/** + * Checks whether all entries in a record meet a specific condition. + * + * **Example** (Checking every record value) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.every({ a: 1, b: 2 }, (n) => n > 0) // => true + * Record.every({ a: 1, b: -1 }, (n) => n > 0) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const every: { + ( + refinement: (value: A, key: K) => value is B + ): (self: ReadonlyRecord) => self is ReadonlyRecord + (predicate: (value: A, key: K) => boolean): (self: ReadonlyRecord) => boolean + ( + self: ReadonlyRecord, + refinement: (value: A, key: K) => value is B + ): self is ReadonlyRecord + (self: ReadonlyRecord, predicate: (value: A, key: K) => boolean): boolean +} = dual( + 2, + ( + self: ReadonlyRecord, + refinement: (value: A, key: K) => value is B + ): self is ReadonlyRecord => { + for (const key of keys(self)) { + if (!refinement(self[key], key)) { + return false + } + } + return true + } +) + +/** + * Checks whether any entry in a record meets a specific condition. + * + * **Example** (Checking for any matching value) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.some({ a: 1, b: 2 }, (n) => n > 1) // => true + * Record.some({ a: 1, b: 2 }, (n) => n > 2) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const some: { + (predicate: (value: A, key: K) => boolean): (self: ReadonlyRecord) => boolean + (self: ReadonlyRecord, predicate: (value: A, key: K) => boolean): boolean +} = dual( + 2, + (self: ReadonlyRecord, predicate: (value: A, key: K) => boolean): boolean => { + for (const key of keys(self)) { + if (predicate(self[key], key)) { + return true + } + } + return false + } +) + +/** + * Merges two records, preserving entries that exist in either of the records. + * For keys that exist in both records, the provided combine function is used to merge the values. + * + * **Example** (Merging records with union) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.union({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { a: 1, b: 5, c: 4 } + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const union: { + ( + that: ReadonlyRecord, + combine: (selfValue: A, thatValue: B) => C + ): (self: ReadonlyRecord) => Record + ( + self: ReadonlyRecord, + that: ReadonlyRecord, + combine: (selfValue: A, thatValue: B) => C + ): Record +} = dual( + 3, + ( + self: ReadonlyRecord, + that: ReadonlyRecord, + combine: (selfValue: A, thatValue: B) => C + ): Record => { + if (isEmptyRecord(self)) { + return { ...that } as any + } + if (isEmptyRecord(that)) { + return { ...self } as any + } + const out: Record = empty() + for (const key of keys(self)) { + if (has(that, key as any)) { + InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) + } else { + InternalRecord.assignProperty(out, key, self[key]) + } + } + for (const key of keys(that)) { + if (!has(out, key)) { + InternalRecord.assignProperty(out, key, that[key]) + } + } + return out + } +) + +/** + * Merges two records, retaining only the entries that exist in both records. + * For intersecting keys, the provided combine function is used to merge the values. + * + * **Example** (Merging intersecting keys) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.intersection({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { b: 5 } + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const intersection: { + ( + that: ReadonlyRecord, + combine: (selfValue: A, thatValue: B) => C + ): (self: ReadonlyRecord) => Record, C> + ( + self: ReadonlyRecord, + that: ReadonlyRecord, + combine: (selfValue: A, thatValue: B) => C + ): Record, C> +} = dual( + 3, + ( + self: ReadonlyRecord, + that: ReadonlyRecord, + combine: (selfValue: A, thatValue: B) => C + ): Record, C> => { + const out: Record = empty() + if (isEmptyRecord(self) || isEmptyRecord(that)) { + return out + } + for (const key of keys(self)) { + if (has(that, key as any)) { + InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) + } + } + return out + } +) + +/** + * Merges two records, preserving only the entries that are unique to each record. + * Keys that exist in both records are excluded from the result. + * + * **Example** (Keeping keys unique to each record) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.difference({ a: 1, b: 2 }, { b: 3, c: 4 }) // => { a: 1, c: 4 } + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const difference: { + ( + that: ReadonlyRecord + ): (self: ReadonlyRecord) => Record + ( + self: ReadonlyRecord, + that: ReadonlyRecord + ): Record +} = dual(2, ( + self: ReadonlyRecord, + that: ReadonlyRecord +): Record => { + if (isEmptyRecord(self)) { + return { ...that } as any + } + if (isEmptyRecord(that)) { + return { ...self } as any + } + const out = {} as Record + for (const key of keys(self)) { + if (!has(that, key as any)) { + InternalRecord.assignProperty(out, key, self[key]) + } + } + for (const key of keys(that)) { + if (!has(self, key as any)) { + InternalRecord.assignProperty(out, key, that[key]) + } + } + return out +}) + +/** + * Create an `Equivalence` for records using the provided `Equivalence` for values. + * Two records are considered equivalent if they have the same keys and their corresponding values are equivalent. + * + * **Example** (Comparing records with a value equivalence) + * + * ```ts import.meta.vitest + * import { Equal, Record } from "effect" + * + * const recordEquivalence = Record.makeEquivalence(Equal.asEquivalence()) + * + * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true + * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 3 }) // => false + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence = ( + equivalence: Equivalence +): Equivalence> => { + const is = isSubrecordBy(equivalence) + return (self, that) => is(self, that) && is(that, self) +} + +/** + * Create a non-empty record from a single element. + * + * **Example** (Creating a singleton record) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * Record.singleton("a", 1) // => { a: 1 } + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const singleton = (key: K, value: A): Record => ({ + [key]: value +} as any) + +/** + * Creates a `Reducer` for combining `Record`s using union, with values for keys that exist in both records combined + * using the provided `Combiner`. + * + * **When to use** + * + * Use to build a reusable reducer for accumulating many records into one + * union-shaped record, preserving keys from every input and combining + * overlapping values with the supplied combiner. + * + * **Details** + * + * The returned reducer uses `Record.union` for combine and an empty record as + * `initialValue`, so the default `combineAll` folds from `{}` and accumulates + * keys from each input record. + * + * @see {@link union} for one-off record merging with the same union semantics + * @see {@link makeReducerIntersection} for a reducer that keeps only keys present on both sides + * + * @category combining + * @since 4.0.0 + */ +export function makeReducerUnion(combiner: Combiner.Combiner): Reducer.Reducer> { + return Reducer.make>( + (self, that) => union(self, that, combiner.combine), + {} as Record + ) +} + +/** + * Creates a `Reducer` whose `combine` operation intersects two records and + * combines values for keys present in both records. + * + * **When to use** + * + * Use to build a `Reducer` that combines records by retaining only keys shared + * by both inputs and combining matching values with a `Combiner`. + * + * **Gotchas** + * + * The reducer's `initialValue` is an empty record. Because intersection with + * an empty record is empty, the default `combineAll` folds from `{}` and + * therefore produces `{}` for ordinary non-empty inputs. + * + * @see {@link makeReducerUnion} for a reducer that preserves keys from either input record + * @see {@link intersection} for applying the shared-key merge to one pair of records + * + * @category combining + * @since 4.0.0 + */ +export function makeReducerIntersection( + combiner: Combiner.Combiner +): Reducer.Reducer> { + return Reducer.make( + (self, that) => intersection(self, that, combiner.combine) as any, + {} as Record + ) +} + +/** + * Returns the first entry that satisfies the specified + * predicate, or `None` if no such entry exists. + * + * **Example** (Finding the first matching entry) + * + * ```ts import.meta.vitest + * import { Option, Record } from "effect" + * + * const record = { a: 1, b: 2, c: 3 } + * Record.findFirst( + * record, + * (value, key) => value > 1 && key !== "b" + * ) // => Option.some(["c", 3]) + * ``` + * + * @category searching + * @since 3.14.0 + */ +export const findFirst: { + ( + refinement: (value: NoInfer, key: NoInfer) => value is V2 + ): (self: ReadonlyRecord) => Option.Option<[K, V2]> + ( + predicate: (value: NoInfer, key: NoInfer) => boolean + ): (self: ReadonlyRecord) => Option.Option<[K, V]> + ( + self: ReadonlyRecord, + refinement: (value: NoInfer, key: NoInfer) => value is V2 + ): Option.Option<[K, V2]> + ( + self: ReadonlyRecord, + predicate: (value: NoInfer, key: NoInfer) => boolean + ): Option.Option<[K, V]> +} = dual( + 2, + ( + self: ReadonlyRecord, + f: (value: V, key: K) => boolean + ): Option.Option<[K, V]> => { + const k = keys(self) + for (let i = 0; i < k.length; i++) { + const key = k[i] + if (f(self[key], key)) { + return Option.some([key, self[key]]) + } + } + return Option.none() + } +) diff --git a/.repos/effect/packages/effect/src/Redactable.ts b/.repos/effect/packages/effect/src/Redactable.ts new file mode 100644 index 000000000..b02eb30f2 --- /dev/null +++ b/.repos/effect/packages/effect/src/Redactable.ts @@ -0,0 +1,175 @@ +/** + * Context-aware redaction for sensitive values. + * + * The `Redactable` module provides a protocol for objects that need to present + * alternative representations of themselves depending on the runtime context. + * Typical use cases include masking secrets, tokens, or personal data in logs, traces, + * and serialized output. + * + * @since 4.0.0 + */ +import type * as Context from "./Context.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" + +/** + * Defines the symbol used to identify objects that implement the {@link Redactable} + * protocol. + * + * **When to use** + * + * Use as the property key when implementing the `Redactable` protocol. + * + * **Details** + * + * Add a method under this key to make an object redactable. The method receives + * the current `Context` and must return the replacement value. The symbol is + * registered globally via `Symbol.for("~effect/Redactable")`, so it is + * identical across multiple copies of the library at runtime. + * + * **Example** (Masking an API key) + * + * ```ts import.meta.vitest + * import { Context, Redactable } from "effect" + * + * class ApiKey { + * constructor(readonly raw: string) {} + * + * [Redactable.symbolRedactable](_ctx: Context.Context) { + * return this.raw.slice(0, 4) + "..." + * } + * } + * + * Redactable.redact(new ApiKey("secret-key")) // => "secr..." + * ``` + * + * @see {@link Redactable} for the interface this symbol belongs to + * @see {@link isRedactable} to check whether a value has this symbol + * @category symbols + * @since 3.10.0 + */ +export const symbolRedactable: unique symbol = Symbol.for("~effect/Redactable") + +/** + * Interface for objects that provide context-aware redacted representations. + * + * **When to use** + * + * Use to define classes or objects that hold sensitive data and should present + * a sanitized form when inspected or logged. + * + * **Details** + * + * The `[symbolRedactable]` method receives the current fiber's `Context`. If no + * fiber is active, an empty `Context` is provided. + * + * **Example** (Masking an API key) + * + * ```ts import.meta.vitest + * import { Context, Redactable } from "effect" + * + * class ApiKey { + * constructor(readonly raw: string) {} + * + * [Redactable.symbolRedactable](_ctx: Context.Context) { + * return this.raw.slice(0, 4) + "..." + * } + * } + * + * Redactable.redact(new ApiKey("secret-key")) // => "secr..." + * ``` + * + * @see {@link symbolRedactable} for the symbol key to implement + * @see {@link redact} to apply redaction to any value + * @see {@link isRedactable} for the type guard for this interface + * @category models + * @since 3.10.0 + */ +export interface Redactable { + readonly [symbolRedactable]: (context: Context.Context) => unknown +} + +/** + * Type guard that checks whether a value implements the {@link Redactable} + * interface. + * + * **When to use** + * + * Use to narrow an unknown value before calling redaction-specific helpers. + * + * @see {@link Redactable} for the interface being checked + * @see {@link redact} to apply redaction if the value is redactable + * @category guards + * @since 3.10.0 + */ +export const isRedactable = (u: unknown): u is Redactable => hasProperty(u, symbolRedactable) + +/** + * Returns a redacted value if it implements {@link Redactable}, otherwise returns it + * unchanged. + * + * **When to use** + * + * Use as the general-purpose entry point for redaction when the input may + * or may not implement the redaction protocol. + * + * **Details** + * + * This function calls {@link isRedactable} and, when it returns `true`, + * delegates to {@link getRedacted}. + * + * **Gotchas** + * + * Redaction is not recursive. Nested redactable values inside the returned + * object are not automatically redacted. + * + * @see {@link isRedactable} to check before redacting + * @see {@link getRedacted} for the lower-level variant for known redactables + * @category destructors + * @since 3.10.0 + */ +export function redact(u: unknown): unknown { + if (isRedactable(u)) return getRedacted(u) + return u +} + +/** + * Returns the result of calling `[symbolRedactable]` on a value that is + * already known to be {@link Redactable}. + * + * **When to use** + * + * Use when you need to read the redacted representation from a value already + * verified as `Redactable`. + * + * **Details** + * + * This function reads the current fiber's `Context` from the global fiber + * reference and passes it to the redaction method. + * + * **Gotchas** + * + * If no fiber is active, an empty `Context` is passed to the redaction method. + * + * @see {@link redact} for the higher-level variant that handles non-redactable values + * @see {@link isRedactable} for the type guard to verify before calling this + * @category destructors + * @since 4.0.0 + */ +export function getRedacted(redactable: Redactable): unknown { + return redactable[symbolRedactable]((globalThis as any)[currentFiberTypeId]?.context ?? emptyContext) +} + +/** @internal */ +export const currentFiberTypeId = "~effect/Fiber/currentFiber" + +const emptyMap = new Map() +const emptyContext: Context.Context = { + "~effect/Context": {} as any, + base: emptyMap, + depth: 0, + mapUnsafe: emptyMap, + pipe() { + return pipeArguments(this, arguments) + } +} as any diff --git a/.repos/effect/packages/effect/src/Redacted.ts b/.repos/effect/packages/effect/src/Redacted.ts new file mode 100644 index 000000000..881ec810d --- /dev/null +++ b/.repos/effect/packages/effect/src/Redacted.ts @@ -0,0 +1,314 @@ +/** + * Wraps sensitive values so normal output does not reveal them. + * + * A `Redacted` shows a redacted placeholder in string, JSON, and inspection + * output, while still storing the original value for trusted code that needs to + * recover it. This helps reduce accidental leaks in logs and diagnostics. This + * module includes constructors, runtime checks, value recovery, wiping of stored + * values, and comparison helpers that avoid exposing the wrapped value at the + * call site. + * + * @since 3.3.0 + */ +import * as Equal from "./Equal.ts" +import * as Equivalence from "./Equivalence.ts" +import * as Hash from "./Hash.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as redacted from "./internal/redacted.ts" +import type { Pipeable } from "./Pipeable.ts" +import { hasProperty, isString } from "./Predicate.ts" +import type { Covariant } from "./Types.ts" + +const TypeId = "~effect/data/Redacted" + +/** + * A wrapper for sensitive values whose string, JSON, and inspection output is + * redacted. + * + * **When to use** + * + * Use to carry sensitive values while reducing accidental exposure in string, + * JSON, and inspection output. + * + * **Gotchas** + * + * The underlying value is still stored in memory and can be recovered with + * `Redacted.value` until the wrapper is wiped or becomes unreachable. Use + * `Redacted` to reduce accidental disclosure in logs and diagnostics, not as a + * cryptographic protection mechanism. + * + * **Example** (Creating redacted values) + * + * ```ts import.meta.vitest + * import { Redacted } from "effect" + * + * // Create a redacted value to protect sensitive information + * const apiKey = Redacted.make("secret-key") + * const userPassword = Redacted.make("user-password") + * + * // TypeScript will infer the types as Redacted + * Array.of(String(apiKey), String(userPassword)) // => ["", ""] + * ``` + * + * @category models + * @since 3.3.0 + */ +export interface Redacted extends Redacted.Variance, Equal.Equal, Pipeable { + readonly label: string | undefined +} + +/** + * Namespace containing type-level members associated with `Redacted` values. + * + * **When to use** + * + * Use to access type-level helpers associated with `Redacted`. + * + * **Example** (Using namespace utilities) + * + * ```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 + * Redacted.isRedacted(secret) // => true + * ``` + * + * @since 3.3.0 + */ +export declare namespace Redacted { + /** + * Type-level variance marker for `Redacted`. + * + * **When to use** + * + * Use when defining internals that need to preserve the covariant value type + * carried by `Redacted`. + * + * **Details** + * + * This interface records the covariant value type carried by a `Redacted` + * value and is not normally referenced directly by users. + * + * @category models + * @since 3.3.0 + */ + export interface Variance { + readonly [TypeId]: { + readonly _A: Covariant + } + } + + /** + * Extracts the underlying value type from a `Redacted` type. + * + * **When to use** + * + * Use to infer the sensitive value type from an existing `Redacted` type. + * + * **Example** (Extracting the redacted value type) + * + * ```ts import.meta.vitest + * import { Redacted } from "effect" + * + * type ApiKey = Redacted.Redacted<{ readonly token: string }> + * type ApiKeyValue = Redacted.Redacted.Value + * + * const rotate = (value: ApiKeyValue): ApiKeyValue => ({ + * token: `${value.token}:rotated` + * }) + * + * rotate({ token: "secret" }) // => { token: "secret:rotated" } + * ``` + * + * @category utility types + * @since 3.3.0 + */ + export type Value> = [T] extends [Redacted] ? _A : never +} + +/** + * Returns `true` if a value is a `Redacted` wrapper. + * + * **When to use** + * + * Use to validate unknown input and narrow it to `Redacted`. + * + * **Details** + * + * When this function returns `true`, TypeScript narrows the value to + * `Redacted`. + * + * **Example** (Checking for redacted values) + * + * ```ts import.meta.vitest + * import { Redacted } from "effect" + * + * const secret = Redacted.make("my-secret") + * const plainString = "not-secret" + * + * Redacted.isRedacted(secret) // => true + * Redacted.isRedacted(plainString) // => false + * ``` + * + * @category guards + * @since 3.3.0 + */ +export const isRedacted = (u: unknown): u is Redacted => hasProperty(u, TypeId) + +/** + * Creates a `Redacted` wrapper for a sensitive value. + * + * **When to use** + * + * Use to wrap a sensitive value so normal string, JSON, and inspection output + * is redacted. + * + * **Details** + * + * The wrapper redacts string, JSON, and inspection output to reduce accidental + * disclosure. The original value remains retrievable with `Redacted.value` + * until the wrapper is wiped or becomes unreachable. + * + * **Example** (Creating a redacted value) + * + * ```ts import.meta.vitest + * import { Redacted } from "effect" + * + * const API_KEY = Redacted.make("1234567890") + * String(API_KEY) // => "" + * ``` + * + * @category constructors + * @since 3.3.0 + */ +export const make = (value: T, options?: { + readonly label?: string | undefined +}): Redacted => { + const self = Object.create(Proto) + if (options?.label) { + self.label = options.label + } + redacted.redactedRegistry.set(self, value) + return self +} + +const Proto = { + [TypeId]: { + _A: (_: never) => _ + }, + label: undefined, + ...PipeInspectableProto, + toJSON() { + return this.toString() + }, + toString() { + return `` + }, + [Hash.symbol](this: Redacted): number { + return Hash.hash(redacted.redactedRegistry.get(this)) + }, + [Equal.symbol](this: Redacted, that: unknown): boolean { + return ( + isRedacted(that) && + Equal.equals( + redacted.redactedRegistry.get(this), + redacted.redactedRegistry.get(that) + ) + ) + } +} + +/** + * Retrieves the original value from a `Redacted` instance. Use this function + * with caution, as it exposes the sensitive data. + * + * **When to use** + * + * Use when you need the underlying sensitive value at a trusted boundary. + * + * **Example** (Retrieving a redacted value) + * + * ```ts import.meta.vitest + * import { Redacted } from "effect" + * + * const API_KEY = Redacted.make("1234567890") + * + * Redacted.value(API_KEY) // => "1234567890" + * ``` + * + * @category getters + * @since 3.3.0 + */ +export const value: (self: Redacted) => T = redacted.value + +/** + * Deletes the stored value for a `Redacted` wrapper, making future + * `Redacted.value` calls on that wrapper fail. + * + * **When to use** + * + * Use when a `Redacted` wrapper should no longer be able to reveal its stored + * value. + * + * **Gotchas** + * + * This unsafe operation does not zero memory and does not affect other + * references to the original value. It only removes the value from the + * internal redacted registry. + * + * **Example** (Wiping a redacted value) + * + * ```ts import.meta.vitest + * import { Redacted, Result } from "effect" + * + * const API_KEY = Redacted.make("1234567890") + * + * Redacted.value(API_KEY) // => "1234567890" + * + * Redacted.wipeUnsafe(API_KEY) + * + * 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 + * @since 4.0.0 + */ +export const wipeUnsafe = (self: Redacted): boolean => redacted.redactedRegistry.delete(self) + +/** + * Generates an equivalence relation for `Redacted` values based on an + * equivalence relation for the underlying values `A`. This function is useful + * for comparing `Redacted` instances without exposing their contents. + * + * **When to use** + * + * Use when you need to compare wrapped secrets through an approved equality + * rule without exposing the underlying values at each comparison site. + * + * **Example** (Comparing redacted values) + * + * ```ts import.meta.vitest + * import { Equivalence, Redacted } from "effect" + * + * const API_KEY1 = Redacted.make("1234567890") + * const API_KEY2 = Redacted.make("1-34567890") + * const API_KEY3 = Redacted.make("1234567890") + * + * const equivalence = Redacted.makeEquivalence(Equivalence.strictEqual()) + * + * equivalence(API_KEY1, API_KEY2) // => false + * equivalence(API_KEY1, API_KEY3) // => true + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence = (isEquivalent: Equivalence.Equivalence): Equivalence.Equivalence> => + Equivalence.make((x, y) => isEquivalent(value(x), value(y))) diff --git a/.repos/effect/packages/effect/src/Reducer.ts b/.repos/effect/packages/effect/src/Reducer.ts new file mode 100644 index 000000000..6f1a6e42b --- /dev/null +++ b/.repos/effect/packages/effect/src/Reducer.ts @@ -0,0 +1,171 @@ +/** + * Reusable strategies for reducing many values into one value. A `Reducer` + * extends `Combiner.Combiner` with an `initialValue` for empty collections and + * a `combineAll` method for folding an entire iterable. This module provides a + * constructor for reducers and a helper for reversing the order in which values + * are combined. + * + * @since 4.0.0 + */ + +import type * as Combiner from "./Combiner.ts" + +/** + * Represents a strategy for reducing a collection of values of type `A` into + * a single result. + * + * **When to use** + * + * Use when you need to fold/reduce a collection into a single value. + * - You want a reusable reducing strategy that can be passed to library + * functions like `Struct.makeReducer`, `Option.makeReducer`, or + * `Record.makeReducerUnion`. + * - You need both the combining logic *and* a known starting value. + * + * **Details** + * + * Extends {@link Combiner.Combiner} with: + * + * - `initialValue` – the identity/neutral element for `combine`. + * - `combineAll` – folds an entire `Iterable` from `initialValue`. + * + * Many modules ship pre-built reducers: + * + * - `Number.ReducerSum`, `Number.ReducerMultiply` + * - `String.ReducerConcat` + * - `Boolean.ReducerAnd`, `Boolean.ReducerOr` + * + * **Example** (String concatenation reducer) + * + * ```ts import.meta.vitest + * import { Reducer } from "effect" + * + * const Concat = Reducer.make((a, b) => a + b, "") + * + * Concat.combineAll(["hello", " ", "world"]) // => "hello world" + * ``` + * + * @see {@link make} – create a `Reducer` from a function and initial value + * @see {@link Combiner.Combiner} – parent interface without `initialValue` + * + * @category models + * @since 4.0.0 + */ +export interface Reducer extends Combiner.Combiner { + /** + * Neutral starting value (combining with this changes nothing). + * + * **When to use** + * + * Use to seed a reduction and represent the result of reducing an empty collection. + */ + readonly initialValue: A + + /** + * Combines all values in the collection, starting from `initialValue`. + * + * **When to use** + * + * Use to reduce an iterable with this reducer's initial value and combining operation. + */ + readonly combineAll: (collection: Iterable) => A +} + +/** + * Creates a `Reducer` from a `combine` function and an `initialValue`. + * + * **When to use** + * + * Use when you have a custom reducing operation not covered by a pre-built reducer. + * - You want to provide an optimized `combineAll` (e.g. short-circuiting on + * a known absorbing element like `0` for multiplication). + * + * **Details** + * + * - If `combineAll` is omitted, a default left-to-right fold starting from + * `initialValue` is used. + * - If `combineAll` is provided, it completely replaces the default fold. + * + * **Example** (Multiplying with short-circuit) + * + * ```ts import.meta.vitest + * import { Reducer } from "effect" + * + * const Product = Reducer.make( + * (a, b) => a * b, + * 1, + * (collection) => { + * let acc = 1 + * for (const n of collection) { + * if (n === 0) return 0 + * acc *= n + * } + * return acc + * } + * ) + * + * Product.combineAll([2, 3, 4]) // => 24 + * Product.combineAll([2, 0, 4]) // => 0 + * ``` + * + * @see {@link Reducer} – the interface this creates + * @see {@link flip} – reverse the argument order + * + * @category constructors + * @since 4.0.0 + */ +export function make( + combine: (self: A, that: A) => A, + initialValue: A, + combineAll?: (collection: Iterable) => A +): Reducer { + return { + combine, + initialValue, + combineAll: combineAll ?? + ((collection) => { + let out = initialValue + for (const value of collection) { + out = combine(out, value) + } + return out + }) + } +} + +/** + * Reverses the argument order of a reducer's `combine` method. + * + * **When to use** + * + * Use when you want the right-hand value to act as the accumulator, or need to + * reverse a non-commutative reducer such as string concatenation. + * + * **Details** + * + * - Returns a new `Reducer` where `combine(self, that)` calls the original + * reducer as `combine(that, self)`. + * - The `initialValue` is preserved from the original reducer. + * - The `combineAll` is re-derived from the flipped `combine` (using the + * default left-to-right fold), not carried over from the original. + * + * **Example** (Reversing string concatenation) + * + * ```ts import.meta.vitest + * import { Reducer, String } from "effect" + * + * const Prepend = Reducer.flip(String.ReducerConcat) + * + * Prepend.combine("a", "b") // => "ba" + * Prepend.combineAll(["a", "b", "c"]) // => "cba" + * ``` + * + * @see {@link make} + * @see {@link Combiner.flip} – the same operation on a plain `Combiner` + * + * @category combinators + * @since 4.0.0 + */ +export function flip(reducer: Reducer): Reducer { + return make((self, that) => reducer.combine(that, self), reducer.initialValue) +} diff --git a/.repos/effect/packages/effect/src/Ref.ts b/.repos/effect/packages/effect/src/Ref.ts new file mode 100644 index 000000000..a5c783fca --- /dev/null +++ b/.repos/effect/packages/effect/src/Ref.ts @@ -0,0 +1,747 @@ +/** + * Stores fiber-safe mutable state inside Effect programs. + * + * A `Ref` holds one value and exposes reads, writes, and atomic + * transformations as effects, so state changes compose with Effect's + * concurrency model. This module includes constructors, safe and unsafe reads, + * set and get-and-set helpers, update and modify helpers, and conditional + * update variants that leave the value unchanged when an `Option.none` result + * is returned. + * + * @since 2.0.0 + */ +import * as Effect from "./Effect.ts" +import { dual, identity } from "./Function.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as MutableRef from "./MutableRef.ts" +import type * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { Invariant } from "./Types.ts" + +const TypeId = "~effect/Ref" + +/** + * A mutable reference that provides atomic read, write, and update operations. + * + * **When to use** + * + * Use to keep shared mutable state that is read and updated inside Effect + * programs. + * + * **Details** + * + * A `Ref` is a thread-safe mutable reference type for shared state. It supports + * simple read and write operations as well as atomic transformations. + * + * **Example** (Reading and updating a ref) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(0) + * const value = yield* Ref.get(counter) + * yield* Ref.update(counter, (n) => n + 1) + * const newValue = yield* Ref.get(counter) + * return [value, newValue] + * }) + * + * await Effect.runPromise(program) // => [0, 1] + * ``` + * + * @see {@link make} for creating a `Ref` + * @see {@link get} for reading the current value + * @see {@link set} for replacing the current value + * + * @category models + * @since 2.0.0 + */ +export interface Ref extends Ref.Variance, Pipeable { + readonly ref: MutableRef.MutableRef +} + +/** + * The Ref namespace containing type definitions and utilities. + * + * **When to use** + * + * Use when referring to type members nested under the `Ref` namespace. + * + * @since 2.0.0 + */ +export declare namespace Ref { + /** + * Variance interface for Ref types, defining the type parameter constraints. + * + * **When to use** + * + * Use when working with the type-level variance marker carried by `Ref`. + * + * **Example** (Using invariant refs) + * + * ```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) + * yield* Ref.set(ref, value + 1) + * return yield* Ref.get(ref) + * }) + * + * await Effect.runPromise(program) // => 43 + * ``` + * + * @category models + * @since 2.0.0 + */ + export interface Variance { + readonly [TypeId]: { + readonly _A: Invariant + } + } +} + +const RefProto = { + [TypeId]: { + _A: identity + }, + ...PipeInspectableProto, + toJSON(this: Ref) { + return { + _id: "Ref", + ref: this.ref + } + } +} + +/** + * Creates a new Ref with the specified initial value (unsafe version). + * + * **When to use** + * + * Use when you need immediate synchronous construction and can guarantee + * that creating the `Ref` outside of `Effect` is safe. + * + * **Gotchas** + * + * Prefer `Ref.make` for Effect-wrapped creation in Effect programs. + * + * **Example** (Creating a ref unsafely) + * + * ```ts import.meta.vitest + * import { Ref } from "effect" + * + * const counter = Ref.makeUnsafe(0) + * Ref.getUnsafe(counter) // => 0 + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeUnsafe = (value: A): Ref => { + const self = Object.create(RefProto) + self.ref = MutableRef.make(value) + return self +} + +/** + * Creates a new Ref with the specified initial value. + * + * **When to use** + * + * Use to create a `Ref` for shared mutable state inside an Effect program. + * + * **Example** (Creating a ref) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* Ref.make(42) + * return yield* Ref.get(ref) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @see {@link makeUnsafe} for synchronous construction outside Effect code + * + * @category constructors + * @since 2.0.0 + */ +export const make = (value: A): Effect.Effect> => Effect.sync(() => makeUnsafe(value)) + +/** + * Gets the current value of the Ref. + * + * **When to use** + * + * Use to read the current `Ref` value without changing it. + * + * **Example** (Getting the current value) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* Ref.make(42) + * return yield* Ref.get(ref) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @see {@link set} for replacing the current value + * + * @category getters + * @since 2.0.0 + */ +export const get = (self: Ref) => Effect.sync(() => self.ref.current) + +/** + * Sets the value of the Ref to the specified value. + * + * **When to use** + * + * Use to replace the current `Ref` value with a known value. + * + * **Example** (Setting a value) + * + * ```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) + * return yield* Ref.get(ref) + * }) + * + * const program2 = Effect.gen(function*() { + * const ref = yield* Ref.make(0) + * yield* Ref.set(ref, 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 mutations + * @since 2.0.0 + */ +export const set = dual< + (value: A) => (self: Ref) => Effect.Effect, + (self: Ref, value: A) => Effect.Effect +>(2, (self: Ref, value: A) => Effect.sync(() => MutableRef.set(self.ref, value))) + +/** + * Gets the current value of the Ref, sets it to the specified value, and returns the previous value atomically. + * + * **When to use** + * + * Use to replace a plain `Ref` value while returning the previous value. + * + * **Example** (Replacing a value atomically) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* Ref.make("initial") + * + * const previous = yield* Ref.getAndSet(ref, "updated") + * const current = yield* Ref.get(ref) + * return [previous, current] + * }) + * + * await Effect.runPromise(program) // => ["initial", "updated"] + * ``` + * + * @see {@link set} for setting without returning the previous value + * @see {@link getAndUpdate} for deriving the new value from the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const getAndSet = dual< + (value: A) => (self: Ref) => Effect.Effect, + (self: Ref, value: A) => Effect.Effect +>(2, (self: Ref, value: A) => + Effect.sync(() => { + const current = self.ref.current + self.ref.current = value + return current + })) + +/** + * Gets the current value of the Ref, updates it with the given function, and returns the previous value atomically. + * + * **When to use** + * + * Use to derive a new `Ref` value while returning the previous value. + * + * **Example** (Updating and returning the previous value) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(10) + * + * const previous = yield* Ref.getAndUpdate(counter, (n) => n * 2) + * const current = yield* Ref.get(counter) + * return [previous, current] + * }) + * + * await Effect.runPromise(program) // => [10, 20] + * ``` + * + * @see {@link update} for updating without returning the previous value + * @see {@link updateAndGet} for returning the new value instead + * + * @category mutations + * @since 2.0.0 + */ +export const getAndUpdate = dual< + (f: (a: A) => A) => (self: Ref) => Effect.Effect, + (self: Ref, f: (a: A) => A) => Effect.Effect +>(2, (self: Ref, f: (a: A) => A) => + Effect.sync(() => { + const current = self.ref.current + self.ref.current = f(current) + return current + })) + +/** + * Gets the current value of the Ref and updates it atomically with the given partial function. + * + * **When to use** + * + * Use to return the previous `Ref` value while applying a conditional update. + * + * **Details** + * + * If the partial function returns `Option.some`, the Ref is updated with the + * new value. If it returns `Option.none`, the Ref is left unchanged. The effect + * always returns the value that was in the Ref before the attempted update. + * + * **Example** (Conditionally updating and returning the previous value) + * + * ```ts import.meta.vitest + * import { Effect, Option, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(5) + * + * const previous1 = yield* Ref.getAndUpdateSome( + * counter, + * (n) => n > 3 ? Option.some(n * 2) : Option.none() + * ) + * const current1 = yield* Ref.get(counter) + * const previous2 = yield* Ref.getAndUpdateSome( + * counter, + * (n) => n < 3 ? Option.some(n * 2) : Option.none() + * ) + * const current2 = yield* Ref.get(counter) + * return [previous1, current1, previous2, current2] + * }) + * + * await Effect.runPromise(program) // => [5, 10, 10, 10] + * ``` + * + * @see {@link getAndUpdate} for always applying an update + * @see {@link updateSome} for conditional updates without returning the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const getAndUpdateSome = dual< + (pf: (a: A) => Option.Option) => (self: Ref) => Effect.Effect, + (self: Ref, pf: (a: A) => Option.Option) => Effect.Effect +>(2, (self: Ref, pf: (a: A) => Option.Option) => + Effect.sync(() => { + const current = self.ref.current + const option = pf(current) + if (option._tag === "Some") { + self.ref.current = option.value + } + return current + })) + +/** + * Sets the value of the Ref atomically to the specified value and returns the new value. + * + * **When to use** + * + * Use when you want to set a `Ref` value and immediately get it back in one + * atomic operation. + * + * **Example** (Setting and returning the new value) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* Ref.make(10) + * + * const newValue = yield* Ref.setAndGet(ref, 42) + * const current = yield* Ref.get(ref) + * return [newValue, current] + * }) + * + * const program2 = Effect.gen(function*() { + * const counter = yield* Ref.make(0) + * return yield* Ref.setAndGet(counter, 20) + * }) + * + * await Effect.runPromise(program) // => [42, 42] + * await Effect.runPromise(program2) // => 20 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const setAndGet = dual< + (value: A) => (self: Ref) => Effect.Effect, + (self: Ref, value: A) => Effect.Effect +>(2, (self: Ref, value: A) => Effect.sync(() => self.ref.current = value)) + +/** + * Modifies the value of the Ref atomically using the given function. + * + * **When to use** + * + * Use to compute both a separate return value and the next stored `Ref` value + * in one atomic update. + * + * **Details** + * + * The function receives the current value and returns a tuple of + * `[result, newValue]`. The Ref is updated with `newValue`, and `result` is + * returned by the effect. + * + * **Example** (Modifying a value atomically) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(10) + * + * const result = yield* Ref.modify(counter, (n) => [ + * `Previous value was ${n}`, + * n * 2 + * ]) + * const current = yield* Ref.get(counter) + * return [result, current] + * }) + * + * const program2 = Effect.gen(function*() { + * const state = yield* Ref.make({ count: 0, total: 0 }) + * return yield* Ref.modify(state, (s) => [ + * s.count, + * { count: s.count + 1, total: s.total + s.count + 1 } + * ]) + * }) + * + * 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 mutations + * @since 2.0.0 + */ +export const modify = dual< + (f: (a: A) => readonly [B, A]) => (self: Ref) => Effect.Effect, + (self: Ref, f: (a: A) => readonly [B, A]) => Effect.Effect +>(2, (self, f) => + Effect.sync(() => { + const [b, a] = f(self.ref.current) + self.ref.current = a + return b + })) + +/** + * Computes a result atomically and optionally updates the value of the `Ref`. + * + * **When to use** + * + * Use to compute a return value while optionally updating a plain `Ref`. + * + * **Details** + * + * The callback receives the current value and returns `[result, nextValue]`, + * where `nextValue` is an `Option`. If `nextValue` is `Option.some(value)`, + * the `Ref` is updated to `value`; if it is `Option.none()`, the `Ref` is left + * unchanged. The returned effect always succeeds with `result`. + * + * **Example** (Conditionally modifying a value) + * + * ```ts import.meta.vitest + * import { Effect, Option, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(5) + * + * const result1 = yield* Ref.modifySome( + * counter, + * (n) => + * n > 3 + * ? [`incremented ${n}`, Option.some(n + 10)] + * : ["no change", Option.none()] + * ) + * const current1 = yield* Ref.get(counter) + * const result2 = yield* Ref.modifySome( + * counter, + * (n) => + * n < 10 + * ? [`decremented ${n}`, Option.some(n - 5)] + * : ["no change", Option.none()] + * ) + * const current2 = yield* Ref.get(counter) + * 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 mutations + * @since 2.0.0 + */ +export const modifySome: { + (pf: (a: A) => readonly [B, Option.Option]): (self: Ref) => Effect.Effect + (self: Ref, pf: (a: A) => readonly [B, Option.Option]): Effect.Effect +} = dual< + ( + pf: (a: A) => readonly [B, Option.Option] + ) => (self: Ref) => Effect.Effect, + ( + self: Ref, + pf: (a: A) => readonly [B, Option.Option] + ) => Effect.Effect +>(2, (self, pf) => + modify(self, (value) => { + const [b, option] = pf(value) + return [b, option._tag === "None" ? value : option.value] + })) + +/** + * Updates the value of the Ref atomically using the given function. + * + * **When to use** + * + * Use to apply a `Ref` state transition without returning a value. + * + * **Example** (Updating a value) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(5) + * + * yield* Ref.update(counter, (n) => n * 2) + * return yield* Ref.get(counter) + * }) + * + * const program2 = Effect.gen(function*() { + * const counter = yield* Ref.make(5) + * yield* Ref.update(counter, (n: number) => n + 10) + * 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 mutations + * @since 2.0.0 + */ +export const update = dual< + (f: (a: A) => A) => (self: Ref) => Effect.Effect, + (self: Ref, f: (a: A) => A) => Effect.Effect +>(2, (self: Ref, f: (a: A) => A) => + Effect.sync(() => { + self.ref.current = f(self.ref.current) + })) + +/** + * Updates the value of the Ref atomically using the given function and returns the new value. + * + * **When to use** + * + * Use to apply a `Ref` state transition and return the new stored value. + * + * **Example** (Updating and returning the new value) + * + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(5) + * + * const newValue = yield* Ref.updateAndGet(counter, (n) => n * 3) + * const current = yield* Ref.get(counter) + * return [newValue, current] + * }) + * + * await Effect.runPromise(program) // => [15, 15] + * ``` + * + * @see {@link update} for updating without returning the new value + * @see {@link getAndUpdate} for returning the previous value instead + * + * @category mutations + * @since 2.0.0 + */ +export const updateAndGet = dual< + (f: (a: A) => A) => (self: Ref) => Effect.Effect, + (self: Ref, f: (a: A) => A) => Effect.Effect +>(2, (self: Ref, f: (a: A) => A) => Effect.sync(() => self.ref.current = f(self.ref.current))) + +/** + * Updates the value of the Ref atomically using the given partial function. + * + * **When to use** + * + * Use to apply a conditional `Ref` update without returning a value. + * + * **Details** + * + * If the partial function returns `Option.some`, the Ref is updated with the + * new value. If it returns `Option.none`, the Ref is left unchanged. + * + * **Example** (Conditionally updating a value) + * + * ```ts import.meta.vitest + * import { Effect, Option, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(5) + * + * yield* Ref.updateSome( + * counter, + * (n) => n % 2 === 0 ? Option.some(n * 2) : Option.none() + * ) + * 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() + * ) + * 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 mutations + * @since 2.0.0 + */ +export const updateSome = dual< + (f: (a: A) => Option.Option) => (self: Ref) => Effect.Effect, + (self: Ref, f: (a: A) => Option.Option) => Effect.Effect +>(2, (self: Ref, f: (a: A) => Option.Option) => + Effect.sync(() => { + const option = f(self.ref.current) + if (option._tag === "Some") { + self.ref.current = option.value + } + })) + +/** + * Updates the value of the Ref atomically using the given partial function and returns the current value. + * + * **When to use** + * + * Use to apply a conditional `Ref` update and return the resulting current + * value. + * + * **Details** + * + * If the partial function returns `Option.some`, the Ref is updated with the + * new value. If it returns `Option.none`, the Ref is left unchanged. The effect + * returns the current value of the Ref after the potential update. + * + * **Example** (Conditionally updating and returning the current value) + * + * ```ts import.meta.vitest + * import { Effect, Option, Ref } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* Ref.make(10) + * + * const result1 = yield* Ref.updateSomeAndGet( + * counter, + * (n) => n > 5 ? Option.some(n / 2) : Option.none() + * ) + * const result2 = yield* Ref.updateSomeAndGet( + * counter, + * (n) => n > 5 ? Option.some(n / 2) : Option.none() + * ) + * return [result1, result2] + * }) + * + * await Effect.runPromise(program) // => [5, 5] + * ``` + * + * @see {@link updateSome} for conditional updates without returning a value + * @see {@link updateAndGet} for always updating and returning the new value + * + * @category mutations + * @since 2.0.0 + */ +export const updateSomeAndGet = dual< + (pf: (a: A) => Option.Option) => (self: Ref) => Effect.Effect, + (self: Ref, pf: (a: A) => Option.Option) => Effect.Effect +>(2, (self: Ref, pf: (a: A) => Option.Option) => + Effect.sync(() => { + const option = pf(self.ref.current) + if (option._tag === "Some") { + self.ref.current = option.value + } + return self.ref.current + })) + +/** + * Gets the current value of the Ref synchronously (unsafe version). + * + * **When to use** + * + * Use when you need immediate synchronous access and can guarantee that + * reading the `Ref` outside of `Effect` is safe. + * + * **Gotchas** + * + * Prefer `Ref.get` for Effect-wrapped access in Effect programs. + * + * **Example** (Reading a ref unsafely) + * + * ```ts import.meta.vitest + * import { Ref } from "effect" + * + * const counter = Ref.makeUnsafe(42) + * Ref.getUnsafe(counter) // => 42 + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const getUnsafe = (self: Ref): A => self.ref.current diff --git a/.repos/effect/packages/effect/src/References.ts b/.repos/effect/packages/effect/src/References.ts new file mode 100644 index 000000000..5d5e51dd9 --- /dev/null +++ b/.repos/effect/packages/effect/src/References.ts @@ -0,0 +1,674 @@ +/** + * The `References` module exposes the built-in `Context.Reference` keys that + * the Effect runtime consults for execution settings and diagnostic metadata. + * These references cover concurrency, scheduling, logging, tracing, and + * low-level diagnostic state. + * + * A `Context.Reference` is a service key with a default value. Reading one + * of these references returns the value from the current fiber context, and + * providing a new value changes behavior for the provided effect and the fibers + * it starts. + * + * @since 4.0.0 + */ +import type * as Context from "./Context.ts" +import * as internalEffect from "./internal/effect.ts" +import * as references from "./internal/references.ts" +import type { Logger } from "./Logger.ts" +import type { LogLevel, Severity } from "./LogLevel.ts" +import type { ReadonlyRecord } from "./Record.ts" +import { MaxOpsBeforeYield, PreventSchedulerYield } from "./Scheduler.ts" +import { CurrentTraceLevel, DisablePropagation, MinimumTraceLevel, type SpanLink, Tracer } from "./Tracer.ts" + +export { + /** + * Context reference for the current trace level used for dynamic trace filtering. + * + * **When to use** + * + * Use to set the default trace level for spans created in a scope when span + * options do not provide `level`. + * + * @see {@link MinimumTraceLevel} for configuring the threshold that decides whether spans at a given level are sampled or exported + * + * @category references + * @since 4.0.0 + */ + CurrentTraceLevel, + /** + * Context reference for disabling trace propagation in the current context. + * + * **When to use** + * + * Use to mark tracing work as non-propagating while still allowing local span + * tracking. + * + * **Details** + * + * Annotated spans become non-propagating no-op spans, and parent selection + * skips spans marked with disabled propagation. + * + * @see {@link TracerEnabled} for disabling span registration instead of only propagation + * + * @category references + * @since 4.0.0 + */ + DisablePropagation, + /** + * Context reference for the maximum operation budget before a fiber yields to the scheduler. + * + * **When to use** + * + * Use to configure the runtime reference for the fiber operation budget that + * triggers a scheduler yield. + * + * **Details** + * + * The default value is `2048` operations. + * + * @see {@link PreventSchedulerYield} for bypassing scheduler yield checks instead of changing the operation budget + * + * @category references + * @since 4.0.0 + */ + MaxOpsBeforeYield, + /** + * Context reference for the minimum trace level threshold for span sampling. + * + * **When to use** + * + * Use to set the trace-level threshold that decides whether newly created + * spans are sampled and exported. + * + * @see {@link CurrentTraceLevel} for setting the level assigned to spans before this threshold is applied + * + * @category references + * @since 4.0.0 + */ + MinimumTraceLevel, + /** + * Context reference for whether the runtime bypasses scheduler yield checks. + * + * **When to use** + * + * Use to bypass automatic scheduler yield checks in a controlled runtime scope + * where throughput is preferred over scheduler fairness. + * + * **Details** + * + * When set to `true`, the fiber run loop skips `Scheduler.shouldYield`. The + * default value is `false`. + * + * **Gotchas** + * + * Disabling automatic yield checks can let long-running fibers monopolize the + * JavaScript thread. + * + * @see {@link MaxOpsBeforeYield} for tuning the operation budget while keeping scheduler yield checks enabled + * + * @category references + * @since 4.0.0 + */ + PreventSchedulerYield, + /** + * Context reference for the active tracer service used to create spans. + * + * **When to use** + * + * Use to access or override the active tracer service through the references + * module when working directly with Effect runtime references. + * + * @category references + * @since 4.0.0 + */ + Tracer +} + +/** + * 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. + * + * **When to use** + * + * Use to attach shared contextual metadata to every log entry emitted in the + * current context. + * + * **Example** (Managing log annotations) + * + * ```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 + * const defaultCount = Object.keys(current).length + * + * // Run with custom log annotations + * const custom = yield* Effect.provideService( + * Effect.gen(function*() { + * const annotations = yield* References.CurrentLogAnnotations + * return [annotations.requestId, annotations.userId, annotations.version] + * }), + * References.CurrentLogAnnotations, + * { + * requestId: "req-123", + * userId: "user-456", + * version: "1.0.0" + * } + * ) + * + * // Run with extended annotations + * const extended = yield* Effect.provideService( + * Effect.gen(function*() { + * const annotations = yield* References.CurrentLogAnnotations + * return [annotations.operation, annotations.timestamp] + * }), + * References.CurrentLogAnnotations, + * { + * requestId: "req-123", + * userId: "user-456", + * version: "1.0.0", + * operation: "data-sync", + * timestamp: 1234567890 + * } + * ) + * + * return [defaultCount, custom, extended] + * }) + * + * await Effect.runPromise(logAnnotationExample) // => [0, ["req-123", "user-456", "1.0.0"], ["data-sync", 1234567890]] + * ``` + * + * @category references + * @since 4.0.0 + */ +export const CurrentLogAnnotations: Context.Reference> = + references.CurrentLogAnnotations + +/** + * Context reference for the current log severity used by `Effect.log` when no explicit + * level is provided. + * + * **When to use** + * + * Use to set the default severity for `Effect.log` entries that do not provide + * an explicit level. + * + * **Details** + * + * Use `MinimumLogLevel` to control which log entries are filtered out. + * + * **Example** (Changing the level of an unqualified log) + * + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * + * const levels: Array = [] + * const logger = Logger.make(({ logLevel }) => { + * levels.push(logLevel) + * }) + * + * 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 + * @since 4.0.0 + */ +export const CurrentLogLevel: Context.Reference = references.CurrentLogLevel + +/** + * Context reference for managing log spans that track the duration and hierarchy of operations. + * Each span represents a labeled time period for performance analysis and debugging. + * + * **When to use** + * + * Use to carry the active log span stack that should be included with log + * entries in the current context. + * + * **Example** (Tracking log spans) + * + * ```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 + * const defaultCount = current.length + * + * // Add a log span manually + * const databaseConnectionStartedAt = 0 + * const database = yield* Effect.provideService( + * Effect.gen(function*() { + * const spans = yield* References.CurrentLogSpans + * return spans.map(([label]) => label) + * }), + * References.CurrentLogSpans, + * [["database-connection", databaseConnectionStartedAt]] + * ) + * + * // Add another span + * const dataProcessingStartedAt = 100 + * const processing = yield* Effect.provideService( + * Effect.gen(function*() { + * const spans = yield* References.CurrentLogSpans + * return spans.map(([label]) => label) + * }), + * References.CurrentLogSpans, + * [ + * ["database-connection", databaseConnectionStartedAt], + * ["data-processing", dataProcessingStartedAt] + * ] + * ) + * + * // Clear spans when operations complete + * const cleared = yield* Effect.provideService( + * Effect.gen(function*() { + * const spans = yield* References.CurrentLogSpans + * return spans.length + * }), + * References.CurrentLogSpans, + * [] + * ) + * + * return [defaultCount, database, processing, cleared] + * }) + * + * await Effect.runPromise(logSpanExample) // => [0, ["database-connection"], ["database-connection", "data-processing"], 0] + * ``` + * + * @category references + * @since 4.0.0 + */ +export const CurrentLogSpans: Context.Reference> = + references.CurrentLogSpans + +/** + * Context reference for the current captured stack-frame chain for the running + * fiber. + * + * **When to use** + * + * Use when writing low-level tracing or diagnostic integrations that need direct + * access to the stack-frame chain carried by the current fiber. + * + * **Details** + * + * Effect and Layer tracing use this reference to attach stack-frame information + * to failures and interruption causes. It is normally managed by tracing APIs + * rather than provided directly by application code. + * + * @see {@link StackFrame} for the frame node stored in this reference + * + * @category references + * @since 4.0.0 + */ +export const CurrentStackFrame: Context.Reference = references.CurrentStackFrame + +/** + * Context reference for setting the minimum log level threshold. Log entries below this + * level will be filtered out completely. + * + * **When to use** + * + * Use to filter out log entries below a severity threshold. + * + * **Example** (Filtering logs below the minimum level) + * + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * + * const levels: Array = [] + * const logger = Logger.make(({ logLevel }) => { + * levels.push(logLevel) + * }) + * + * 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 + * @since 4.0.0 + */ +export const MinimumLogLevel: Context.Reference = references.MinimumLogLevel + +/** + * Context reference for controlling whether tracing is enabled globally. When set to false, + * spans will not be registered with the tracer and tracing overhead is minimized. + * + * **When to use** + * + * Use to disable or re-enable span registration in the current context. + * + * **Example** (Toggling tracing) + * + * ```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 + * + * // Disable tracing globally + * const disabled = yield* Effect.provideService( + * References.TracerEnabled, + * References.TracerEnabled, + * false + * ) + * + * // Re-enable tracing + * const enabled = yield* Effect.provideService( + * References.TracerEnabled, + * References.TracerEnabled, + * true + * ) + * + * return [current, disabled, enabled] + * }) + * + * await Effect.runPromise(tracingControl) // => [true, false, true] + * ``` + * + * @category references + * @since 4.0.0 + */ +export const TracerEnabled: Context.Reference = references.TracerEnabled + +/** + * Context reference for managing span annotations that are automatically added to all new spans. + * These annotations provide context and metadata that applies across multiple spans. + * + * **When to use** + * + * Use to attach shared metadata to every span created in the current context. + * + * **Example** (Managing span annotations) + * + * ```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 + * const defaultCount = Object.keys(current).length + * + * // Set global span annotations + * const configured = yield* Effect.provideService( + * Effect.gen(function*() { + * // Get current annotations + * const annotations = yield* References.TracerSpanAnnotations + * return [annotations.service, annotations.version, annotations.environment] + * }), + * References.TracerSpanAnnotations, + * { + * service: "user-service", + * version: "1.2.3", + * environment: "production" + * } + * ) + * + * // Clear annotations + * const cleared = yield* Effect.provideService( + * Effect.gen(function*() { + * const annotations = yield* References.TracerSpanAnnotations + * 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 + * @since 4.0.0 + */ +export const TracerSpanAnnotations: Context.Reference> = + references.TracerSpanAnnotations + +/** + * Context reference for managing span links that are automatically added to all new spans. + * Span links connect related spans that are not in a parent-child relationship. + * + * **When to use** + * + * Use to attach shared links to every span created in the current context. + * + * **Example** (Managing span links) + * + * ```ts import.meta.vitest + * import { Effect, References, Tracer } from "effect" + * + * const spanLinksExample = Effect.gen(function*() { + * // Get current links (empty by default) + * const current = yield* References.TracerSpanLinks + * const defaultCount = current.length + * + * // Create an external span for the example + * const externalSpan = Tracer.externalSpan({ + * spanId: "external-span-123", + * traceId: "trace-456" + * }) + * + * // Create span links + * const spanLink: Tracer.SpanLink = { + * span: externalSpan, + * attributes: { + * relationship: "follows-from", + * priority: "high" + * } + * } + * + * // Set global span links + * const configuredCount = yield* Effect.provideService( + * Effect.map(References.TracerSpanLinks, (links) => links.length), + * References.TracerSpanLinks, + * [spanLink] + * ) + * + * // Clear links + * 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 + * @since 4.0.0 + */ +export const TracerSpanLinks: Context.Reference> = references.TracerSpanLinks + +/** + * Context reference for controlling whether trace timing is enabled globally. When set + * to false, spans will not contain timing information (trace time will always + * be set to zero). + * + * **When to use** + * + * Use to disable or re-enable timing capture for spans in the current context. + * + * **Example** (Toggling trace timing) + * + * ```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 + * + * // Disable trace timing globally + * const disabled = yield* Effect.provideService( + * References.TracerTimingEnabled, + * References.TracerTimingEnabled, + * false + * ) + * + * // Re-enable trace timing + * const enabled = yield* Effect.provideService( + * References.TracerTimingEnabled, + * References.TracerTimingEnabled, + * true + * ) + * + * return [current, disabled, enabled] + * }) + * + * await Effect.runPromise(tracingControl) // => [true, false, true] + * ``` + * + * @category references + * @since 4.0.0 + */ +export const TracerTimingEnabled: Context.Reference = references.TracerTimingEnabled + +/** + * Context reference for the log severity used when a pool finalizer reports an + * unhandled error. + * + * **When to use** + * + * Use to choose whether and at which severity pool finalizer failures are + * reported. + * + * **Details** + * + * The default level is `"Error"`. + * + * **Gotchas** + * + * Providing `undefined` suppresses this report; it does not fall back to + * `CurrentLogLevel`. + * + * @see {@link CurrentLogLevel} for the default severity used by ordinary `Effect.log` calls + * @see {@link MinimumLogLevel} for filtering emitted log entries by threshold + * + * @category references + * @since 4.0.0 + */ +export const UnhandledLogLevel: Context.Reference = references.UnhandledLogLevel + +/** + * A captured stack-frame node used to describe the traced execution path. + * + * **When to use** + * + * Use when reading or supplying the stack-frame chain that Effect tracing uses + * to attach diagnostic call-site information to failures and interruptions. + * + * **Details** + * + * Each frame has a span or operation `name`, a lazy `stack` supplier, and an + * optional `parent` frame that links it to the previous captured frame. + * + * @see {@link CurrentStackFrame} for the fiber reference carrying the active stack-frame chain + * + * @category references + * @since 4.0.0 + */ +export interface StackFrame { + readonly name: string + readonly stack: () => string | undefined + readonly parent: StackFrame | undefined +} + +/** + * Context reference for the set of loggers currently used by Effect logging + * operations. + * + * **When to use** + * + * Use to inspect or provide the complete set of loggers used by Effect logging + * in the current context. + * + * **Details** + * + * The default set contains the built-in default logger and tracer logger. + * Providing this reference changes which `Logger` instances receive log entries + * in the current context. + * + * @category references + * @since 4.0.0 + */ +export const CurrentLoggers: Context.Reference>> = internalEffect.CurrentLoggers + +/** + * Context reference for controlling whether built-in console loggers write to stderr. + * + * **When to use** + * + * Use to configure the runtime reference that controls whether built-in console + * loggers write to stderr. + * + * **Details** + * + * The default value is `false`. When set to `true`, the built-in default logger + * and TTY pretty console logger call `console.error` instead of `console.log`. + * + * @category references + * @since 4.0.0 + */ +export const LogToStderr: Context.Reference = internalEffect.LogToStderr + +export { + /** + * Context reference for the current scheduler implementation used by the Effect runtime. + * Controls how Effects are scheduled and executed. + * + * **When to use** + * + * Use to provide the scheduler implementation that fibers use in the current + * context. + * + * **Example** (Providing a custom scheduler) + * + * ```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 + * const isDefaultMixed = current instanceof Scheduler.MixedScheduler + * + * // Use a 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 + * @since 4.0.0 + */ + Scheduler +} from "./Scheduler.ts" diff --git a/.repos/effect/packages/effect/src/RegExp.ts b/.repos/effect/packages/effect/src/RegExp.ts new file mode 100644 index 000000000..c1c97ea3e --- /dev/null +++ b/.repos/effect/packages/effect/src/RegExp.ts @@ -0,0 +1,78 @@ +/** + * Tools for working with JavaScript regular expressions from the Effect module + * namespace. The module exposes the native `RegExp` constructor, a guard for + * narrowing unknown values, and escaping for literal text that will be embedded + * in a pattern. + * + * Reach for `RegExp` when you need to build a regular expression from user or + * data-driven text, check whether an unknown value is already a `RegExp`, or + * access the native constructor without leaving the Effect namespace. + * + * @since 2.0.0 + */ +import * as predicate from "./Predicate.ts" + +/** + * Exposes the JavaScript regular expression constructor from `globalThis`. + * + * **When to use** + * + * Use to construct JavaScript regular expressions through the Effect module + * namespace. + * + * **Example** (Creating a regular expression) + * + * ```ts import.meta.vitest + * import { RegExp } from "effect" + * + * const pattern = new RegExp.RegExp("hello", "i") + * pattern // => /hello/i + * pattern.test("Hello World") // => true + * pattern.test("goodbye") // => false + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const RegExp = globalThis.RegExp + +/** + * Checks whether a value is a `RegExp`. + * + * **When to use** + * + * Use to validate unknown input before treating it as a regular expression. + * + * **Example** (Checking for regular expressions) + * + * ```ts import.meta.vitest + * import { RegExp } from "effect" + * + * RegExp.isRegExp(/a/) // => true + * RegExp.isRegExp("a") // => false + * ``` + * + * @category guards + * @since 3.9.0 + */ +export const isRegExp: (input: unknown) => input is RegExp = predicate.isRegExp + +/** + * Escapes special characters in a regular expression pattern. + * + * **When to use** + * + * Use to turn literal text into a safe regular expression pattern fragment. + * + * **Example** (Escaping a pattern string) + * + * ```ts import.meta.vitest + * import { RegExp } from "effect" + * + * RegExp.escape("a*b") // => "a\\*b" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const escape = (string: string): string => string.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&") diff --git a/.repos/effect/packages/effect/src/Request.ts b/.repos/effect/packages/effect/src/Request.ts new file mode 100644 index 000000000..5ccb1032e --- /dev/null +++ b/.repos/effect/packages/effect/src/Request.ts @@ -0,0 +1,606 @@ +/** + * Typed request values for data loading with `Effect.request`. + * + * A request describes one logical piece of work without performing it. It + * records the success type, typed error, service requirements, and fields a + * resolver needs to complete the request. Requests are paired with + * `RequestResolver`, which performs backend-specific loading and completes each + * pending request entry with a success, failure, cause, exit, or effect. + * + * @since 2.0.0 + */ +import type * as Cause from "./Cause.ts" +import type * as Context from "./Context.ts" +import type * as Effect from "./Effect.ts" +import * as Equal from "./Equal.ts" +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" + +const TypeId = "~effect/Request" + +/** + * A `Request` is a request from a data source for a value of type `A` + * that may fail with an `E` and have requirements of type `R`. + * + * **Example** (Defining typed requests) + * + * ```ts import.meta.vitest + * import type { Request } from "effect" + * + * // Define a request that fetches a user by ID + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: number + * } + * + * // Define a request that fetches all users + * interface GetAllUsers extends Request.Request, Error> { + * readonly _tag: "GetAllUsers" + * } + * + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Request extends Variance {} + +/** + * Alias for any `Request`, regardless of its success, error, or service + * requirements. + * + * **When to use** + * + * Use as a generic constraint for APIs that accept any request while preserving + * each concrete request's success, error, and service types. + * + * @see {@link Request} for the request interface + * @see {@link Success} for extracting a request's success type + * @see {@link Error} for extracting a request's error type + * @see {@link Services} for extracting a request's service requirements + * @see {@link Result} for the exit type produced by completing a request + * + * @category utility types + * @since 4.0.0 + */ +export type Any = Request + +/** + * Variance marker carried by every `Request`. + * + * **Details** + * + * This marker preserves the success, error, and service requirement types for + * Effect's type-level machinery. Users normally get it by extending `Request`. + * + * @category models + * @since 2.0.0 + */ +export interface Variance { + readonly [TypeId]: { + readonly _A: Types.Covariant + readonly _E: Types.Covariant + readonly _R: Types.Covariant + } +} + +/** + * The constructor type returned by `Request.of` and `Request.tagged`. + * + * **Details** + * + * The constructor accepts the request's data fields, excluding request variance + * fields and any fields already supplied by the constructor such as `_tag`, and + * returns a value of the request type. + * + * **Example** (Using generated request constructors) + * + * ```ts import.meta.vitest + * import { Request } from "effect" + * + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: number + * } + * + * // Constructor type is used internally by Request.of() and Request.tagged() + * const GetUser = Request.tagged("GetUser") + * const request = GetUser({ id: 123 }) + * + * request._tag // => "GetUser" + * request.id // => 123 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Constructor, T extends keyof R = never> { + (args: Types.VoidIfEmpty)>>>): R +} + +/** + * A utility type to extract the error type from a `Request`. + * + * **Example** (Extracting a request error type) + * + * ```ts import.meta.vitest + * import type { Request } from "effect" + * + * interface GetUser extends Request.Request { + * readonly id: number + * } + * + * // Extract the error type from a Request using the utility + * type UserError = Request.Error // Error + * + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export type Error> = [T] extends [Request] ? _E : never + +/** + * A utility type to extract the value type from a `Request`. + * + * **Example** (Extracting a request success type) + * + * ```ts import.meta.vitest + * import type { Request } from "effect" + * + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: number + * } + * + * // Extract the success type from a Request using the utility + * type UserSuccess = Request.Success // string + * + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export type Success> = [T] extends [Request] ? _A + : never + +/** + * A utility type to extract the requirements type from a `Request`. + * + * @category utility types + * @since 4.0.0 + */ +export type Services> = [T] extends [Request] ? _R + : never + +/** + * A utility type to extract the result type from a `Request`. + * + * **Example** (Extracting a request result type) + * + * ```ts import.meta.vitest + * import type { Request } from "effect" + * + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: number + * } + * + * // Extract the result type from a Request using the utility + * type UserResult = Request.Result // Exit.Exit + * + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export type Result> = T extends Request ? Exit.Exit + : never + +const requestVariance = Equal.byReferenceUnsafe({ + /* c8 ignore next */ + _E: (_: never) => _, + /* c8 ignore next */ + _A: (_: never) => _, + /* c8 ignore next */ + _R: (_: never) => _ +}) + +/** + * Prototype used by Effect's request constructors. + * + * **Details** + * + * This low-level value provides the structural request marker for values + * created by `Request.of`, `Request.tagged`, `Request.Class`, and + * `Request.TaggedClass`. Most users should use those constructors instead of + * interacting with the prototype directly. + * + * @category prototypes + * @since 4.0.0 + */ +export const RequestPrototype: Request = { + ...core.StructuralProto, + [TypeId]: requestVariance +} + +/** + * Checks whether a value is a `Request`. + * + * **Example** (Checking request values) + * + * ```ts import.meta.vitest + * import { Request } from "effect" + * + * declare const User: unique symbol + * declare const UserNotFound: unique symbol + * type User = typeof User + * type UserNotFound = typeof UserNotFound + * + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: string + * } + * const GetUser = Request.tagged("GetUser") + * + * const request = GetUser({ id: "123" }) + * Request.isRequest(request) // => true + * Request.isRequest("not a request") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isRequest = (u: unknown): u is Request => hasProperty(u, TypeId) + +/** + * Creates a constructor function for a specific Request type. + * + * **Example** (Creating untagged request constructors) + * + * ```ts import.meta.vitest + * import { Request } from "effect" + * + * declare const UserProfile: unique symbol + * declare const ProfileError: unique symbol + * type UserProfile = typeof UserProfile + * type ProfileError = typeof ProfileError + * + * interface GetUserProfile extends Request.Request { + * readonly id: string + * readonly includeSettings: boolean + * } + * + * const GetUserProfile = Request.of() + * + * const request = GetUserProfile({ + * id: "user-123", + * includeSettings: true + * }) + * + * request.id // => "user-123" + * request.includeSettings // => true + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const of = >(): Constructor => (args) => + Object.setPrototypeOf({ ...(args as R) }, RequestPrototype) + +/** + * Creates a constructor function for a tagged Request type. The tag is automatically + * added to the request, making it useful for discriminated unions. + * + * **Example** (Creating tagged request constructors) + * + * ```ts import.meta.vitest + * import { Request } from "effect" + * + * declare const User: unique symbol + * declare const UserNotFound: unique symbol + * declare const Post: unique symbol + * declare const PostNotFound: unique symbol + * type User = typeof User + * type UserNotFound = typeof UserNotFound + * type Post = typeof Post + * type PostNotFound = typeof PostNotFound + * + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: string + * } + * + * interface GetPost extends Request.Request { + * readonly _tag: "GetPost" + * readonly id: string + * } + * + * const GetUser = Request.tagged("GetUser") + * const GetPost = Request.tagged("GetPost") + * + * const userRequest = GetUser({ id: "user-123" }) + * const postRequest = GetPost({ id: "post-456" }) + * + * // _tag is automatically set + * Array.of(userRequest._tag, postRequest._tag) // => ["GetUser", "GetPost"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const tagged = & { _tag: string }>( + tag: R["_tag"] +): Constructor => +(args) => { + return Object.setPrototypeOf({ ...(args as R), _tag: tag }, RequestPrototype) +} + +/** + * Defines request types with TypeScript classes. + * + * **Details** + * + * Subclasses pass their data fields to `super`, and instances are marked as + * `Request` values while retaining the provided readonly fields. + * + * **Example** (Defining request classes) + * + * ```ts import.meta.vitest + * import { Request } from "effect" + * + * class GetUser extends Request.Class<{ id: number }, string, Error> { + * constructor(readonly id: number) { + * super({ id }) + * } + * } + * + * const getUserRequest = new GetUser(123) + * getUserRequest.id // => 123 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const Class: new, Success, Error = never, Context = never>( + 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: object, args: object | undefined) { + if (args) { + InternalRecord.assignProperties(this, args) + } + } + Class.prototype = RequestPrototype + return Class as any +})() + +/** + * Creates a class constructor for requests with a fixed `_tag` field. + * + * **Details** + * + * Use this when defining class-based request types that should participate in + * tagged unions or tag-based request resolvers. + * + * **Example** (Defining tagged request classes) + * + * ```ts import.meta.vitest + * import { Request } from "effect" + * + * class GetUserById + * extends Request.TaggedClass("GetUserById")<{ id: number }, string, Error> + * {} + * + * const request = new GetUserById({ id: 123 }) + * + * request._tag // => "GetUserById" + * request.id // => 123 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const TaggedClass = ( + tag: Tag +): new, Success, Error = never, Services = never>( + args: Types.Equals>, {}> extends true ? void + : { readonly [P in keyof A as P extends "_tag" | keyof Request ? never : P]: A[P] } +) => Request & Readonly & { readonly _tag: Tag } => { + return class TaggedClass extends Class { + readonly _tag = tag + } as any +} + +/** + * Completes a request entry with the provided result. + * + * **When to use** + * + * Use when you need to finish a `Request.Entry` with a prebuilt final `Exit` + * result. + * + * @see {@link completeEffect} for completing an entry from an effect that may succeed or fail + * @see {@link succeed} for completing an entry with a successful value + * @see {@link fail} for completing an entry with a typed failure + * @see {@link failCause} for completing an entry with a failure `Cause` + * + * @category completion + * @since 2.0.0 + */ +export const complete: { + (result: Result): (self: Entry) => Effect.Effect + (self: Entry, result: Result): Effect.Effect +} = dual( + 2, + (self: Entry, result: Result): Effect.Effect => + internalEffect.sync(() => self.completeUnsafe(result)) +) + +/** + * Completes a request entry with the result of an effect. + * + * **When to use** + * + * Use to finish a `Request.Entry` by running an effect whose success or typed + * failure should become the request result. + * + * **Details** + * + * If the effect succeeds, the entry is completed successfully with its value. + * If the effect fails, the entry is completed with that failure. + * + * **Gotchas** + * + * The returned effect itself does not fail with the request error. + * + * @see {@link complete} for completing an entry with a prebuilt `Exit` + * @see {@link succeed} for completing an entry with a successful value + * @see {@link fail} for completing an entry with a typed failure + * @see {@link failCause} for completing an entry with a failure `Cause` + * + * @category completion + * @since 2.0.0 + */ +export const completeEffect: { + (effect: Effect.Effect, Error, R>): (self: Entry) => Effect.Effect + (self: Entry, effect: Effect.Effect, Error, R>): Effect.Effect +} = dual( + 2, + (self: Entry, effect: Effect.Effect, Error, R>): Effect.Effect => + internalEffect.matchEffect(effect, { + onFailure: (error) => complete(self, core.exitFail(error) as any), + onSuccess: (value) => complete(self, core.exitSucceed(value) as any) + }) +) + +/** + * Completes a request entry with a typed failure. + * + * **When to use** + * + * Use to report a request-specific typed error while implementing a + * `RequestResolver`. + * + * @see {@link failCause} for completing an entry with a full `Cause` + * @see {@link complete} for completing an entry with an existing `Exit` + * @see {@link completeEffect} for completing an entry from an effect result + * @see {@link succeed} for completing an entry successfully + * + * @category completion + * @since 2.0.0 + */ +export const fail: { + (error: Error): (self: Entry) => Effect.Effect + (self: Entry, error: Error): Effect.Effect +} = dual( + 2, + (self: Entry, error: Error): Effect.Effect => complete(self, core.exitFail(error) as any) +) + +/** + * Completes a request entry with a failure `Cause`. + * + * **When to use** + * + * Use when you need a `RequestResolver` to complete an entry with structured + * cause information rather than only the request's typed error value. + * + * @see {@link fail} for completing an entry with a typed error value + * @see {@link complete} for completing an entry with an existing `Exit` + * @see {@link completeEffect} for completing an entry from an effect result + * @see {@link succeed} for completing an entry successfully + * + * @category completion + * @since 2.0.0 + */ +export const failCause: { + (cause: Cause.Cause>): (self: Entry) => Effect.Effect + (self: Entry, cause: Cause.Cause>): Effect.Effect +} = dual( + 2, + (self: Entry, cause: Cause.Cause>): Effect.Effect => + complete(self, core.exitFailCause(cause) as any) +) + +/** + * Completes a request entry successfully with the supplied value. + * + * **When to use** + * + * Use when you need to finish a `Request.Entry` with a successful request + * value. + * + * @see {@link complete} for completing an entry with a prebuilt `Exit` + * @see {@link completeEffect} for completing an entry from an effect result + * @see {@link fail} for completing an entry with a typed failure + * @see {@link failCause} for completing an entry with a failure `Cause` + * + * @category completion + * @since 2.0.0 + */ +export const succeed: { + (value: Success): (self: Entry) => Effect.Effect + (self: Entry, value: Success): Effect.Effect +} = dual( + 2, + (self: Entry, value: Success): Effect.Effect => + complete(self, core.exitSucceed(value) as any) +) + +/** + * A pending request handed to a `RequestResolver`. + * + * **Details** + * + * An entry contains the original request, the fiber context needed to run it, + * an `uninterruptible` flag used by batching and caching internals, and the + * `completeUnsafe` callback used by resolvers to supply the final `Exit`. + * + * @category models + * @since 2.0.0 + */ +export interface Entry { + readonly request: R + readonly context: Context.Context< + [R] extends [Request] ? _R : never + > + uninterruptible: boolean + completeUnsafe( + exit: Exit.Exit< + [R] extends [Request] ? _A : never, + [R] extends [Request] ? _E : never + > + ): void +} + +/** + * Creates a `Request.Entry` from its component fields. + * + * **Details** + * + * This is a low-level helper for request runtime and resolver infrastructure; + * most application code receives entries from a `RequestResolver` instead of + * constructing them directly. + * + * @category constructors + * @since 2.0.0 + */ +export const makeEntry = (options: { + readonly request: R + readonly context: Context.Context< + [R] extends [Request] ? _R : never + > + readonly uninterruptible: boolean + readonly completeUnsafe: ( + exit: Exit.Exit< + [R] extends [Request] ? _A : never, + [R] extends [Request] ? _E : never + > + ) => void +}): Entry => options diff --git a/.repos/effect/packages/effect/src/RequestResolver.ts b/.repos/effect/packages/effect/src/RequestResolver.ts new file mode 100644 index 000000000..2061d4b86 --- /dev/null +++ b/.repos/effect/packages/effect/src/RequestResolver.ts @@ -0,0 +1,1298 @@ +/** + * Resolves data requests made with `Effect.request`. + * + * A `Request` describes what a fiber needs, while a `RequestResolver` describes + * how to collect request entries, group them into batches, run backend work, + * and complete each waiting entry. This module includes constructors for common + * resolver shapes and tools for controlling batching, grouping, delays, + * tracing, caching, racing, hooks around resolver execution, and persistence. + * + * @since 2.0.0 + */ +import type { NonEmptyArray } from "./Array.ts" +import * as Arr from "./Array.ts" +import * as Cache from "./Cache.ts" +import * as Context from "./Context.ts" +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 * as effect from "./internal/effect.ts" +import * as internal from "./internal/request.ts" +import * as Iterable from "./Iterable.ts" +import * as MutableHashMap from "./MutableHashMap.ts" +import { type Pipeable, pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Request from "./Request.ts" +import type * as Schema from "./Schema.ts" +import type { Scope } from "./Scope.ts" +import * as Tracer from "./Tracer.ts" +import type * as Types from "./Types.ts" +import type * as Persistable from "./unstable/persistence/Persistable.ts" +import * as Persistence from "./unstable/persistence/Persistence.ts" + +const TypeId = "~effect/RequestResolver" + +/** + * A resolver that executes and completes batched `Request` entries. + * + * **Details** + * + * A resolver controls how requests are grouped, delayed, optionally + * pre-checked, and finally run. Its `runAll` method receives a non-empty batch + * of `Request.Entry` values for a single batch key and must complete every + * received entry, usually by calling `completeUnsafe` or one of the `Request` + * completion helpers. + * + * **Gotchas** + * + * If a resolver finishes without completing an entry, the waiting request fails + * because the resolver did not supply a result. + * + * **Example** (Defining a request resolver) + * + * ```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) => + * Effect.sync(() => { + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed(`User ${entry.request.id}`)) + * } + * }) + * ) + * + * const program = Effect.request(GetUserRequest({ id: 1 }), resolver) + * await Effect.runPromise(program) // => "User 1" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface RequestResolver extends RequestResolver.Variance, Pipeable { + readonly delay: Effect.Effect + + /** + * Get a batch key for the given request. + */ + batchKey(entry: Request.Entry): unknown + + /** + * An optional pre-check function that can be used to filter requests before + * they are added to a batch. If the function returns `false`, the request + * will not be processed. + */ + readonly preCheck: ((entry: Request.Entry) => boolean) | undefined + + /** + * Should the resolver continue collecting requests? Otherwise, it will + * immediately execute the collected requests cutting the delay short. + */ + collectWhile(entries: ReadonlySet>): boolean + + /** + * Execute a collection of requests. + */ + runAll(entries: NonEmptyArray>, key: unknown): Effect.Effect> +} + +/** + * Namespace containing type-level helpers associated with `RequestResolver`. + * + * @since 2.0.0 + */ +export declare namespace RequestResolver { + /** + * Variance marker carried by every `RequestResolver`. + * + * **Details** + * + * This marker preserves the request type accepted by the resolver for + * Effect's type-level machinery. Users normally do not implement it directly. + * + * @category models + * @since 2.0.0 + */ + export interface Variance { + readonly [TypeId]: { + readonly _A: Types.Contravariant + } + } +} + +const RequestResolverProto = { + [TypeId]: { + _A: identity, + _R: identity + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +/** + * Returns `true` if the specified value is a `RequestResolver`, `false` otherwise. + * + * **When to use** + * + * Use to narrow unknown values before passing them to APIs that require a + * `RequestResolver`. + * + * @see {@link RequestResolver} for the type narrowed by this guard + * + * @category guards + * @since 2.0.0 + */ +export const isRequestResolver = (u: unknown): u is RequestResolver => hasProperty(u, TypeId) + +/** + * Creates a request resolver with fine-grained + * control over its behavior. + * + * **When to use** + * + * Use when you need to supply the resolver batching primitives directly, + * including the batch key, optional pre-check, delay effect, collection cutoff, + * and batch runner. + * + * **Details** + * + * `batchKey` groups request entries, `delay` schedules batch execution, + * `collectWhile` can end collection early, and `runAll` receives a non-empty + * batch for one key. + * + * **Gotchas** + * + * Accepted entries must be completed. If `runAll` succeeds with incomplete + * entries, waiting requests fail. If `preCheck` returns `false`, the entry is + * not batched, so it must be completed or linked to another completion path. + * + * @see {@link make} for constructing a resolver from a batch runner + * @see {@link makeGrouped} for constructing a resolver that groups requests by key + * + * @category constructors + * @since 4.0.0 + */ +export const makeWith = (options: { + readonly batchKey: (request: Request.Entry) => unknown + readonly preCheck?: ((entry: Request.Entry) => boolean) | undefined + readonly delay: Effect.Effect + readonly collectWhile: (requests: ReadonlySet>) => boolean + readonly runAll: (entries: NonEmptyArray>, key: unknown) => Effect.Effect> +}): RequestResolver => { + const self = Object.create(RequestResolverProto) + self.batchKey = options.batchKey + self.preCheck = options.preCheck + self.delay = options.delay + self.collectWhile = options.collectWhile + self.runAll = options.runAll + return self +} + +const defaultKeyObject = {} +const defaultKey = (_request: unknown): unknown => defaultKeyObject + +/** + * Constructs a request resolver with the specified method to run requests. + * + * **Example** (Creating a request resolver) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * // Define a request type + * interface GetUserRequest extends Request.Request { + * readonly _tag: "GetUserRequest" + * readonly id: number + * } + * const GetUserRequest = Request.tagged("GetUserRequest") + * + * // Create a resolver that handles the requests + * const UserResolver = RequestResolver.make((entries) => + * Effect.sync(() => { + * for (const entry of entries) { + * // Complete each request with a result + * entry.completeUnsafe(Exit.succeed(`User ${entry.request.id}`)) + * } + * }) + * ) + * + * // Use the resolver to handle requests + * const getUserEffect = Effect.request(GetUserRequest({ id: 123 }), UserResolver) + * await Effect.runPromise(getUserEffect) // => "User 123" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = ( + runAll: (entries: NonEmptyArray>, key: unknown) => Effect.Effect> +): RequestResolver => + makeWith({ + batchKey: defaultKey, + delay: Effect.yieldNow, + collectWhile: constTrue, + runAll + }) + +/** + * Constructs a request resolver with the requests grouped by a calculated key. + * + * **Details** + * + * The key can use the Equal trait to determine if two keys are equal. + * + * **Example** (Grouping requests by key) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetUserByRole extends Request.Request { + * readonly _tag: "GetUserByRole" + * readonly role: string + * readonly id: number + * } + * 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(() => { + * batches.push([role, entries.length]) + * for (const entry of entries) { + * entry.completeUnsafe( + * Exit.succeed(`User ${entry.request.id} with role ${role}`) + * ) + * } + * }) + * }) + * + * 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 + * @since 4.0.0 + */ +export const makeGrouped = (options: { + readonly key: (entry: Request.Entry) => K + readonly resolver: (entries: NonEmptyArray>, key: K) => Effect.Effect> +}): RequestResolver => + makeWith({ + batchKey: hashGroupKey(options.key), + delay: Effect.yieldNow, + collectWhile: constTrue, + runAll: options.resolver as any + }) + +const hashGroupKey = (get: (entry: Request.Entry) => K) => { + const groupKeys = MutableHashMap.empty() + return (entry: Request.Entry): unknown => { + const key = get(entry) + const okey = MutableHashMap.get(groupKeys, key) + if (okey._tag === "Some") { + return okey.value + } + MutableHashMap.set(groupKeys, key, key) + return key + } +} + +/** + * Constructs a request resolver from a pure function. + * + * **Example** (Creating a resolver from a pure function) + * + * ```ts import.meta.vitest + * import { Effect, Request, RequestResolver } from "effect" + * + * interface GetSquareRequest extends Request.Request { + * readonly _tag: "GetSquareRequest" + * readonly value: number + * } + * const GetSquareRequest = Request.tagged("GetSquareRequest") + * + * // Create a resolver from a pure function + * const SquareResolver = RequestResolver.fromFunction( + * (entry) => entry.request.value * entry.request.value + * ) + * + * // Usage + * const getSquareEffect = Effect.request( + * GetSquareRequest({ value: 5 }), + * SquareResolver + * ) + * await Effect.runPromise(getSquareEffect) // => 25 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromFunction = ( + f: (entry: Request.Entry) => Request.Success +): RequestResolver => + make( + (entries) => + Effect.sync(() => { + for (let i = 0; i < entries.length; i++) { + const entry = entries[i] + entry.completeUnsafe(exitSucceed(f(entry))) + } + }) + ) + +/** + * Constructs a request resolver from a pure function that takes a list of requests + * and returns a list of results of the same size. Each item in the result + * list must correspond to the item at the same index in the request list. + * + * **Example** (Batching pure request handling) + * + * ```ts import.meta.vitest + * import { Effect, Request, RequestResolver } from "effect" + * + * interface GetDoubleRequest extends Request.Request { + * readonly _tag: "GetDoubleRequest" + * readonly value: number + * } + * const GetDoubleRequest = Request.tagged("GetDoubleRequest") + * + * // Create a resolver that processes multiple requests in a batch + * const DoubleResolver = RequestResolver.fromFunctionBatched( + * (entries) => entries.map((entry) => entry.request.value * 2) + * ) + * + * // Usage with multiple requests + * const effects = [1, 2, 3].map((value) => + * Effect.request(GetDoubleRequest({ value }), DoubleResolver) + * ) + * const batchedEffect = Effect.all(effects) + * await Effect.runPromise(batchedEffect) // => [2, 4, 6] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromFunctionBatched = ( + f: (entries: NonEmptyArray>) => Iterable> +): RequestResolver => + make( + (entries) => + Effect.sync(() => { + let i = 0 + for (const result of f(entries)) { + const entry = entries[i++] + entry.completeUnsafe(exitSucceed(result)) + } + }) + ) + +/** + * Constructs a request resolver from an effectual function. + * + * **Example** (Creating a resolver from an effectful function) + * + * ```ts import.meta.vitest + * import { Effect, Request, RequestResolver } from "effect" + * + * interface GetUserFromAPIRequest extends Request.Request { + * readonly _tag: "GetUserFromAPIRequest" + * readonly id: number + * } + * const GetUserFromAPIRequest = Request.tagged( + * "GetUserFromAPIRequest" + * ) + * + * // Create a resolver that uses effects (like HTTP calls) + * const UserAPIResolver = RequestResolver.fromEffect( + * (entry) => Effect.succeed(`User ${entry.request.id} from API`) + * ) + * + * // Usage + * const getUserEffect = Effect.request( + * GetUserFromAPIRequest({ id: 123 }), + * UserAPIResolver + * ) + * await Effect.runPromise(getUserEffect) // => "User 123 from API" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromEffect = ( + f: (entry: Request.Entry) => Effect.Effect, Request.Error> +): RequestResolver => { + effect.interruptChildrenPatch() // ensure middleware is registered + return make((entries) => + Effect.callback((resume) => { + const parent = effect.getCurrentFiber()! + let done = 0 + for (let i = 0; i < entries.length; i++) { + const entry = entries[i] + const fiber = effect.forkUnsafe(parent as any, f(entry), true) + fiber.addObserver((exit) => { + entry.completeUnsafe(exit) + done++ + if (done === entries.length) { + resume(effect.void) + } + }) + } + }) + ) +} + +/** + * Constructs a request resolver from a list of tags paired to functions, that takes + * a list of requests and returns a list of results of the same size. Each item + * in the result list must correspond to the item at the same index in the + * request list. + * + * **Example** (Handling tagged request batches) + * + * ```ts import.meta.vitest + * import { Effect, Request, RequestResolver } from "effect" + * + * interface GetUser extends Request.Request { + * readonly _tag: "GetUser" + * readonly id: number + * } + * + * interface GetPost extends Request.Request { + * readonly _tag: "GetPost" + * readonly id: number + * } + * + * 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()({ + * GetUser: (requests) => + * Effect.succeed(requests.map((req) => `User ${req.request.id}`)), + * 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 + * @since 2.0.0 + */ +export const fromEffectTagged = () => +< + Fns extends { + readonly [Tag in A["_tag"]]: [Extract] extends [infer Req] + ? Req extends Request.Request ? + (requests: Array>) => Effect.Effect, ReqE> + : never + : never + } +>( + fns: Fns +): RequestResolver => + make( + (entries): Effect.Effect => { + const grouped = new Map>>() + for (let i = 0, len = entries.length; i < len; i++) { + const group = grouped.get(entries[i].request._tag) + if (group) { + group.push(entries[i]) + } else { + grouped.set(entries[i].request._tag, [entries[i]]) + } + } + return Effect.forEach( + grouped, + ([tag, requests]) => + 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) + } + }, + onSuccess: (res) => { + for (let i = 0; i < res.length; i++) { + const entry = requests[i] + entry.completeUnsafe(exitSucceed(res[i]) as any) + } + } + }), + { concurrency: "unbounded", discard: true } + ) as Effect.Effect + } + ) as any + +/** + * Sets the batch delay effect for this request resolver. + * + * **Example** (Setting an effectful batch delay) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetDataRequest extends Request.Request { + * readonly _tag: "GetDataRequest" + * } + * const GetDataRequest = Request.tagged("GetDataRequest") + * + * const resolver = RequestResolver.make((entries) => + * Effect.sync(() => { + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed("data")) + * } + * }) + * ) + * + * let delayRan = false + * + * // Set a custom delay effect + * const resolverWithCustomDelay = RequestResolver.setDelayEffect( + * resolver, + * Effect.sync(() => { + * delayRan = true + * }) + * ) + * + * await Effect.runPromise(resolverWithCustomDelay.delay) + * Array.of(delayRan, RequestResolver.isRequestResolver(resolverWithCustomDelay)) // => [true, true] + * ``` + * + * @category delays & timeouts + * @since 4.0.0 + */ +export const setDelayEffect: { + (delay: Effect.Effect): (self: RequestResolver) => RequestResolver + (self: RequestResolver, delay: Effect.Effect): RequestResolver +} = dual( + 2, + (self: RequestResolver, delay: Effect.Effect): RequestResolver => + makeWith({ + ...self, + delay + }) +) + +/** + * Sets the batch delay window for this request resolver to the specified duration. + * + * **Example** (Setting a batch delay) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetDataRequest extends Request.Request { + * readonly _tag: "GetDataRequest" + * } + * const GetDataRequest = Request.tagged("GetDataRequest") + * + * const resolver = RequestResolver.make((entries) => + * Effect.sync(() => { + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed("data")) + * } + * }) + * ) + * + * // Add a 100ms delay to batch requests together + * const delayedResolver = RequestResolver.setDelay(resolver, "100 millis") + * + * const program = Effect.request(GetDataRequest(), delayedResolver) + * await Effect.runPromise(program) // => "data" + * ``` + * + * @category delays & timeouts + * @since 4.0.0 + */ +export const setDelay: { + (duration: Duration.Input): (self: RequestResolver) => RequestResolver + (self: RequestResolver, duration: Duration.Input): RequestResolver +} = dual( + 2, + (self: RequestResolver, duration: Duration.Input): RequestResolver => + makeWith({ + ...self, + delay: Effect.sleep(duration) + }) +) + +/** + * Wraps request resolver execution between `before` and `after` effects. + * + * **Example** (Running effects around request resolution) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetDataRequest extends Request.Request { + * readonly _tag: "GetDataRequest" + * } + * const GetDataRequest = Request.tagged("GetDataRequest") + * + * const events: Array = [] + * + * const resolver = RequestResolver.make((entries) => + * Effect.sync(() => { + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed("data")) + * } + * }) + * ) + * + * // Add setup and cleanup around request execution + * const resolverWithAround = RequestResolver.around( + * resolver, + * (entries) => + * Effect.gen(function*() { + * events.push(`Starting batch of ${entries.length} requests`) + * return entries.length + * }), + * (entries, 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 + * @since 2.0.0 + */ +export const around: { + ( + before: (entries: NonEmptyArray>>) => Effect.Effect>, + after: (entries: NonEmptyArray>>, a: A2) => Effect.Effect> + ): (self: RequestResolver) => RequestResolver + ( + self: RequestResolver, + before: (entries: NonEmptyArray>>) => Effect.Effect>, + after: (entries: NonEmptyArray>>, a: A2) => Effect.Effect> + ): RequestResolver +} = dual(3, ( + self: RequestResolver, + before: (entries: NonEmptyArray>>) => Effect.Effect>, + after: (entries: NonEmptyArray>>, a: A2) => Effect.Effect> +): RequestResolver => + makeWith({ + ...self, + runAll: (entries, key) => + Effect.acquireUseRelease( + before(entries), + () => self.runAll(entries, key), + (a) => after(entries, a) + ) + })) + +/** + * Creates a request resolver that never executes requests. + * + * **When to use** + * + * Use as a resolver value for request types that are statically impossible and + * should never be issued. + * + * **Gotchas** + * + * If this resolver is used for an actual request, the request waits forever + * unless the fiber is interrupted. + * + * @see {@link make} for constructing a resolver that executes batches and completes request entries + * + * @category constructors + * @since 2.0.0 + */ +export const never: RequestResolver = make(() => Effect.never) + +/** + * Returns a request resolver that collects at most `n` requests into each + * batch. + * + * **Details** + * + * 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. + * + * **Example** (Limiting parallel request batches) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetDataRequest extends Request.Request { + * readonly _tag: "GetDataRequest" + * readonly id: number + * } + * const GetDataRequest = Request.tagged("GetDataRequest") + * + * const batchSizes: Array = [] + * + * const resolver = RequestResolver.make((entries) => + * Effect.sync(() => { + * batchSizes.push(entries.length) + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed(`data-${entry.request.id}`)) + * } + * }) + * ) + * + * // Limit batches to maximum 5 requests + * const limitedResolver = RequestResolver.batchN(resolver, 5) + * + * // When more than 5 requests are made, they'll be split into multiple batches + * const requests = Array.from( + * { 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 + * @since 2.0.0 + */ +export const batchN: { + (n: number): (self: RequestResolver) => RequestResolver + (self: RequestResolver, n: number): RequestResolver +} = dual(2, (self: RequestResolver, n: number): RequestResolver => + makeWith({ + ...self, + collectWhile: (requests) => requests.size < n + })) + +/** + * Transforms a request resolver by grouping requests using the specified key + * function. + * + * **Example** (Grouping resolver requests) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetUserRequest extends Request.Request { + * readonly _tag: "GetUserRequest" + * readonly userId: number + * readonly department: string + * } + * const GetUserRequest = Request.tagged("GetUserRequest") + * + * const batchSizes: Array = [] + * + * const resolver = RequestResolver.make((entries) => + * Effect.sync(() => { + * batchSizes.push(entries.length) + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed(`User ${entry.request.userId}`)) + * } + * }) + * ) + * + * // Group requests by department for more efficient processing + * const groupedResolver = RequestResolver.grouped( + * resolver, + * ({ request }) => request.department + * ) + * + * // Requests for the same department will be batched together + * const requests = [ + * Effect.request( + * GetUserRequest({ userId: 1, department: "Engineering" }), + * groupedResolver + * ), + * Effect.request( + * GetUserRequest({ userId: 2, department: "Engineering" }), + * groupedResolver + * ), + * Effect.request( + * GetUserRequest({ userId: 3, department: "Marketing" }), + * 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 + * @since 4.0.0 + */ +export const grouped: { + (f: (entry: Request.Entry) => K): (self: RequestResolver) => RequestResolver + (self: RequestResolver, f: (entry: Request.Entry) => K): RequestResolver +} = dual( + 2, + (self: RequestResolver, f: (entry: Request.Entry) => K): RequestResolver => + makeWith({ + ...self, + batchKey: hashGroupKey(f) + }) +) + +/** + * Returns a request resolver that sends each batch to both resolvers and + * completes with the first resolver to finish. + * + * **Details** + * + * The losing resolver run is interrupted after the winning resolver completes + * the batch. + * + * **Example** (Racing request resolvers) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetDataRequest extends Request.Request { + * readonly _tag: "GetDataRequest" + * readonly id: number + * } + * const GetDataRequest = Request.tagged("GetDataRequest") + * + * // Fast resolver (simulating cache) + * const fastResolver = RequestResolver.make((entries) => + * Effect.gen(function*() { + * yield* Effect.sleep("10 millis") + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed(`fast-${entry.request.id}`)) + * } + * }) + * ) + * + * // Slow resolver (simulating database) + * const slowResolver = RequestResolver.make((entries) => + * Effect.gen(function*() { + * yield* Effect.sleep("100 millis") + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed(`slow-${entry.request.id}`)) + * } + * }) + * ) + * + * // 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 + * @since 2.0.0 + */ +export const race: { + ( + that: RequestResolver + ): (self: RequestResolver) => RequestResolver + ( + self: RequestResolver, + that: RequestResolver + ): RequestResolver +} = dual(2, ( + self: RequestResolver, + that: RequestResolver +): RequestResolver => + make( + (requests, key) => effect.race(self.runAll(requests, key), that.runAll(requests, key)) + )) + +/** + * Adds a tracing span to the request resolver, which will also add any span + * links from the request's. + * + * **Example** (Adding a tracing span) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * + * interface GetDataRequest extends Request.Request { + * readonly _tag: "GetDataRequest" + * readonly id: number + * } + * const GetDataRequest = Request.tagged("GetDataRequest") + * + * const resolver = RequestResolver.make((entries) => + * Effect.sync(() => { + * for (const entry of entries) { + * entry.completeUnsafe(Exit.succeed(`data-${entry.request.id}`)) + * } + * }) + * ) + * + * // Add tracing span with custom name and attributes + * const tracedResolver = RequestResolver.withSpan( + * resolver, + * "user-data-resolver", + * { + * attributes: { + * "resolver.type": "user-data", + * "resolver.version": "1.0" + * } + * } + * ) + * + * // 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 + * @since 4.0.0 + */ +export const withSpan: { + ( + name: string, + options?: Tracer.SpanOptions | ((entries: NonEmptyArray>) => Tracer.SpanOptions) | undefined + ): (self: RequestResolver) => RequestResolver + ( + self: RequestResolver, + name: string, + options?: Tracer.SpanOptions | ((entries: NonEmptyArray>) => Tracer.SpanOptions) | undefined + ): RequestResolver +} = dual((args) => isRequestResolver(args[0]), ( + self: RequestResolver, + name: string, + options?: Tracer.SpanOptions | ((entries: NonEmptyArray>) => Tracer.SpanOptions) | undefined +): RequestResolver => + makeWith({ + ...self, + runAll: (entries, key) => + Effect.suspend(() => { + const opts = typeof options === "function" ? options(entries) : options + const links = opts?.links ? opts.links.slice() : [] + const seen = new Set() + for (const entry of entries) { + const span = Context.getOption(entry.context, Tracer.ParentSpan) + if (span._tag === "None" || seen.has(span.value)) continue + seen.add(span.value) + links.push({ span: span.value, attributes: {} }) + } + return Effect.withSpan(self.runAll(entries, key), name, { + ...options, + links, + attributes: { + batchSize: entries.length, + ...opts?.attributes + } + }) + }) + })) + +/** + * Wraps a request resolver in a cache, allowing it to cache results up to a + * specified capacity and optional time-to-live. + * + * **When to use** + * + * Use to turn a request resolver into a first-class `Cache` when callers need + * cache lookup, refresh, invalidation, or inspection around request results. + * + * **Details** + * + * The request value is the cache key. Cache misses run the resolver via + * `Effect.request`, `timeToLive` receives the request `Exit` and the request, + * and `requireServicesAt` controls whether services are required at lookup time + * or construction time. + * + * **Gotchas** + * + * Cache hits depend on the request value's equality semantics. + * + * @see {@link withCache} for keeping caching behind a resolver used with `Effect.request` + * @see {@link persisted} for storing persistable request results outside process memory + * @see {@link Cache.Cache} for operations available on the returned cache + * + * @category caching + * @since 4.0.0 + */ +export const asCache: { + < + A extends Request.Any, + ServiceMode extends "lookup" | "construction" = never + >(options: { + readonly capacity: number + readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined + readonly requireServicesAt?: ServiceMode | undefined + }): (self: RequestResolver) => Effect.Effect< + Cache.Cache< + A, + Request.Success, + Request.Error, + "construction" extends ServiceMode ? never : Request.Services + >, + never, + "construction" extends ServiceMode ? Request.Services : never + > + < + A extends Request.Any, + ServiceMode extends "lookup" | "construction" = never + >(self: RequestResolver, options: { + readonly capacity: number + readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined + readonly requireServicesAt?: ServiceMode | undefined + }): Effect.Effect< + Cache.Cache< + A, + Request.Success, + Request.Error, + "construction" extends ServiceMode ? never : Request.Services + >, + never, + "construction" extends ServiceMode ? Request.Services : never + > +} = dual(2, < + A extends Request.Any, + ServiceMode extends "lookup" | "construction" = never +>(self: RequestResolver, options: { + readonly capacity: number + readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined + readonly requireServicesAt?: ServiceMode | undefined +}): Effect.Effect< + Cache.Cache< + A, + Request.Success, + Request.Error, + "construction" extends ServiceMode ? never : Request.Services + >, + never, + "construction" extends ServiceMode ? Request.Services : never +> => + Cache.makeWith((req: A) => internal.request(req, self), { + capacity: options.capacity, + timeToLive: options.timeToLive as any, + requireServicesAt: options.requireServicesAt ?? "lookup" as ServiceMode + }) as any) + +/** + * Adds a bounded in-memory cache to a request resolver. + * + * **When to use** + * + * Use to reuse completed results for repeated equal request values while still + * passing a `RequestResolver` to `Effect.request`. + * + * **Details** + * + * Running the returned effect creates the cache and returns a wrapped resolver. + * The cache stores completed success or failure results by request equality up + * to `capacity`. The `strategy` option controls eviction order and defaults to + * `"lru"`; `"fifo"` keeps insertion order. + * + * **Gotchas** + * + * Entries do not expire by time, and completed failures are cached the same as + * successes. 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 + * + * @category caching + * @since 4.0.0 + */ +export const withCache: { + (options: { + readonly capacity: number + readonly strategy?: "lru" | "fifo" | undefined + }): (self: RequestResolver) => Effect.Effect> + (self: RequestResolver, options: { + readonly capacity: number + readonly strategy?: "lru" | "fifo" | undefined + }): Effect.Effect> +} = dual(2, (self: RequestResolver, options: { + readonly capacity: number + readonly strategy?: "lru" | "fifo" | undefined +}): Effect.Effect> => + Effect.sync(() => { + const strategy = options.strategy ?? "lru" + const cache = MutableHashMap.empty + exit: Request.Result | undefined + }>() + return makeWith({ + ...self, + runAll(entries, key) { + return Effect.onExit(self.runAll(entries, key), () => { + let toRemove = MutableHashMap.size(cache) - options.capacity + if (toRemove <= 0) return Effect.void + for (const k of MutableHashMap.keys(cache)) { + MutableHashMap.remove(cache, k) + toRemove-- + if (toRemove <= 0) break + } + return Effect.void + }) + }, + preCheck(entry) { + const ocached = MutableHashMap.get(cache, entry.request) + if (ocached._tag === "None") { + const cached = { entry, exit: undefined as Request.Result | undefined } + MutableHashMap.set(cache, entry.request, cached) + const prevComplete = entry.completeUnsafe + entry.completeUnsafe = function(exit) { + cached.exit = exit as any + prevComplete(exit) + } + return true + } + const cached = ocached.value + if (cached.exit) { + if (strategy === "lru") { + MutableHashMap.remove(cache, cached.entry.request) + MutableHashMap.set(cache, cached.entry.request, cached) + } + entry.completeUnsafe(cached.exit as any) + } else { + cached.entry.uninterruptible = true + const prevComplete = cached.entry.completeUnsafe + cached.entry.completeUnsafe = function(exit) { + prevComplete(exit) + entry.completeUnsafe(exit) + } + } + return false + } + }) + })) + +/** + * Wraps a request resolver with persistent storage for persistable requests. + * + * **When to use** + * + * Use to keep a `RequestResolver` interface while reusing completed + * `Persistable` request results through a `Persistence` store. + * + * **Details** + * + * Cached results are loaded from the configured persistence store before + * running the underlying resolver. Missing entries are resolved normally and + * written back to the store. Entries marked stale by `staleWhileRevalidate` + * receive the stored result and are also resolved again so the refreshed result + * can be written back to the store. Creating the persisted resolver requires + * `Persistence.Persistence` and `Scope`. + * + * @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 caching + * @since 4.0.0 + */ +export const persisted: { + & Persistable.Any>( + options: { + readonly storeId: string + readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined + readonly staleWhileRevalidate?: ((exit: Request.Result, request: A) => boolean) | undefined + } + ): (self: RequestResolver) => Effect.Effect< + RequestResolver, + never, + Persistence.Persistence | Scope + > + < + A extends Request.Request & Persistable.Any + >( + self: RequestResolver, + options: { + readonly storeId: string + readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined + readonly staleWhileRevalidate?: ((exit: Request.Result, request: A) => boolean) | undefined + } + ): Effect.Effect< + RequestResolver, + never, + Persistence.Persistence | Scope + > +} = dual( + 2, + Effect.fnUntraced(function*< + A extends Request.Request & Persistable.Any + >( + self: RequestResolver, + options: { + readonly storeId: string + readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined + readonly staleWhileRevalidate?: ((exit: Request.Result, request: A) => boolean) | undefined + } + ) { + const store = yield* (yield* Persistence.Persistence).make(options as any) + return makeWith({ + ...self, + runAll: Effect.fnUntraced(function*(entries, key) { + const results = yield* (store.getMany(Iterable.map(entries, (_) => _.request)).pipe( + Effect.provideContext(entries[0].context) + ) as Effect.Effect< + Array | undefined>, + Request.Error + >) + const leftover: Array> = [] + const toPersist = new Map>() + for (let i = 0; i < results.length; i++) { + const entry = entries[i] + const exit = results[i] + if ( + exit === undefined || + (options.staleWhileRevalidate && options.staleWhileRevalidate(exit as any, entry.request)) + ) { + const prevComplete = entry.completeUnsafe + entry.completeUnsafe = function(exit) { + toPersist.set(entry.request, exit as any) + prevComplete(exit) + } + leftover.push(entry) + if (exit === undefined) continue + } + entry.completeUnsafe(exit as any) + } + if (!Arr.isArrayNonEmpty(leftover)) { + return + } + yield* Effect.catchCause(self.runAll(leftover, key), (cause) => { + for (let i = 0; i < leftover.length; i++) { + const entry = leftover[i] + if (!toPersist.has(entry.request)) continue + entry.completeUnsafe(Exit.failCause(cause) as any) + } + return Effect.void + }) + yield* (store.setMany(toPersist).pipe( + Effect.provideContext(entries[0].context) + ) as Effect.Effect>) + }) + }) + }) +) diff --git a/.repos/effect/packages/effect/src/Resource.ts b/.repos/effect/packages/effect/src/Resource.ts new file mode 100644 index 000000000..7e6d5383e --- /dev/null +++ b/.repos/effect/packages/effect/src/Resource.ts @@ -0,0 +1,184 @@ +/** + * Stores refreshable scoped values. + * + * A `Resource` keeps the latest successful or failed acquisition result. + * It can be read repeatedly, refreshed manually, or refreshed automatically on a + * schedule. Resource acquisition runs in a scope, so replacements and final + * cleanup release the resources owned by previous values. + * + * @since 2.0.0 + */ +import * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import { identity } from "./Function.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import type { Pipeable } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Schedule from "./Schedule.ts" +import type * as Scope from "./Scope.ts" +import * as ScopedRef from "./ScopedRef.ts" + +const TypeId = "~effect/Resource" as const + +/** + * A `Resource` is a value loaded into memory that can be refreshed manually or + * automatically according to a schedule. + * + * **When to use** + * + * Use to model a scoped value whose latest acquisition result is kept available + * for repeated reads and can be refreshed manually or on a schedule. + * + * @see {@link manual} for creating a resource refreshed by the caller + * @see {@link auto} for creating a resource refreshed according to a schedule + * @see {@link get} for reading the currently stored acquisition result + * @see {@link refresh} for forcing a new acquisition + * + * @category models + * @since 2.0.0 + */ +export interface Resource extends Pipeable { + readonly [TypeId]: typeof TypeId + readonly scopedRef: ScopedRef.ScopedRef> + readonly acquire: Effect.Effect +} + +/** + * Returns `true` if the specified value is a `Resource`. + * + * **When to use** + * + * Use to validate unknown values at runtime boundaries before treating them as + * `Resource` values. + * + * **Details** + * + * This predicate narrows the input to `Resource`. + * + * @category guards + * @since 4.0.0 + */ +export const isResource: (u: unknown) => u is Resource = ( + u: unknown +): u is Resource => hasProperty(u, TypeId) + +const Proto = { + ...PipeInspectableProto, + [TypeId]: TypeId, + toJSON() { + return { + _id: "Resource" + } + } +} + +const makeUnsafe = ( + scopedRef: ScopedRef.ScopedRef>, + acquire: Effect.Effect +): Resource => { + const self = Object.create(Proto) + self.scopedRef = scopedRef + self.acquire = acquire + return self +} + +/** + * Creates a `Resource` that must be refreshed manually. + * + * **When to use** + * + * Use when you need manual control over resource refresh timing rather than an + * automatic schedule. + * + * @see {@link auto} for schedule-driven automatic refreshes + * @see {@link refresh} to manually trigger a resource refresh + * @category constructors + * @since 2.0.0 + */ +export const manual = ( + acquire: Effect.Effect +): Effect.Effect, never, Scope.Scope | R> => + Effect.contextWith((context: Context.Context) => { + const providedAcquire = Effect.updateContext( + acquire, + (input: Context.Context) => Context.merge(context, input) + ) + return Effect.map( + ScopedRef.fromAcquire(Effect.exit(providedAcquire)), + (scopedRef) => makeUnsafe(scopedRef, providedAcquire) + ) + }) + +/** + * Creates a `Resource` that refreshes automatically according to the supplied + * schedule. + * + * **When to use** + * + * Use when a resource should refresh in the background according to a schedule + * for the lifetime of its scope. + * + * @see {@link manual} for caller-controlled refresh timing + * @see {@link refresh} to trigger a refresh explicitly + * + * @category constructors + * @since 2.0.0 + */ +export const auto = ( + acquire: Effect.Effect, + policy: Schedule.Schedule +): Effect.Effect, never, R | R2 | Scope.Scope> => + Effect.tap( + manual(acquire), + (self) => Effect.forkScoped(Effect.repeat(refresh(self), policy)) + ) + +/** + * Retrieves the current value stored in this resource. + * + * **When to use** + * + * Use to read the value currently cached by a `Resource`. + * + * **Gotchas** + * + * If the resource currently stores a failed acquisition result, the returned + * effect fails with the stored error. + * + * @see {@link refresh} to re-run acquisition and update the stored value before a later read + * + * @category getters + * @since 2.0.0 + */ +export const get = (self: Resource): Effect.Effect => + Effect.flatMap(ScopedRef.get(self.scopedRef), identity) + +/** + * Re-runs this resource's acquisition effect and updates the current value. + * + * **When to use** + * + * Use to force an existing `Resource` to reacquire its value at a + * caller-controlled point. + * + * **Details** + * + * When acquisition succeeds, refreshing replaces the value stored in the + * resource's scoped reference and releases resources associated with the + * previous value. + * + * **Gotchas** + * + * If acquisition fails, the returned effect fails and the previously stored + * result is left as what `get` reads. + * + * @see {@link get} for reading the current stored value + * @see {@link manual} for resources refreshed only by caller action + * @see {@link auto} for schedule-driven automatic refreshes + * + * @category resource management + * @since 2.0.0 + */ +export const refresh = (self: Resource): Effect.Effect => + ScopedRef.set(self.scopedRef, Effect.map(self.acquire, Exit.succeed)) diff --git a/.repos/effect/packages/effect/src/Result.ts b/.repos/effect/packages/effect/src/Result.ts new file mode 100644 index 000000000..eb7a4e014 --- /dev/null +++ b/.repos/effect/packages/effect/src/Result.ts @@ -0,0 +1,1852 @@ +/** + * Models a value that has already succeeded or failed. + * + * A `Result` is `Success` when a value is available and + * `Failure` when an error is available. It is plain data, so inspecting + * or transforming it does not run side effects. This module includes helpers + * for creating, checking, mapping, combining, and extracting results, plus + * conversions to and from `Option` and nullable values. + * + * @since 4.0.0 + */ + +import * as Equivalence from "./Equivalence.ts" +import type { LazyArg } from "./Function.ts" +import { constNull, constUndefined, dual, identity } from "./Function.ts" +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" +import type { Predicate, Refinement } from "./Predicate.ts" +import { isFunction } from "./Predicate.ts" +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" + +/** + * A value that is either `Success` or `Failure`. + * + * **When to use** + * + * Use when both success and failure should remain available as data and + * `Option` would lose failure information. + * + * **Details** + * + * - Use {@link succeed} / {@link fail} to construct + * - Use {@link match} to fold both branches + * - Use {@link isSuccess} / {@link isFailure} to narrow the type + * + * `E` defaults to `never`, so `Result` means a result that cannot fail. + * + * **Example** (Creating and matching a Result) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.match(Result.succeed(42), { + * onSuccess: (value) => `Success: ${value}`, + * onFailure: (error) => `Error: ${error}` + * }) // => "Success: 42" + * ``` + * + * @see {@link succeed} / {@link fail} to create values + * @see {@link match} to fold both branches + * @see {@link isSuccess} / {@link isFailure} for type guards + * + * @category models + * @since 4.0.0 + */ +export type Result = Success | Failure + +/** + * The failure variant of {@link Result}. Wraps an error of type `E`. + * + * **Details** + * + * - Access the error via the `.failure` property + * - Use {@link isFailure} to narrow a `Result` to `Failure` + * - Create with {@link fail} + * + * **Example** (Accessing the failure value) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * const failure = Result.fail("Network error") + * + * if (Result.isFailure(failure)) { + * failure.failure // => "Network error" + * } + * ``` + * + * @see {@link fail} to create a Failure + * @see {@link isFailure} to narrow the type + * @see {@link Success} for the other variant + * + * @category models + * @since 4.0.0 + */ +export interface Failure extends Pipeable, Inspectable { + readonly _tag: "Failure" + readonly _op: "Failure" + readonly failure: E + readonly [TypeId]: { + readonly _A: Covariant + readonly _E: Covariant + } + [Symbol.iterator](): ResultIterator> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: ResultUnify + [Unify.ignoreSymbol]?: ResultUnifyIgnore +} + +/** + * Iterator protocol used to yield a `Result` inside {@link gen}, returning the + * success value type back to the generator. + * + * **When to use** + * + * Use when defining or typing `[Symbol.iterator]()` for `Result` values so + * `yield*` can pass the success value type back into `Result.gen`. + * + * @see {@link gen} for writing generator-based `Result` code that consumes this iterator protocol + * + * @category generators + * @since 4.0.0 + */ +export interface ResultIterator> { + next( + ...args: ReadonlyArray + ): IteratorResult> +} + +/** + * The success variant of {@link Result}. Wraps a value of type `A`. + * + * **Details** + * + * - Access the value via the `.success` property + * - Use {@link isSuccess} to narrow a `Result` to `Success` + * - Create with {@link succeed} + * + * **Example** (Accessing the success value) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * const success = Result.succeed(42) + * + * if (Result.isSuccess(success)) { + * success.success // => 42 + * } + * ``` + * + * @see {@link succeed} to create a Success + * @see {@link isSuccess} to narrow the type + * @see {@link Failure} for the other variant + * + * @category models + * @since 4.0.0 + */ +export interface Success extends Pipeable, Inspectable { + readonly _tag: "Success" + readonly _op: "Success" + readonly success: A + readonly [TypeId]: { + readonly _A: Covariant + readonly _E: Covariant + } + [Symbol.iterator](): ResultIterator> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: ResultUnify + [Unify.ignoreSymbol]?: ResultUnifyIgnore +} + +/** + * Type-level utility for unifying `Result` types in generic contexts. + * + * **Details** + * + * This is an internal interface used by the Effect type system. You typically + * do not need to reference it directly. + * + * @category models + * @since 4.0.0 + */ +export interface ResultUnify { + Result?: () => T[Unify.typeSymbol] extends Result | infer _ ? Result : never +} + +/** + * Marker interface for ignoring unification in `Result` types. + * + * **Details** + * + * This is an internal interface used by the Effect type system. You typically + * do not need to reference it directly. + * + * @category models + * @since 4.0.0 + */ +export interface ResultUnifyIgnore {} + +/** + * Higher-kinded type representation for `Result`. + * + * **Details** + * + * Used internally to integrate `Result` with generic type-class utilities + * (e.g., `map`, `flatMap` abstractions). You typically do not need to + * reference this directly. + * + * @category utility types + * @since 4.0.0 + */ +export interface ResultTypeLambda extends TypeLambda { + readonly type: Result +} + +/** + * Namespace containing type-level utilities for extracting the inner types + * of a `Result`. + * + * **Example** (Extracting inner types) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * type R = Result.Result + * + * // number + * type A = Result.Result.Success + * + * // string + * type E = Result.Result.Failure + * + * const success: A = 42 + * const failure: E = "error" + * ``` + * + * @since 4.0.0 + */ +export declare namespace Result { + /** + * Extracts the failure type `E` from `Result`. + * + * @category utility types + * @since 4.0.0 + */ + export type Failure> = [T] extends [Result] ? _E : never + /** + * Extracts the success type `A` from `Result`. + * + * @category utility types + * @since 4.0.0 + */ + export type Success> = [T] extends [Result] ? _A : never +} + +/** + * Creates a `Result` holding a `Success` value. + * + * **Details** + * + * - Use when you have a value and want to lift it into the `Result` type + * - The error type `E` defaults to `never` + * + * **Example** (Wrapping a value) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.succeed(42) // => Result.succeed(42) + * ``` + * + * @see {@link fail} to create a Failure + * @see {@link void_ void} for a pre-built `Success` + * + * @category constructors + * @since 4.0.0 + */ +export const succeed: (right: A) => Result = result.succeed + +/** + * Creates a `Result` holding a `Failure` value. + * + * **When to use** + * + * Use to represent a failed `Result` with a typed failure value. + * + * **Details** + * + * - The success type `A` defaults to `never` + * + * **Example** (Creating a failure) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.fail("Something went wrong") // => Result.fail("Something went wrong") + * ``` + * + * @see {@link succeed} to create a Success + * @see {@link mapError} to transform the error + * + * @category constructors + * @since 4.0.0 + */ +export const fail: (left: E) => Result = result.fail + +const void_: Result = succeed(void 0) +export { + /** + * Provides a pre-built successful `Result` that carries `undefined`. + * + * **When to use** + * + * Use when you need a successful `Result` value that signals completion + * without carrying meaningful data. + * + * **Details** + * + * This is equivalent to `Result.succeed(undefined)`, but reuses a shared + * `Success` wrapper instead of allocating one each time. + * + * **Example** (Referencing void results) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * const result: Result.Result = Result.void // => Result.succeed(undefined) + * ``` + * + * @see {@link succeed} to create a Success with a specific value + * + * @category constructors + * @since 3.13.0 + */ + void_ as void +} + +/** + * Provides a pre-built failed `Result` whose failure value is `undefined`. + * + * **When to use** + * + * Use when you need a failed `Result` value that acts only as a control signal + * without failure data. + * + * **Details** + * + * This is equivalent to `Result.fail(undefined)` with type + * `Result`, but reuses a shared `Failure` wrapper instead of + * allocating one each time. + * + * **Example** (Failing without a payload) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.failVoid // => Result.fail(undefined) + * ``` + * + * @see {@link fail} to create a Failure with a specific value + * + * @category constructors + * @since 4.0.0 + */ +export const failVoid: Result = fail(void 0) + +/** + * Converts a possibly `null` or `undefined` value into a `Result`. + * + * **When to use** + * + * Use when you need `null` or `undefined` input to become a `Failure` while + * present values remain available as `Success`. + * + * **Details** + * + * - Non-nullish values become `Success>` + * - `null` or `undefined` becomes `Failure` using the provided function + * - Supports both data-first and data-last (piped) usage + * - The `onNullish` callback receives the original value + * + * **Example** (Handling nullable values) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.fromNullishOr(1, () => "fallback") // => Result.succeed(1) + * + * Result.fromNullishOr(null, () => "fallback") // => Result.fail("fallback") + * ``` + * + * @see {@link fromOption} to convert from an Option + * @see {@link succeed} / {@link fail} for direct construction + * + * @category constructors + * @since 4.0.0 + */ +export const fromNullishOr: { + (onNullish: (a: A) => E): (self: A) => Result, E> + (self: A, onNullish: (a: A) => E): Result, E> +} = dual( + 2, + (self: A, onNullish: (a: A) => E): Result, E> => + self == null ? fail(onNullish(self)) : succeed(self) +) + +/** + * Converts an `Option` into a `Result`. + * + * **When to use** + * + * Use when an existing `Option` should become a `Result`, preserving `Some` as + * success and turning `None` into a caller-provided failure. + * + * **Details** + * + * - `Some` becomes `Success` + * - `None` becomes `Failure` using the provided function + * - Supports both data-first and data-last (piped) usage + * + * **Example** (Converting an Option to a Result) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Result.fromOption(Option.some(1), () => "missing") // => Result.succeed(1) + * + * Result.fromOption(Option.none(), () => "missing") // => Result.fail("missing") + * ``` + * + * @see {@link getSuccess} to extract the success value as an Option + * @see {@link getFailure} to extract the failure value as an Option + * @see {@link fromNullishOr} to build a Result from nullable values + * + * @category constructors + * @since 2.0.0 + */ +export const fromOption: { + (onNone: () => E): (self: Option) => Result + (self: Option, onNone: () => E): Result +} = result.fromOption + +const try_: { + ( + options: { + readonly try: LazyArg + readonly catch: (error: unknown) => E + } + ): Result + (evaluate: LazyArg): Result +} = ( + evaluate: LazyArg | { + readonly try: LazyArg + readonly catch: (error: unknown) => E + } +) => { + if (isFunction(evaluate)) { + try { + return succeed(evaluate()) + } catch (e) { + return fail(e) + } + } else { + try { + return succeed(evaluate.try()) + } catch (e) { + return fail(evaluate.catch(e)) + } + } +} + +export { + /** + * Wraps a synchronous computation that may throw into a `Result` safely. + * + * **Details** + * + * - If the function returns normally, the result is `Success` + * - If the function throws, the exception is caught and becomes `Failure` + * - With a single function argument, the error type is `unknown` + * - With `{ try, catch }` options, the `catch` function maps the thrown value to `E` + * + * **Example** (Catching JSON parse errors) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.try(() => JSON.parse('{"name": "Alice"}')) // => Result.succeed({ name: "Alice" }) + * + * const err = Result.try({ + * try: () => JSON.parse("not json"), + * catch: (e) => `Parse failed: ${e}` + * }) + * Result.isFailure(err) // => true + * ``` + * + * @see {@link succeed} / {@link fail} for direct construction + * @see {@link fromNullishOr} for nullable values + * + * @category constructors + * @since 2.0.0 + */ + try_ as try +} + +/** + * Checks whether a value is a `Result` (either `Success` or `Failure`). + * + * **When to use** + * + * Use to validate unknown input before operating on it as a `Result`. + * + * **Details** + * + * - Returns `true` for both `Success` and `Failure` variants + * - Acts as a TypeScript type guard, narrowing to `Result` + * + * **Example** (Checking if a value is a Result) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.isResult(Result.succeed(1)) // => true + * + * Result.isResult({ value: 1 }) // => false + * ``` + * + * @see {@link isSuccess} / {@link isFailure} to narrow to a specific variant + * + * @category guards + * @since 4.0.0 + */ +export const isResult: (input: unknown) => input is Result = result.isResult + +/** + * Checks whether a `Result` is a `Failure`. + * + * **When to use** + * + * Use to narrow a known `Result` to the `Failure` variant. + * + * **Details** + * + * - Acts as a TypeScript type guard, narrowing to `Failure` + * - After narrowing, you can access `.failure` to read the error value + * + * **Example** (Narrowing to failure) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * const result = Result.fail("oops") + * + * if (Result.isFailure(result)) { + * result.failure // => "oops" + * } + * ``` + * + * @see {@link isSuccess} for the opposite check + * @see {@link isResult} to check if a value is any Result + * + * @category guards + * @since 4.0.0 + */ +export const isFailure: (self: Result) => self is Failure = result.isFailure + +/** + * Checks whether a `Result` is a `Success`. + * + * **When to use** + * + * Use to narrow a known `Result` to the `Success` variant. + * + * **Details** + * + * - Acts as a TypeScript type guard, narrowing to `Success` + * - After narrowing, you can access `.success` to read the value + * + * **Example** (Narrowing to success) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * const result = Result.succeed(42) + * + * if (Result.isSuccess(result)) { + * result.success // => 42 + * } + * ``` + * + * @see {@link isFailure} for the opposite check + * @see {@link isResult} to check if a value is any Result + * + * @category guards + * @since 4.0.0 + */ +export const isSuccess: (self: Result) => self is Success = result.isSuccess + +/** + * Extracts the success value as an `Option`, discarding the failure. + * + * **When to use** + * + * Use when you need to extract the success value from a `Result` as an + * `Option` and discard failure information. + * + * **Details** + * + * - `Success` becomes `Some` + * - `Failure` becomes `None` + * + * **Example** (Extracting the success as an Option) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Result.getSuccess(Result.succeed("ok")) // => Option.some("ok") + * + * Result.getSuccess(Result.fail("err")) // => Option.none() + * ``` + * + * @see {@link getFailure} to extract the error instead + * @see {@link fromOption} for the reverse conversion + * + * @category getters + * @since 4.0.0 + */ +export const getSuccess: (self: Result) => Option = result.getSuccess + +/** + * Extracts the failure value as an `Option`, discarding the success. + * + * **When to use** + * + * Use when you need to extract the failure value from a `Result` as an + * `Option` and discard successful values. + * + * **Details** + * + * - `Failure` becomes `Some` + * - `Success` becomes `None` + * + * **Example** (Extracting the failure as an Option) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Result.getFailure(Result.succeed("ok")) // => Option.none() + * + * Result.getFailure(Result.fail("err")) // => Option.some("err") + * ``` + * + * @see {@link getSuccess} to extract the success instead + * @see {@link fromOption} for the reverse conversion + * + * @category getters + * @since 4.0.0 + */ +export const getFailure: (self: Result) => Option = result.getFailure + +/** + * Creates an `Equivalence` for comparing two `Result` values. + * + * **Details** + * + * - Two `Success` values are equal when the `success` equivalence says so + * - Two `Failure` values are equal when the `failure` equivalence says so + * - A `Success` and a `Failure` are never equal + * + * **Example** (Comparing Results for equality) + * + * ```ts import.meta.vitest + * import { Equivalence, Result } from "effect" + * + * const eq = Result.makeEquivalence( + * Equivalence.strictEqual(), + * Equivalence.strictEqual() + * ) + * + * eq(Result.succeed(1), Result.succeed(1)) // => true + * + * eq(Result.succeed(1), Result.fail("x")) // => false + * ``` + * + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence = ( + success: Equivalence.Equivalence, + failure: Equivalence.Equivalence +): Equivalence.Equivalence> => + Equivalence.make((x, y) => + isFailure(x) ? + isFailure(y) && failure(x.failure, y.failure) : + isSuccess(y) && success(x.success, y.success) + ) + +/** + * Transforms both the success and failure channels of a `Result`. + * + * **When to use** + * + * Use to transform both success and failure values without changing whether the + * result succeeds or fails. + * + * **Details** + * + * - Applies `onSuccess` if the result is a `Success` + * - Applies `onFailure` if the result is a `Failure` + * + * **Example** (Mapping both channels) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.succeed(1), + * Result.mapBoth({ + * onSuccess: (n) => n + 1, + * onFailure: (e) => `Error: ${e}` + * }) + * ) // => Result.succeed(2) + * ``` + * + * @see {@link map} to transform only the success value + * @see {@link mapError} to transform only the error value + * @see {@link match} to fold into a single value + * + * @category mapping + * @since 2.0.0 + */ +export const mapBoth: { + (options: { + readonly onFailure: (left: E) => E2 + readonly onSuccess: (right: A) => A2 + }): (self: Result) => Result + (self: Result, options: { + readonly onFailure: (left: E) => E2 + readonly onSuccess: (right: A) => A2 + }): Result +} = dual( + 2, + (self: Result, { onFailure, onSuccess }: { + readonly onFailure: (left: E) => E2 + readonly onSuccess: (right: A) => A2 + }): Result => isFailure(self) ? fail(onFailure(self.failure)) : succeed(onSuccess(self.success)) +) + +/** + * Transforms the failure channel of a `Result`, leaving the success channel unchanged. + * + * **When to use** + * + * Use to transform only the failure channel while preserving success values. + * + * **Details** + * + * - If the result is a `Failure`, applies `f` to the error and returns a new `Failure` + * - If the result is a `Success`, returns it as-is + * + * **Example** (Adding context to an error) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.fail("not found"), + * Result.mapError((e) => `Error: ${e}`) + * ) // => Result.fail("Error: not found") + * ``` + * + * @see {@link map} to transform only the success value + * @see {@link mapBoth} to transform both channels + * + * @category mapping + * @since 4.0.0 + */ +export const mapError: { + (f: (err: E) => E2): (self: Result) => Result + (self: Result, f: (err: E) => E2): Result +} = dual( + 2, + (self: Result, f: (err: E) => E2): Result => + isFailure(self) ? fail(f(self.failure)) : self as unknown as Result +) + +/** + * Transforms the success channel of a `Result`, leaving the failure channel unchanged. + * + * **When to use** + * + * Use to apply a transformation to the success value of a `Result` while + * preserving any existing failure. + * + * **Details** + * + * - If the result is a `Success`, applies `f` to the value and returns a new `Success` + * - If the result is a `Failure`, returns it as-is + * - Use {@link flatMap} if `f` returns a `Result` (to avoid nested Results) + * + * **Example** (Doubling the success value) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.succeed(3), + * Result.map((n) => n * 2) + * ) // => Result.succeed(6) + * ``` + * + * @see {@link mapError} to transform only the error value + * @see {@link mapBoth} to transform both channels + * @see {@link flatMap} when `f` returns a `Result` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (ok: A) => A2): (self: Result) => Result + (self: Result, f: (ok: A) => A2): Result +} = dual( + 2, + (self: Result, f: (ok: A) => A2): Result => + isSuccess(self) ? succeed(f(self.success)) : self as unknown as Result +) + +/** + * Folds a `Result` into a single value by applying one of two functions. + * + * **When to use** + * + * Use when a `Result`'s success and failure branches should be collapsed into + * one plain output type. + * + * **Details** + * + * - Applies `onSuccess` if the result is a `Success` + * - Applies `onFailure` if the result is a `Failure` + * - Both branches must return the same type (or a common supertype) + * + * **Example** (Folding to a string) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * const format = Result.match({ + * onSuccess: (n: number) => `Got ${n}`, + * onFailure: (e: string) => `Err: ${e}` + * }) + * + * format(Result.succeed(42)) // => "Got 42" + * + * format(Result.fail("timeout")) // => "Err: timeout" + * ``` + * + * @see {@link merge} to extract `A | E` without mapping + * @see {@link getOrElse} to unwrap only the success with a fallback + * + * @category pattern matching + * @since 2.0.0 + */ +export const match: { + (options: { + readonly onFailure: (error: E) => B + readonly onSuccess: (ok: A) => C + }): (self: Result) => B | C + (self: Result, options: { + readonly onFailure: (error: E) => B + readonly onSuccess: (ok: A) => C + }): B | C +} = dual( + 2, + (self: Result, { onFailure, onSuccess }: { + readonly onFailure: (error: E) => B + readonly onSuccess: (ok: A) => C + }): B | C => isFailure(self) ? onFailure(self.failure) : onSuccess(self.success) +) + +/** + * Lifts a value into a `Result` based on a predicate or refinement. + * + * **When to use** + * + * Use to construct a `Result` from a raw value guarded by a predicate or + * refinement. + * + * **Details** + * + * - If the predicate returns `true`, the value becomes `Success` + * - If the predicate returns `false`, `orFailWith` produces the error for `Failure` + * - Also accepts a `Refinement` to narrow the success type + * - Supports both data-first and data-last (piped) usage + * + * **Example** (Validating a number) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * 5, + * Result.liftPredicate( + * (n: number) => n > 0, + * (n) => `${n} is not positive` + * ) + * ) // => Result.succeed(5) + * ``` + * + * @see {@link filterOrFail} to validate a value that is already in a `Result` + * @see {@link fromNullishOr} for nullable-based construction + * + * @category constructors + * @since 3.4.0 + */ +export const liftPredicate: { + (refinement: Refinement, orFailWith: (a: A) => E): (a: A) => Result + ( + predicate: Predicate, + orFailWith: (a: A) => E + ): (a: B) => Result + ( + self: A, + refinement: Refinement, + orFailWith: (a: A) => E + ): Result + ( + self: B, + predicate: Predicate, + orFailWith: (a: A) => E + ): Result +} = dual( + 3, + (a: A, predicate: Predicate, orFailWith: (a: A) => E): Result => + predicate(a) ? succeed(a) : fail(orFailWith(a)) +) + +/** + * Validates the success value of a `Result` using a predicate, failing with a + * custom error if the predicate returns `false`. + * + * **When to use** + * + * Use to validate an already-successful `Result` value with a predicate or + * refinement. + * + * **Details** + * + * - If the result is already a `Failure`, it is returned as-is + * - If the predicate passes, the `Success` is returned unchanged + * - If the predicate fails, `orFailWith` produces the error for a new `Failure` + * - Also accepts a `Refinement` to narrow the success type + * - The error type of the output is the union of both error types + * + * **Example** (Filtering a success value) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.succeed(0), + * Result.filterOrFail( + * (n) => n > 0, + * (n) => `${n} is not positive` + * ) + * ) // => Result.fail("0 is not positive") + * ``` + * + * @see {@link liftPredicate} to create a `Result` from a raw value with a predicate + * @see {@link flatMap} for general conditional chaining + * + * @category filtering + * @since 4.0.0 + */ +export const filterOrFail: { + ( + refinement: Refinement, B>, + orFailWith: (value: NoInfer) => E2 + ): (self: Result) => Result + ( + predicate: Predicate>, + orFailWith: (value: NoInfer) => E2 + ): (self: Result) => Result + ( + self: Result, + refinement: Refinement, + orFailWith: (value: A) => E2 + ): Result + (self: Result, predicate: Predicate, orFailWith: (value: A) => E2): Result +} = dual(3, ( + self: Result, + predicate: Predicate, + orFailWith: (value: A) => E2 +): Result => flatMap(self, (a) => predicate(a) ? succeed(a) : fail(orFailWith(a)))) + +/** + * Unwraps a `Result` into `A | E` by returning the inner value regardless + * of whether it is a success or failure. + * + * **Details** + * + * - `Success` returns `A` + * - `Failure` returns `E` + * - Useful when both channels share a compatible type + * + * **Example** (Extracting the inner value) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.merge(Result.succeed(42)) // => 42 + * + * Result.merge(Result.fail("error")) // => "error" + * ``` + * + * @see {@link match} to map each branch to a common type + * @see {@link getOrElse} to provide a fallback for failures + * + * @category getters + * @since 2.0.0 + */ +export const merge: (self: Result) => E | A = match({ onFailure: identity, onSuccess: identity }) + +/** + * Extracts the success value, or computes a fallback from the error. + * + * **When to use** + * + * Use when you need the success value from a `Result`, with a fallback computed + * from the failure value. + * + * **Details** + * + * - `Success` returns the inner value + * - `Failure` applies `onFailure` to the error and returns the result + * - The return type is `A | A2` (union of both branches) + * + * **Example** (Providing a fallback) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.getOrElse(Result.succeed(1), () => 0) // => 1 + * + * Result.getOrElse(Result.fail("err"), () => 0) // => 0 + * ``` + * + * @see {@link getOrNull} / {@link getOrUndefined} for simpler fallbacks + * @see {@link getOrThrow} to throw on failure + * @see {@link match} to map both branches + * @see {@link orElse} to recover with another Result instead of unwrapping + * + * @category getters + * @since 2.0.0 + */ +export const getOrElse: { + (onFailure: (err: E) => A2): (self: Result) => A2 | A + (self: Result, onFailure: (err: E) => A2): A | A2 +} = dual( + 2, + (self: Result, onFailure: (err: E) => A2): A | A2 => + isFailure(self) ? onFailure(self.failure) : self.success +) + +/** + * Extracts the success value, or returns `null` on failure. + * + * **When to use** + * + * Use when you need to pass failed `Result` values to APIs that represent + * absence as `null`. + * + * **Details** + * + * - `Success` returns `A` + * - `Failure` returns `null` + * + * **Example** (Unwrapping to nullable) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.getOrNull(Result.succeed(1)) // => 1 + * + * Result.getOrNull(Result.fail("err")) // => null + * ``` + * + * @see {@link getOrUndefined} to return `undefined` instead + * @see {@link getOrElse} for a custom fallback + * + * @category getters + * @since 2.0.0 + */ +export const getOrNull: (self: Result) => A | null = getOrElse(constNull) + +/** + * Extracts the success value, or returns `undefined` on failure. + * + * **When to use** + * + * Use when you need to pass failed `Result` values to APIs that represent + * absence as `undefined`. + * + * **Details** + * + * - `Success` returns `A` + * - `Failure` returns `undefined` + * + * **Example** (Unwrapping to optional) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.getOrUndefined(Result.succeed(1)) // => 1 + * + * Result.getOrUndefined(Result.fail("err")) // => undefined + * ``` + * + * @see {@link getOrNull} to return `null` instead + * @see {@link getOrElse} for a custom fallback + * + * @category getters + * @since 2.0.0 + */ +export const getOrUndefined: (self: Result) => A | undefined = getOrElse(constUndefined) + +/** + * Extracts the success value or throws a custom error derived from the failure. + * + * **When to use** + * + * Use when converting a `Result` into a thrown exception with a custom error + * message or error type. + * + * **Details** + * + * - `Success` returns `A` + * - `Failure` throws the value returned by `onFailure(e)` + * + * **Example** (Throwing a custom error) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.getOrThrowWith(Result.succeed(1), () => new Error("fail")) // => 1 + * + * 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 + * @see {@link getOrElse} for a non-throwing alternative + * + * @category getters + * @since 2.0.0 + */ +export const getOrThrowWith: { + (onFailure: (err: E) => unknown): (self: Result) => A + (self: Result, onFailure: (err: E) => unknown): A +} = dual(2, (self: Result, onFailure: (err: E) => unknown): A => { + if (isSuccess(self)) { + return self.success + } + throw onFailure(self.failure) +}) + +/** + * Extracts the success value or throws the raw failure value `E`. + * + * **When to use** + * + * Use when unchecked boundaries should turn failures into thrown exceptions. + * + * **Details** + * + * - `Success` returns `A` + * - `Failure` throws `E` directly + * - Use {@link getOrThrowWith} for a custom error object + * + * **Example** (Unwrapping or throwing) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.getOrThrow(Result.succeed(1)) // => 1 + * + * const failure = Result.try(() => Result.getOrThrow(Result.fail("error"))) + * Result.merge(failure) // => "error" + * ``` + * + * @see {@link getOrThrowWith} for custom error mapping + * @see {@link getOrElse} for a non-throwing alternative + * + * @category getters + * @since 2.0.0 + */ +export const getOrThrow: (self: Result) => A = getOrThrowWith(identity) + +/** + * Returns the original `Result` if it is a `Success`, otherwise applies + * `that` to the error and returns the resulting `Result`. + * + * **When to use** + * + * Use when a failure should recover into another `Result` while keeping + * successes unchanged. + * + * **Details** + * + * - `Success` is returned unchanged + * - `Failure` calls `that(e)` to produce a new `Result` + * + * **Example** (Recovering from a failure) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.fail("primary failed"), + * Result.orElse(() => Result.succeed(99)) + * ) // => Result.succeed(99) + * ``` + * + * @see {@link getOrElse} to unwrap with a fallback value (not a Result) + * @see {@link mapError} to transform the error without recovering + * + * @category error handling + * @since 2.0.0 + */ +export const orElse: { + (that: (err: E) => Result): (self: Result) => Result + (self: Result, that: (err: E) => Result): Result +} = dual( + 2, + (self: Result, that: (err: E) => Result): Result => + isFailure(self) ? that(self.failure) : succeed(self.success) +) + +/** + * Chains a function that returns a `Result` onto a successful value. + * + * **When to use** + * + * Use to sequence `Result`-returning computations that should short-circuit on + * failure. + * + * **Details** + * + * - If `self` is a `Success`, applies `f` to the value and returns the resulting `Result` + * - If `self` is a `Failure`, short-circuits and returns it unchanged + * - The error types are merged into a union (`E | E2`) + * - This is the monadic `bind` / `>>=` for `Result` + * + * **Example** (Validating sequentially) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.succeed(5), + * Result.flatMap((n) => + * n > 0 ? Result.succeed(n * 2) : Result.fail("not positive") + * ) + * ) // => Result.succeed(10) + * ``` + * + * @see {@link andThen} for a more flexible variant that also accepts plain values + * @see {@link map} when `f` does not return a `Result` + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + (f: (a: A) => Result): (self: Result) => Result + (self: Result, f: (a: A) => Result): Result +} = dual( + 2, + (self: Result, f: (a: A) => Result): Result => + isFailure(self) ? fail(self.failure) : f(self.success) +) + +/** + * Provides a flexible variant of {@link flatMap} that accepts multiple input shapes. + * + * **When to use** + * + * Use to sequence a next step that may be a `Result`, a function, or a plain + * value. + * + * **Details** + * + * The second argument can be: + * - A function `(a: A) => Result` (same as `flatMap`) + * - A function `(a: A) => A2` (auto-wrapped in `succeed`) + * - A `Result` value (ignores the success of `self`) + * - A plain value `A2` (auto-wrapped in `succeed`, ignores `self`) + * + * If `self` is a `Failure`, the second argument is never evaluated. + * + * **Example** (Chaining Result values with different argument types) + * + * ```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")) // => Result.succeed("done") + * ``` + * + * @see {@link flatMap} for the stricter variant (function returning Result only) + * @see {@link map} when you always return a plain value + * + * @category sequencing + * @since 2.0.0 + */ +export const andThen: { + (f: (a: A) => Result): (self: Result) => Result + (f: Result): (self: Result) => Result + (f: (a: A) => A2): (self: Result) => Result + (right: NotFunction): (self: Result) => Result + (self: Result, f: (a: A) => Result): Result + (self: Result, f: Result): Result + (self: Result, f: (a: A) => A2): Result + (self: Result, f: NotFunction): Result +} = dual( + 2, + ( + self: Result, + f: ((a: A) => Result | A2) | Result | A2 + ): Result => + flatMap(self, (a) => { + const out = isFunction(f) ? f(a) : f + return isResult(out) ? out : succeed(out) + }) +) + +/** + * Collects a structure of `Result`s into a single `Result` of collected values. + * + * **When to use** + * + * Use to collect independent `Result` values into one `Result` while preserving + * the original structure. + * + * **Details** + * + * Accepts: + * - A tuple/array: returns `Result` with a tuple/array of success values + * - A struct (record): returns `Result` with a struct of success values + * - An iterable: returns `Result` with an array of success values + * + * Short-circuits on the first `Failure` encountered; later elements are not inspected. + * + * **Example** (Collecting a tuple and a struct) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * // Tuple + * Result.all([Result.succeed(1), Result.succeed("two")]) // => Result.succeed([1, "two"]) + * + * // Struct + * Result.all({ x: Result.succeed(1), y: Result.fail("err") }) // => Result.fail("err") + * ``` + * + * @see {@link flatMap} for chaining two Results sequentially + * @see {@link gen} for generator-based composition of multiple Results + * + * @category sequencing + * @since 2.0.0 + */ +// @ts-expect-error +export const all: > | Record>>( + input: I +) => [I] extends [ReadonlyArray>] ? Result< + { -readonly [K in keyof I]: [I[K]] extends [Result] ? R : never }, + I[number] extends never ? never : [I[number]] extends [Result] ? L : never + > + : [I] extends [Iterable>] ? Result, L> + : Result< + { -readonly [K in keyof I]: [I[K]] extends [Result] ? R : never }, + I[keyof I] extends never ? never : [I[keyof I]] extends [Result] ? L : never + > = ( + input: Iterable> | Record> + ): Result => { + if (Symbol.iterator in input) { + const out: Array> = [] + for (const e of input) { + if (isFailure(e)) { + return e + } + out.push(e.success) + } + return succeed(out) + } + + const out: Record = {} + for (const key of Object.keys(input)) { + const e = input[key] + if (isFailure(e)) { + return e + } + InternalRecord.assignProperty(out, key, e.success) + } + return succeed(out) + } + +/** + * Swaps the success and failure channels of a `Result`. + * + * **When to use** + * + * Use to swap channels when failure-focused operations are easier through + * success-oriented combinators. + * + * **Details** + * + * - `Success` becomes `Failure` (i.e., `Result`) + * - `Failure` becomes `Success` (i.e., `Result`) + * - Useful when you want to apply success-oriented operations (like `map`) + * to the error channel, then flip back + * + * **Example** (Swapping channels) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.flip(Result.succeed(42)) // => Result.fail(42) + * + * Result.flip(Result.fail("error")) // => Result.succeed("error") + * ``` + * + * @see {@link mapError} to transform the error without swapping + * + * @category transforming + * @since 2.0.0 + */ +export const flip = (self: Result): Result => + isFailure(self) ? succeed(self.failure) : fail(self.success) + +/** + * Provides generator-based syntax for composing `Result` values sequentially. + * + * **When to use** + * + * Use when you need generator syntax to compose sequential `Result` + * computations instead of nested `flatMap` calls. + * + * **Details** + * + * - Use `yield*` to unwrap a `Result` inside the generator; if any yielded + * `Result` is a `Failure`, the generator short-circuits and returns that failure + * - The return value of the generator is wrapped in `Success` + * - Evaluated eagerly and synchronously (unlike `Effect.gen`) + * + * **Example** (Composing multiple Results) + * + * ```ts import.meta.vitest + * import { Result } from "effect" + * + * Result.gen(function*() { + * const a = yield* Result.succeed(1) + * const b = yield* Result.succeed(2) + * return a + b + * }) // => Result.succeed(3) + * ``` + * + * @see {@link flatMap} for point-free sequential composition + * @see {@link all} to collect multiple independent Results + * + * @category generators + * @since 2.0.0 + */ +export const gen: Gen.Gen = (...args) => { + const f = args.length === 1 ? args[0] : args[1].bind(args[0]) + const iterator = f() + let state: IteratorResult = iterator.next() + while (!state.done) { + const current = state.value + if (isFailure(current)) { + return current + } + state = iterator.next(current.success as never) + } + return succeed(state.value) as any +} + +// ------------------------------------------------------------------------------------- +// do notation +// ------------------------------------------------------------------------------------- + +/** + * Provides the starting point for the "do notation" simulation with `Result`. + * + * **When to use** + * + * Use to start a `Result` do-notation pipeline from an empty successful record + * before adding named fields from `Result`-producing computations and pure + * computed values. + * + * **Details** + * + * Creates a `Result<{}>` (success with an empty object). Use with + * {@link bind} to add `Result`-producing fields and {@link let_ let} + * to add pure computed fields. + * + * **Example** (Building an object step by step) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.Do, + * Result.bind("x", () => Result.succeed(2)), + * Result.bind("y", () => Result.succeed(3)), + * Result.let("sum", ({ x, y }) => x + y) + * ) // => Result.succeed({ x: 2, y: 3, sum: 5 }) + * ``` + * + * @see {@link bind} to add Result-producing fields + * @see {@link let_ let} to add pure computed fields + * @see {@link gen} for an alternative generator-based syntax + * @see {@link bindTo} for starting a do-notation chain from an existing Result + * + * @category constructors + * @since 2.0.0 + */ +export const Do: Result<{}> = succeed({}) + +/** + * Adds a named field to the do-notation accumulator by running a `Result`-producing + * function that receives the current accumulated object. + * + * **When to use** + * + * Use when you need to add a `Result`-producing step to a `Result` + * do-notation pipeline and store its successful value under a named field in + * the accumulated object. + * + * **Details** + * + * - Short-circuits on the first `Failure` + * - The field name must not collide with existing keys + * - Use {@link let_ let} for pure (non-Result) computed fields + * + * **Example** (Binding Result values) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.Do, + * Result.bind("x", () => Result.succeed(2)), + * Result.bind("y", ({ x }) => Result.succeed(x + 3)) + * ) // => 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 sequencing + * @since 2.0.0 + */ +export const bind: { + ( + name: Exclude, + f: (a: NoInfer) => Result + ): (self: Result) => Result<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, L1 | L2> + ( + self: Result, + name: Exclude, + f: (a: NoInfer) => Result + ): Result<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, L1 | L2> +} = doNotation.bind(map, flatMap) + +/** + * Wraps the success value of a `Result` into a named field, producing a + * `Result>`. + * + * **When to use** + * + * Use to name the success value of an existing `Result` before continuing a + * do-notation pipeline. + * + * **Details** + * + * This is typically used to start a do-notation chain from an existing + * `Result`. + * + * **Example** (Wrapping a value into a named field) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.succeed(42), + * Result.bindTo("answer") + * ) // => Result.succeed({ answer: 42 }) + * ``` + * + * @see {@link Do} to start from an empty object + * @see {@link bind} to add more fields + * + * @category mapping + * @since 2.0.0 + */ +export const bindTo: { + (name: N): (self: Result) => Result, L> + (self: Result, name: N): Result, L> +} = doNotation.bindTo(map) + +const let_: { + ( + name: Exclude, + f: (r: NoInfer) => B + ): (self: Result) => Result<{ [K in N | keyof R]: K extends keyof R ? R[K] : B }, L> + ( + self: Result, + name: Exclude, + f: (r: NoInfer) => B + ): Result<{ [K in N | keyof R]: K extends keyof R ? R[K] : B }, L> +} = doNotation.let_(map) + +export { + /** + * Adds a named field to the do-notation accumulator by computing a pure + * (non-Result) value from the current accumulated object. + * + * **When to use** + * + * Use when you need to add a derived field that cannot fail inside a + * do-notation pipeline. + * + * **Details** + * + * - Use {@link bind} when the computation returns a `Result` + * - The field name must not collide with existing keys + * + * **Example** (Adding a computed field) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * pipe( + * Result.Do, + * Result.bind("x", () => Result.succeed(2)), + * Result.bind("y", () => Result.succeed(3)), + * Result.let("sum", ({ x, y }) => x + y) + * ) // => 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 mapping + * @since 2.0.0 + */ + let_ as let +} + +/** + * Transforms `Option>` into `Result, E>`. + * + * **When to use** + * + * Use when optional absence should be treated as a successful `None`, while an + * inner `Result` failure should still fail the whole result. + * + * **Details** + * + * - `None` becomes `Success(None)` + * - `Some(Success(a))` becomes `Success(Some(a))` + * - `Some(Failure(e))` becomes `Failure(e)` + * + * **Example** (Transposing an Option of a Result) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Result.transposeOption(Option.some(Result.succeed(42))) // => Result.succeed(Option.some(42)) + * + * Result.transposeOption(Option.none>()) // => Result.succeed(Option.none()) + * ``` + * + * @see {@link transposeMapOption} to map and transpose in one step + * + * @category transposing + * @since 3.14.0 + */ +export const transposeOption = ( + self: Option> +): Result, E> => { + return option_.isNone(self) ? succeedNone : map(self.value, option_.some) +} + +/** + * Maps an `Option` value with a `Result`-producing function, then transposes + * the structure from `Option>` to `Result, E>`. + * + * **When to use** + * + * Use when an optional value should be validated only when present, preserving + * absence as a successful `None`. + * + * **Details** + * + * - `None` becomes `Success(None)` (the function is never called) + * - `Some(a)` where `f(a)` is `Success(b)` becomes `Success(Some(b))` + * - `Some(a)` where `f(a)` is `Failure(e)` becomes `Failure(e)` + * + * **Example** (Mapping and transposing in one step) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * const parse = (s: string) => + * isNaN(Number(s)) + * ? Result.fail("not a number" as const) + * : Result.succeed(Number(s)) + * + * Result.transposeMapOption(Option.some("42"), parse) // => Result.succeed(Option.some(42)) + * + * Result.transposeMapOption(Option.none(), parse) // => Result.succeed(Option.none()) + * ``` + * + * @see {@link transposeOption} when the Option already contains a Result + * + * @category transposing + * @since 3.15.0 + */ +export const transposeMapOption = dual< + ( + f: (self: A) => Result + ) => (self: Option) => Result, E>, + ( + self: Option, + f: (self: A) => Result + ) => Result, E> +>(2, (self, f) => option_.isNone(self) ? succeedNone : map(f(self.value), option_.some)) + +/** + * Provides a pre-built `Result>` that succeeds with `None`. + * + * **When to use** + * + * Use when an optional success should be absent, such as the `None` branch of + * `transposeOption` or `transposeMapOption`. + * + * **Details** + * + * This is equivalent to `Result.succeed(Option.none())`, but reuses a shared + * `Success` wrapper instead of allocating one each time. + * + * **Example** (Succeeding with None) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Result.succeedNone // => Result.succeed(Option.none()) + * ``` + * + * @see {@link succeedSome} for the `Some` counterpart + * @see {@link transposeOption} to transpose an Option that already contains a Result + * @see {@link transposeMapOption} to map and transpose an Option in one step + * + * @category constructors + * @since 4.0.0 + */ +export const succeedNone = succeed(option_.none) + +/** + * Creates a `Result>` that succeeds with `Some(a)`. + * + * **Details** + * + * - Equivalent to `Result.succeed(Option.some(a))` + * - Useful with {@link transposeOption} patterns + * + * **Example** (Wrapping a value in Some inside a Result) + * + * ```ts import.meta.vitest + * import { Option, Result } from "effect" + * + * Result.succeedSome(42) // => Result.succeed(Option.some(42)) + * ``` + * + * @see {@link succeedNone} for the `None` counterpart + * + * @category constructors + * @since 4.0.0 + */ +export const succeedSome = (a: A): Result, E> => succeed(option_.some(a)) + +/** + * Runs a side-effect on the success value without altering the `Result`. + * + * **Details** + * + * - If the result is a `Success`, calls `f` with the value (return value is ignored) + * - If the result is a `Failure`, `f` is not called + * - Returns the original `Result` unchanged (same reference) + * - Useful for logging, debugging, or performing mutations outside the Result chain + * + * **Example** (Logging a success value) + * + * ```ts import.meta.vitest + * import { pipe, Result } from "effect" + * + * const values: Array = [] + * const result = pipe( + * Result.succeed(42), + * Result.tap((n) => values.push(n)) + * ) + * + * values // => [42] + * result // => Result.succeed(42) + * ``` + * + * @see {@link map} to transform the success value + * + * @category mapping + * @since 4.0.0 + */ +export const tap: { + (f: (a: A) => void): (self: Result) => Result + (self: Result, f: (a: A) => void): Result +} = dual( + 2, + (self: Result, f: (a: A) => void): Result => { + if (isSuccess(self)) { + f(self.success) + } + return self + } +) diff --git a/.repos/effect/packages/effect/src/Runtime.ts b/.repos/effect/packages/effect/src/Runtime.ts new file mode 100644 index 000000000..ab7ad6809 --- /dev/null +++ b/.repos/effect/packages/effect/src/Runtime.ts @@ -0,0 +1,407 @@ +/** + * Helpers for turning an `Effect` program into a host application's main entry + * point. This module is the low-level layer used by platform adapters to run a + * main effect, observe its fiber, report unhandled failures, and translate the + * resulting `Exit` into an application or process exit code. It provides + * `makeRunMain`, the default teardown behavior, and error markers for custom + * exit codes and already-reported failures. Application code usually calls the + * platform-provided runner instead of using this module directly. + * + * @since 4.0.0 + */ +import * as Cause from "effect/Cause" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import { constVoid, dual } from "effect/Function" +import type * as Fiber from "./Fiber.ts" + +/** + * Represents a teardown function that handles program completion and determines the exit code. + * + * **When to use** + * + * Use when integrating {@link makeRunMain} with a host platform that needs to + * translate an Effect `Exit` into a process, worker, or application exit code. + * + * **Details** + * + * A teardown function is called when an Effect program completes, either + * successfully or with a failure. It determines the appropriate exit code and + * can perform cleanup before invoking the supplied `onExit` callback. + * + * **Example** (Customizing teardown behavior) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Runtime } from "effect" + * + * // Custom teardown that maps completion status to an exit code + * const customTeardown: Runtime.Teardown = (exit, onExit) => { + * 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) => resolve([exit, code])) + * }) + * }) + * + * const program = Effect.succeed("Hello, World!") + * runMain(program, { teardown: customTeardown }) + * }) + * + * await completed // => [Exit.succeed("Hello, World!"), 0] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface Teardown { + (exit: Exit.Exit, onExit: (code: number) => void): void +} + +/** + * The default teardown function that determines exit codes from an Effect exit. + * + * **When to use** + * + * Use as the standard teardown for main programs with conventional process + * exit codes and support for {@link errorExitCode}. + * + * **Details** + * + * This teardown follows these exit-code rules: + * + * - `0` for successful completion. + * - `130` for interruption-only failures. + * - The squashed error's {@link errorExitCode} value for other failures when + * present. + * - `1` for other failures. + * + * **Gotchas** + * + * The `130` code is used only when the Cause contains interruptions and no + * other failure reasons. Mixed causes use the squashed error path instead. + * + * **Example** (Referencing default teardown) + * + * ```ts import.meta.vitest + * import { Exit, Runtime } from "effect" + * + * const exitCodes: Array = [] + * const collectExitCode = (exit: Exit.Exit) => + * Runtime.defaultTeardown(exit, (code) => exitCodes.push(code)) + * + * collectExitCode(Exit.succeed(42)) + * collectExitCode(Exit.fail("error")) + * collectExitCode(Exit.interrupt(123)) + * + * exitCodes // => [0, 1, 130] + * ``` + * + * @see {@link errorExitCode} for customizing failure exit codes + * + * @category running + * @since 4.0.0 + */ +export const defaultTeardown: Teardown = ( + exit: Exit.Exit, + onExit: (code: number) => void +) => { + if (Exit.isSuccess(exit)) return onExit(0) + if (Cause.hasInterruptsOnly(exit.cause)) return onExit(130) + return onExit(getErrorExitCode(Cause.squash(exit.cause))) +} + +/** + * Creates a platform-specific main program runner that handles Effect execution lifecycle. + * + * **When to use** + * + * Use when building a runtime adapter for a host platform. + * + * **Details** + * + * The runner executes Effect programs as main entry points. The provided + * function receives a forked fiber and a teardown callback so it can install + * platform-specific signal handling, fiber observers, and final exit behavior. + * + * Most applications should use a platform-provided runner, such as + * `NodeRuntime.runMain`, rather than constructing one directly. + * + * `disableErrorReporting` disables the automatic log emitted for unreported + * non-interruption failures. It does not change exit-code calculation or the + * custom teardown callback. + * + * **Gotchas** + * + * The setup function is responsible for observing the fiber and eventually + * invoking teardown. `makeRunMain` also tries to keep the host process alive + * with a long interval while the main fiber is running; if the host blocks + * timers, the runner still starts but cannot use that keep-alive fallback. + * + * **Example** (Creating platform runners) + * + * ```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 }) => { + * // Handle fiber completion + * fiber.addObserver((exit) => { + * teardown(exit, (code) => resolve([exit, code])) + * }) + * }) + * + * // Use the runner + * const program = Effect.sync(() => { + * events.push("Starting program", "Program completed") + * return "success" + * }) + * + * 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 + * @since 4.0.0 + */ +export const makeRunMain = ( + f: ( + options: { + readonly fiber: Fiber.Fiber + readonly teardown: Teardown + } + ) => void +): { + ( + options?: { + readonly disableErrorReporting?: boolean | undefined + readonly teardown?: Teardown | undefined + } + ): (effect: Effect.Effect) => void + ( + effect: Effect.Effect, + options?: { + readonly disableErrorReporting?: boolean | undefined + readonly teardown?: Teardown | undefined + } + ): void +} => + dual((args) => Effect.isEffect(args[0]), (effect: Effect.Effect, options?: { + readonly disableErrorReporting?: boolean | undefined + readonly teardown?: Teardown | undefined + }) => { + const fiber = options?.disableErrorReporting === true + ? Effect.runFork(effect) + : Effect.runFork( + Effect.tapCause(effect, (cause) => { + if (Cause.hasInterruptsOnly(cause)) return Effect.void + const isReported = getErrorReported(Cause.squash(cause)) + return isReported ? Effect.logError(cause) : Effect.void + }) + ) + try { + const keepAlive = globalThis.setInterval(constVoid, 2_147_483_647) + fiber.addObserver(() => { + clearInterval(keepAlive) + }) + } catch {} + const teardown = options?.teardown ?? defaultTeardown + return f({ fiber, teardown }) + }) + +declare global { + interface Error { + readonly [errorExitCode]?: number + readonly [errorReported]?: boolean + } +} + +/** + * Type-level key for the `Runtime.errorExitCode` marker. + * + * **When to use** + * + * Use to type properties keyed by `Runtime.errorExitCode` on custom error + * values. + * + * @category symbols + * @since 4.0.0 + */ +export type errorExitCode = "~effect/Runtime/errorExitCode" + +/** + * Allows associating an exit code with an error for determining the process + * exit code on failure. + * + * **When to use** + * + * Use when error classes should map failures to a specific process exit code + * when handled by {@link defaultTeardown}. + * + * **Details** + * + * Attach this marker as a readonly property on an error object. When the main + * program fails, {@link defaultTeardown} squashes the Cause and reads the marker + * from the resulting error value. + * + * **Gotchas** + * + * The marker is read from the squashed failure value. If a Cause contains + * multiple failures, the selected squashed error determines the exit code. + * + * **Example** (Setting a process exit code) + * + * ```ts import.meta.vitest + * import { Data, Runtime } from "effect" + * + * class MyError extends Data.TaggedError("MyError") { + * readonly [Runtime.errorExitCode] = 42 + * } + * + * Runtime.getErrorExitCode(new MyError()) // => 42 + * ``` + * + * @see {@link errorReported} for controlling automatic error logging + * @see {@link defaultTeardown} for the default failure exit-code rules that read this marker + * @see {@link getErrorExitCode} for reading the marker from unknown error values + * + * @category symbols + * @since 4.0.0 + */ +export const errorExitCode: errorExitCode = "~effect/Runtime/errorExitCode" + +/** + * Reads the runtime exit-code marker from an unknown error value. + * + * **When to use** + * + * Use to read a custom failure exit code from an unknown error value, falling + * back to the default failure code. + * + * **Details** + * + * Returns the numeric `[Runtime.errorExitCode]` property when it is present on + * an object. Otherwise returns `1`, the default failure exit code used by + * `defaultTeardown`. + * + * **Gotchas** + * + * Non-object values, missing markers, and non-number marker values all return + * `1`. + * + * @see {@link errorExitCode} for the marker read by this function + * + * @category getters + * @since 4.0.0 + */ +export const getErrorExitCode = (u: unknown): number => { + if (typeof u === "object" && u !== null && errorExitCode in u) { + const code = u[errorExitCode] + if (typeof code === "number") { + return code + } + } + return 1 +} + +/** + * Type-level key for the `Runtime.errorReported` marker. + * + * **When to use** + * + * Use to type properties keyed by `Runtime.errorReported` on custom error + * values. + * + * @category symbols + * @since 4.0.0 + */ +export type errorReported = "~effect/Runtime/errorReported" + +/** + * Defines the runtime marker that controls default `runMain` error logging for an error. + * + * **When to use** + * + * Use when you need error classes reported by application code to avoid being + * logged again by the default main runner. + * + * **Details** + * + * Set `[Runtime.errorReported]` to `false` on an error object to suppress the + * runtime log because the error has already been reported. Omitted or + * non-boolean values are treated as `true`, so failures are logged by default. + * + * **Gotchas** + * + * This marker controls only automatic error logging. It does not change the + * failure Cause or the process exit code. + * `makeRunMain` reads the marker from `Cause.squash(cause)`, so for causes + * with multiple failures, the squashed error determines whether default logging + * is suppressed. + * + * **Example** (Suppressing error reporting) + * + * ```ts import.meta.vitest + * import { Data, Runtime } from "effect" + * + * class MyError extends Data.TaggedError("MyError") { + * readonly [Runtime.errorReported] = false + * } + * + * Runtime.getErrorReported(new MyError()) // => false + * ``` + * + * @see {@link errorExitCode} for controlling failure exit codes + * @see {@link getErrorReported} for reading the marker from unknown error values + * + * @category symbols + * @since 4.0.0 + */ +export const errorReported: errorReported = "~effect/Runtime/errorReported" + +/** + * Reads the runtime error-reporting marker from an unknown error value. + * + * **When to use** + * + * Use to read whether an unknown error value should be treated as already + * reported by the default main runner. + * + * **Details** + * + * Returns a boolean `[Runtime.errorReported]` property when it is present on an + * object. Otherwise returns `true`, so failures are logged by default. + * + * **Gotchas** + * + * Non-object values, missing markers, and non-boolean marker values all return + * `true`. + * + * @see {@link errorReported} for the marker read by this function + * + * @category getters + * @since 4.0.0 + */ +export const getErrorReported = (u: unknown): boolean => { + if (typeof u === "object" && u !== null && errorReported in u) { + const isReported = u[errorReported] + if (typeof isReported === "boolean") { + return isReported + } + } + return true +} diff --git a/.repos/effect/packages/effect/src/Schedule.ts b/.repos/effect/packages/effect/src/Schedule.ts new file mode 100644 index 000000000..a9d4816da --- /dev/null +++ b/.repos/effect/packages/effect/src/Schedule.ts @@ -0,0 +1,1517 @@ +/** + * Describes policies for retrying, repeating, and pacing Effect programs. + * + * A `Schedule` is stepped with an input value. Each + * step either stops or produces an output together with the delay before the + * next step. Schedules are used by retry, repeat, stream, and channel APIs to + * decide when work should continue, how long to wait, and when to stop. + * + * @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 { constant, dual, identity } from "./Function.ts" +import { isEffect } from "./internal/core.ts" +import * as effect from "./internal/effect.ts" +import * as random from "./internal/random.ts" +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, UnionToIntersection } from "./Types.ts" + +const TypeId = "~effect/Schedule" + +const randomNext: Effect = random.Random.useSync((random) => random.nextDoubleUnsafe()) + +/** + * A Schedule defines a strategy for repeating or retrying effects based on some policy. + * + * **Example** (Defining retry and repeat schedules) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" + * + * const executions: Array = [] + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(Schedule.recurs(2)), + * Effect.as(executions) + * ) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Schedule + extends Schedule.Variance, Pipeable +{} + +/** + * Metadata provided to schedule functions containing timing and input information. + * + * @category metadata + * @since 4.0.0 + */ +export interface InputMetadata { + readonly input: Input + readonly attempt: number + readonly start: number + readonly now: number + readonly elapsed: number + readonly elapsedSincePrevious: number +} + +/** + * Extended metadata that includes both input metadata and the output value from the schedule. + * + * @category metadata + * @since 4.0.0 + */ +export interface Metadata extends InputMetadata { + readonly output: Output + readonly duration: Duration.Duration +} + +/** + * Context reference containing metadata for the currently running schedule step. + * + * **Details** + * + * Repeat, retry, stream, and channel scheduling operations provide this service + * to effects run between schedule steps. The default value contains undefined + * input and output values, zero duration, and zeroed timing fields before any + * schedule step has produced metadata. + * + * @category services + * @since 4.0.0 + */ +export const CurrentMetadata = Context.Reference("effect/Schedule/CurrentMetadata", { + defaultValue: constant({ + input: undefined, + output: undefined, + duration: Duration.zero, + attempt: 0, + start: 0, + now: 0, + elapsed: 0, + elapsedSincePrevious: 0 + }) +}) + +/** + * The Schedule namespace contains types and utilities for working with schedules. + * + * @since 2.0.0 + */ +export declare namespace Schedule { + /** + * Variance interface that defines the type parameter relationships for Schedule. + * + * **Example** (Understanding schedule variance) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const schedule: Schedule.Schedule = Schedule.recurs(2) + * Schedule.isSchedule(schedule) // => true + * ``` + * + * @category models + * @since 2.0.0 + */ + export interface Variance { + readonly [TypeId]: VarianceStruct + } + + /** + * Type-level marker used by `Schedule.Variance` to record the variance of + * `Schedule` type parameters. + * + * **Details** + * + * This interface exists for TypeScript inference and assignability. Users + * normally do not construct or inspect it directly. + * + * @category models + * @since 4.0.0 + */ + export interface VarianceStruct { + readonly _Out: Covariant + readonly _In: Contravariant + readonly _Error: Covariant + readonly _Env: Covariant + } +} + +/** + * 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, + _In: identity, + _Env: identity + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +/** + * Type guard that checks if a value is a Schedule. + * + * **Example** (Checking for schedules) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const schedule = Schedule.exponential("100 millis") + * const notSchedule = { foo: "bar" } + * + * Schedule.isSchedule(schedule) // => true + * Schedule.isSchedule(notSchedule) // => false + * Schedule.isSchedule(null) // => false + * Schedule.isSchedule(undefined) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isSchedule = (u: unknown): u is Schedule => hasProperty(u, TypeId) + +/** + * Creates a Schedule from a step function that returns a Pull. + * + * **Example** (Creating a custom schedule from a step function) + * + * ```ts import.meta.vitest + * import { Cause, Duration, Effect, Schedule } from "effect" + * + * const schedule = Schedule.fromStep(Effect.sync(() => { + * let count = 0 + * + * return (_now: number, _input: string) => { + * if (count >= 3) { + * return Cause.done(count) + * } + * return Effect.succeed([count++, Duration.millis(100)] as [number, Duration.Duration]) + * } + * })) + * + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(schedule) + * const [output] = yield* step(0, "input") + * return output + * }) + * + * await Effect.runPromise(program) // => 0 + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromStep = ( + step: Effect< + (now: number, input: Input) => Pull.Pull<[Output, Duration.Duration], ErrorX, Output, EnvX>, + Error, + Env + > +): Schedule, Env | EnvX> => { + const self = Object.create(ScheduleProto) + self.step = step + return self +} + +const metadataFn = () => { + let n = 0 + let previous: number | undefined + let start: number | undefined + return (now: number, input: In): InputMetadata => { + if (start === undefined) start = now + const elapsed = now - start + const elapsedSincePrevious = previous === undefined ? 0 : now - previous + previous = now + return { input, attempt: ++n, start, now, elapsed, elapsedSincePrevious } + } +} + +/** + * Creates a Schedule from a step function that receives metadata about the schedule's execution. + * + * **Example** (Creating a metadata-aware schedule) + * + * ```ts import.meta.vitest + * import { Cause, Duration, Effect, Schedule } from "effect" + * + * const firstThreeInputs = Schedule.fromStepWithMetadata(Effect.succeed((metadata: Schedule.InputMetadata) => { + * if (metadata.attempt > 3) { + * return Cause.done("finished") + * } + * + * return Effect.succeed([ + * `attempt ${metadata.attempt}: ${metadata.input}`, + * 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 + * @since 4.0.0 + */ +export const fromStepWithMetadata = ( + step: Effect< + (options: InputMetadata) => Pull.Pull<[Output, Duration.Duration], ErrorX, Output, EnvX>, + Error, + Env + > +): Schedule, Env | EnvX> => + fromStep(effect.map(step, (f) => { + const meta = metadataFn() + return (now, input) => f(meta(now, input)) + })) + +/** + * Extracts the step function from a Schedule. + * + * **Example** (Extracting a schedule step function) + * + * ```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.upTo({ times: 3 })) + * + * const program = Effect.gen(function*() { + * const stepFn = yield* Schedule.toStep(schedule) + * + * // 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 + * return yield* stepFn(now, "input") + * }) + * + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const toStep = ( + schedule: Schedule +): Effect< + (now: number, input: Input) => Pull.Pull<[Output, Duration.Duration], Error, Output, Env>, + never, + Env +> => + effect.catchCause( + (schedule as any).step, + (cause) => effect.succeed(() => effect.failCause(cause) as any) + ) + +/** + * Extracts a step function from a `Schedule` that sleeps for each computed + * delay and returns metadata for the completed step. + * + * **When to use** + * + * Use to drive a schedule manually while preserving the computed output, + * delay, input, attempt, and elapsed timing metadata for each step. + * + * **Details** + * + * The returned step reads the current time from `Clock` when invoked, calls the + * schedule step with that timestamp and input, sleeps for the returned + * duration, and then yields `Metadata`. + * + * @see {@link toStep} for manually supplying the timestamp and handling the returned delay yourself + * @see {@link toStepWithSleep} for the same automatic sleeping behavior when only the schedule output is needed + * + * @category destructors + * @since 4.0.0 + */ +export const toStepWithMetadata = ( + schedule: Schedule +): Effect< + (input: Input) => Pull.Pull, Error, Output, Env>, + never, + Env +> => + effect.clockWith((clock) => + effect.map( + toStep(schedule), + (step) => { + const metaFn = metadataFn() + return (input) => + effect.suspend(() => { + const now = clock.currentTimeMillisUnsafe() + return effect.flatMap( + step(now, input), + ([output, duration]) => { + const meta = metaFn(now, input) as Mutable> + meta.output = output + meta.duration = duration + return effect.as(effect.sleep(duration), meta) + } + ) + }) + } + ) + ) + +/** + * Extracts a step function from a Schedule that automatically handles sleep delays. + * + * **Example** (Extracting a sleeping step function) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" + * + * const schedule = Schedule.recurs(3) + * + * const program = Effect.gen(function*() { + * const stepWithSleep = yield* Schedule.toStepWithSleep(schedule) + * + * return [yield* stepWithSleep("first"), yield* stepWithSleep("second")] + * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [0, 1] + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const toStepWithSleep = ( + schedule: Schedule +): Effect< + (input: Input) => Pull.Pull, + never, + Env +> => + effect.map( + toStepWithMetadata(schedule), + (step) => (input) => effect.map(step(input), (meta) => meta.output) + ) + +/** + * Returns a new `Schedule` that adds the delay computed by the specified + * effectful function to the next recurrence of the schedule. + * + * **Example** (Adding extra delay to a schedule) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" + * + * const schedule = Schedule.recurs(1).pipe( + * Schedule.addDelay(() => Effect.succeed("25 millis")) + * ) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(schedule) + * const [, delay] = yield* step(0, undefined) + * return delay + * }) + * + * await Effect.runPromise(program) // => Duration.millis(25) + * ``` + * + * @category delays & timeouts + * @since 2.0.0 + */ +export const addDelay: { + ( + f: (metadata: Metadata) => Effect + ): ( + self: Schedule + ) => Schedule + ( + self: Schedule, + f: (metadata: Metadata) => Effect + ): Schedule +} = dual(2, ( + self: Schedule, + f: (metadata: Metadata) => Effect +): Schedule => + modifyDelay( + self, + (metadata) => effect.map(f(metadata), (d) => Duration.sum(Duration.fromInputUnsafe(d), metadata.duration)) + )) + +/** + * Returns a schedule that runs `self` to completion, then runs `other`, and + * merges their outputs. + * + * **Example** (Sequencing quick and slow retries) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const schedule = Schedule.concat(Schedule.recurs(1), Schedule.recurs(2)) + * Schedule.isSchedule(schedule) // => true + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const concat: { + ( + other: Schedule + ): ( + self: Schedule + ) => Schedule + ( + self: Schedule, + other: Schedule + ): Schedule +} = dual(2, ( + self: Schedule, + other: Schedule +): Schedule => + map(concatResult(self, other), ({ output }) => effect.succeed(Result.merge(output)))) + +/** + * Returns a schedule that runs `self` to completion, then runs `other`, and + * preserves which schedule produced each output. + * + * **Details** + * + * The resulting schedule emits a `Result` to indicate which phase produced + * each output: outputs from `self` are emitted as `Failure`, and outputs from + * `other` are emitted as `Success`. + * + * **Example** (Tracking sequential schedule phases) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const schedule = Schedule.concatResult(Schedule.recurs(1), Schedule.recurs(2)) + * Schedule.isSchedule(schedule) // => true + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const concatResult: { + ( + other: Schedule + ): ( + self: Schedule + ) => Schedule, Input & Input2, Error | Error2, Env | Env2> + ( + self: Schedule, + other: Schedule + ): Schedule, Input & Input2, Error | Error2, Env | Env2> +} = dual(2, ( + self: Schedule, + other: Schedule +): 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], + Error | Error2, + Result.Result, + Env | Env2 + >) + 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], + Error | Error2, + Result.Result, + Env | Env2 + > { + if (currentStep) return currentStep(now, input) + return toStep< + Result.Result, + Input & Input2, + Error | Error2, + Env | Env2 + >(currentSide === 0 ? left : right).pipe( + effect.flatMap((step) => { + currentSide++ + if (currentSide === 1) { + currentStep = (now, input) => + Pull.catchDone(step(now, input), (_) => { + currentStep = undefined + return recur(now, input) + }) + return currentStep(now, input) + } + currentStep = step + return currentStep(now, input) + }) + ) + } + }))) + +/** + * 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 a combined policy should continue only while every schedule still + * recurs, and should wait for the slowest schedule between recurrences. + * + * **Example** (Combining retry schedules by their maximum delay) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const schedule = Schedule.max([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")]) + * Schedule.isSchedule(schedule) // => true + * ``` + * + * @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. + * + * **Example** (Scheduling work with cron expressions) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const everyMinute = Schedule.cron("* * * * *") + * Schedule.isSchedule(everyMinute) // => true + * ``` + * + * @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, _) => { + 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] + }) + })) +} + +/** + * 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.meta.vitest + * import { Schedule } from "effect" + * + * Schedule.isSchedule(Schedule.duration("1 second")) // => true + * ``` + * + * @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.meta.vitest + * import { Schedule } from "effect" + * + * Schedule.isSchedule(Schedule.during("5 seconds")) // => true + * ``` + * + * @see {@link duration} for one delayed recurrence + * + * @category constructors + * @since 4.0.0 + */ +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 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 a combined policy should continue while any schedule still recurs, + * and should wait for the fastest schedule between recurrences. + * + * **Example** (Combining retry schedules by their minimum delay) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const schedule = Schedule.min([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")]) + * Schedule.isSchedule(schedule) // => true + * ``` + * + * @category combining + * @since 4.0.0 + */ +export const min = < + 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 = 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 +} + +/** + * 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.meta.vitest + * import { Duration, Effect, Schedule } from "effect" + * + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(Schedule.exponential("100 millis")) + * return yield* step(0, undefined) + * }) + * + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const exponential = ( + base: Duration.Input, + factor: number = 2 +): Schedule => { + const baseMillis = Duration.toMillis(Duration.fromInputUnsafe(base)) + return fromStepWithMetadata(effect.succeed((meta) => { + const duration = Duration.millis(baseMillis * Math.pow(factor, meta.attempt - 1)) + return effect.succeed([duration, duration]) + })) +} + +/** + * Schedule that always recurs, increasing delays by summing the preceding + * two delays (similar to the Fibonacci sequence). Returns the current + * duration between recurrences. + * + * **Example** (Retrying with Fibonacci backoff) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" + * + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(Schedule.fibonacci("100 millis")) + * return yield* step(0, undefined) + * }) + * + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fibonacci = (one: Duration.Input): Schedule => { + const oneMillis = Duration.toMillis(Duration.fromInputUnsafe(one)) + return fromStep(effect.sync(() => { + let a = 0 + let b = oneMillis + return constant(effect.sync(() => { + const next = a + b + a = b + b = next + const duration = Duration.millis(next) + return [duration, duration] + })) + })) +} + +/** + * Returns a `Schedule` that recurs on the specified fixed `interval` and + * outputs the number of repetitions of the schedule so far. + * + * **When to use** + * + * Use when recurrences should stay aligned to a regular cadence. + * + * **Gotchas** + * + * If the action run between recurrences takes longer than the interval, the + * next recurrence happens immediately, but missed intervals are not replayed. + * + * ```text + * |-----interval-----|-----interval-----|-----interval-----| + * |---------action--------||action|-----|action|-----------| + * ``` + * + * **Example** (Repeating on fixed intervals) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" + * + * const program = Effect.gen(function*() { + * 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 + * + * @category constructors + * @since 2.0.0 + */ +export const fixed = (interval: Duration.Input): Schedule => { + const window = Duration.toMillis(Duration.fromInputUnsafe(interval)) + return fromStepWithMetadata(effect.sync(() => { + let start = 0 + let lastRun = 0 + return (meta) => + effect.sync(() => { + if (window === 0) { + return [meta.attempt - 1, Duration.zero] as const + } + if (meta.attempt === 1) { + start = meta.now + lastRun = meta.now + window + return [0, Duration.millis(window)] as const + } + const runningBehind = meta.now > (lastRun + window) + const boundary = window - ((meta.now - start) % window) + const delay = runningBehind ? 0 : boundary === 0 ? window : boundary + lastRun = runningBehind ? meta.now : meta.now + delay + return [meta.attempt - 1, Duration.millis(delay)] as const + }) + })) +} + +/** + * Returns a new `Schedule` that maps each schedule decision to a new output + * using the full schedule metadata. + * + * **Details** + * + * 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. + * + * **Example** (Mapping schedule outputs) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * + * const countSchedule = Schedule.recurs(5).pipe( + * Schedule.map(({ output: count }) => Effect.succeed(`Execution #${count + 1}`)) + * ) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(countSchedule) + * const [output] = yield* step(0, undefined) + * return output + * }) + * + * await Effect.runPromise(program) // => "Execution #1" + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + ( + f: (metadata: Metadata) => Output2 | Effect + ): ( + self: Schedule + ) => Schedule + ( + self: Schedule, + f: (metadata: Metadata) => Output2 | Effect + ): Schedule +} = dual(2, ( + self: Schedule, + 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 metadata) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" + * + * const schedule = Schedule.spaced("10 millis").pipe( + * Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.times(duration, 2))) + * ) + * const program = Effect.gen(function*() { + * 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: ( + metadata: Metadata + ) => Effect + ): ( + self: Schedule + ) => Schedule + ( + self: Schedule, + f: ( + metadata: Metadata + ) => Effect + ): Schedule +} = dual(2, ( + self: Schedule, + f: ( + metadata: Metadata + ) => Effect +): Schedule => + 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. + * + * **When to use** + * + * Use to add random variation to an existing schedule's recurrence delays while + * preserving its output and completion behavior. + * + * **Details** + * + * Each recurrence delay is scaled by a random factor between `0.8` and `1.2`. + * + * @see {@link modifyDelay} for replacing recurrence delays with a custom effectful transformation + * + * @category delays & timeouts + * @since 2.0.0 + */ +export const jittered = ( + self: Schedule +): Schedule => + modifyDelay(self, ({ duration }) => + effect.map(randomNext, (random) => { + const millis = Duration.toMillis(duration) + return Duration.millis(millis * 0.8 * (1 - random) + millis * 1.2 * random) + })) + +/** + * Returns a new `Schedule` that outputs the inputs of the specified schedule. + * + * **Example** (Passing inputs through as outputs) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * + * const inputSchedule = Schedule.passthrough( + * Schedule.exponential("100 millis").pipe(Schedule.upTo({ times: 3 })) + * ) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(inputSchedule) + * const [output] = yield* step(0, "input") + * return output + * }) + * + * await Effect.runPromise(program) // => "input" + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const passthrough = ( + self: Schedule +): Schedule => + fromStep(effect.map(toStep(self), (step) => (now, input) => + Pull.matchEffect(step(now, input), { + onSuccess: (result) => effect.succeed([input, result[1]]), + onFailure: effect.failCause, + onDone: () => Cause.done(input) + }))) + +/** + * 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.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" + * + * const executions: Array = [] + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(Schedule.recurs(3)), + * Effect.as(executions) + * ) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3, 4] + * ``` + * + * @see {@link upTo} 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 schedule that recurs continuously, each repetition spaced the + * specified duration from the last run. + * + * **When to use** + * + * Use when each delay should start after the previous action completes. + * + * **Example** (Repeating with fixed spacing) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" + * + * const program = Effect.gen(function*() { + * 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 + * + * @category constructors + * @since 2.0.0 + */ +export const spaced = (duration: Duration.Input): Schedule => { + const decoded = Duration.fromInputUnsafe(duration) + return fromStepWithMetadata(effect.succeed((meta) => effect.succeed([meta.attempt - 1, decoded]))) +} + +/** + * Returns a new `Schedule` that allows execution of an effectful function for + * every decision of the schedule, but does not alter the inputs and outputs of + * the schedule. + * + * **Details** + * + * The callback receives the full schedule metadata, including the input, output, + * computed delay duration, current attempt, and elapsed timing information. + * + * **Example** (Tapping schedule metadata) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * + * 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 + * @since 4.0.0 + */ +export const tap: { + ( + f: (metadata: Metadata) => Effect + ): ( + self: Schedule + ) => Schedule + ( + self: Schedule, + f: (metadata: Metadata) => Effect + ): Schedule +} = dual(2, ( + self: Schedule, + f: (metadata: Metadata) => Effect +): Schedule => + fromStep(effect.map(toStep(self), (step) => { + const meta = metadataFn() + return (now, input) => + effect.tap(step(now, input), ([output, duration]) => f({ ...meta(now, input), output, duration })) + }))) + +/** + * Returns a new `Schedule` that limits an existing schedule by elapsed + * duration, number of outputs, or both. + * + * **When to use** + * + * 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** + * + * 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. + * + * 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. + * + * **Example** (Limiting by duration and recurrence count) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" + * + * 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) + * ) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const upTo: { + (options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + }): ( + self: Schedule + ) => Schedule + ( + self: Schedule, + options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + } + ): Schedule +} = dual(2, ( + self: Schedule, + 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 + ) => boolean | Effect + ): ( + self: Schedule + ) => Schedule + >( + self: Schedule, + predicate: ( + metadata: Metadata + ) => metadata is Meta + ): Schedule + ( + self: Schedule, + predicate: ( + metadata: Metadata + ) => boolean | Effect + ): Schedule +} = dual(2, ( + self: Schedule, + predicate: ( + metadata: Metadata + ) => boolean | Effect +): Schedule => + fromStep(effect.map(toStep(self), (step) => { + const meta = metadataFn() + return (now, input) => + effect.flatMap(step(now, input), (result) => { + const [output, duration] = result + const eff = predicate({ ...meta(now, input), output, duration }) + return effect.flatMap( + isEffect(eff) ? eff : effect.succeed(eff), + (check) => (check ? effect.succeed(result) : Cause.done(output)) + ) + }) + }))) + +export { + /** + * Returns a new schedule that continues while the predicate returns `true`. + * + * **When to use** + * + * Use to stop an existing schedule based on its full metadata, such as the + * current input, output, attempt, delay, or elapsed time. + * + * **Details** + * + * The predicate receives `Metadata`, may return `boolean` or an + * `Effect`, preserves the output and delay when it returns + * `true`, and stops the schedule when it returns `false`. + * + * @see {@link upTo} for stopping after a fixed number of schedule outputs + * + * @category filtering + * @since 4.0.0 + */ + while_ as while +} + +/** + * Schedule that divides the timeline to `interval`-long windows, and sleeps + * until the nearest window boundary every time it recurs. + * + * **Details** + * + * For example, `Schedule.windowed("10 seconds")` would produce a schedule as + * follows: + * + * ```text + * 10s 10s 10s 10s + * |----------|----------|----------|----------| + * |action------|sleep---|act|-sleep|action----| + * ``` + * + * **Example** (Repeating on aligned windows) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" + * + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(Schedule.windowed("5 seconds")) + * return yield* step(0, undefined) + * }) + * + * await Effect.runPromise(program) // => [0, Duration.seconds(5)] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const windowed = (interval: Duration.Input): Schedule => { + const window = Duration.toMillis(Duration.fromInputUnsafe(interval)) + return fromStepWithMetadata(effect.succeed((meta) => + effect.sync(() => [ + meta.attempt - 1, + window === 0 ? Duration.zero : Duration.millis(window - (meta.elapsed % window)) + ]) + )) +} + +/** + * Returns a new `Schedule` that will recur forever. + * + * **Details** + * + * The output of the schedule is the current count of its repetitions thus far + * (i.e. `0, 1, 2, ...`). + * + * **Example** (Repeating forever) + * + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" + * + * 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) + * ) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const forever: Schedule = spaced(Duration.zero) + +const constIdentity = fromStep( + effect.succeed((_now, input: unknown) => effect.succeed([input, Duration.zero] as [unknown, Duration.Duration])) +) + +const identity_ = (): Schedule => constIdentity as Schedule + +export { + /** + * Creates a schedule that always recurs, passing inputs directly as outputs. + * + * **When to use** + * + * Use when you need an infinite schedule that preserves input values as + * outputs. + * + * **Details** + * + * This schedule runs indefinitely, returning each input value as its output + * without modification. It effectively acts as a pass-through that simply + * echoes its input values at each step. + * + * @see {@link forever} for an infinite schedule that returns incrementing step counts + * @category constructors + * @since 2.0.0 + */ + identity_ as identity +} + +/** + * Sets the input type of the provided schedule without altering its behavior. + * + * **When to use** + * + * Use to adapt a schedule that does not depend on its input values. + * + * **Details** + * + * This helper is checked at compile time and does not change the schedule's + * runtime behavior. + * + * **Example** (Setting a schedule input type) + * + * ```ts import.meta.vitest + * import { Schedule } from "effect" + * + * const schedule = Schedule.recurs(3).pipe( + * Schedule.setInputType() + * ) + * Schedule.isSchedule(schedule) // => true + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export const setInputType = + () => (self: Schedule): Schedule => self diff --git a/.repos/effect/packages/effect/src/Scheduler.ts b/.repos/effect/packages/effect/src/Scheduler.ts new file mode 100644 index 000000000..1a6b7f87d --- /dev/null +++ b/.repos/effect/packages/effect/src/Scheduler.ts @@ -0,0 +1,298 @@ +/** + * Controls how runnable Effect fiber tasks are dispatched. + * + * A scheduler decides how tasks are queued, when queued tasks run, and when a + * fiber should pause so other work can continue. This module includes the + * scheduler service reference, the default `MixedScheduler`, dispatcher types + * for queued tasks, and references for tuning or disabling automatic scheduler + * yields. + * + * @since 2.0.0 + */ +import * as Context from "./Context.ts" +import type * as Fiber from "./Fiber.ts" + +/** + * A scheduler manages the execution of Effect fibers by controlling when queued + * tasks run. + * + * **When to use** + * + * Use to define or provide custom runtime scheduling behavior for Effect fibers. + * + * **Details** + * + * A scheduler determines the execution mode, schedules tasks with different + * priorities, and decides when fibers should yield control after consuming + * their operation budget. + * + * @category services + * @since 2.0.0 + */ +export interface Scheduler { + readonly executionMode: "sync" | "async" + shouldYield(fiber: Fiber.Fiber): boolean + makeDispatcher(): SchedulerDispatcher +} + +/** + * A dispatcher created by a `Scheduler` for enqueuing tasks and forcing queued + * tasks to run. + * + * **When to use** + * + * Use when implementing or testing scheduler-created dispatchers that enqueue + * prioritized runtime tasks and flush queued work deterministically. + * + * **Details** + * + * `scheduleTask` queues a task with a priority. `flush` drains pending work + * synchronously, which is useful when callers need deterministic completion of + * already scheduled tasks. Lower priority numbers run first, and equal + * priorities run in FIFO order. + * + * @category models + * @since 4.0.0 + */ +export interface SchedulerDispatcher { + scheduleTask(task: () => void, priority: number): void + flush(): void +} + +/** + * Context reference for the scheduler used by the Effect runtime. + * + * **When to use** + * + * Use when you need to replace scheduling behavior globally in tests or runtime + * setup, such as forcing deterministic task dispatch. + * + * **Details** + * + * The default value creates a `MixedScheduler`. Provide this service to + * customize execution mode, task dispatching, or yield behavior. + * + * @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) => { + // @ts-ignore + const timer = globalThis.setImmediate(f) + // @ts-ignore + return (): void => globalThis.clearImmediate(timer) + } + : (f: () => void) => { + const timer = setTimeout(f, 0) + return (): void => clearTimeout(timer) + } + +const setMicrotask = (f: () => void) => { + let cancelled = false + Promise.resolve().then(() => { + if (!cancelled) f() + }) + return (): void => { + cancelled = true + } +} + +class PriorityBuckets { + buckets: Array<[priority: number, tasks: Array<() => void>]> = [] + + scheduleTask(task: () => void, priority: number): void { + const buckets = this.buckets + const len = buckets.length + let bucket: [number, Array<() => void>] | undefined + let index = 0 + for (; index < len; index++) { + if (buckets[index][0] > priority) break + bucket = buckets[index] + } + if (bucket && bucket[0] === priority) { + bucket[1].push(task) + } else if (index === len) { + buckets.push([priority, [task]]) + } else { + buckets.splice(index, 0, [priority, [task]]) + } + } + + drain() { + const buckets = this.buckets + this.buckets = [] + return buckets + } +} + +/** + * Provides a scheduler implementation that batches queued tasks and dispatches them by + * priority. + * + * **When to use** + * + * Use when you need the default runtime scheduler directly, including a + * scheduler that batches queued work by priority and preserves FIFO order within + * each priority. + * + * **Details** + * + * `MixedScheduler` supports synchronous and asynchronous execution modes, uses + * operation counts to decide when fibers should yield, and is the default + * scheduler implementation. + * + * @category models + * @since 2.0.0 + */ +export class MixedScheduler implements Scheduler { + readonly executionMode: "sync" | "async" + readonly setImmediate: (f: () => void) => () => void + + constructor( + executionMode: "sync" | "async" = "async", + setImmediateFn?: (f: () => void) => () => void + ) { + this.executionMode = executionMode + this.setImmediate = setImmediateFn ?? (executionMode === "sync" ? setMicrotask : setImmediate) + } + + /** + * Returns whether the fiber has reached its operation budget and should yield. + * + * **When to use** + * + * Use to decide whether a fiber should yield after consuming its current + * operation budget. + * + * @since 2.0.0 + */ + shouldYield(fiber: Fiber.Fiber) { + return fiber.currentOpCount >= fiber.maxOpsBeforeYield + } + + /** + * Creates a dispatcher that schedules work through this scheduler. + * + * **When to use** + * + * Use when you need a standalone dispatcher from a scheduler instance, for + * example in tests that enqueue tasks and then flush them deterministically. + * + * @since 4.0.0 + */ + makeDispatcher() { + return new MixedSchedulerDispatcher(this.setImmediate) + } +} + +class MixedSchedulerDispatcher implements SchedulerDispatcher { + private tasks = new PriorityBuckets() + private running: (() => void) | undefined = undefined + readonly setImmediate: (f: () => void) => () => void + + constructor( + setImmediateFn: (f: () => void) => () => void = setImmediate + ) { + this.setImmediate = setImmediateFn + } + + /** + * @since 2.0.0 + */ + scheduleTask(task: () => void, priority: number) { + this.tasks.scheduleTask(task, priority) + if (this.running === undefined) { + this.running = this.setImmediate(this.afterScheduled) + } + } + + /** + * @since 2.0.0 + */ + afterScheduled = () => { + this.running = undefined + this.runTasks() + } + + /** + * @since 2.0.0 + */ + runTasks() { + const buckets = this.tasks.drain() + for (let i = 0; i < buckets.length; i++) { + const toRun = buckets[i][1] + for (let j = 0; j < toRun.length; j++) { + toRun[j]() + } + } + } + + /** + * @since 2.0.0 + */ + flush() { + while (this.tasks.buckets.length > 0) { + if (this.running !== undefined) { + this.running() + this.running = undefined + } + this.runTasks() + } + } +} + +/** + * Context reference that controls the maximum number of operations a fiber + * can perform before yielding control back to the scheduler. + * + * **When to use** + * + * Use to tune scheduler fairness for CPU-bound fibers by changing the scheduler + * operation budget that triggers a yield. + * + * **Details** + * + * The default value is `2048` operations, which balances performance and + * fairness by helping prevent long-running fibers from monopolizing the + * execution thread. + * + * @see {@link PreventSchedulerYield} for bypassing scheduler yield checks entirely rather than tuning the operation budget + * + * @category services + * @since 4.0.0 + */ +export const MaxOpsBeforeYield = Context.Reference("effect/Scheduler/MaxOpsBeforeYield", { + fiberCached: true, + defaultValue: () => 2048 +}) + +/** + * Context reference that controls whether the runtime should bypass scheduler + * yield checks. When set to `true`, the fiber run loop won't call + * `Scheduler.shouldYield`. + * + * **When to use** + * + * Use to bypass scheduler yield checks for controlled runtime workloads where + * cooperative yielding should be disabled. + * + * **Gotchas** + * + * Setting this reference to `true` can let long-running fibers monopolize the + * JavaScript thread. + * + * @see {@link MaxOpsBeforeYield} for tuning yield frequency without disabling yield checks + * @see {@link Scheduler} for providing custom scheduler yield behavior + * + * @category services + * @since 4.0.0 + */ +export const PreventSchedulerYield = Context.Reference("effect/Scheduler/PreventSchedulerYield", { + fiberCached: true, + defaultValue: () => false +}) diff --git a/.repos/effect/packages/effect/src/Schema.ts b/.repos/effect/packages/effect/src/Schema.ts new file mode 100644 index 000000000..2924d92fc --- /dev/null +++ b/.repos/effect/packages/effect/src/Schema.ts @@ -0,0 +1,17104 @@ +/** + * Describes data shapes and how unknown input becomes trusted values. + * + * A schema can validate input, decode it into an application type, and encode + * that value back to another representation. This module contains the main + * schema, codec, decoder, and encoder APIs, together with schemas for common + * JavaScript values and Effect data types. It also supports refinements, + * transformations, defaults, classes, JSON Schema generation, test data + * generation, formatting, equivalence, optics, and differs derived from schema + * definitions. + * + * @since 4.0.0 + */ + +/** @effect-diagnostics schemaStructWithTag:skip-file */ +import * as Arr from "./Array.ts" +import * as BigDecimal_ from "./BigDecimal.ts" +import type * as Brand from "./Brand.ts" +import * as Cause_ from "./Cause.ts" +import * as Chunk_ from "./Chunk.ts" +import * as Data from "./Data.ts" +import * as DateTime from "./DateTime.ts" +import type { Differ } from "./Differ.ts" +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 * as Exit_ from "./Exit.ts" +import type { Formatter } from "./Formatter.ts" +import { format, formatPropertyKey } from "./Formatter.ts" +import { identity, memoize } from "./Function.ts" +import * 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 InternalSchema from "./internal/schema/schema.ts" +import * as InternalArbitrary from "./internal/schema/toArbitrary.ts" +import * as InternalEquivalence from "./internal/schema/toEquivalence.ts" +import * as InternalToJsonSchemaDocument from "./internal/schema/toJsonSchemaDocument.ts" +import * as InternalToRepresentation from "./internal/schema/toRepresentation.ts" +import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" +import * as JsonPatch from "./JsonPatch.ts" +import * as JsonSchema from "./JsonSchema.ts" +import { remainder } from "./Number.ts" +import * 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 type * as FastCheck from "./testing/FastCheck.ts" +import type { RequiredKeys, UnionToIntersection } from "./Types.ts" +import type { Unify } from "./Unify.ts" + +const TypeId = InternalSchema.TypeId + +/** + * Whether a schema field is required or optional within a struct. + * + * @see {@link optionalKey} — mark a struct field as optional + * @see {@link optional} — mark a struct field as optional with `| undefined` + * + * @category models + * @since 4.0.0 + */ +export type Optionality = "required" | "optional" + +/** + * Whether a schema field is readonly or mutable within a struct. + * + * @see {@link mutableKey} — mark a struct field as mutable + * + * @category models + * @since 4.0.0 + */ +export type Mutability = "readonly" | "mutable" + +/** + * Whether a schema field has a constructor default value. + * + * @see {@link withConstructorDefault} — add a default to a schema field + * @see {@link tag} — creates a literal field with a constructor default + * + * @category models + * @since 4.0.0 + */ +export type ConstructorDefault = "no-default" | "with-default" + +/** + * Options for `makeEffect`, `make`, and Class constructors. + * + * **When to use** + * + * Use when passing `disableChecks: true` to skip validation when you trust the data. + * - Pass `parseOptions` to control error reporting behavior. + * + * @see {@link BottomWithoutNew.makeEffect} + * @see {@link BottomWithoutNew.make} + * + * @category options + * @since 3.13.4 + */ +export interface MakeOptions { + /** + * The parse options to use for the schema. + */ + readonly parseOptions?: SchemaAST.ParseOptions | undefined + /** + * Whether to disable validation for the schema. + */ + readonly disableChecks?: boolean | undefined + + /** @internal */ + readonly "~payload"?: { + readonly token: unknown + readonly value: unknown + } +} + +/** + * 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 as the base for schema interfaces that provide a specialized construct + * signature. + * + * @category models + * @since 4.0.0 + */ +export interface BottomWithoutNew< + 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 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.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"] + rebuild(ast: this["ast"]): this["Rebuild"] + /** + * Constructs a value from the make input representation synchronously. + * + * **When to use** + * + * Use when constructor input is trusted or when validation failure + * should abort with a thrown `Error`. + * + * **Details** + * + * Applies constructor defaults and type-side validation according to + * `MakeOptions`. + * + * **Gotchas** + * + * Throws an `Error` with the schema issue in its `cause` when validation + * 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 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"] + /** + * Constructs a value from the make input representation, returning `Option.none` + * when validation fails. + * + * **When to use** + * + * Use when you only need to know whether construction succeeds + * and do not need validation details. + * + * **Details** + * + * Applies constructor defaults and type-side validation according to + * `MakeOptions`. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `None`. Causes + * that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @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 + /** + * Constructs a value from the make input representation, returning validation + * failures in the `Effect` error channel. + * + * **When to use** + * + * Use when constructor input may fail validation and you want to + * compose that failure with other `Effect` operations instead of throwing. + * + * **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 +} + +/** + * 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 `BottomWithoutNew` variant for schema implementations that + * compute their public views on demand. + * + * **When to use** + * + * 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. + * `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 BottomWithoutNew} for the fully parameterized schema interface when every + * view must be supplied directly. + * + * @category utility types + * @since 4.0.0 + */ +export interface BottomLazyWithoutNew< + 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 + BottomWithoutNew< + unknown, + unknown, + unknown, + unknown, + Ast, + Rebuild, + unknown, + unknown, + TypeParameters, + unknown, + TypeMutability, + TypeOptionality, + TypeConstructorDefault, + EncodedMutability, + 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}. + * + * @category constructors + * @since 4.0.0 + */ +export interface declareConstructor, Iso = T> extends + Bottom< + T, + E, + TypeParameters[number]["DecodingServices"], + TypeParameters[number]["EncodingServices"], + SchemaAST.Declaration, + declareConstructor, + T, + Iso, + TypeParameters + > +{} + +/** + * Creates a schema for a **parametric** type (a generic container such as + * `Array`, `Option`, etc.) by accepting a list of type-parameter schemas + * and a decoder factory. + * + * **When to use** + * + * Use when you are defining a schema for a generic container whose validation + * depends on one or more type-parameter schemas. + * + * **Details** + * + * The outer call `declareConstructor()` fixes the decoded type `T`, + * the encoded type `E`, and the optional iso type. The inner call receives: + * - `typeParameters` — the concrete schemas for each type variable + * - `run` — a factory that, given resolved codecs for each type parameter, + * returns a parsing function `(u, ast, options) => Effect` + * - `annotations` — optional metadata + * + * @see {@link declare} for creating schemas for non-parametric types. + * + * **Example** (Schema for a parametric `Box` type) + * + * ```ts import.meta.vitest + * import { Effect, Schema, SchemaIssue, SchemaParser } from "effect" + * + * interface Box { + * readonly value: A + * } + * + * const isBox = (u: unknown): u is Box => + * typeof u === "object" && u !== null && "value" in u + * + * const Box = (item: A) => + * Schema.declareConstructor, Box>()( + * [item], + * ([itemCodec]) => + * (u, ast, options) => { + * if (!isBox(u)) { + * return Effect.fail(new SchemaIssue.InvalidType(ast, u, options)) + * } + * return Effect.map( + * SchemaParser.decodeUnknownEffect(itemCodec)(u.value, options), + * (value) => ({ value }) + * ) + * } + * ) + * + * const schema = Box(Schema.Number) + * Effect.runSync(Schema.decodeUnknownEffect(schema)({ value: 1 })) // => { value: 1 } + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export function declareConstructor() { + return >( + typeParameters: TypeParameters, + run: ( + typeParameters: { + readonly [K in keyof TypeParameters]: Codec + } + ) => ( + u: unknown, + self: SchemaAST.Declaration, + options: SchemaAST.ParseOptions + ) => Effect.Effect, + annotations?: Annotations.Declaration + ): declareConstructor => { + return make( + new SchemaAST.Declaration( + typeParameters.map(SchemaAST.getAST), + (typeParameters) => run(typeParameters.map((ast) => make(ast)) as any), + annotations + ) + ) + } +} + +/** + * Type-level representation returned by {@link declare}. + * + * @category constructors + * @since 3.13.3 + */ +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 + * `true`, failing with an `InvalidType` issue otherwise. + * + * **When to use** + * + * Use when you are defining a schema for an opaque type with no type parameters + * and validation can be expressed as a type guard. + * + * **Example** (Defining a schema for a custom `UserId` branded type) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * type UserId = string & { readonly _tag: "UserId" } + * + * const isUserId = (u: unknown): u is UserId => + * typeof u === "string" && u.startsWith("user_") + * + * const UserId = Schema.declare(isUserId, { + * title: "UserId", + * description: "A user identifier starting with 'user_'" + * }) + * Schema.decodeUnknownSync(UserId)("user_123") // => "user_123" + * ``` + * + * @see {@link declareConstructor} for creating schemas for parametric types. + * + * @category constructors + * @since 3.10.0 + */ +export function declare( + is: (u: unknown) => u is T, + annotations?: Annotations.Declaration | undefined +): declare { + return declareConstructor()( + [], + () => (input, ast, options) => + is(input) ? + Effect.succeed(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. + * + * **Details** + * + * Normally, concrete schema interfaces (e.g. `Schema`) hide most type + * parameters. `revealBottom` is useful when writing generic utilities that need + * to inspect or propagate the complete set of type parameters. + * + * **Example** (Inspecting all type parameters of a schema) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.String + * + * // Widen to Bottom to access all 14 type parameters + * const bottom = Schema.revealBottom(schema) + * + * // `bottom` now exposes Type, Encoded, DecodingServices, EncodingServices, + * // ast, Rebuild, ~type.make.in, Iso, ~type.parameters, etc. + * type T = typeof bottom["Type"] // string + * type E = typeof bottom["Encoded"] // string + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export function revealBottom( + bottom: S +): Bottom< + S["Type"], + S["Encoded"], + S["DecodingServices"], + S["EncodingServices"], + S["ast"], + S["Rebuild"], + S["~type.make.in"], + S["Iso"], + S["~type.parameters"], + S["~type.make"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] +> { + return bottom +} + +/** + * Adds metadata annotations to a schema without changing its runtime behavior. + * This is the pipeable (curried) counterpart of the `.annotate` method. + * + * **Details** + * + * Annotations provide extra context used by documentation generators, JSON + * Schema converters, error formatters, and other tooling. Common keys include + * `title`, `description`, `examples`, `message`, and `identifier`. + * + * **Example** (Adding a title and description) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * 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. + * + * @category annotations + * @since 4.0.0 + */ +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 + * `annotate`, which targets the decoded (Type) side. + * + * **Details** + * + * Internally the schema is flipped so that `Encoded` becomes `Type`, + * annotated, and then flipped back. + * + * **Example** (Adding a title to the encoded representation) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.NumberFromString.pipe( + * Schema.annotateEncoded({ + * title: "my title" + * }) + * ) + * + * Schema.toEncoded(schema).ast.annotations?.title // => "my title" + * ``` + * + * @see {@link annotate} to annotate the type side instead. + * + * @category annotations + * @since 4.0.0 + */ +export function annotateEncoded(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. + * + * **Details** + * + * Key annotations apply to a field's position inside a `Struct` or `Tuple` + * rather than to the field's value type. They can carry a + * `messageMissingKey` to customise the error shown when the field is absent, + * as well as standard documentation fields such as `title`, `description`, + * and `examples`. + * + * **Example** (Customizing the missing-key message for a required field) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Struct({ + * username: Schema.String.pipe( + * Schema.annotateKey({ + * description: "The username used to log in", + * messageMissingKey: "Username is required" + * }) + * ) + * }) + * schema.fields.username.ast.context?.annotations?.messageMissingKey // => "Username is required" + * ``` + * + * @category annotations + * @since 4.0.0 + */ +export function annotateKey(annotations: Annotations.Key) { + return (self: S): S["Rebuild"] => { + return self.rebuild(SchemaAST.annotateKey(self.ast, annotations)) + } +} + +/** + * The existential "any schema" type — all type parameters are erased to `unknown`. + * + * **Details** + * + * Use `Top` as a constraint when writing generic utilities that must accept *any* + * schema regardless of its `Type`, `Encoded`, or service requirements. It is the + * widest possible schema type and therefore gives you the least static information. + * + * In user code prefer the narrower interfaces: + * - {@link Schema}`` — when you only care about the decoded type + * - {@link Codec}`` — when you need the encoded type and service requirements + * - {@link ConstraintDecoder}`` — for decode-only APIs + * - {@link ConstraintEncoder}`` — for encode-only APIs + * + * @category models + * @since 4.0.0 + */ +export interface Top extends + Bottom< + unknown, + unknown, + unknown, + unknown, + SchemaAST.AST, + Top, + unknown, + unknown, + any, // this is because TypeParameters is invariant + unknown, + Mutability, + Optionality, + ConstructorDefault, + Mutability, + Optionality + > +{} + +/** + * Lightweight structural constraint for APIs that accept schema values but only + * read their data and type-level views. + * + * **When to use** + * + * Use when you need to constrain a generic value to be a schema, but the API + * only reads properties such as `ast`, `Type`, `Encoded`, service + * requirements, constructor input views, or modifier flags. + * + * **Details** + * + * `Constraint` keeps the schema type identifier and the property surface needed + * by schema constructors, while avoiding the full `Bottom` protocol. Use + * {@link Top} when an API calls schema methods such as `annotate`, `check`, + * `rebuild`, `make`, or `makeEffect`. + * + * @see {@link Top} for the complete schema protocol. + * + * @category models + * @since 4.0.0 + */ +export interface Constraint { + readonly [TypeId]: typeof TypeId + readonly "ast": SchemaAST.AST + + readonly "Type": unknown + readonly "Encoded": unknown + readonly "DecodingServices": unknown + readonly "EncodingServices": unknown + + readonly "~type.parameters": any + readonly "~type.make.in": unknown + readonly "~type.make": unknown + readonly "Iso": unknown + + readonly "~type.optionality": Optionality + readonly "~type.mutability": Mutability + readonly "~type.constructor.default": ConstructorDefault + 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. + * + * **When to use** + * + * Use when you need to preserve decoded type, encoded type, and service + * requirements for a schema value, but the API does not call schema methods + * such as `annotate`, `check`, `rebuild`, `make`, or `makeEffect`. + * + * @see {@link Constraint} for the generic lightweight schema constraint. + * @see {@link Codec} for the full schema protocol with codec type views. + * + * @category models + * @since 4.0.0 + */ +export interface ConstraintCodec extends Constraint { + readonly "Type": T + readonly "Encoded": E + readonly "DecodingServices": RD + readonly "EncodingServices": RE +} + +/** + * Lightweight structural constraint for APIs that need decoder type views but + * do not need the full schema protocol. + * + * **When to use** + * + * Use when you need to preserve a schema's decoded type and decoding services, + * but the API does not constrain the encoded type, encoding services, or call + * schema methods such as `annotate`, `check`, `rebuild`, `make`, or + * `makeEffect`. + * + * @see {@link ConstraintCodec} for APIs that need both decoded and encoded codec views. + * @see {@link Codec} for the full schema protocol with codec type views. + * + * @category models + * @since 4.0.0 + */ +export interface ConstraintDecoder extends ConstraintCodec {} + +/** + * Lightweight structural constraint for APIs that need encoder type views but + * do not need the full schema protocol. + * + * **When to use** + * + * Use when you need to preserve a schema's encoded type and encoding services, + * but the API does not constrain the decoded type, decoding services, or call + * schema methods such as `annotate`, `check`, `rebuild`, `make`, or + * `makeEffect`. + * + * @see {@link ConstraintCodec} for APIs that need both decoded and encoded codec views. + * @see {@link Codec} for the full schema protocol with codec type views. + * + * @category models + * @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. + * + * **When to use** + * + * Use when an API needs to read `Rebuild` in addition to the schema views + * exposed by {@link Constraint}, but does not call methods such as `annotate`, + * `check`, `rebuild`, `make`, or `makeEffect`. + * + * @category models + * @since 4.0.0 + */ +export interface ConstraintRebuildable extends Constraint { + readonly "Rebuild": Constraint +} + +/** + * Namespace of type-level helpers for {@link Schema}. + * + * @since 3.10.0 + */ +export declare namespace Schema { + /** + * Extracts the decoded `Type` from a schema. + * + * **Example** (Extracting the decoded type) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const Person = Schema.Struct({ name: Schema.String, age: Schema.Number }) + * type Person = Schema.Schema.Type + * // { readonly name: string; readonly age: number } + * ``` + * + * @category utility types + * @since 3.10.0 + */ + export type Type = S extends { readonly "Type": infer T } ? T : never +} + +/** + * A typed view of a schema that tracks only the decoded (output) type `T`. + * + * **Details** + * + * Use `Schema` as a constraint when you want to accept "any schema that + * decodes to `T`" and do not need to know or constrain the encoded + * representation, required services, or any other type parameters. + * + * This is a structural interface — concrete schema values are produced by the + * constructors in this module (e.g. {@link Struct}, {@link String}, {@link Number}). + * When you also need the encoded type or service requirements, use {@link Codec}. + * + * **Example** (Accepting any schema decoding to `string`) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const accept = (_schema: Schema.Schema): void => {} + * + * accept(Schema.String) + * accept(Schema.NonEmptyString) + * ``` + * + * @see {@link Codec} — also tracks Encoded, DecodingServices, EncodingServices + * @see {@link Schema.Type} — extract the decoded type at the type level + * + * @category models + * @since 3.10.0 + */ +export interface Schema extends Top { + readonly "Type": T + readonly "Rebuild": Schema +} + +/** + * Namespace of type-level helpers for {@link Codec}. + * + * @since 4.0.0 + */ +export declare namespace Codec { + /** + * Extracts the encoded (`Encoded`) type from a schema. + * + * **Example** (Extracting the encoded type) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.NumberFromString + * type Enc = Schema.Codec.Encoded + * // string + * ``` + * + * @category utility types + * @since 3.10.0 + */ + export 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 import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.String + * type RD = Schema.Codec.DecodingServices + * // never + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export 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 import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.String + * type RE = Schema.Codec.EncodingServices + * // never + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export 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`). + * + * **Details** + * + * Use `Codec` when you need to preserve full type information + * about a schema — both what it decodes to and what it serializes from/to. + * Most concrete schemas produced by this module implement `Codec`. + * + * For APIs that only need one direction, prefer the narrower views: + * - {@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 import.meta.vitest + * import { Schema } from "effect" + * + * const serialize = (codec: Schema.Codec, value: T): string => + * Schema.encodeSync(codec)(value) + * + * serialize(Schema.NumberFromString, 42) // => "42" + * ``` + * + * @see {@link Codec.Encoded} — extract the encoded type + * @see {@link Codec.DecodingServices} — extract required decoding services + * @see {@link Codec.EncodingServices} — extract required encoding services + * @see {@link revealCodec} — helper to make TypeScript infer the full Codec type + * + * @category models + * @since 4.0.0 + */ +export interface Codec extends Schema { + readonly "Encoded": E + readonly "DecodingServices": RD + 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`). + * + * **Details** + * + * When a schema is stored in a variable typed as `Schema` or `Top`, the + * encoded type and service requirements are erased. Passing the value through + * `revealCodec` recovers those parameters without any runtime cost. + * + * **Example** (Recovering encoded type from a schema variable) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema: Schema.Schema = Schema.NumberFromString + * + * // Without revealCodec, Encoded is unknown + * const codec = Schema.revealCodec(schema) + * type Enc = typeof codec["Encoded"] // string + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export function revealCodec(codec: Codec) { + return codec +} + +/** + * 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 +} + +const SchemaErrorTypeId = "~effect/SchemaError/SchemaError" + +/** + * Error thrown or returned 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 and + * the expected type or constraint. The `message` field renders the issue tree + * with the default formatter. + * + * **Gotchas** + * + * 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. + * + * **Example** (Inspecting a SchemaError) + * + * ```ts import.meta.vitest + * import { Result, Schema } from "effect" + * + * 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 Predicate.hasProperty(u, SchemaErrorTypeId) && u[SchemaErrorTypeId] === SchemaErrorTypeId +} + +function makeStandardResult(exit: Exit_.Exit>): StandardSchemaV1.Result { + return Exit_.isSuccess(exit) ? exit.value : { + issues: [{ message: Cause_.pretty(exit.cause) }] + } +} + +/** + * 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 + * }) + * + * 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 converting + * @since 4.0.0 + */ +export function toStandardSchemaV1>( + self: S, + options?: { + readonly leafHook?: SchemaIssue.LeafHook | undefined + readonly checkHook?: SchemaIssue.CheckHook | undefined + 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("sync") + 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}`) +} + +/** + * Converts a schema to an experimental Standard JSON Schema V1 representation. + * + * **Details** + * + * https://github.com/standard-schema/standard-schema/pull/134 + * + * @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 + }) + } +} + +/** + * Creates a type guard function that checks if a value conforms to a given + * schema. + * + * **Details** + * + * This function returns a predicate that performs a type-safe check, narrowing + * the type of the input value if the check passes. The predicate returns `false` + * for schema mismatches. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `false`. Causes + * that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * **Example** (Defining a basic type guard) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const isString = Schema.is(Schema.String) + * + * isString("hello") // => true + * isString(42) // => false + * + * // Type narrowing in action + * const value: unknown = "hello" + * if (isString(value)) { + * // value is now typed as string + * value.toUpperCase() // => "HELLO" + * } + * ``` + * + * @category guards + * @since 3.10.0 + */ +export const is = SchemaParser.is + +/** + * Creates an assertion function that throws an error if the input does not match + * the schema. + * + * **When to use** + * + * Use to validate unknown input at runtime while narrowing the value with a + * TypeScript assertion signature. + * + * **Details** + * + * 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** + * + * Causes that contain defects, interruptions, or other non-schema reasons throw + * with the underlying `Cause` attached instead of being converted to schema + * validation errors. + * + * **Example** (Asserting and narrowing an input) + * + * ```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) + * input.toUpperCase() // => "HELLO" + * + * // This will throw an error + * try { + * const invalid: unknown = 123 + * Schema.asserts(Schema.String, invalid) + * } catch (error) { + * if (error instanceof Error) { + * SchemaIssue.isIssue(error.cause) // => true + * } + * } + * ``` + * + * @category guards + * @since 4.0.0 + */ +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}. + * + * **When to use** + * + * Use when you need to decode unknown input in an `Effect` whose failure + * channel is `SchemaError`. + * + * **Details** + * + * Prefer {@link decodeEffect} when the input is already typed as the schema's + * `Encoded` type. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * + * @see {@link SchemaParser.decodeUnknownEffect} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUnknownEffect(schema: S, options?: SchemaAST.ParseOptions) { + const parser = SchemaParser.decodeUnknownEffect(schema, options) + return ( + input: unknown, + options?: SchemaAST.ParseOptions + ): Effect.Effect => { + return fromIssueEffect(parser(input, options)) + } +} + +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))) + ) +} + +/** + * 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 + * {@link SchemaError}. + * + * **When to use** + * + * Use when you need to decode input already typed as the schema's `Encoded` + * type in an `Effect` whose failure channel is `SchemaError`. + * + * **Details** + * + * For `unknown` input use {@link decodeUnknownEffect}. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * + * @see {@link SchemaParser.decodeEffect} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category decoding + * @since 4.0.0 + */ +export const decodeEffect: ( + schema: S, + options?: SchemaAST.ParseOptions +) => ( + 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`. + * + * **When to use** + * + * Use when you need to decode unknown input into an `Exit` and capture schema + * mismatches as `SchemaError`. + * + * **Details** + * + * Only usable with schemas that have no `DecodingServices` requirement. Prefer + * {@link decodeExit} when the input is already typed as the schema's `Encoded` + * type. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * Schema mismatches are represented by a `Failure` cause containing + * `SchemaError`. + * + * **Gotchas** + * + * Schema issue fail reasons are wrapped as `SchemaError`. Defects, + * interruptions, and other non-schema reasons remain in the returned `Cause`, + * including when they are mixed with schema issues. + * + * @see {@link SchemaParser.decodeUnknownExit} for the adapter whose failure contains `SchemaIssue.Issue` directly + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUnknownExit>(schema: S, options?: SchemaAST.ParseOptions) { + const parser = SchemaParser.decodeUnknownExit(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit => { + return fromIssueExit(parser(input, options)) + } +} + +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))) +} + +/** + * Decodes a typed input (the schema's `Encoded` type) against a schema + * synchronously, returning an `Exit` that is either a `Success` with the decoded + * value or a `Failure`. + * + * **When to use** + * + * Use when you need to decode already typed `Encoded` input into an `Exit` and + * capture schema mismatches as `SchemaError`. + * + * **Details** + * + * Only usable with schemas that have no `DecodingServices` requirement. For + * `unknown` input use {@link decodeUnknownExit}. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * Schema mismatches are represented by a `Failure` cause containing + * `SchemaError`. + * + * **Gotchas** + * + * Schema issue fail reasons are wrapped as `SchemaError`. Defects, + * interruptions, and other non-schema reasons remain in the returned `Cause`, + * including when they are mixed with schema issues. + * + * @see {@link SchemaParser.decodeExit} for the adapter whose failure contains `SchemaIssue.Issue` directly + * + * @category decoding + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use when you do not know the input type statically and only need to know + * whether decoding succeeded. + * + * **Details** + * + * Prefer this over {@link decodeUnknownExit} or {@link decodeUnknownEffect} + * when you don't need error details. For input already typed as the schema's + * `Encoded` type use {@link decodeOption}. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `None`. Causes + * that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @category decoding + * @since 3.10.0 + */ +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 + * `None` for schema mismatches. + * + * **When to use** + * + * Use when you already have input typed as the schema's `Encoded` type and + * only need to know whether decoding succeeded. + * + * **Details** + * + * For `unknown` input use {@link decodeUnknownOption}. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `None`. Causes + * that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @category decoding + * @since 3.10.0 + */ +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 + * mismatches. + * + * **When to use** + * + * Use when you do not know the input type statically and want schema mismatches + * returned as `Result.fail` with `SchemaError`. + * + * **Details** + * + * For input already typed as the schema's `Encoded` type use + * {@link decodeResult}. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * Schema mismatches are returned as `Result.fail` with `SchemaError`. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are returned as `Result.fail`. + * Causes that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @see {@link SchemaParser.decodeUnknownResult} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUnknownResult>(schema: S, options?: SchemaAST.ParseOptions) { + const parser = SchemaParser.decodeUnknownResult(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): Result_.Result => { + 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 + * {@link SchemaError} for schema mismatches. + * + * **When to use** + * + * Use when you already have input typed as the schema's `Encoded` type and want + * schema mismatches returned as `Result.fail` with `SchemaError`. + * + * **Details** + * + * For `unknown` input use {@link decodeUnknownResult}. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * Schema mismatches are returned as `Result.fail` with `SchemaError`. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are returned as `Result.fail`. + * Causes that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @see {@link SchemaParser.decodeResult} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category decoding + * @since 4.0.0 + */ +export const decodeResult: >( + schema: S, + 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 + * schema mismatches. + * + * **When to use** + * + * Use when you need decoding of unknown input to return a JavaScript `Promise` + * that rejects with `SchemaError` for schema mismatches. + * + * **Details** + * + * For input already typed as the schema's `Encoded` type use + * {@link decodePromise}. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Non-schema failures may reject with a runtime failure instead of + * `SchemaError`. + * + * @see {@link SchemaParser.decodeUnknownPromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue` + * + * @category decoding + * @since 3.10.0 + */ +export function decodeUnknownPromise>( + schema: S, + options?: SchemaAST.ParseOptions +) { + const parser = decodeUnknownEffect(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): Promise => { + 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 + * {@link SchemaError} for schema mismatches. + * + * **When to use** + * + * Use when you already have input typed as the schema's `Encoded` type and + * need decoding to return a JavaScript `Promise` that rejects with + * `SchemaError` for schema mismatches. + * + * **Details** + * + * For `unknown` input use `decodeUnknownPromise`. + * Options may be provided either when creating the decoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Non-schema failures may reject with a runtime failure instead of + * `SchemaError`. + * + * @see {@link SchemaParser.decodePromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue` + * + * @category decoding + * @since 3.10.0 + */ +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. + * + * **When to use** + * + * Use when you need to validate unknown data at a synchronous boundary and want + * schema mismatches to throw `SchemaError`. + * + * **Details** + * + * For input already typed as the schema's `Encoded` type use `decodeSync`. + * Only service-free schemas can be decoded synchronously. For alternatives that + * do not throw on schema mismatches, see `decodeUnknownOption`, + * `decodeUnknownExit`, or `decodeUnknownEffect`. Options may be provided either + * when creating the decoder or when applying it; application options override + * creation options. + * + * **Gotchas** + * + * Non-schema failures may throw a runtime failure instead of `SchemaError`. + * + * **Example** (Decoding with a transformation schema) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const NumberFromString = Schema.NumberFromString + * + * Schema.decodeUnknownSync(NumberFromString)("42") // => 42 + * ``` + * + * @see {@link SchemaParser.decodeUnknownSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue` + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUnknownSync>(schema: S, options?: SchemaAST.ParseOptions) { + const parser = decodeUnknownEffect(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): S["Type"] => { + 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} + * for schema mismatches. + * + * **When to use** + * + * Use when you already have input typed as the schema's `Encoded` type and + * want schema mismatches to throw `SchemaError` synchronously. + * + * **Details** + * + * For `unknown` input use `decodeUnknownSync`. + * Only service-free schemas can be decoded synchronously. Options may be + * provided either when creating the decoder or when applying it; application + * options override creation options. + * + * **Gotchas** + * + * Non-schema failures may throw a runtime failure instead of `SchemaError`. + * + * @see {@link SchemaParser.decodeSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue` + * + * @category decoding + * @since 4.0.0 + */ +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}. + * + * **When to use** + * + * Use when you need to encode unknown input in an `Effect` whose failure + * channel is `SchemaError`. + * + * **Details** + * + * Prefer {@link encodeEffect} when the value is already typed as the schema's + * `Type`. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * + * **Example** (Encoding a value to a string) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * const NumberFromString = Schema.NumberFromString + * + * await Effect.runPromise(Schema.encodeUnknownEffect(NumberFromString)(42)) // => "42" + * ``` + * + * @see {@link SchemaParser.encodeUnknownEffect} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUnknownEffect(schema: S, options?: SchemaAST.ParseOptions) { + const parser = SchemaParser.encodeUnknownEffect(schema, options) + return ( + input: unknown, + options?: SchemaAST.ParseOptions + ): Effect.Effect => { + 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 + * {@link SchemaError}. + * + * **When to use** + * + * Use when you need to encode input already typed as the schema's `Type` in + * an `Effect` whose failure channel is `SchemaError`. + * + * **Details** + * + * For `unknown` input use {@link encodeUnknownEffect}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * + * @see {@link SchemaParser.encodeEffect} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category encoding + * @since 4.0.0 + */ +export const encodeEffect: ( + schema: S, + options?: SchemaAST.ParseOptions +) => ( + 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`. + * + * **When to use** + * + * Use when you need to encode unknown input into an `Exit` and capture schema + * mismatches as `SchemaError`. + * + * **Details** + * + * Only usable with schemas that have no `EncodingServices` requirement. Prefer + * {@link encodeExit} when the value is already typed as the schema's `Type`. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * Schema mismatches are represented by a `Failure` cause containing + * `SchemaError`. + * + * **Gotchas** + * + * Schema issue fail reasons are wrapped as `SchemaError`. Defects, + * interruptions, and other non-schema reasons remain in the returned `Cause`, + * including when they are mixed with schema issues. + * + * @see {@link SchemaParser.encodeUnknownExit} for the adapter whose failure contains `SchemaIssue.Issue` directly + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUnknownExit>(schema: S, options?: SchemaAST.ParseOptions) { + const parser = SchemaParser.encodeUnknownExit(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit => { + 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 + * `Failure`. + * + * **When to use** + * + * Use when you need to encode already typed schema values into an `Exit` and + * capture schema mismatches as `SchemaError`. + * + * **Details** + * + * Only usable with schemas that have no `EncodingServices` requirement. For + * `unknown` input use {@link encodeUnknownExit}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * Schema mismatches are represented by a `Failure` cause containing + * `SchemaError`. + * + * **Gotchas** + * + * Schema issue fail reasons are wrapped as `SchemaError`. Defects, + * interruptions, and other non-schema reasons remain in the returned `Cause`, + * including when they are mixed with schema issues. + * + * @see {@link SchemaParser.encodeExit} for the adapter whose failure contains `SchemaIssue.Issue` directly + * + * @category encoding + * @since 4.0.0 + */ +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. + * + * **When to use** + * + * Use when you do not know the input type statically and only need to know + * whether encoding succeeded. + * + * **Details** + * + * Prefer this over {@link encodeUnknownExit} or {@link encodeUnknownEffect} + * when you don't need error details. For values already typed as the schema's + * `Type` use {@link encodeOption}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `None`. Causes + * that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @category encoding + * @since 3.10.0 + */ +export const encodeUnknownOption: >( + schema: S, + 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 + * mismatches. + * + * **When to use** + * + * Use when you already have a value typed as the schema's `Type` and only need + * to know whether encoding succeeded. + * + * **Details** + * + * For `unknown` input use {@link encodeUnknownOption}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `None`. Causes + * that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @category encoding + * @since 3.10.0 + */ +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 + * mismatches. + * + * **When to use** + * + * Use when you do not know the input type statically and want schema mismatches + * returned as `Result.fail` with `SchemaError`. + * + * **Details** + * + * For values already typed as the schema's `Type` use {@link encodeResult}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * Schema mismatches are returned as `Result.fail` with `SchemaError`. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are returned as `Result.fail`. + * Causes that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @see {@link SchemaParser.encodeUnknownResult} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUnknownResult>(schema: S, options?: SchemaAST.ParseOptions) { + const parser = SchemaParser.encodeUnknownResult(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): Result_.Result => { + 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 + * {@link SchemaError} for schema mismatches. + * + * **When to use** + * + * Use when you already have a value typed as the schema's `Type` and want schema + * mismatches returned as `Result.fail` with `SchemaError`. + * + * **Details** + * + * For `unknown` input use {@link encodeUnknownResult}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * Schema mismatches are returned as `Result.fail` with `SchemaError`. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are returned as `Result.fail`. + * Causes that contain defects, interruptions, or other non-schema reasons throw + * instead. + * + * @see {@link SchemaParser.encodeResult} for the adapter that fails with `SchemaIssue.Issue` directly + * + * @category encoding + * @since 4.0.0 + */ +export const encodeResult: >( + schema: S, + 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 + * schema mismatches. + * + * **When to use** + * + * Use when you need encoding of unknown input to return a JavaScript `Promise` + * that rejects with `SchemaError` for schema mismatches. + * + * **Details** + * + * For values already typed as the schema's `Type` use {@link encodePromise}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Non-schema failures may reject with a runtime failure instead of + * `SchemaError`. + * + * @see {@link SchemaParser.encodeUnknownPromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue` + * + * @category encoding + * @since 3.10.0 + */ +export function encodeUnknownPromise>( + schema: S, + options?: SchemaAST.ParseOptions +) { + const parser = encodeUnknownEffect(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): Promise => { + 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 + * {@link SchemaError} for schema mismatches. + * + * **When to use** + * + * Use when you already have a value typed as the schema's `Type` and need + * encoding to return a JavaScript `Promise` that rejects with `SchemaError` for + * schema mismatches. + * + * **Details** + * + * For `unknown` input use {@link encodeUnknownPromise}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Non-schema failures may reject with a runtime failure instead of + * `SchemaError`. + * + * @see {@link SchemaParser.encodePromise} for the adapter that rejects with an `Error` whose cause is `SchemaIssue.Issue` + * + * @category encoding + * @since 3.10.0 + */ +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. + * + * **When to use** + * + * Use when you need to serialize unknown data at a synchronous boundary and + * want schema mismatches to throw `SchemaError`. + * + * **Details** + * + * For alternatives that do not throw on schema mismatches, see + * {@link encodeUnknownOption}, {@link encodeUnknownExit}, or + * {@link encodeUnknownEffect}. For values already typed as the schema's `Type` + * use {@link encodeSync}. Options may be provided either when creating the + * encoder or when applying it; application options override creation options. + * + * **Gotchas** + * + * Non-schema failures may throw a runtime failure instead of `SchemaError`. + * + * @see {@link SchemaParser.encodeUnknownSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue` + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUnknownSync>(schema: S, options?: SchemaAST.ParseOptions) { + const parser = encodeUnknownEffect(schema, options) + return (input: unknown, options?: SchemaAST.ParseOptions): S["Encoded"] => { + 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. + * + * **When to use** + * + * Use when you already have a value typed as the schema's `Type` and want + * schema mismatches to throw `SchemaError` synchronously. + * + * **Details** + * + * For `unknown` input use {@link encodeUnknownSync}. + * Options may be provided either when creating the encoder or when applying it; + * application options override creation options. + * + * **Gotchas** + * + * Non-schema failures may throw a runtime failure instead of `SchemaError`. + * + * @see {@link SchemaParser.encodeSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue` + * + * @category encoding + * @since 4.0.0 + */ +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. + * + * **Details** + * + * This is the fundamental constructor for all schemas in the Effect Schema + * library. It takes an AST node and wraps it in a fully-typed schema that + * preserves all type information and provides the complete schema API. + * + * The `make` function is used internally to create all primitive schemas like + * `String`, `Number`, `Boolean`, etc., as well as more complex schemas. It's + * the bridge between the untyped AST representation and the strongly-typed + * schema. + * + * @category constructors + * @since 3.10.0 + */ +export const make: (ast: S["ast"], options?: object) => S = InternalSchema.make + +/** + * Checks whether a value is a `Schema`. + * + * @category guards + * @since 3.10.0 + */ +export function isSchema(u: unknown): u is Top { + return Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId +} + +/** + * Type-level representation returned by {@link optionalKey}. + * + * @category models + * @since 4.0.0 + */ +export interface optionalKey extends + BottomLazy< + S["ast"], + optionalKey, + S["~type.parameters"], + S["~type.mutability"], + "optional", + S["~type.constructor.default"], + S["~encoded.mutability"], + "optional" + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + 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 + * completely omitted from the object. + * + * **Example** (Creating a struct with optional key) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Struct({ + * name: Schema.String, + * age: Schema.optionalKey(Schema.Number) + * }) + * + * // Type: { readonly name: string; readonly age?: number } + * type Person = typeof schema["Type"] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const optionalKey = 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. + * + * **When to use** + * + * Use to remove optional-key wrapping from a schema field that was previously + * wrapped with {@link optionalKey}. + * + * @category combinators + * @since 4.0.0 + */ +export const requiredKey = Struct_.lambda((self) => self.schema) + +/** + * Type-level representation returned by {@link optional}. + * + * @category models + * @since 3.10.0 + */ +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`. + * + * **Details** + * + * The resulting property may be absent or explicitly set to `undefined`. + * Equivalent to `optionalKey(UndefinedOr(S))`. + * + * Use {@link optionalKey} instead if you want exact optional semantics (absent + * only, not `undefined`). + * + * **Example** (Defining an optional field accepting undefined) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Struct({ + * name: Schema.String, + * age: Schema.optional(Schema.Number) + * }) + * + * // { readonly name: string; readonly age?: number | undefined } + * type Person = typeof schema.Type + * ``` + * + * @category combinators + * @since 3.10.0 + */ +export const optional = 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. + * + * **When to use** + * + * Use to remove optional wrapping from a schema field that was previously + * wrapped with {@link optional}. + * + * **Details** + * + * This also unwraps the `UndefinedOr` member added by `optional`. + * + * @category combinators + * @since 3.10.0 + */ +export const required = Struct_.lambda((self) => self.schema.members[0]) + +/** + * Type-level representation returned by {@link mutableKey}. + * + * @category models + * @since 4.0.0 + */ +export interface mutableKey extends + BottomLazy< + S["ast"], + mutableKey, + S["~type.parameters"], + "mutable", + S["~type.optionality"], + S["~type.constructor.default"], + "mutable", + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + 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. + * + * @category combinators + * @since 4.0.0 + */ +export const mutableKey = 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. + * + * **When to use** + * + * Use to remove mutable-key wrapping from a schema field that was previously + * wrapped with {@link mutableKey}. + * + * @category combinators + * @since 4.0.0 + */ +export const readonlyKey = Struct_.lambda((self) => self.schema) + +/** + * Type-level representation returned by {@link toType}. + * + * @category transforming + * @since 4.0.0 + */ +export interface toType extends + BottomLazy< + S["ast"], + toType, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Type"] + readonly "DecodingServices": never + readonly "EncodingServices": never + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + 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. + * + * @category transforming + * @since 4.0.0 + */ +export const toType = Struct_.lambda((schema) => make(SchemaAST.toType(schema.ast), { schema })) + +/** + * Type-level representation returned by {@link toEncoded}. + * + * @category transforming + * @since 4.0.0 + */ +export interface toEncoded extends + BottomLazy< + SchemaAST.AST, + toEncoded, + ReadonlyArray, + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Encoded"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": never + readonly "EncodingServices": never + readonly "~type.make.in": S["Encoded"] + readonly "~type.make": S["Encoded"] + 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. + * + * @category transforming + * @since 4.0.0 + */ +export const toEncoded = Struct_.lambda((schema) => make(SchemaAST.toEncoded(schema.ast), { schema })) + +const FlipTypeId = "~effect/Schema/flip" + +/** + * Type-level representation returned by {@link flip}. + * + * @category transforming + * @since 4.0.0 + */ +export interface flip extends + BottomLazy< + SchemaAST.AST, + flip, + ReadonlyArray, + S["~encoded.mutability"], + S["~encoded.optionality"], + ConstructorDefault, + S["~type.mutability"], + S["~type.optionality"] + > +{ + readonly "Type": S["Encoded"] + readonly "Encoded": S["Type"] + readonly "DecodingServices": S["EncodingServices"] + readonly "EncodingServices": S["DecodingServices"] + readonly "~type.make.in": S["Encoded"] + readonly "~type.make": S["Encoded"] + readonly "Iso": S["Encoded"] + readonly [FlipTypeId]: typeof FlipTypeId + readonly schema: S +} + +function isFlip$(schema: Top): schema is flip { + return Predicate.hasProperty(schema, FlipTypeId) && schema[FlipTypeId] === FlipTypeId +} + +/** + * Swaps the decoded and encoded sides of a schema. + * + * **When to use** + * + * Use to invert a schema transformation direction. + * + * **Details** + * + * Calling `flip` twice returns the original schema. + * + * **Example** (Flipping a number-from-string schema) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * // NumberFromString: decodes string → number + * const flipped = Schema.flip(Schema.NumberFromString) + * Schema.decodeSync(flipped)(42) // => "42" + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function flip(schema: S): S extends flip ? F["Rebuild"] : flip +export function flip(schema: S): flip { + if (isFlip$(schema)) { + return schema.schema.rebuild(SchemaAST.flip(schema.ast)) + } + return make(SchemaAST.flip(schema.ast), { [FlipTypeId]: FlipTypeId, schema }) +} + +/** + * Type-level representation returned by {@link Literal}. + * + * @category models + * @since 3.10.0 + */ +export interface Literal + extends Bottom> +{ + 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 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. + * @see {@link tag} for a schema that represents a literal value that can be + * used as a discriminator field in tagged unions and has a constructor default. + * @category constructors + * @since 3.10.0 + */ +export function Literal(literal: L): Literal { + const out = make>(new SchemaAST.Literal(literal), { + literal, + transform(to: L2): decodeTo, Literal> { + return out.pipe(decodeTo(Literal(to), { + decode: SchemaGetter.transform(() => to), + encode: SchemaGetter.transform(() => literal) + })) + } + }) + return out +} + +/** + * Namespace for {@link TemplateLiteral} helper types. + * + * @since 3.10.0 + */ +export declare namespace TemplateLiteral { + /** + * Constraint for schema parts that can appear inside a `TemplateLiteral`. + * + * **Details** + * + * The schema's encoded value must be a `string`, `number`, or `bigint` so it can + * be converted into a template literal string segment. + * + * @category utility types + * @since 4.0.0 + */ + export interface SchemaPart extends Constraint { + readonly Encoded: string | number | bigint + } + + /** + * Literal value that can be used directly as a part of a `TemplateLiteral`. + * + * @category utility types + * @since 4.0.0 + */ + export type LiteralPart = string | number | bigint + + /** + * A single part of a `TemplateLiteral`, either an interpolated schema part or a + * literal `string`, `number`, or `bigint`. + * + * @category utility types + * @since 4.0.0 + */ + export type Part = SchemaPart | LiteralPart + + /** + * Ordered list of parts used to construct a `TemplateLiteral` schema. + * + * @category utility types + * @since 4.0.0 + */ + export type Parts = ReadonlyArray + + type AppendType< + Template extends string, + Next + > = Next extends LiteralPart ? `${Template}${Next}` + : Next extends { readonly Encoded: infer E extends LiteralPart } ? `${Template}${E}` + : never + + /** + * Computes the encoded string literal type produced by concatenating the encoded + * forms of all template literal parts. + * + * @category utility types + * @since 3.10.0 + */ + export type Encoded = Parts extends readonly [...infer Init, infer Last] ? AppendType, Last> + : `` +} + +/** + * Type-level representation returned by {@link TemplateLiteral}. + * + * @category models + * @since 3.10.0 + */ +export interface TemplateLiteral extends + Bottom< + TemplateLiteral.Encoded, + TemplateLiteral.Encoded, + never, + never, + SchemaAST.TemplateLiteral, + TemplateLiteral + > +{ + readonly parts: Parts +} + +function templateLiteralFromParts(parts: Parts) { + return new SchemaAST.TemplateLiteral( + parts.map((part) => isSchema(part) ? part.ast : new SchemaAST.Literal(part as TemplateLiteral.LiteralPart)) + ) +} + +/** + * Creates a schema that validates strings by matching ordered template literal + * parts. + * + * **When to use** + * + * Use when the decoded value should remain the matched string and you do not + * need the individual template parts parsed into a tuple. + * + * **Details** + * + * Each part can be a literal `string`, `number`, or `bigint`, or a schema whose + * encoded type is `string`, `number`, or `bigint`. Checks on string, number, + * and bigint schema parts are applied while matching each segment. + * + * **Example** (Defining a URL path pattern) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.TemplateLiteral(["/user/", Schema.Number]) + * Schema.is(schema)("/user/123") // => true + * ``` + * + * @see {@link TemplateLiteralParser} for a schema that also parses matched parts into a tuple. + * @category constructors + * @since 3.10.0 + */ +export function TemplateLiteral(parts: Parts): TemplateLiteral { + return make(templateLiteralFromParts(parts), { parts }) +} + +/** + * Namespace for {@link TemplateLiteralParser} helper types. + * + * @since 3.10.0 + */ +export declare namespace TemplateLiteralParser { + /** + * Computes the decoded tuple type produced by `TemplateLiteralParser`. + * + * **Details** + * + * Literal parts contribute their literal value to the tuple. Schema parts + * contribute their decoded `Type`. + * + * @category utility types + * @since 3.10.0 + */ + export type Type = Parts extends readonly [infer Head, ...infer Tail] ? readonly [ + Head extends TemplateLiteral.LiteralPart ? Head : + Head extends ConstraintDecoder ? T + : never, + ...Type + ] + : [] +} + +/** + * Type-level representation returned by {@link TemplateLiteralParser}. + * + * @category models + * @since 3.10.0 + */ +export interface TemplateLiteralParser extends + BottomLazy< + SchemaAST.Arrays, + TemplateLiteralParser + > +{ + readonly "Type": TemplateLiteralParser.Type + readonly "Encoded": TemplateLiteral.Encoded + readonly "DecodingServices": never + readonly "EncodingServices": never + readonly "~type.make.in": TemplateLiteralParser.Type + readonly "~type.make": TemplateLiteralParser.Type + readonly "Iso": TemplateLiteralParser.Type + readonly parts: Parts +} + +/** + * Schema for parsing matched template literal strings into typed tuple parts. + * + * **When to use** + * + * Use to validate a template literal string and decode the matched parts into + * typed values. + * + * **Details** + * + * Unlike {@link TemplateLiteral}, this schema decodes the matched string into a + * readonly tuple with one element per schema part. Checks on string, number, + * and bigint schema parts are applied while matching each segment. + * + * **Example** (Parsing path parameters) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.TemplateLiteralParser(["/user/", Schema.NumberFromString]) + * Schema.decodeSync(schema)("/user/42") // => ["/user/", 42] + * ``` + * + * @see {@link TemplateLiteral} for a validation-only version that keeps the string encoded. + * @category constructors + * @since 3.10.0 + */ +export function TemplateLiteralParser( + parts: Parts +): TemplateLiteralParser { + return make(templateLiteralFromParts(parts).asTemplateLiteralParser(), { parts }) +} + +/** + * Type-level representation returned by {@link Enum}. + * + * @category models + * @since 4.0.0 + */ +export interface Enum + extends Bottom> +{ + readonly enums: A +} + +/** + * Creates a schema from a TypeScript enum object. Validates that the input is one of the enum's values. + * + * **Example** (Defining a direction enum) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * enum Direction { + * Up = "Up", + * Down = "Down" + * } + * + * const schema = Schema.Enum(Direction) + * Schema.decodeSync(schema)(Direction.Up) // => "Up" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export function Enum(enums: A): Enum { + return make( + new SchemaAST.Enum( + Object.keys(enums).filter( + (key) => typeof enums[enums[key]] !== "number" + ).map((key) => [key, enums[key]]) + ), + { enums } + ) +} + +/** + * Type-level representation of {@link Never}. + * + * @category models + * @since 3.10.0 + */ +export interface Never extends Bottom {} + +/** + * Schema for the `never` type. Always fails validation — no value satisfies it. + * + * @category schemas + * @since 3.10.0 + */ +export const Never: Never = make(SchemaAST.never) + +/** + * Type-level representation of {@link Any}. + * + * @category models + * @since 3.10.0 + */ +export interface Any extends Bottom {} + +/** + * Schema for the `any` type. Accepts any value without validation. + * + * @see {@link Unknown} for a safer alternative that uses `unknown`. + * @category schemas + * @since 3.10.0 + */ +export const Any: Any = make(SchemaAST.any) + +/** + * Type-level representation of {@link Unknown}. + * + * @category models + * @since 3.10.0 + */ +export interface Unknown extends Bottom {} + +/** + * Schema for the `unknown` type. Accepts any value without validation. + * + * **When to use** + * + * Use as a top schema when you need to accept any input while preserving + * TypeScript's `unknown` safety at use sites. + * + * @see {@link Any} for the `any` variant. + * @category schemas + * @since 3.10.0 + */ +export const Unknown: Unknown = make(SchemaAST.unknown) + +/** + * Type-level representation of {@link Null}. + * + * @category models + * @since 3.10.0 + */ +export interface Null extends Bottom {} + +/** + * Schema for the `null` literal. Validates that the input is strictly `null`. + * + * @see {@link NullOr} for a union with another schema. + * @category schemas + * @since 3.10.0 + */ +export const Null: Null = make(SchemaAST.null) + +/** + * Type-level representation of {@link Undefined}. + * + * @category models + * @since 3.10.0 + */ +export interface Undefined extends Bottom {} + +/** + * Schema for the `undefined` literal. Validates that the input is strictly `undefined`. + * + * @see {@link UndefinedOr} for a union with another schema. + * @category schemas + * @since 3.10.0 + */ +export const Undefined: Undefined = make(SchemaAST.undefined) + +/** + * Type-level representation of {@link String}. + * + * @category models + * @since 4.0.0 + */ +export interface String extends Bottom {} + +/** + * Schema for `string` values. Validates that the input is `typeof` `"string"`. + * + * @category schemas + * @since 4.0.0 + */ +export const String: String = make(SchemaAST.string) + +/** + * Type-level representation of {@link Number}. + * + * @category models + * @since 4.0.0 + */ +export interface Number extends Bottom {} + +/** + * Schema for `number` values, including `NaN`, `Infinity`, and `-Infinity`. + * + * **Details** + * + * Default JSON serializer: + * + * - Finite numbers are serialized as numbers. + * - Non-finite values are serialized as strings (`"NaN"`, `"Infinity"`, `"-Infinity"`). + * + * @see {@link Finite} for a schema that excludes non-finite values. + * @category schemas + * @since 4.0.0 + */ +export const Number: Number = make(SchemaAST.number) + +/** + * Type-level representation of {@link Boolean}. + * + * @category models + * @since 4.0.0 + */ +export interface Boolean extends Bottom {} + +/** + * Schema for `boolean` values. Validates that the input is `typeof` `"boolean"`. + * + * **When to use** + * + * Use to validate values that are already JavaScript booleans. + * + * @see {@link BooleanFromBit} for a schema that decodes bit literals `0` or `1` into a boolean + * + * @category schemas + * @since 4.0.0 + */ +export const Boolean: Boolean = make(SchemaAST.boolean) + +/** + * Type-level representation of {@link Symbol}. + * + * @category models + * @since 4.0.0 + */ +export interface Symbol extends Bottom {} + +/** + * Schema for `symbol` values. Validates that the input is `typeof` `"symbol"`. + * + * @see {@link UniqueSymbol} for a schema that matches a specific symbol. + * @category schemas + * @since 4.0.0 + */ +export const Symbol: Symbol = make(SchemaAST.symbol) + +/** + * Type-level representation of {@link BigInt}. + * + * @category models + * @since 4.0.0 + */ +export interface BigInt extends Bottom {} + +/** + * Schema for `bigint` values. Validates that the input is `typeof` `"bigint"`. + * + * **When to use** + * + * Use when the input is already a bigint and the schema should validate and + * preserve bigint values without parsing from another representation. + * + * @see {@link BigIntFromString} for parsing string input into a bigint + * + * @category schemas + * @since 4.0.0 + */ +export const BigInt: BigInt = make(SchemaAST.bigInt) + +/** + * Type-level representation of {@link Void}. + * + * @category models + * @since 3.10.0 + */ +export interface Void extends Bottom {} + +/** + * Schema for a TypeScript `void` return value. + * + * **When to use** + * + * Use when you need to model the return value of a function, RPC, or endpoint + * whose result is intentionally ignored. + * + * **Details** + * + * Runtime parsing accepts any present value and discards it, producing + * `undefined`. The public decoded and encoded TypeScript representation remains + * `void`, so typed construction, decoding, and encoding APIs are still modeled + * as `void`. + * + * @see {@link Undefined} for a schema that matches only the exact `undefined` value. + * @category schemas + * @since 3.10.0 + */ +export const Void: Void = make(SchemaAST.void) + +/** + * Type-level representation of {@link ObjectKeyword}. + * + * @category models + * @since 4.0.0 + */ +export interface ObjectKeyword extends Bottom {} + +/** + * Schema for the `object` type. Validates that the input is a non-null object or function + * (i.e. `typeof value === "object" && value !== null || typeof value === "function"`). + * + * @category schemas + * @since 4.0.0 + */ +export const ObjectKeyword: ObjectKeyword = make(SchemaAST.objectKeyword) + +/** + * Type-level representation returned by {@link UniqueSymbol}. + * + * @category models + * @since 4.0.0 + */ +export interface UniqueSymbol + extends Bottom> +{} + +/** + * Creates a schema for a specific symbol. Only that exact symbol satisfies the schema. + * + * **Example** (Defining a specific symbol) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const mySymbol = Symbol.for("mySymbol") + * const schema = Schema.UniqueSymbol(mySymbol) + * Schema.decodeSync(schema)(mySymbol) === mySymbol // => true + * ``` + * + * @see {@link Symbol} for a schema that accepts any symbol. + * @category constructors + * @since 4.0.0 + */ +export function UniqueSymbol(symbol: sym): UniqueSymbol { + return make(new SchemaAST.UniqueSymbol(symbol)) +} + +/** + * Namespace for struct field type utilities. + * + * **Details** + * + * These types compute the decoded `Type`, encoded `Encoded`, and constructor + * input `MakeIn` of a {@link Struct} from its field map, handling optional, + * mutable, and other field modifiers automatically. + * + * - `Struct.Fields` — constraint for the field map object + * - `Struct.Type` — decoded type of the struct + * - `Struct.Encoded` — encoded type of the struct + * - `Struct.MakeIn` — constructor input (optional/defaulted fields may be omitted) + * - `Struct.DecodingServices` / `Struct.EncodingServices` — required services + * + * @since 3.10.0 + */ +export declare namespace Struct { + /** + * Constraint for a struct field map: an object whose values are schemas. + * + * @category utility types + * @since 3.10.0 + */ + export type Fields = { readonly [x: PropertyKey]: Constraint } + + type TypeOptionalKeys = { + [K in keyof Fields]: Fields[K] extends { readonly "~type.optionality": "optional" } ? K + : never + }[keyof Fields] + + type TypeMutableKeys = { + [K in keyof Fields]: Fields[K] extends { readonly "~type.mutability": "mutable" } ? K + : never + }[keyof Fields] + + type SetOptional = Omit & Partial> + + type Mutable = { -readonly [K in keyof A]: A[K] } + + type SetMutable = Omit & Mutable> + + type Side = "Type" | "Iso" | "Encoded" + + type EncodedOptionalKeys = { + [K in keyof Fields]: Fields[K] extends { readonly "~encoded.optionality": "optional" } ? K + : never + }[keyof Fields] + + type EncodedMutableKeys = { + [K in keyof Fields]: Fields[K] extends { readonly "~encoded.mutability": "mutable" } ? K + : never + }[keyof Fields] + + type SideOptionalKeys = S extends "Encoded" ? EncodedOptionalKeys + : TypeOptionalKeys + + type SideMutableKeys = S extends "Encoded" ? EncodedMutableKeys + : TypeMutableKeys + + type ReadonlySide = { readonly [K in keyof F]: F[K][S] } + + type View< + F extends Fields, + S extends Side, + O extends keyof F = SideOptionalKeys, + M extends keyof F = SideMutableKeys + > = [O | M] extends [never] ? Simplify> + : [M] extends [never] ? Simplify, O>> + : [O] extends [never] ? Simplify, M>> + : Simplify< + SetMutable< + SetOptional, O>, + Extract, O>, M> + > + > + + /** + * Computes the decoded object type for a struct field map. + * + * **Details** + * + * Field schemas contribute their decoded `Type`. `optionalKey` and `optional` + * produce optional properties, while `mutableKey` produces writable properties. + * + * @category utility types + * @since 3.10.0 + */ + export type Type = View + + /** + * Computes the iso object type for a struct field map from each field schema's + * `Iso` type. + * + * **Details** + * + * The resulting property optionality and mutability follow the same field + * modifiers used by `Struct.Type`. + * + * @category utility types + * @since 4.0.0 + */ + export type Iso = View + + /** + * Computes the encoded object type for a struct field map. + * + * **Details** + * + * Field schemas contribute their `Encoded` type. Encoded-side optionality and + * mutability modifiers determine whether properties are optional or writable in + * the encoded shape. + * + * @category utility types + * @since 3.10.0 + */ + export type Encoded = View + + /** + * Union of all decoding service requirements needed by the schemas in a struct + * field map. + * + * @category utility types + * @since 4.0.0 + */ + export type DecodingServices = { readonly [K in keyof F]: F[K]["DecodingServices"] }[keyof F] + + /** + * Union of all encoding service requirements needed by the schemas in a struct + * field map. + * + * @category utility types + * @since 4.0.0 + */ + export type EncodingServices = { readonly [K in keyof F]: F[K]["EncodingServices"] }[keyof F] + + type TypeConstructorDefaultedKeys = { + [K in keyof Fields]: Fields[K] extends { readonly "~type.constructor.default": "with-default" } ? K + : never + }[keyof Fields] + + type ReadonlyMakeIn = { readonly [K in keyof F]: F[K]["~type.make"] } + + type MakeInView< + F extends Fields, + O extends keyof F = TypeOptionalKeys | TypeConstructorDefaultedKeys + > = [O] extends [never] ? Simplify> : Simplify, O>> + + /** + * Computes the input object type accepted when constructing a struct value. + * + * **Details** + * + * Required fields use each field schema's `~type.make` input. Fields marked + * optional or with a constructor default may be omitted. + * + * @category utility types + * @since 4.0.0 + */ + export type MakeIn = MakeInView +} + +/** + * Type-level representation returned by {@link Struct}. + * + * @category models + * @since 3.10.0 + */ +export interface Struct extends BottomLazy> { + readonly "Type": Struct.Type + readonly "Encoded": Struct.Encoded + readonly "DecodingServices": Struct.DecodingServices + readonly "EncodingServices": Struct.EncodingServices + readonly "~type.make.in": Struct.MakeIn + readonly "~type.make": Struct.MakeIn + readonly "Iso": Struct.Iso + /** + * The field definitions of this struct. Spread them into a new struct to + * reuse fields across schemas. + * + * **Example** (Reusing fields across structs) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const Timestamped = Schema.Struct({ + * createdAt: Schema.Date, + * updatedAt: Schema.Date + * }) + * + * const User = Schema.Struct({ + * ...Timestamped.fields, + * name: Schema.String, + * email: Schema.String + * }) + * Object.keys(User.fields) // => ["createdAt", "updatedAt", "name", "email"] + * ``` + */ + readonly fields: Fields + /** + * Returns a new struct with the fields modified by the provided function. + * + * **Details** + * + * Options: + * + * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints + * that were attached to the original union. Defaults to `false`. + * + * **Warning**: This is an unsafe operation. Since `mapFields` + * transformations change the schema type, the original refinement functions + * may no longer be valid or safe to apply to the transformed schema. Only + * use this option if you have verified that your refinements remain correct + * after the transformation. + */ + mapFields( + f: (fields: Fields) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Struct>> +} + +function makeStruct(ast: SchemaAST.Objects, fields: Fields): Struct { + return make(ast, { + fields, + mapFields( + this: Struct, + f: (fields: Fields) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Struct { + const fields = f(this.fields) + return makeStruct(SchemaAST.struct(fields, options?.unsafePreserveChecks ? this.ast.checks : undefined), fields) + } + }) +} + +/** + * Defines a struct schema from a map of field schemas. + * + * **Details** + * + * Each field value is a schema. Use {@link optionalKey} or {@link optional} to + * mark fields as optional, and {@link mutableKey} to mark them as mutable. + * + * The resulting schema's `Type` is a readonly object type with the fields' + * decoded types. The `Encoded` form mirrors the field schemas' encoded types. + * + * **Example** (Defining a basic struct) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const Person = Schema.Struct({ + * name: Schema.String, + * age: Schema.Number, + * email: Schema.optionalKey(Schema.String) + * }) + * + * // { readonly name: string; readonly age: number; readonly email?: string } + * type Person = typeof Person.Type + * + * Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 } + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function Struct(fields: Fields): Struct { + return makeStruct(SchemaAST.struct(fields, undefined), fields) +} + +interface fieldsAssign extends Lambda { + ( + struct: Struct + ): Struct>> + readonly "~lambda.out": this["~lambda.in"] extends Struct + ? Struct>> + : "Error: schema not eligible for fieldsAssign" +} + +/** + * Adds fields to a struct schema through a struct-mapping lambda. + * + * **When to use** + * + * Use to add the same fields to an existing struct or every struct member of a + * union. + * + * **Details** + * + * This is a shortcut for `MyStruct.mapFields(Struct.assign(fields))`. + * + * **Example** (Adding fields to a union of structs) + * + * ```ts import.meta.vitest + * import { Schema, Tuple } from "effect" + * + * // Add a new field to all members of a union of structs + * const schema = Schema.Union([ + * Schema.Struct({ a: Schema.String }), + * Schema.Struct({ b: Schema.Number }) + * ]).mapMembers(Tuple.map(Schema.fieldsAssign({ c: Schema.Number }))) + * Schema.decodeSync(schema)({ a: "a", c: 1 }) // => { a: "a", c: 1 } + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export function fieldsAssign(fields: NewFields) { + return Struct_.lambda>((struct) => struct.mapFields(Struct_.assign(fields))) +} + +/** + * Type-level representation returned by {@link encodeKeys}. + * + * @category transforming + * @since 4.0.0 + */ +export interface encodeKeys< + S extends Constraint & { readonly fields: Struct.Fields }, + M extends { readonly [K in keyof S["fields"]]?: PropertyKey } +> extends + decodeTo< + S, + Struct< + { + [ + K in keyof S["fields"] as K extends keyof M ? M[K] extends PropertyKey ? M[K] : K : K + ]: toEncoded + } + > + > +{} + +const canonicalPropertyKey = (key: PropertyKey): string | symbol => + typeof key === "symbol" ? key : globalThis.String(key) + +/** + * Renames struct keys in the encoded form without changing the decoded type. + * + * **Details** + * + * Takes a partial mapping `{ decodedKey: encodedKey }` and produces a + * transformation schema that decodes from the renamed keys and encodes back to + * the renamed keys. Keys not present in the mapping are left unchanged. + * If two existing fields would produce the same encoded key, construction + * fails. + * + * **Example** (Renaming `name` to `full_name` in the encoded form) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const Person = Schema.Struct({ name: Schema.String, age: Schema.Number }) + * const Encoded = Person.pipe(Schema.encodeKeys({ name: "full_name" })) + * + * // Decodes { full_name: "Alice", age: 30 } → { name: "Alice", age: 30 } + * Schema.decodeUnknownSync(Encoded)({ full_name: "Alice", age: 30 }) // => { name: "Alice", age: 30 } + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function encodeKeys< + S extends Constraint & { readonly fields: Struct.Fields }, + const M extends { readonly [K in keyof S["fields"]]?: PropertyKey } +>(mapping: M) { + return function(self: S): encodeKeys { + const fields: any = {} + const appliedMapping: any = Object.create(null) + const reverseMapping: any = Object.create(null) + const seenEncodedKeys = new Set() + for (const k of Reflect.ownKeys(self.fields)) { + const encoded = toEncoded(self.fields[k]) + const hasMapping = Object.hasOwn(mapping, k) + const encodedKey = hasMapping ? (mapping as any)[k] as PropertyKey : k + const canonical = canonicalPropertyKey(encodedKey) + if (seenEncodedKeys.has(canonical)) { + throw new globalThis.Error(`Duplicate encoded keys: ${formatPropertyKey(encodedKey)}`) + } + seenEncodedKeys.add(canonical) + InternalRecord.assignProperty(fields, encodedKey, encoded) + if (hasMapping) { + appliedMapping[k] = encodedKey + reverseMapping[encodedKey] = k + } + } + return Struct(fields).pipe(decodeTo( + self, + SchemaTransformation.transform({ + decode: Struct_.renameKeys(reverseMapping), + encode: Struct_.renameKeys(appliedMapping) + }) + )) as any + } +} + +/** + * Adds derived fields to a struct schema during decoding. + * + * **Details** + * + * Each new field is derived from the decoded struct value via a function that + * returns `Option`. On encoding the derived fields are stripped. This allows + * computed or enriched fields to live in the decoded type without appearing in + * the encoded form. + * + * **Example** (Adding a computed `fullName` field) + * + * ```ts import.meta.vitest + * import { Option, Schema } from "effect" + * + * const Person = Schema.Struct({ first: Schema.String, last: Schema.String }) + * const Extended = Person.pipe( + * Schema.extendTo( + * { fullName: Schema.String }, + * { fullName: (p) => Option.some(`${p.first} ${p.last}`) } + * ) + * ) + * + * const alice = Schema.decodeUnknownSync(Extended)({ first: "Alice", last: "Smith" }) + * alice.fullName // => "Alice Smith" + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function extendTo, const Fields extends Struct.Fields>( + /** The new fields to add */ + fields: Fields, + /** A function per field to derive its value from the original input */ + derive: { readonly [K in keyof Fields]: (s: S["Type"]) => Option_.Option } +) { + return ( + self: S + ): decodeTo } & Fields>>, S> => { + const f = Record_.map(self.fields, toType) + const to = Struct({ ...f, ...fields }) + return self.pipe(decodeTo( + to, + SchemaTransformation.transform({ + decode: (input) => { + const out: any = { ...input } + for (const k in fields) { + const f = derive[k] + const o = f(input) + if (Option_.isSome(o)) { + InternalRecord.assignProperty(out, k, o.value) + } + } + return out + }, + encode: (input) => { + const out = { ...input } + for (const k in fields) { + delete out[k] + } + return out + } + }) + )) as any + } +} + +/** + * Namespace for `Record` type utilities. + * + * **Details** + * + * - `Record.Key` — constraint for the key schema (must encode to `PropertyKey`) + * - `Record.Type` — decoded type of the record + * - `Record.Encoded` — encoded type of the record + * + * @since 3.10.0 + */ +export declare namespace Record { + /** + * Constraint for schemas that can be used as record keys. + * + * **Details** + * + * The key schema must decode and encode property keys (`string`, `number`, or + * `symbol`) so it can describe object property names. + * + * @category utility types + * @since 4.0.0 + */ + export interface Key extends Codec { + readonly "~type.make": PropertyKey + readonly "Iso": PropertyKey + } + + /** + * Computes the decoded object type for a record schema from its key and value + * schemas. + * + * **Details** + * + * The key schema supplies the property keys and the value schema supplies each + * property's decoded `Type`. Optional and mutable value schemas affect the + * resulting property optionality and writability. + * + * @category utility types + * @since 3.10.0 + */ + export type Type = Value extends + { readonly "~type.optionality": "optional" } ? + Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Type"]]?: Value["Type"] } + : { readonly [P in Key["Type"]]?: Value["Type"] } + : Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Type"]]: Value["Type"] } + : { readonly [P in Key["Type"]]: Value["Type"] } + + /** + * Computes the iso object type for a record schema from the key schema's `Iso` + * keys and the value schema's `Iso` values. + * + * @category utility types + * @since 4.0.0 + */ + export type Iso = Value extends + { readonly "~type.optionality": "optional" } ? + Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Iso"]]?: Value["Iso"] } + : { readonly [P in Key["Iso"]]?: Value["Iso"] } + : Value extends { readonly "~type.mutability": "mutable" } ? { [P in Key["Iso"]]: Value["Iso"] } + : { readonly [P in Key["Iso"]]: Value["Iso"] } + + /** + * Computes the encoded object type for a record schema from the key and value + * schemas' encoded types. + * + * **Details** + * + * Encoded-side optionality and mutability on the value schema determine whether + * the encoded record properties are optional or writable. + * + * @category utility types + * @since 3.10.0 + */ + export type Encoded = Value extends + { readonly "~encoded.optionality": "optional" } ? + Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["Encoded"]]?: Value["Encoded"] } + : { readonly [P in Key["Encoded"]]?: Value["Encoded"] } + : Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["Encoded"]]: Value["Encoded"] } + : { readonly [P in Key["Encoded"]]: Value["Encoded"] } + + /** + * Union of the decoding service requirements of a record's key schema and value + * schema. + * + * @category utility types + * @since 4.0.0 + */ + export type DecodingServices = + | Key["DecodingServices"] + | Value["DecodingServices"] + + /** + * Union of the encoding service requirements of a record's key schema and value + * schema. + * + * @category utility types + * @since 4.0.0 + */ + export type EncodingServices = + | Key["EncodingServices"] + | Value["EncodingServices"] + + /** + * Computes the input object type accepted when constructing a record value. + * + * **Details** + * + * Keys use the key schema's `~type.make` type and values use the value schema's + * `~type.make` type. Value optionality and mutability determine whether + * properties are optional or writable. + * + * @category utility types + * @since 4.0.0 + */ + export type MakeIn = Value extends + { readonly "~encoded.optionality": "optional" } ? + Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["~type.make"]]?: Value["~type.make"] } + : { readonly [P in Key["~type.make"]]?: Value["~type.make"] } + : Value extends { readonly "~encoded.mutability": "mutable" } ? { [P in Key["~type.make"]]: Value["~type.make"] } + : { readonly [P in Key["~type.make"]]: Value["~type.make"] } +} + +/** + * Type-level representation returned by {@link Record}. + * + * @category models + * @since 4.0.0 + */ +export interface $Record extends + BottomLazy< + SchemaAST.Objects, + $Record + > +{ + readonly "Type": Record.Type + readonly "Encoded": Record.Encoded + readonly "DecodingServices": Record.DecodingServices + readonly "EncodingServices": Record.EncodingServices + readonly "~type.make.in": Simplify> + readonly "~type.make": Simplify> + readonly "Iso": Record.Iso + readonly key: Key + readonly value: Value +} + +/** + * Defines a record schema whose dynamic properties are selected by a key schema + * and decoded with a value schema. + * + * **Details** + * + * For dynamic keys, the key schema selects matching own properties and the + * value schema decodes or encodes only those selected properties. Checks on + * string, number, symbol, and template literal key schemas narrow which + * properties are selected. + * + * For transformed key schemas, property selection is based on encoded property + * names before the selected key is decoded. + * + * **Gotchas** + * + * When decoded or encoded key transformations produce the same property key, + * sequential parsing applies selected own properties in selection order, so + * the later selected property overwrites the earlier value. With concurrency + * greater than `1`, completion order determines which value is retained. + * + * **Example** (Defining a string-keyed record of numbers) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Record(Schema.String, Schema.Number) + * + * // { readonly [x: string]: number } + * type R = typeof schema.Type + * + * Schema.decodeUnknownSync(schema)({ a: 1, b: 2 }) // => { a: 1, b: 2 } + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function Record( + key: Key, + value: Value +): $Record { + return make(SchemaAST.record(key.ast, value.ast), { key, value }) +} + +/** + * Namespace for `StructWithRest` type utilities. + * + * **Details** + * + * - `StructWithRest.Type` — decoded type (struct type intersected with record types) + * - `StructWithRest.Encoded` — encoded type + * + * @since 4.0.0 + */ +export declare namespace StructWithRest { + /** + * Constraint for object-like schemas that can be used as the fixed portion of a + * `StructWithRest` schema. + * + * @category utility types + * @since 4.0.0 + */ + export type Objects = Constraint & { readonly ast: SchemaAST.Objects } + + /** + * Readonly list of record schemas that provide the additional index signatures + * for a `StructWithRest` schema. + * + * @category utility types + * @since 3.10.0 + */ + export type Records = ReadonlyArray<$Record> + + type MergeTuple> = T extends readonly [infer Head, ...infer Tail] ? + Head & MergeTuple + : {} + + type Intersect< + S extends Objects, + Records extends StructWithRest.Records, + Side extends "Type" | "Iso" | "Encoded" | "~type.make" + > = + & S[Side] + & MergeTuple<{ readonly [K in keyof Records]: Records[K][Side] }> + + /** + * Computes the decoded type for `StructWithRest` by intersecting the base object + * schema's decoded `Type` with the decoded types of all rest record schemas. + * + * @category utility types + * @since 3.10.0 + */ + export type Type = Intersect + + /** + * Computes the iso type for `StructWithRest` by intersecting the base object + * schema's `Iso` type with the `Iso` types of all rest record schemas. + * + * @category utility types + * @since 4.0.0 + */ + export type Iso = Intersect + + /** + * Computes the encoded type for `StructWithRest` by intersecting the base object + * schema's encoded type with the encoded types of all rest record schemas. + * + * @category utility types + * @since 3.10.0 + */ + export type Encoded = Intersect + + /** + * Computes the input type accepted when constructing a `StructWithRest` value by + * intersecting the base object's make input with the make inputs of all rest + * record schemas. + * + * @category utility types + * @since 4.0.0 + */ + export type MakeIn = Intersect + + type Services< + S extends Objects, + Records extends StructWithRest.Records, + Side extends "DecodingServices" | "EncodingServices" + > = + | S[Side] + | { [K in keyof Records]: Records[K][Side] }[number] + + /** + * Union of the decoding service requirements of the base object schema and all + * rest record schemas. + * + * @category utility types + * @since 4.0.0 + */ + export type DecodingServices = Services< + S, + Records, + "DecodingServices" + > + + /** + * Union of the encoding service requirements of the base object schema and all + * rest record schemas. + * + * @category utility types + * @since 4.0.0 + */ + export type EncodingServices = Services< + S, + Records, + "EncodingServices" + > + + type IncompatibleKeys> = { + [K in OK]: Required>[K] extends B[K] ? never : K + }[OK] + + type IncompatibleSideKeys< + S extends Objects, + Records extends StructWithRest.Records, + Side extends "Type" | "Encoded" | "Iso" | "~type.make" + > = { + [I in keyof Records]: Records[I][Side] extends object ? IncompatibleKeys : never + }[number] + + type IncompatibleRecords = + | IncompatibleSideKeys + | IncompatibleSideKeys + | IncompatibleSideKeys + | IncompatibleSideKeys + + /** + * Checks whether fixed fields are compatible with the rest record schemas. + * + * **Details** + * + * Returns `true` when all fixed fields can also satisfy the matching rest + * index signatures. Returns a diagnostic object when TypeScript would make + * the resulting intersection too narrow for one or more fixed keys. + * + * **Example** (Checking record compatibility) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const user = Schema.Struct({ id: Schema.String }) + * const stringExtras = [Schema.Record(Schema.String, Schema.String)] as const + * + * type UserCheck = Schema.StructWithRest.ValidateRecords + * + * const userCheck: UserCheck = true + * void userCheck + * + * const counter = Schema.Struct({ count: Schema.NumberFromString }) + * + * type CounterCheck = Schema.StructWithRest.ValidateRecords + * // ^? { "incompatible index signatures": "count" } + * + * const counterCheck = null as unknown as CounterCheck + * void counterCheck + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type ValidateRecords< + S extends Objects, + Records extends StructWithRest.Records + > = [IncompatibleRecords] extends [never] ? true + : { + "incompatible index signatures": IncompatibleRecords + } +} + +/** + * Type-level representation returned by {@link StructWithRest}. + * + * @category models + * @since 4.0.0 + */ +export interface StructWithRest< + S extends StructWithRest.Objects, + Records extends StructWithRest.Records +> extends + BottomLazy< + SchemaAST.Objects, + StructWithRest + > +{ + readonly "Type": Simplify> + readonly "Encoded": Simplify> + readonly "DecodingServices": StructWithRest.DecodingServices + readonly "EncodingServices": StructWithRest.EncodingServices + readonly "~type.make.in": Simplify> + readonly "~type.make": Simplify> + readonly "Iso": Simplify> + readonly schema: S + readonly records: Records +} + +/** + * Extends a struct schema with one or more record (index-signature) schemas, + * producing a schema whose decoded type intersects the struct and all records. + * + * **Gotchas** + * + * TypeScript index signatures also apply to fixed keys. `StructWithRest` does + * not reject incompatible fixed fields at the call site; use + * `StructWithRest.ValidateRecords` when you want an explicit type-level + * compatibility check. + * + * **Example** (Defining structs with string-indexed extra keys) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.StructWithRest( + * Schema.Struct({ id: Schema.Number }), + * [Schema.Record(Schema.String, Schema.Number)] + * ) + * + * // { readonly id: number, readonly [x: string]: number } + * type T = typeof schema.Type + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export function StructWithRest< + const S extends StructWithRest.Objects, + const Records extends StructWithRest.Records +>( + schema: S, + records: Records +): StructWithRest { + return make(SchemaAST.structWithRest(schema.ast, records.map(SchemaAST.getAST)), { schema, records }) +} + +/** + * Namespace for `Tuple` type utilities. + * + * **Details** + * + * - `Tuple.Elements` — constraint for the element schema array + * - `Tuple.Type` — decoded tuple type + * - `Tuple.Encoded` — encoded tuple type + * - `Tuple.MakeIn` — constructor input tuple + * + * @since 3.10.0 + */ +export declare namespace Tuple { + /** + * Constraint for the readonly array of element schemas used to define a + * fixed-length `Tuple` schema. + * + * @category utility types + * @since 3.10.0 + */ + export type Elements = ReadonlyArray + + type Type_< + Elements, + Out extends ReadonlyArray = readonly [] + > = Elements extends readonly [infer Head, ...infer Tail] ? + Head extends { readonly "Type": infer T } ? + Head extends { readonly "~type.optionality": "optional" } ? Type_ + : Type_ + : Out + : Out + + /** + * Computes the decoded tuple type for a tuple element schema array. + * + * **Details** + * + * Each element contributes its decoded `Type`; optional element schemas produce + * optional tuple positions. + * + * @category utility types + * @since 3.10.0 + */ + export type Type = Type_ + + type Iso_< + Elements, + Out extends ReadonlyArray = readonly [] + > = Elements extends readonly [infer Head, ...infer Tail] ? + Head extends { readonly "Iso": infer T } ? + Head extends { readonly "~type.optionality": "optional" } ? Iso_ + : Iso_ + : Out + : Out + + /** + * Computes the iso tuple type for a tuple element schema array from each + * element schema's `Iso` type. + * + * @category utility types + * @since 4.0.0 + */ + export type Iso = Iso_ + + type Encoded_< + Elements, + Out extends ReadonlyArray = readonly [] + > = Elements extends readonly [infer Head, ...infer Tail] ? + Head extends { readonly "Encoded": infer T } ? + Head extends { readonly "~encoded.optionality": "optional" } ? Encoded_ + : Encoded_ + : Out + : Out + + /** + * Computes the encoded tuple type for a tuple element schema array. + * + * **Details** + * + * Each element contributes its `Encoded` type; encoded-side optional element + * schemas produce optional tuple positions. + * + * @category utility types + * @since 3.10.0 + */ + export type Encoded = Encoded_ + + /** + * Union of all decoding service requirements needed by the tuple element + * schemas. + * + * @category utility types + * @since 4.0.0 + */ + export type DecodingServices = E[number]["DecodingServices"] + + /** + * Union of all encoding service requirements needed by the tuple element + * schemas. + * + * @category utility types + * @since 4.0.0 + */ + export type EncodingServices = E[number]["EncodingServices"] + + type MakeIn_< + E, + Out extends ReadonlyArray = readonly [] + > = E extends readonly [infer Head, ...infer Tail] ? + Head extends { "~type.make": infer T } ? + Head extends + { readonly "~type.optionality": "optional" } | { readonly "~type.constructor.default": "with-default" } ? + MakeIn_ : + MakeIn_ + : Out : + Out + + /** + * Computes the input tuple type accepted when constructing a tuple value. + * + * **Details** + * + * Each element uses its `~type.make` input type. Optional elements and elements + * with constructor defaults produce optional tuple positions. + * + * @category utility types + * @since 4.0.0 + */ + export type MakeIn = MakeIn_ +} + +/** + * Type-level representation returned by {@link Tuple}. + * + * @category models + * @since 3.10.0 + */ +export interface Tuple extends + BottomLazy< + SchemaAST.Arrays, + Tuple + > +{ + readonly "Type": Tuple.Type + readonly "Encoded": Tuple.Encoded + readonly "DecodingServices": Tuple.DecodingServices + readonly "EncodingServices": Tuple.EncodingServices + readonly "~type.make.in": Tuple.MakeIn + readonly "~type.make": Tuple.MakeIn + readonly "Iso": Tuple.Iso + readonly elements: Elements + /** + * Returns a new tuple with the elements modified by the provided function. + * + * **Details** + * + * Options: + * + * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints + * that were attached to the original union. Defaults to `false`. + * + * **Warning**: This is an unsafe operation. Since `mapFields` + * transformations change the schema type, the original refinement functions + * may no longer be valid or safe to apply to the transformed schema. Only + * use this option if you have verified that your refinements remain correct + * after the transformation. + */ + mapElements( + f: (elements: Elements) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Tuple>> +} + +function makeTuple(ast: SchemaAST.Arrays, elements: Elements): Tuple { + return make(ast, { + elements, + mapElements( + this: Tuple, + f: (elements: Elements) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Tuple>> { + const elements = f(this.elements) + return makeTuple(SchemaAST.tuple(elements, options?.unsafePreserveChecks ? this.ast.checks : undefined), elements) + } + }) +} + +/** + * Defines a fixed-length tuple schema from an array of element schemas. + * + * **Example** (Defining a pair of string and number) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Tuple([Schema.String, Schema.Number]) + * + * Schema.decodeUnknownSync(schema)(["hello", 42]) // => ["hello", 42] + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function Tuple>(elements: Elements): Tuple { + return makeTuple(SchemaAST.tuple(elements), elements) +} + +/** + * Namespace for `TupleWithRest` type utilities. + * + * **Details** + * + * - `TupleWithRest.TupleType` — constraint for the leading tuple schema + * - `TupleWithRest.Rest` — the rest element schema(s) + * - `TupleWithRest.Type` — decoded type (fixed elements + rest) + * - `TupleWithRest.Encoded` — encoded type + * + * @since 4.0.0 + */ +export declare namespace TupleWithRest { + /** + * Constraint for tuple-like schemas that can be used as the fixed leading + * portion of a `TupleWithRest` schema. + * + * @category utility types + * @since 3.10.0 + */ + export type TupleType = Constraint & { + readonly Type: ReadonlyArray + readonly Encoded: ReadonlyArray + readonly ast: SchemaAST.Arrays + readonly "~type.make": ReadonlyArray + readonly "Iso": ReadonlyArray + } + + /** + * Non-empty list of schemas used for the rest portion of a `TupleWithRest`. + * + * **Details** + * + * The first schema describes the repeated rest element. Additional schemas, when + * present, describe trailing tuple elements after the repeated rest segment. + * + * @category utility types + * @since 3.10.0 + */ + export type Rest = readonly [Constraint, ...Array] + + /** + * Computes the decoded tuple type for a `TupleWithRest`. + * + * **Details** + * + * The output starts with the fixed tuple elements, continues with zero or more + * values decoded by the first rest schema, and includes any trailing rest schemas + * as fixed tuple positions. + * + * @category utility types + * @since 3.10.0 + */ + export type Type, Rest extends TupleWithRest.Rest> = Rest extends + readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray] ? Readonly<[ + ...T, + ...Array, + ...{ readonly [K in keyof Tail]: Tail[K]["Type"] } + ]> : + T + + /** + * Computes the iso tuple type for a `TupleWithRest`. + * + * **Details** + * + * The output starts with the fixed tuple's `Iso` elements, continues with zero + * or more values using the first rest schema's `Iso`, and includes any trailing + * rest schemas as fixed tuple positions. + * + * @category utility types + * @since 4.0.0 + */ + export type Iso, Rest extends TupleWithRest.Rest> = Rest extends + readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray] ? Readonly<[ + ...T, + ...Array, + ...{ readonly [K in keyof Tail]: Tail[K]["Iso"] } + ]> : + T + + /** + * Computes the encoded tuple type for `TupleWithRest`. + * + * **Details** + * + * The leading tuple's encoded elements are kept first. The encoded type of the + * first rest schema may repeat zero or more times, and the encoded types of any + * additional rest schemas become required trailing tuple elements. + * + * @category utility types + * @since 3.10.0 + */ + export type Encoded, Rest extends TupleWithRest.Rest> = Rest extends + readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray] ? readonly [ + ...E, + ...Array, + ...{ readonly [K in keyof Tail]: Tail[K]["Encoded"] } + ] : + E + + /** + * Computes the constructor input tuple type for `TupleWithRest`. + * + * **Details** + * + * The leading tuple's make input elements are kept first. The make input type of + * the first rest schema may repeat zero or more times, and the make input types + * of any additional rest schemas become required trailing tuple elements. + * + * @category utility types + * @since 4.0.0 + */ + export type MakeIn, Rest extends TupleWithRest.Rest> = Rest extends + readonly [infer Head extends Constraint, ...infer Tail extends ReadonlyArray] ? readonly [ + ...M, + ...Array, + ...{ readonly [K in keyof Tail]: Tail[K]["~type.make"] } + ] : + M +} + +/** + * Type-level representation returned by {@link TupleWithRest}. + * + * @category models + * @since 4.0.0 + */ +export interface TupleWithRest< + S extends TupleWithRest.TupleType, + Rest extends TupleWithRest.Rest +> extends + BottomLazy< + SchemaAST.Arrays, + TupleWithRest + > +{ + readonly "Type": TupleWithRest.Type + readonly "Encoded": TupleWithRest.Encoded + readonly "DecodingServices": S["DecodingServices"] | Rest[number]["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] | Rest[number]["EncodingServices"] + readonly "~type.make.in": TupleWithRest.MakeIn + readonly "~type.make": TupleWithRest.MakeIn + readonly "Iso": TupleWithRest.Iso + readonly schema: S + readonly rest: Rest +} + +/** + * Extends a fixed-length tuple schema with a variadic rest segment. + * + * **Details** + * + * The resulting tuple starts with the fixed elements from `schema`. The first + * schema in `rest` is the repeatable element schema, and any additional schemas + * in `rest` are required trailing tuple elements after the variadic segment. For + * example, `[Schema.Boolean, Schema.String]` represents zero or more booleans + * followed by a final string. + * + * **Example** (Defining tuples with rest elements) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * // [string, number, ...boolean[]] + * const schema = Schema.TupleWithRest( + * Schema.Tuple([Schema.String, Schema.Number]), + * [Schema.Boolean] + * ) + * + * Schema.decodeUnknownSync(schema)(["hello", 1, true, false]) // => ["hello", 1, true, false] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export function TupleWithRest, const Rest extends TupleWithRest.Rest>( + schema: S, + rest: Rest +): TupleWithRest { + return make(SchemaAST.tupleWithRest(schema.ast, rest.map(SchemaAST.getAST)), { schema, rest }) +} + +/** + * Type-level representation returned by {@link Array}. + * + * @category models + * @since 4.0.0 + */ +export interface $Array extends + BottomLazy< + SchemaAST.Arrays, + $Array + > +{ + readonly "Type": ReadonlyArray + readonly "Encoded": ReadonlyArray + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": ReadonlyArray + readonly "~type.make": ReadonlyArray + readonly "Iso": ReadonlyArray + readonly value: S +} + +interface ArrayLambda extends Lambda { + (self: S): $Array + readonly "~lambda.out": this["~lambda.in"] extends Constraint ? $Array : never +} + +/** + * @category constructors + * @since 4.0.0 + */ +const ArraySchema = Struct_.lambda((schema) => + make(new SchemaAST.Arrays(false, [], [schema.ast]), { value: schema }) +) + +export { + /** + * Defines a `ReadonlyArray` schema for a given element schema. + * + * **Example** (Defining an array of strings) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Array(Schema.String) + * + * Schema.decodeUnknownSync(schema)(["a", "b", "c"]) // => ["a", "b", "c"] + * ``` + * + * @category constructors + * @since 4.0.0 + */ + ArraySchema as Array +} + +/** + * Type-level representation returned by {@link NonEmptyArray}. + * + * @category models + * @since 3.10.0 + */ +export interface NonEmptyArray extends + BottomLazy< + SchemaAST.Arrays, + NonEmptyArray + > +{ + readonly "Type": readonly [S["Type"], ...Array] + readonly "Encoded": readonly [S["Encoded"], ...Array] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": readonly [S["~type.make"], ...Array] + readonly "~type.make": readonly [S["~type.make"], ...Array] + readonly "Iso": readonly [S["Iso"], ...Array] + readonly value: S +} + +interface NonEmptyArrayLambda extends Lambda { + (self: S): NonEmptyArray + readonly "~lambda.out": this["~lambda.in"] extends Constraint ? NonEmptyArray : never +} + +/** + * Defines a non-empty `ReadonlyArray` schema — at least one element required. + * Type is `readonly [T, ...T[]]`. + * + * **Example** (Defining a non-empty array of numbers) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.NonEmptyArray(Schema.Number) + * + * Schema.decodeUnknownSync(schema)([1, 2, 3]) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export const NonEmptyArray = Struct_.lambda((schema) => + make(new SchemaAST.Arrays(false, [schema.ast], [schema.ast]), { value: schema }) +) + +/** + * Type-level representation returned by {@link ArrayEnsure}. + * + * @category constructors + * @since 3.10.0 + */ +export interface ArrayEnsure extends decodeTo<$Array>, Union]>> { + readonly "Rebuild": ArrayEnsure +} + +/** + * Creates a schema that accepts either a value decoded by `schema` or an array + * decoded by `Schema.Array(schema)`, then returns an array. + * + * **When to use** + * + * Use to accept input that may be provided either as one item or as an array, + * while normalizing decoded values to a readonly array. + * + * **Details** + * + * During encoding, one-element arrays are encoded as the single element. Empty + * arrays and arrays with two or more elements are encoded as arrays. + * + * **Gotchas** + * + * The single-value branch is tried before the array branch. If `schema` itself + * accepts arrays, an array input can be treated as one value and wrapped in a + * one-element array. + * + * @see {@link Array} for accepting only array input + * @see {@link NonEmptyArray} for requiring at least one decoded element + * + * @category constructors + * @since 3.10.0 + */ +export function ArrayEnsure(schema: S): ArrayEnsure { + return Union([schema, ArraySchema(schema)]).pipe(decodeTo( + ArraySchema(toType(schema)), + SchemaTransformation.transform({ + decode: Arr.ensure, + encode: (array) => array.length === 1 ? array[0] : array + }) + )) +} + +/** + * Type-level representation returned by {@link UniqueArray}. + * + * @category models + * @since 4.0.0 + */ +export interface UniqueArray extends $Array { + readonly "Rebuild": UniqueArray +} + +/** + * Returns a new array schema that ensures all elements are unique. + * + * **Details** + * + * The equivalence used to determine uniqueness is the one provided by + * `Schema.toEquivalence(item)`. + * + * @category constructors + * @since 4.0.0 + */ +export function UniqueArray(item: S): UniqueArray { + return ArraySchema(item).check(isUnique()) +} + +/** + * Type-level representation returned by {@link mutable}. + * + * @category transforming + * @since 3.10.0 + */ +export interface mutable extends + BottomLazy< + S["ast"], + mutable, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": Mutable + readonly "Encoded": Mutable + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + // "~type.make" and "~type.make.in" as they are because they are contravariant + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] + readonly schema: S +} + +interface mutableLambda extends Lambda { + (self: S): mutable + readonly "~lambda.out": this["~lambda.in"] extends Constraint & { readonly "ast": SchemaAST.Arrays } ? + mutable + : "Error: schema not eligible for mutable" +} + +/** + * Makes an array or tuple schema mutable, removing the `readonly` modifier. + * + * **Example** (Defining mutable arrays) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.mutable(Schema.Array(Schema.Number)) + * + * // number[] (mutable) + * type T = typeof schema.Type + * const value: T = [1, 2] + * value.push(3) + * value // => [1, 2, 3] + * ``` + * + * @category transforming + * @since 3.10.0 + */ +export const mutable = Struct_.lambda((schema) => { + return make(new SchemaAST.Arrays(true, schema.ast.elements, schema.ast.rest), { schema }) +}) + +/** + * Type-level representation returned by {@link Union}. + * + * @category models + * @since 3.10.0 + */ +export interface Union> extends + BottomLazy< + SchemaAST.Union<{ [K in keyof Members]: Members[K]["ast"] }[number]>, + Union + > +{ + readonly "Type": { [K in keyof Members]: Members[K]["Type"] }[number] + readonly "Encoded": { [K in keyof Members]: Members[K]["Encoded"] }[number] + readonly "DecodingServices": { [K in keyof Members]: Members[K]["DecodingServices"] }[number] + readonly "EncodingServices": { [K in keyof Members]: Members[K]["EncodingServices"] }[number] + readonly "~type.make.in": { [K in keyof Members]: Members[K]["~type.make"] }[number] + readonly "~type.make": { [K in keyof Members]: Members[K]["~type.make"] }[number] + readonly "Iso": { [K in keyof Members]: Members[K]["Iso"] }[number] + readonly members: Members + /** + * Returns a new union with the members modified by the provided function. + * + * **Details** + * + * Options: + * + * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints + * that were attached to the original union. Defaults to `false`. + * + * **Warning**: This is an unsafe operation. Since `mapFields` + * transformations change the schema type, the original refinement functions + * may no longer be valid or safe to apply to the transformed schema. Only + * use this option if you have verified that your refinements remain correct + * after the transformation. + */ + mapMembers>( + f: (members: Members) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Union>> +} + +function makeUnion>( + ast: SchemaAST.Union, + members: Members +): Union { + return make(ast, { + members, + mapMembers>( + this: Union, + f: (members: Members) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Union>> { + const members = f(this.members) + return makeUnion( + SchemaAST.union(members, this.ast.mode, options?.unsafePreserveChecks ? this.ast.checks : undefined), + members + ) + } + }) +} + +/** + * Creates a union schema from an array of member schemas. Members are tested in + * order; the first match is returned. + * + * **Details** + * + * Optionally, specify `mode`: + * - `"anyOf"` (default) — matches if any member matches. + * - `"oneOf"` — matches if exactly one member matches. + * + * **Example** (Defining a string or number union) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Union([Schema.String, Schema.Number]) + * + * Schema.decodeUnknownSync(schema)("hello") // => "hello" + * Schema.decodeUnknownSync(schema)(42) // => 42 + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function Union>( + members: Members, + options?: { mode?: "anyOf" | "oneOf" } +): Union { + return makeUnion(SchemaAST.union(members, options?.mode ?? "anyOf", undefined), members) +} + +/** + * Type-level representation returned by {@link Literals}. + * + * @category models + * @since 4.0.0 + */ +export interface Literals> + extends Bottom, Literals> +{ + readonly literals: L + readonly members: { readonly [K in keyof L]: Literal } + /** + * Map over the members of the union. + */ + mapMembers>(f: (members: this["members"]) => To): Union>> + + pick>(literals: L2): Literals + + transform( + to: L2 + ): Union<{ [I in keyof L]: decodeTo, Literal> }> +} + +/** + * Creates a union schema from an array of literal values. + * + * **Example** (Defining status codes) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Literals(["active", "inactive", "pending"]) + * Schema.decodeSync(schema)("active") // => "active" + * ``` + * + * @see {@link Literal} for a schema that represents a single literal. + * @category constructors + * @since 4.0.0 + */ +export function Literals>(literals: L): Literals { + const members = literals.map(Literal) as { readonly [K in keyof L]: Literal } + return make(SchemaAST.union(members, "anyOf", undefined), { + literals, + members, + mapMembers>( + this: Literals, + f: (members: Literals["members"]) => To + ): Union>> { + return Union(f(this.members)) + }, + pick>(literals: L2): Literals { + return Literals(literals) + }, + transform( + to: L2 + ): Union<{ [I in keyof L]: decodeTo, Literal> }> { + return Union(members.map((member, index) => member.transform(to[index]))) as any + } + }) +} + +/** + * Type-level representation returned by {@link NullOr}. + * + * @category models + * @since 3.10.0 + */ +export interface NullOr extends Union { + readonly "Rebuild": NullOr +} + +interface NullOrLambda extends Lambda { + (self: S): NullOr + readonly "~lambda.out": this["~lambda.in"] extends Constraint ? NullOr : never +} + +/** + * Creates a union schema of `S | null`. + * + * @category constructors + * @since 3.10.0 + */ +export const NullOr = Struct_.lambda((self) => Union([self, Null])) + +/** + * Type-level representation returned by {@link UndefinedOr}. + * + * @category models + * @since 3.10.0 + */ +export interface UndefinedOr extends Union { + readonly "Rebuild": UndefinedOr +} + +interface UndefinedOrLambda extends Lambda { + (self: S): UndefinedOr + readonly "~lambda.out": this["~lambda.in"] extends Constraint ? UndefinedOr : never +} + +/** + * Creates a union schema of `S | undefined`. + * + * @category constructors + * @since 3.10.0 + */ +export const UndefinedOr = Struct_.lambda((self) => Union([self, Undefined])) + +/** + * Type-level representation returned by {@link NullishOr}. + * + * @category models + * @since 3.10.0 + */ +export interface NullishOr extends Union { + readonly "Rebuild": NullishOr +} + +interface NullishOrLambda extends Lambda { + (self: S): NullishOr + readonly "~lambda.out": this["~lambda.in"] extends Constraint ? NullishOr : never +} + +/** + * Creates a union schema of `S | null | undefined`. + * + * @category constructors + * @since 3.10.0 + */ +export const NullishOr = Struct_.lambda((self) => Union([self, Null, Undefined])) + +/** + * Type-level representation returned by {@link suspend}. + * + * @category models + * @since 3.10.0 + */ +export interface suspend extends + BottomLazy< + SchemaAST.Suspend, + suspend, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] +} + +/** + * Creates a suspended schema that defers evaluation until needed. This is + * essential for creating recursive schemas where a schema references itself, + * preventing infinite recursion during schema definition. + * + * **Example** (Defining recursive tree schemas) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * interface Tree { + * readonly value: number + * readonly children: ReadonlyArray + * } + * + * const Tree = Schema.Struct({ + * value: Schema.Number, + * children: Schema.Array(Schema.suspend((): Schema.Codec => Tree)) + * }) + * Schema.decodeSync(Tree)({ value: 1, children: [] }) // => { value: 1, children: [] } + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function suspend(f: () => S): suspend { + return make(new SchemaAST.Suspend(() => f().ast)) +} + +/** + * Attaches one or more filter checks to a schema without changing the + * TypeScript type. + * + * **Example** (Adding checks to a schema) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const AgeSchema = Schema.Finite.pipe( + * Schema.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(120)) + * ) + * Schema.is(AgeSchema)(42) // => true + * Schema.is(AgeSchema)(121) // => false + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export function check( + ...checks: readonly [SchemaAST.Check, ...Array>] +) { + return (self: S): S["Rebuild"] => self.check(...checks) +} + +/** + * Type-level representation returned by {@link refine}. + * + * @category filtering + * @since 3.10.0 + */ +export interface refine extends + BottomLazy< + S["ast"], + refine, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": T + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": T + readonly "Iso": T + readonly schema: S +} + +/** + * Narrows the TypeScript type of a schema's output via a type guard predicate, + * attaching the guard as a runtime filter check. + * + * **Details** + * + * The `annotations` parameter annotates the filter created by the refinement. + * With the default formatter, failed refinements use `message` first, + * `expected` second, and `` when neither is provided. `identifier` + * names type-level failures before the refinement runs; it does not name the + * failed refinement itself. + * + * @category filtering + * @since 3.10.0 + */ +export function refine( + refinement: (value: S["Type"]) => value is T, + annotations?: Annotations.Filter +) { + return (schema: S): refine => + make(SchemaAST.appendChecks(schema.ast, [SchemaAST.makeFilterByGuard(refinement, annotations)]), { schema }) +} + +type DistributeBrands = UnionToIntersection : never> + +/** + * Type-level representation returned by {@link brand}. + * + * @category branding + * @since 3.10.0 + */ +export interface brand extends + BottomLazy< + S["ast"], + brand, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] & DistributeBrands + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["Type"] & DistributeBrands + readonly "Iso": S["Type"] & DistributeBrands + readonly schema: S + readonly identifier: string +} + +/** + * Adds a nominal brand to a schema, intersecting the output type with + * `Brand.Brand` to prevent accidental mixing of structurally identical types. + * + * **When to use** + * + * Use to make values decoded by an existing schema nominally distinct when the + * schema already carries the runtime validation you need. + * + * **Gotchas** + * + * `brand` adds brand metadata and narrows the TypeScript output type, but it + * does not add runtime checks. + * + * @see {@link fromBrand} for applying a Brand constructor's checks along with the brand tag + * + * @category branding + * @since 3.10.0 + */ +export function brand(identifier: B) { + return (schema: S): brand => + make(SchemaAST.brand(schema.ast, identifier), { schema, identifier }) +} + +/** + * Creates a branded schema from a {@link Brand.Constructor}, applying the + * constructor's checks and brand tag to the underlying schema. + * + * @category branding + * @since 3.10.0 + */ +export function fromBrand>(identifier: string, ctor: Brand.Constructor) { + return }>( + self: S + ): brand> => { + return (ctor.checks ? self.check(...ctor.checks) : self).pipe(brand(identifier)) + } +} + +/** + * Type-level representation returned by {@link middlewareDecoding}. + * + * @category decoding + * @since 4.0.0 + */ +export interface middlewareDecoding extends + BottomLazy< + S["ast"], + middlewareDecoding, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": RD + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] + readonly schema: S +} + +/** + * Intercepts the decoding pipeline of a schema. + * + * **Details** + * + * The provided function receives the current decoding `Effect` and `ParseOptions`, + * and returns a new `Effect` — potentially adding service requirements (`RD`), + * recovering from errors, or augmenting the result. + * + * **Example** (Logging decode failures) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * const events: Array = [] + * const Logged = Schema.String.pipe( + * Schema.middlewareDecoding((effect) => + * Effect.tapError(effect, () => Effect.sync(() => events.push("decode failed"))) + * ) + * ) + * Effect.runSync(Effect.result(Schema.decodeUnknownEffect(Logged)(42))) + * events // => ["decode failed"] + * ``` + * + * @see {@link catchDecoding} for a simpler error-recovery variant + * @category decoding + * @since 4.0.0 + */ +export function middlewareDecoding( + decode: ( + effect: Effect.Effect, SchemaIssue.Issue, S["DecodingServices"]>, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, RD> +) { + return (schema: S): middlewareDecoding => + make( + SchemaAST.middlewareDecoding(schema.ast, new SchemaTransformation.Middleware(decode, identity)), + { schema } + ) +} + +/** + * Type-level representation returned by {@link middlewareEncoding}. + * + * @category encoding + * @since 4.0.0 + */ +export interface middlewareEncoding extends + BottomLazy< + S["ast"], + middlewareEncoding, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": RE + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] + readonly schema: S +} + +/** + * Intercepts the encoding pipeline of a schema. + * + * **Details** + * + * The provided function receives the current encoding `Effect` and `ParseOptions`, + * and returns a new `Effect` — potentially adding service requirements (`RE`), + * recovering from errors, or augmenting the result. + * + * **Example** (Logging encode failures) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * const events: Array = [] + * const Logged = Schema.String.pipe( + * Schema.middlewareEncoding((effect) => + * Effect.tapError(effect, () => Effect.sync(() => events.push("encode failed"))) + * ) + * ) + * Effect.runSync(Effect.result(Schema.encodeUnknownEffect(Logged)(42))) + * events // => ["encode failed"] + * ``` + * + * @see {@link catchEncoding} for a simpler error-recovery variant + * @category encoding + * @since 4.0.0 + */ +export function middlewareEncoding( + encode: ( + effect: Effect.Effect, SchemaIssue.Issue, S["EncodingServices"]>, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, RE> +) { + return (schema: S): middlewareEncoding => + make( + SchemaAST.middlewareEncoding(schema.ast, new SchemaTransformation.Middleware(identity, encode)), + { schema } + ) +} + +/** + * Recovers from a decoding error by providing a fallback value. + * + * **Details** + * + * The handler receives the `Issue` and returns an `Effect` that either + * succeeds with a fallback value or re-fails with a (possibly different) issue. + * + * **Example** (Returning a default on decode failure) + * + * ```ts import.meta.vitest + * import { Effect, Option, Schema } from "effect" + * + * const schema = Schema.Number.pipe( + * Schema.catchDecoding((_issue) => Effect.succeed(Option.some(0))) + * ) + * Effect.runSync(Schema.decodeUnknownEffect(schema)("invalid")) // => 0 + * ``` + * + * @see {@link catchDecodingWithContext} to add service requirements to the handler + * @category error handling + * @since 4.0.0 + */ +export function catchDecoding( + f: (issue: SchemaIssue.Issue) => Effect.Effect, SchemaIssue.Issue> +): (self: S) => middlewareDecoding { + return catchDecodingWithContext(f) +} + +/** + * Recovers from a decoding error with a handler that may require Effect services. + * + * **When to use** + * + * Use when you need decoding fallback logic to require services from the Effect + * context. + * + * **Details** + * + * The handler receives the `Issue` and returns an `Effect` that either succeeds + * with a fallback value or re-fails with a (possibly different) issue. The + * handler's services are added to the schema's decoding services. + * + * @see {@link catchDecoding} for recovery handlers that do not require services + * @see {@link middlewareDecoding} for intercepting or replacing the full decoding pipeline + * + * @category error handling + * @since 4.0.0 + */ +export function catchDecodingWithContext( + f: (issue: SchemaIssue.Issue) => Effect.Effect, SchemaIssue.Issue, R> +) { + return (self: S): middlewareDecoding => + middlewareDecoding(Effect.catchEager(f))(self) +} + +/** + * Recovers from an encoding error by providing a fallback value. + * + * **Details** + * + * The handler receives the `Issue` and returns an `Effect` that either + * succeeds with a fallback value or re-fails with a (possibly different) issue. + * + * @see {@link catchEncodingWithContext} to add service requirements to the handler + * @category error handling + * @since 4.0.0 + */ +export function catchEncoding( + f: (issue: SchemaIssue.Issue) => Effect.Effect, SchemaIssue.Issue> +): (self: S) => middlewareEncoding { + return catchEncodingWithContext(f) +} + +/** + * Recovers from an encoding error with a handler that may require Effect services. + * + * **When to use** + * + * Use when you need encoding fallback logic to require services from the Effect + * context. + * + * **Details** + * + * The handler receives the `Issue` and returns an `Effect` that either succeeds + * with a fallback encoded value or re-fails with a (possibly different) issue. + * The handler's services are added to the schema's encoding services. + * + * @see {@link catchEncoding} for recovery handlers that do not require services + * @see {@link middlewareEncoding} for intercepting or replacing the full encoding pipeline + * + * @category error handling + * @since 4.0.0 + */ +export function catchEncodingWithContext( + f: (issue: SchemaIssue.Issue) => Effect.Effect, SchemaIssue.Issue, R> +) { + return (self: S): middlewareEncoding => + middlewareEncoding(Effect.catchEager(f))(self) +} + +/** + * Type-level representation returned by {@link decodeTo}. + * + * @category transforming + * @since 4.0.0 + */ +export interface decodeTo extends + BottomLazy< + To["ast"], + decodeTo, + To["~type.parameters"], + To["~type.mutability"], + To["~type.optionality"], + To["~type.constructor.default"], + From["~encoded.mutability"], + From["~encoded.optionality"] + > +{ + readonly "Type": To["Type"] + readonly "Encoded": From["Encoded"] + readonly "DecodingServices": To["DecodingServices"] | From["DecodingServices"] | RD + readonly "EncodingServices": To["EncodingServices"] | From["EncodingServices"] | RE + readonly "~type.make.in": To["~type.make.in"] + readonly "~type.make": To["~type.make"] + readonly "Iso": To["Iso"] + readonly from: From + readonly to: To +} + +/** + * Type-level representation returned by {@link decodeTo} without a custom transformation. + * + * @category transforming + * @since 3.10.0 + */ +export interface compose extends decodeTo {} + +/** + * Creates a schema that transforms from a source schema to a target schema. + * + * **When to use** + * + * Use when decoding should change the schema's decoded type or encoded shape, + * with an optional custom bidirectional transformation. + * + * **Details** + * + * Call it with the target schema `to` and then pipe the source schema `from` + * into the returned function. The resulting schema decodes from + * `From["Encoded"]` to `To["Type"]` and encodes from `To["Type"]` back to + * `From["Encoded"]`. + * + * When no transformation is provided, `SchemaTransformation.passthrough()` is + * used, so `From["Type"]` must already be compatible with `To["Encoded"]`. + * The resulting schema combines decoding and encoding services from both + * schemas and any custom transformation. + * + * **Gotchas** + * + * In a custom transformation, `decode` maps `From["Type"]` to `To["Encoded"]` + * and is used on the encoding path, while `encode` maps `To["Encoded"]` to + * `From["Type"]` and is used on the decoding path. + * + * **Example** (Transforming strings to numbers with a schema transformation) + * + * ```ts import.meta.vitest + * import { Schema, SchemaGetter } from "effect" + * + * const NumberFromString = Schema.String.pipe( + * Schema.decodeTo( + * Schema.Number, + * { + * decode: SchemaGetter.transform((s) => Number(s)), + * encode: SchemaGetter.transform((n) => String(n)) + * } + * ) + * ) + * + * Schema.decodeUnknownSync(NumberFromString)("123") // => 123 + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function decodeTo(to: To): (from: From) => compose +export function decodeTo( + to: To, + transformation: { + readonly decode: SchemaGetter.Getter, NoInfer, RD> + readonly encode: SchemaGetter.Getter, NoInfer, RE> + } +): (from: From) => decodeTo +export function decodeTo( + to: To, + transformation?: { + readonly decode: SchemaGetter.Getter + readonly encode: SchemaGetter.Getter + } | undefined +) { + return (from: From) => { + return make( + SchemaAST.decodeTo( + from.ast, + to.ast, + transformation ? SchemaTransformation.make(transformation) : SchemaTransformation.passthrough() + ), + { + from, + to + } + ) + } +} + +/** + * Applies a transformation to a schema, creating a new schema with the same type but transformed encoding/decoding. + * + * **When to use** + * + * Use when the decoded type stays the same and the transformation only + * normalizes values during encoding and decoding. + * + * **Details** + * + * Call it with a transformation object and then pipe a schema into the returned + * function. The resulting schema keeps the same `Type` and `Encoded` types as + * the source schema, while applying the transformation during both decoding and + * encoding. + * + * Internally this uses `toType(self)` as the target schema and combines service + * requirements from the source schema and the transformation. + * + * **Gotchas** + * + * Use {@link decodeTo} instead when the transformation should change the + * decoded type. For this helper, both transformation getters operate on + * `S["Type"]` values. + * + * **Example** (Trimming string values during encoding/decoding) + * + * ```ts import.meta.vitest + * import { Schema, SchemaGetter } from "effect" + * + * const Trimmed = Schema.String.pipe( + * Schema.decode({ + * decode: SchemaGetter.transform((s) => s.trim()), + * encode: SchemaGetter.transform((s) => s.trim()) + * }) + * ) + * + * Schema.decodeUnknownSync(Trimmed)(" hello ") // => "hello" + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function decode(transformation: { + readonly decode: SchemaGetter.Getter + readonly encode: SchemaGetter.Getter +}) { + return (self: S): decodeTo, S, RD, RE> => { + return decodeTo, S, RD, RE>(toType(self), transformation)(self) + } +} + +/** + * Reverses a schema transformation so the encoded schema is supplied first. + * + * **When to use** + * + * Use to define a transformation by naming the encoded schema before the + * decoded schema. + * + * **Details** + * + * `encodeTo(to)(from)` is equivalent to `to.pipe(decodeTo(from))`. The `from` + * schema acts as the target decoded schema and `to` acts as the encoded source. + * + * **Example** (Encoding a number back to a string) + * + * ```ts import.meta.vitest + * import { Schema, SchemaGetter } from "effect" + * + * const NumberFromString = Schema.Number.pipe( + * Schema.encodeTo(Schema.String, { + * decode: SchemaGetter.transform((s: string) => Number(s)), + * encode: SchemaGetter.transform((n: number) => String(n)) + * }) + * ) + * Schema.decodeSync(NumberFromString)("42") // => 42 + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function encodeTo( + to: To +): (from: From) => decodeTo +export function encodeTo( + to: To, + transformation: { + readonly decode: SchemaGetter.Getter, NoInfer, RD> + readonly encode: SchemaGetter.Getter, NoInfer, RE> + } +): (from: From) => decodeTo +export function encodeTo( + to: To, + transformation?: { + readonly decode: SchemaGetter.Getter + readonly encode: SchemaGetter.Getter + } +) { + return (from: From): decodeTo => { + return transformation ? + decodeTo(from, transformation)(to) : + decodeTo(from)(to) + } +} + +/** + * Applies a transformation to a schema's encoded type, creating a new schema where encoding/decoding + * operate on `S["Encoded"]` rather than `S["Type"]`. + * + * **Details** + * + * The `decode` getter maps `S["Encoded"]` → `S["Encoded"]` (applied during decoding), + * and the `encode` getter maps `S["Encoded"]` → `S["Encoded"]` (applied during encoding). + * + * **Example** (Upper-casing encoded strings) + * + * ```ts import.meta.vitest + * import { Schema, SchemaGetter } from "effect" + * + * const UpperFromLower = Schema.String.pipe( + * Schema.encode({ + * decode: SchemaGetter.transform((s: string) => s.toLowerCase()), + * encode: SchemaGetter.transform((s: string) => s.toUpperCase()) + * }) + * ) + * Schema.encodeSync(UpperFromLower)("hello") // => "HELLO" + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function encode(transformation: { + readonly decode: SchemaGetter.Getter + readonly encode: SchemaGetter.Getter +}) { + return (self: S): decodeTo, RD, RE> => { + return decodeTo, RD, RE>(self, transformation)(toEncoded(self)) + } +} + +/** + * Constraint used to ensure a schema field does not already have a constructor default. + * + * **Details** + * + * Only schemas that satisfy this constraint can be passed to {@link withConstructorDefault}. + * + * @category models + * @since 4.0.0 + */ +export interface WithoutConstructorDefault { + readonly "~type.constructor.default": "no-default" +} + +/** + * Type-level representation returned by {@link withConstructorDefault}. + * + * @category constructors + * @since 3.10.0 + */ +export interface withConstructorDefault extends + BottomLazy< + S["ast"], + withConstructorDefault, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + "with-default", + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] + readonly schema: S +} + +/** + * Attaches a constructor default value to a schema field. + * + * **Details** + * + * Constructor defaults are applied only during `make*`, not during decoding or + * encoding. Failures are represented directly as `SchemaIssue.Issue` values. + * + * **Example** (Defining an optional field with a static default) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * const MySchema = Schema.Struct({ + * name: Schema.String.pipe( + * Schema.optionalKey, + * Schema.withConstructorDefault(Effect.succeed("anonymous")) + * ) + * }) + * + * MySchema.make({}).name // => "anonymous" + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function withConstructorDefault( + // `S["~type.make.in"]` instead of `S["Type"]` is intentional here because + // it makes easier to define the default value if there are nested defaults + defaultValue: Effect.Effect +) { + return (schema: S): withConstructorDefault => + make(SchemaAST.withConstructorDefault(schema.ast, defaultValue), { schema }) +} + +function toIssueEffect( + self: Effect.Effect +): Effect.Effect { + return Effect.catchCause(self, (cause) => Effect.failCauseSync(() => Cause_.map(cause, (error) => error.issue))) +} + +/** + * Type-level representation returned by {@link withDecodingDefaultKey}. + * + * @category decoding + * @since 4.0.0 + */ +export interface withDecodingDefaultKey + extends decodeTo>, R> +{ + readonly "Rebuild": withDecodingDefaultKey +} + +/** + * Options for {@link withDecodingDefaultKey} and {@link withDecodingDefault}. + * + * **Details** + * + * - `encodingStrategy`: + * - `"passthrough"` (default): pass the value through during encoding + * - `"omit"`: omit the key from the encoded output + * + * @category options + * @since 4.0.0 + */ +export type DecodingDefaultOptions = { + readonly encodingStrategy?: "omit" | "passthrough" | undefined +} + +/** + * Makes a struct key optional on the `Encoded` side and provides a default + * `Encoded` value when the key is missing during decoding. + * + * **Details** + * + * The key uses `optionalKey` on the encoded side, so it may be absent from the + * input object but **not** `undefined`. The default value is specified in terms + * of the `Encoded` type (before any decoding transformations). + * + * Options: + * + * - `encodingStrategy`: + * - `"passthrough"` (default): include the value in the encoded output. + * - `"omit"`: omit the key from the encoded output. + * + * **Example** (Providing a default for a missing struct key) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * const MySchema = Schema.Struct({ + * name: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed("anonymous"))) + * }) + * + * Schema.decodeUnknownSync(MySchema)({}).name // => "anonymous" + * ``` + * + * @see {@link withDecodingDefault} for the value-level variant (key absent **or** `undefined`) + * @see {@link withDecodingDefaultTypeKey} for the variant where the default is a `Type` value + * @category decoding + * @since 4.0.0 + */ +export function withDecodingDefaultKey( + defaultValue: Effect.Effect, + options?: DecodingDefaultOptions +) { + const encode = options?.encodingStrategy === "omit" ? SchemaGetter.omit() : SchemaGetter.passthrough() + return (self: S): withDecodingDefaultKey => { + return optionalKey(toEncoded(self)).pipe(decodeTo(self, { + decode: SchemaGetter.withDefault(toIssueEffect(defaultValue)), + encode + })) + } +} + +/** + * Type-level representation returned by {@link withDecodingDefaultTypeKey}. + * + * @category decoding + * @since 4.0.0 + */ +export interface withDecodingDefaultTypeKey + extends decodeTo, R>, optionalKey> +{ + readonly "Rebuild": withDecodingDefaultTypeKey +} + +/** + * Makes a struct key optional on the `Encoded` side (`optionalKey`, so the + * key may be absent but **not** `undefined`) and provides a default `Type` + * value when the key is missing during decoding. + * + * **Details** + * + * Unlike {@link withDecodingDefaultKey}, the default value is specified in + * terms of the `Type` (decoded) representation, so it does not need to go + * through the decoding transformation. + * + * Options: + * + * - `encodingStrategy`: + * - `"passthrough"` (default): include the value in the encoded output. + * - `"omit"`: omit the key from the encoded output. + * + * @see {@link withDecodingDefaultKey} for the variant where the default is an `Encoded` value + * @see {@link withDecodingDefaultType} for the value-level variant + * @category decoding + * @since 4.0.0 + */ +export function withDecodingDefaultTypeKey( + defaultValue: Effect.Effect, + options?: DecodingDefaultOptions +) { + return (self: S): withDecodingDefaultTypeKey => { + return toType(self).pipe( + withDecodingDefaultKey, R>(defaultValue, options), + encodeTo(optionalKey(self)) + ) + } +} + +/** + * Type-level representation returned by {@link withDecodingDefault}. + * + * @category decoding + * @since 3.10.0 + */ +export interface withDecodingDefault extends decodeTo>, R> { + readonly "Rebuild": withDecodingDefault +} + +/** + * Wraps the `Encoded` side with `optional` (key absent **or** `undefined`) + * and provides a default `Encoded` value when the field is missing or + * `undefined` during decoding. + * + * **When to use** + * + * Use when the default is expressed in the encoded representation, before the + * field's decoding transformation runs. + * + * **Details** + * + * The default value is specified in terms of the `Encoded` type (before any + * decoding transformations). + * + * Options: + * + * - `encodingStrategy`: + * - `"passthrough"` (default): include the value in the encoded output. + * - `"omit"`: omit the key from the encoded output. + * + * **Example** (Providing a default for an optional field value) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * const MySchema = Schema.Struct({ + * name: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("anonymous"))) + * }) + * + * Schema.decodeUnknownSync(MySchema)({ name: undefined }).name // => "anonymous" + * ``` + * + * @see {@link withDecodingDefaultKey} for the key-level variant (key absent only, not `undefined`) + * @see {@link withDecodingDefaultType} for the variant where the default is a `Type` value + * @category decoding + * @since 3.10.0 + */ +export function withDecodingDefault( + defaultValue: Effect.Effect, + options?: DecodingDefaultOptions +) { + const encode = options?.encodingStrategy === "omit" ? SchemaGetter.omit() : SchemaGetter.passthrough() + return (self: S): withDecodingDefault => { + return optional(toEncoded(self)).pipe(decodeTo(self, { + decode: SchemaGetter.withDefault(toIssueEffect(defaultValue)), + encode + })) + } +} + +/** + * Type-level representation returned by {@link withDecodingDefaultType}. + * + * @category decoding + * @since 4.0.0 + */ +export interface withDecodingDefaultType + extends decodeTo, R>, optional> +{ + readonly "Rebuild": withDecodingDefaultType +} + +/** + * Wraps the `Encoded` side with `optional` (key absent **or** `undefined`) + * and provides a default `Type` value when the field is missing or + * `undefined` during decoding. + * + * **When to use** + * + * Use when the default is already in the decoded representation and should not + * pass through the field's decoding transformation. + * + * **Details** + * + * Unlike {@link withDecodingDefault}, the default value is specified in terms + * of the `Type` (decoded) representation, so it does not need to go through + * the decoding transformation. + * + * Options: + * + * - `encodingStrategy`: + * - `"passthrough"` (default): include the value in the encoded output. + * - `"omit"`: omit the key from the encoded output. + * + * @see {@link withDecodingDefault} for the variant where the default is an `Encoded` value + * @see {@link withDecodingDefaultTypeKey} for the key-level variant + * @category decoding + * @since 4.0.0 + */ +export function withDecodingDefaultType( + defaultValue: Effect.Effect, + options?: DecodingDefaultOptions +) { + return (self: S): withDecodingDefaultType => { + return toType(self).pipe( + withDecodingDefault, R>(defaultValue, options), + encodeTo(optional(self)) + ) + } +} + +/** + * Type-level representation returned by {@link tag}. + * + * @category constructors + * @since 3.10.0 + */ +export interface tag extends withConstructorDefault> {} + +/** + * Combines a {@link Literal} schema with {@link withConstructorDefault}, making it ideal + * for discriminator fields in tagged unions. When constructing via `make`, the + * `_tag` field can be omitted and will be filled automatically. + * + * **Example** (Defining a discriminated union tag) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const A = Schema.Struct({ _tag: Schema.tag("A"), value: Schema.Number }) + * + * // _tag is optional in make, auto-filled to "A" + * const a = A.make({ value: 42 }) + * a // => { _tag: "A", value: 42 } + * ``` + * + * @see {@link tagDefaultOmit} to also omit the tag during encoding + * @see {@link TaggedStruct} for a shorthand that adds `_tag` automatically + * @category constructors + * @since 3.10.0 + */ +export function tag(literal: Tag): tag { + return Literal(literal).pipe(withConstructorDefault(Effect.succeed(literal))) +} + +/** + * Creates a literal `_tag` schema that is omitted from encoded output. + * + * **When to use** + * + * Use to decode data that omits the discriminator field while still constructing + * values with a `_tag` for tagged union matching. + * + * **Details** + * + * The tag is filled during decoding and construction, like {@link tag}, but is + * omitted when encoding. + * + * **Example** (Omitting tags during encoding) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const A = Schema.Struct({ + * _tag: Schema.tagDefaultOmit("A"), + * value: Schema.Number + * }) + * + * // Encode strips the _tag field + * Schema.encodeUnknownSync(A)({ _tag: "A", value: 1 }) // => { value: 1 } + * ``` + * + * @see {@link tag} for the variant that keeps the tag during encoding + * @category constructors + * @since 4.0.0 + */ +export function tagDefaultOmit(literal: Tag) { + return tag(literal).pipe(withDecodingDefaultKey(Effect.succeed(literal), { encodingStrategy: "omit" })) +} + +/** + * Type-level representation returned by {@link TaggedStruct}. + * + * @category models + * @since 3.10.0 + */ +export type TaggedStruct = Struct< + Simplify<{ readonly _tag: tag } & Fields> +> + +/** + * Creates a struct schema with an automatically populated `_tag` field. + * + * **When to use** + * + * Use to define a tagged union case from a literal tag and a set of fields. + * + * **Details** + * + * When using the `make` method, the `_tag` field is optional and will be + * added automatically. However, when decoding or encoding, the `_tag` field + * must be present in the input. + * + * **Example** (Defining a tagged struct shorthand) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * // Defines a struct with a fixed `_tag` field + * const tagged = Schema.TaggedStruct("A", { + * a: Schema.String + * }) + * + * // This is the same as writing: + * const equivalent = Schema.Struct({ + * _tag: Schema.tag("A"), + * a: Schema.String + * }) + * void tagged + * void equivalent + * ``` + * + * **Example** (Accessing the literal value of the tag) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const tagged = Schema.TaggedStruct("A", { + * a: Schema.String + * }) + * + * tagged.fields._tag.schema.literal // => "A" + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function TaggedStruct( + value: Tag, + fields: Fields +): TaggedStruct { + return Struct({ _tag: tag(value), ...fields }) +} + +/** + * Recursively flatten any nested Schema.Union members into a single tuple of leaf schemas. + */ +type Flatten = Schemas extends readonly [infer Head, ...infer Tail] + ? Head extends Union ? [...Flatten, ...Flatten] + : [Head, ...Flatten] + : [] + +type MatchCasesResult = { + [K in keyof Cases]-?: NonNullable extends (...args: Array) => infer R ? R : never +}[keyof Cases] + +type MatchOrElseResult) => any> = Unify< + MatchCasesResult | ReturnType +> + +type TaggedUnionUtils< + Tag extends PropertyKey, + Members extends ReadonlyArray, + Flattened extends ReadonlyArray = Flatten< + Members + > +> = { + /** + * Discriminant values in flattened member order. + */ + readonly discriminants: { readonly [I in keyof Flattened]: Flattened[I]["Type"][Tag] } + readonly cases: Simplify<{ [M in Flattened[number] as M["Type"][Tag]]: M }> + readonly isAnyOf: ( + keys: ReadonlyArray + ) => (value: Members[number]["Type"]) => value is Extract + readonly guards: { [M in Flattened[number] as M["Type"][Tag]]: (u: unknown) => u is M["Type"] } + readonly match: { + < + Cases extends { [M in Flattened[number] as M["Type"][Tag]]: (value: M["Type"]) => any } + >( + value: Members[number]["Type"], + cases: Cases + ): Cases[keyof Cases] extends (value: any) => infer R ? Unify + : never + < + Cases extends { [M in Flattened[number] as M["Type"][Tag]]: (value: M["Type"]) => any } + >( + cases: Cases + ): (value: Members[number]["Type"]) => Cases[keyof Cases] extends (value: any) => infer R ? Unify + : never + } + readonly matchOrElse: { + < + Cases extends + & { [M in Flattened[number] as M["Type"][Tag]]+?: (value: M["Type"]) => any } + & { [K in Exclude]: never }, + OrElse extends ( + value: Exclude + ) => any + >( + value: Members[number]["Type"], + cases: Cases, + orElse: OrElse + ): MatchOrElseResult + < + Cases extends + & { [M in Flattened[number] as M["Type"][Tag]]+?: (value: M["Type"]) => any } + & { [K in Exclude]: never }, + OrElse extends ( + value: Exclude + ) => any + >( + cases: Cases, + orElse: OrElse + ): (value: Members[number]["Type"]) => MatchOrElseResult + } +} + +/** + * Type-level representation returned by {@link toTaggedUnion}. + * + * @category combinators + * @since 4.0.0 + */ +export type toTaggedUnion< + Tag extends PropertyKey, + Members extends ReadonlyArray +> = Union & TaggedUnionUtils + +/** + * Augments an existing {@link Union} of tagged structs with utility methods and an ordered tuple of discriminant + * values. + * + * **Gotchas** + * + * Throws if multiple members use the same discriminant property key. + * + * **Example** (Adding tagged-union utilities to an existing union) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const A = Schema.TaggedStruct("A", { value: Schema.Number }) + * const B = Schema.TaggedStruct("B", { name: Schema.String }) + * + * const MyUnion = Schema.Union([A, B]).pipe(Schema.toTaggedUnion("_tag")) + * + * // Pattern-match on the union + * const result = MyUnion.match({ _tag: "A", value: 1 }, { + * A: (a) => `number: ${a.value}`, + * B: (b) => `name: ${b.name}` + * }) + * result // => "number: 1" + * ``` + * + * @see {@link TaggedUnion} for a shorthand that builds the union from scratch + * @category combinators + * @since 4.0.0 + */ +export function toTaggedUnion(tag: Tag) { + return >( + self: Union + ): toTaggedUnion => { + const cases: Record = {} + const discriminants: Array = [] + const discriminantKeys = new Set() + const guards: Record boolean> = {} + const isAnyOf = (keys: ReadonlyArray) => (value: Members[number]["Type"]) => keys.includes(value[tag]) + + walk(self) + + return Object.assign(self, { cases, discriminants, isAnyOf, guards, match, matchOrElse }) as any + + function walk(schema: Constraint) { + const ast = schema.ast + + if ( + SchemaAST.isUnion(ast) && "members" in schema && globalThis.Array.isArray(schema.members) && + schema.members.every(isSchema) + ) { + return schema.members.forEach(walk) + } + + const sentinels = SchemaAST.collectSentinels(ast) + if (sentinels.length > 0) { + const literal = sentinels.find((s) => s.key === tag)?.literal + if (Predicate.isPropertyKey(literal)) { + const key = typeof literal === "number" ? globalThis.String(literal) : literal + if (discriminantKeys.has(key)) { + throw new globalThis.Error(`Duplicate discriminant: ${globalThis.String(literal)}`) + } + discriminantKeys.add(key) + discriminants.push(literal) + InternalRecord.assignProperty(cases, literal, schema) + InternalRecord.assignProperty(guards, literal, is(toType(schema))) + return + } + } + + throw new globalThis.Error("No literal or unique symbol found") + } + + function match() { + if (arguments.length === 1) { + const cases = arguments[0] + return function(value: any) { + const key = value[tag] + const handler = Object.hasOwn(cases, key) ? cases[key] : undefined + return handler(value) + } + } + const value = arguments[0] + const cases = arguments[1] + const key = value[tag] + const handler = Object.hasOwn(cases, key) ? cases[key] : undefined + return handler(value) + } + + function matchOrElse() { + if (arguments.length === 2) { + const cases = arguments[0] + const orElse = arguments[1] + return function(value: any) { + const key = value[tag] + const handler = Object.hasOwn(cases, key) ? cases[key] ?? orElse : orElse + return handler(value) + } + } + const value = arguments[0] + const cases = arguments[1] + const orElse = arguments[2] + const key = value[tag] + const handler = Object.hasOwn(cases, key) ? cases[key] ?? orElse : orElse + return handler(value) + } + } +} + +/** + * Type-level representation returned by {@link TaggedUnion}. + * + * @category models + * @since 4.0.0 + */ +export interface TaggedUnion> extends + BottomLazy< + SchemaAST.Union, + TaggedUnion + > +{ + readonly "Type": { [K in keyof Cases]: Cases[K]["Type"] }[keyof Cases] + readonly "Encoded": { [K in keyof Cases]: Cases[K]["Encoded"] }[keyof Cases] + readonly "DecodingServices": { [K in keyof Cases]: Cases[K]["DecodingServices"] }[keyof Cases] + readonly "EncodingServices": { [K in keyof Cases]: Cases[K]["EncodingServices"] }[keyof Cases] + readonly "~type.make.in": { [K in keyof Cases]: Cases[K]["~type.make"] }[keyof Cases] + readonly "~type.make": { [K in keyof Cases]: Cases[K]["~type.make"] }[keyof Cases] + readonly "Iso": { [K in keyof Cases]: Cases[K]["Type"] }[keyof Cases] + readonly cases: Cases + readonly isAnyOf: ( + keys: ReadonlyArray + ) => (value: Cases[keyof Cases]["Type"]) => value is Extract + readonly guards: { [K in keyof Cases]: (u: unknown) => u is Cases[K]["Type"] } + readonly match: { + ( + cases: { [K in keyof Cases]: (value: Cases[K]["Type"]) => Output } + ): (value: Cases[keyof Cases]["Type"]) => Output + ( + value: Cases[keyof Cases]["Type"], + cases: { [K in keyof Cases]: (value: Cases[K]["Type"]) => Output } + ): Output + } + readonly matchOrElse: { + ( + value: Cases[keyof Cases]["Type"], + cases: { [K in keyof Cases]?: (value: Cases[K]["Type"]) => Output }, + orElse: (value: Cases[keyof Cases]["Type"]) => Output + ): Output + ( + cases: { [K in keyof Cases]?: (value: Cases[K]["Type"]) => Output }, + orElse: (value: Cases[keyof Cases]["Type"]) => Output + ): (value: Cases[keyof Cases]["Type"]) => Output + } +} + +/** + * Builds a discriminated union from a record of field sets, one per variant. + * Each key becomes the `_tag` literal and the value is passed to {@link TaggedStruct}. + * The result includes `cases`, `guards`, `isAnyOf`, `match`, and `matchOrElse` utilities. + * + * **Example** (Pattern matching a discriminated union) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const Shape = Schema.TaggedUnion({ + * Circle: { radius: Schema.Number }, + * Rectangle: { width: Schema.Number, height: Schema.Number } + * }) + * + * // Pattern-match on a decoded value + * const area = Shape.match({ _tag: "Circle", radius: 5 }, { + * Circle: (c) => Math.PI * c.radius ** 2, + * Rectangle: (r) => r.width * r.height + * }) + * Math.round(area * 100) / 100 // => 78.54 + * ``` + * + * @see {@link toTaggedUnion} to augment an existing union instead + * @category constructors + * @since 4.0.0 + */ +export function TaggedUnion>( + casesByTag: CasesByTag +): TaggedUnion<{ readonly [K in keyof CasesByTag & string]: TaggedStruct }> { + const cases: any = {} + const members: any = [] + for (const key of Object.keys(casesByTag)) { + const member = TaggedStruct(key, casesByTag[key]) + InternalRecord.assignProperty(cases, key, member) + members.push(member) + } + const union = Union(members) + const { guards, isAnyOf, match, matchOrElse } = toTaggedUnion("_tag")(union) + return make(union.ast, { cases, isAnyOf, guards, match, matchOrElse }) +} + +/** + * Type-level representation returned by {@link Opaque}. + * + * @category models + * @since 4.0.0 + */ +export interface Opaque extends + BottomLazyWithoutNew< + S["ast"], + S["Rebuild"], + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": Self + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] + new(_: never): S["Type"] & Brand +} + +/** + * Wraps a struct schema so that its decoded `Type` becomes a nominally distinct type `Self`. + * Useful for creating opaque types that are structurally identical to a base struct + * but type-incompatible with it. + * + * **Example** (Defining opaque structs) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * class Person extends Schema.Opaque()( + * Schema.Struct({ + * name: Schema.String + * }) + * ) {} + * + * // Decoded value is Person, not { name: string } + * const person = Schema.decodeUnknownSync(Person)({ name: "Alice" }) + * person.name // => "Alice" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export function Opaque() { + return (schema: S): Opaque & Omit => { + return schema as any + } +} + +/** + * Type-level representation returned by {@link instanceOf}. + * + * @category models + * @since 3.10.0 + */ +export interface instanceOf extends declare { + readonly "Rebuild": instanceOf +} + +/** + * Creates a schema that validates values using `instanceof`. + * Decoding and encoding pass the value through unchanged. + * + * **Example** (Defining a schema for a built-in class) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const DateSchema = Schema.instanceOf(Date) + * + * const decoded = Schema.decodeUnknownSync(DateSchema)(new Date("2024-01-01")) + * decoded.toISOString() // => "2024-01-01T00:00:00.000Z" + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export function instanceOf any, Iso = InstanceType>( + constructor: C, + annotations?: Annotations.Declaration> | undefined +): instanceOf, Iso> { + return declare((u): u is InstanceType => u instanceof constructor, annotations) +} + +/** + * Constructs an `SchemaAST.Link` that describes how a value of type `T` encodes to and decodes from a `To` schema. + * Used when building low-level AST transformations that bridge two schema types. + * + * @category transforming + * @since 4.0.0 + */ +export function link() { + return ( + encodeTo: To, + transformation: { + readonly decode: SchemaGetter.Getter> + readonly encode: SchemaGetter.Getter, T> + } + ): SchemaAST.Link => { + return new SchemaAST.Link(encodeTo.ast, SchemaTransformation.make(transformation)) + } +} + +// ----------------------------------------------------------------------------- +// Checks +// ----------------------------------------------------------------------------- + +/** + * Creates a custom validation filter from a predicate function. + * + * **Details** + * + * The predicate receives the decoded input value, the schema AST, and parse + * options, and returns a `FilterOutput`. Non-success outputs are normalized into + * schema issues. The `annotations` parameter annotates the filter itself; with + * the default formatter, failures use `message` first, `expected` second, and + * `` when neither is provided. + * + * When `abort` is `true`, parsing stops after this filter fails instead of + * collecting later check failures. + * + * **Example** (Reporting failure at a nested path) + * + * ```ts import.meta.vitest + * import { Result, Schema } from "effect" + * + * const schema = Schema.Struct({ password: Schema.String, confirmPassword: Schema.String }).check( + * Schema.makeFilter((o) => + * o.password === o.confirmPassword + * ? undefined + * : { path: ["password"], issue: "password and confirmPassword must match" } + * ) + * ) + * + * const result = Schema.decodeUnknownResult(schema)({ password: "123456", confirmPassword: "1234567" }) + * if (Result.isFailure(result) && result.failure.issue._tag === "Filter" && result.failure.issue.issue._tag === "Pointer") { + * result.failure.issue.issue.path // => ["password"] + * } + * ``` + * + * **Example** (Reporting multiple failures at once) + * + * ```ts import.meta.vitest + * import { Result, Schema } from "effect" + * + * const schema = Schema.Struct({ a: Schema.Finite, b: Schema.Finite, c: Schema.Finite }).check( + * Schema.makeFilter((o) => { + * const issues: Array = [] + * if (o.a > 0) { + * if (o.b <= 0) issues.push({ path: ["b"], issue: "b must be greater than 0" }) + * if (o.c <= 0) issues.push({ path: ["c"], issue: "c must be greater than 0" }) + * } + * return issues + * }) + * ) + * + * const result = Schema.decodeUnknownResult(schema)({ a: 1, b: 0, c: 0 }) + * if (Result.isFailure(result) && result.failure.issue._tag === "Filter" && result.failure.issue.issue._tag === "Composite") { + * result.failure.issue.issue.issues.map((issue) => issue._tag === "Pointer" ? issue.path : []) // => [["b"], ["c"]] + * } + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeFilter: ( + filter: (input: T, ast: SchemaAST.AST, options: SchemaAST.ParseOptions) => FilterOutput, + annotations?: Annotations.Filter | undefined, + abort?: boolean +) => SchemaAST.Filter = SchemaAST.makeFilter + +/** + * A single failure reported by a filter predicate. Used as the element type + * of the array arm of {@link FilterOutput}, and also accepted on its own. + * + * **Details** + * + * - `string`: failure with that string as the message. Produces an + * {@link SchemaIssue.InvalidValue} with the string used as the issue's + * `message` annotation and honors `reportInput`. + * - {@link SchemaIssue.Issue}: a fully-formed issue, returned as-is. It is not + * enriched when `reportInput` is enabled. + * - `{ path, issue }`: failure attached to a nested path. `issue` is either + * a `string` (wrapped in an {@link SchemaIssue.InvalidValue} that honors + * `reportInput`) or a full {@link SchemaIssue.Issue} (returned unchanged); + * the result is wrapped in an {@link SchemaIssue.Pointer} at the given `path`. + * + * @category models + * @since 3.10.0 + */ +export type FilterIssue = string | SchemaIssue.Issue | { + readonly path: ReadonlyArray + readonly issue: string | SchemaIssue.Issue +} + +/** + * The value a filter predicate (see {@link makeFilter}) may return. + * + * **Details** + * + * Each shape is normalized into an {@link SchemaIssue.Issue} (or `undefined` for + * success) before being attached to the parse result: + * + * - `undefined`: success. The input satisfies the filter. + * - `true`: success. Equivalent to `undefined`, useful when the predicate is + * a plain boolean expression. + * - `false`: generic failure. Produces an {@link SchemaIssue.InvalidValue} + * with no custom message and honors `reportInput`. + * - {@link FilterIssue}: a single failure. See {@link FilterIssue} for the + * shapes (`string`, {@link SchemaIssue.Issue}, or `{ path, issue }`). + * - `ReadonlyArray`: several failures reported together. An + * empty array is treated as success; a single-element array is equivalent + * to returning that element directly; otherwise the entries are grouped + * into an {@link SchemaIssue.Composite}. + * + * @category models + * @since 3.10.0 + */ +export type FilterOutput = + | undefined + | boolean + | FilterIssue + | ReadonlyArray + +/** + * Groups multiple checks into a single {@link SchemaAST.FilterGroup}, applying + * optional shared annotations to the group as a whole. + * + * @category constructors + * @since 4.0.0 + */ +export function makeFilterGroup( + checks: readonly [SchemaAST.Check, ...Array>], + annotations: Annotations.Filter | undefined = undefined +): SchemaAST.FilterGroup { + return new SchemaAST.FilterGroup(checks, annotations) +} + +function makeFixedDeclarationReviver( + id: string, + schema: Top +): SchemaRepresentation.DeclarationReviver { + return InternalSchema.makeDeclarationReviver( + id, + Null, + ({ annotations }) => annotations === undefined ? schema : schema.annotate(annotations) + ) +} + +const TRIMMED_PATTERN = "^\\S[\\s\\S]*\\S$|^\\S$|^$" + +/** + * Validates that a string has no leading or trailing whitespace. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to a `pattern` constraint in JSON Schema that + * matches strings without leading or trailing whitespace. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the trimmed pattern. + * + * @category validation + * @since 4.0.0 + */ +export function isTrimmed(annotations?: Annotations.Filter) { + const regExp = new globalThis.RegExp(TRIMMED_PATTERN) + return makeFilter( + (s: string) => s.trim() === s, + { + expected: "a string with no leading or trailing whitespace", + representation: { + id: "effect/schema/isTrimmed", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isTrimmed()" }), + arbitrary: { + constraint: { + patterns: [TRIMMED_PATTERN] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isTrimmed` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isTrimmed}. + * + * @see {@link isTrimmed} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isTrimmedReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isTrimmed", + Null, + ({ annotations }) => isTrimmed(annotations) +) + +/** + * Validates that a string matches the specified regular expression pattern. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `pattern` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the specified RegExp pattern. + * + * @category validation + * @since 4.0.0 + */ +export function isPattern( + regExp: globalThis.RegExp, + annotations?: Annotations.Filter +): SchemaAST.Filter { + const source = regExp.source + const flags = regExp.flags + const runtimeRegExp = flags === "" + ? `new RegExp(${format(source)})` + : `new RegExp(${format(source)}, ${format(flags)})` + return SchemaAST.isPattern(regExp, { + toCode: () => ({ runtime: `Schema.isPattern(${runtimeRegExp})` }), + ...annotations + }) +} + +const IsPatternPayload = Struct({ + source: String, + flags: String +}).check(makeFilter((payload: { readonly source: string; readonly flags: string }) => { + const result = Result_.try(() => new globalThis.RegExp(payload.source, payload.flags)) + return Result_.isSuccess(result) && + result.success.source === payload.source && + result.success.flags === payload.flags +})) + +/** + * Reviver for persisted `isPattern` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isPattern}. + * + * @see {@link isPattern} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isPatternReviver: SchemaRepresentation.FilterReviver<{ + readonly source: string + readonly flags: string +}> = { + id: "effect/schema/isPattern", + payloadSchema: IsPatternPayload, + revive: ({ annotations, payload }) => isPattern(new globalThis.RegExp(payload.source, payload.flags), annotations) +} + +/** + * Validates that a string represents a finite number. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to a `pattern` constraint in JSON Schema that matches + * strings representing finite numbers. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the number string pattern. + * + * @category validation + * @since 4.0.0 + */ +export function isStringFinite(annotations?: Annotations.Filter): SchemaAST.Filter { + return SchemaAST.isStringFinite({ + toCode: () => ({ runtime: "Schema.isStringFinite()" }), + ...annotations + }) +} + +/** + * Reviver for persisted `isStringFinite` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isStringFinite}. + * + * @see {@link isStringFinite} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isStringFiniteReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isStringFinite", + Null, + ({ annotations }) => isStringFinite(annotations) +) + +/** + * Validates that a string is a signed base-10 integer literal for Effect's + * BigInt string encoding. + * + * **Details** + * + * The check uses the pattern `^-?\d+$`. It does not accept leading `+`, decimal + * points, exponent notation, separators, or non-decimal inputs such as + * hexadecimal strings. + * + * JSON Schema: + * This check corresponds to a `pattern` constraint with the same signed + * base-10 integer pattern. + * + * @category validation + * @since 4.0.0 + */ +export function isStringBigInt(annotations?: Annotations.Filter): SchemaAST.Filter { + return SchemaAST.isStringBigInt({ + toCode: () => ({ runtime: "Schema.isStringBigInt()" }), + ...annotations + }) +} + +/** + * Reviver for persisted `isStringBigInt` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isStringBigInt}. + * + * @see {@link isStringBigInt} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isStringBigIntReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isStringBigInt", + Null, + ({ annotations }) => isStringBigInt(annotations) +) + +/** + * Validates that a string has the `Symbol(description)` format used by Effect's + * symbol string encoding. + * + * **Details** + * + * The check uses the pattern `^Symbol\((.*)\)$`. It is not a general test for + * whether a string can be passed to JavaScript's `Symbol()` function. + * + * @category validation + * @since 4.0.0 + */ +export function isStringSymbol(annotations?: Annotations.Filter): SchemaAST.Filter { + return SchemaAST.isStringSymbol({ + toCode: () => ({ runtime: "Schema.isStringSymbol()" }), + ...annotations + }) +} + +/** + * Reviver for persisted `isStringSymbol` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isStringSymbol}. + * + * @see {@link isStringSymbol} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isStringSymbolReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isStringSymbol", + Null, + ({ annotations }) => isStringSymbol(annotations) +) + +/** + * Returns a RegExp for validating an RFC 9562 / RFC 4122 UUID. + * + * Optionally specify a version 1-8. If no version is specified (`undefined`), all versions are supported. + */ +const getUUIDRegExp = (version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8): globalThis.RegExp => { + if (version) { + return new globalThis.RegExp( + `^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$` + ) + } + return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|[fF]{8}-[fF]{4}-[fF]{4}-[fF]{4}-[fF]{12})$/ +} + +/** + * Validates that a string is a strict Universally Unique Identifier (UUID). + * + * **When to use** + * + * Use when you need UUID semantics, including version and RFC variant bits, + * rather than only the dashed hexadecimal shape. + * + * **Details** + * + * Without a version argument, this accepts UUID versions 1 through 8, the nil + * UUID (`00000000-0000-0000-0000-000000000000`), and the max UUID + * (`ffffffff-ffff-ffff-ffff-ffffffffffff`). With a version argument, this + * accepts only UUIDs with that version and RFC variant bits; nil and max UUIDs + * are not versioned UUIDs and do not match version-specific checks. + * + * JSON Schema: + * + * This check corresponds to a `pattern` constraint in JSON Schema that matches + * UUID format, and includes a `format: "uuid"` annotation. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the UUID pattern. + * + * @see {@link isGUID} for shape-only GUID validation. + * @category validation + * @since 4.0.0 + */ +export function isUUID(version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8, annotations?: Annotations.Filter) { + const regExp = getUUIDRegExp(version) + return isPattern( + regExp, + { + expected: version ? `a UUID v${version}` : "a UUID", + representation: { + id: "effect/schema/isUUID", + payload: { version: version ?? null } + }, + toJsonSchema: () => ({ pattern: regExp.source, format: "uuid" }), + toCode: () => ({ runtime: version === undefined ? "Schema.isUUID()" : `Schema.isUUID(${version})` }), + ...annotations + } + ) +} + +/** + * Reviver for persisted `isUUID` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isUUID}. + * + * @see {@link isUUID} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isUUIDReviver: SchemaRepresentation.FilterReviver<{ + readonly version: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | null +}> = InternalSchema.makeFilterReviver( + "effect/schema/isUUID", + Struct({ version: Union([Literals([1, 2, 3, 4, 5, 6, 7, 8]), Null]) }), + ({ annotations, payload }) => isUUID(payload.version ?? undefined, annotations) +) + +const GUID_REGEXP = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/ + +/** + * Validates that a string has the GUID / UUID textual shape. + * + * **When to use** + * + * Use when you need to accept dashed hexadecimal identifiers without enforcing + * UUID version or variant bits. + * + * **Details** + * + * This check accepts strings in the `8-4-4-4-12` hexadecimal form. JSON Schema + * output includes the corresponding `pattern` constraint and intentionally does + * not include `format: "uuid"` because GUID validation is looser than UUID + * validation. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the GUID pattern. + * + * @see {@link isUUID} for strict UUID validation. + * @category validation + * @since 4.0.0 + */ +export function isGUID(annotations?: Annotations.Filter) { + return isPattern( + GUID_REGEXP, + { + expected: "a GUID", + representation: { + id: "effect/schema/isGUID", + payload: null + }, + toJsonSchema: () => ({ pattern: GUID_REGEXP.source }), + toCode: () => ({ runtime: "Schema.isGUID()" }), + ...annotations + } + ) +} + +/** + * Reviver for persisted `isGUID` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isGUID}. + * + * @see {@link isGUID} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isGUIDReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isGUID", + Null, + ({ annotations }) => isGUID(annotations) +) + +/** + * Validates that a string is a valid ULID (Universally Unique Lexicographically + * Sortable Identifier). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to a `pattern` constraint in JSON Schema that matches + * the ULID format. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the ULID pattern. + * + * @category validation + * @since 4.0.0 + */ +export function isULID(annotations?: Annotations.Filter) { + const regExp = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/ + return isPattern( + regExp, + { + representation: { + id: "effect/schema/isULID", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isULID()" }), + ...annotations + } + ) +} + +/** + * Reviver for persisted `isULID` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isULID}. + * + * @see {@link isULID} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isULIDReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isULID", + Null, + ({ annotations }) => isULID(annotations) +) + +/** + * Validates that a string is valid Base64 encoded data. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to a `pattern` constraint in JSON Schema that matches + * Base64 format. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the Base64 pattern. + * + * @category validation + * @since 4.0.0 + */ +export function isBase64(annotations?: Annotations.Filter) { + const regExp = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/ + return isPattern( + regExp, + { + expected: "a base64 encoded string", + representation: { + id: "effect/schema/isBase64", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isBase64()" }), + ...annotations + } + ) +} + +/** + * Reviver for persisted `isBase64` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isBase64}. + * + * @see {@link isBase64} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isBase64Reviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isBase64", + Null, + ({ annotations }) => isBase64(annotations) +) + +/** + * Validates that a string is valid Base64URL encoded data (Base64 with URL-safe + * characters). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to a `pattern` constraint in JSON Schema that matches + * Base64URL format. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `patterns` + * constraint to ensure generated strings match the Base64URL pattern. + * + * @category validation + * @since 4.0.0 + */ +export function isBase64Url(annotations?: Annotations.Filter) { + const regExp = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/ + return isPattern( + regExp, + { + expected: "a base64url encoded string", + representation: { + id: "effect/schema/isBase64Url", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isBase64Url()" }), + ...annotations + } + ) +} + +/** + * Reviver for persisted `isBase64Url` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isBase64Url}. + * + * @see {@link isBase64Url} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isBase64UrlReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isBase64Url", + Null, + ({ annotations }) => isBase64Url(annotations) +) + +/** + * Validates at runtime that a string starts with the specified literal prefix. + * + * **Details** + * + * RegExp metacharacters in the prefix are escaped in JSON Schema and arbitrary + * metadata so that the generated patterns retain literal `startsWith` semantics. + * + * @category validation + * @since 4.0.0 + */ +export function isStartsWith(startsWith: string, annotations?: Annotations.Filter) { + const formatted = JSON.stringify(startsWith) + const regExp = new globalThis.RegExp(`^${RegExp_.escape(startsWith)}`) + return makeFilter( + (s: string) => s.startsWith(startsWith), + { + expected: `a string starting with ${formatted}`, + representation: { + id: "effect/schema/isStartsWith", + payload: { startsWith } + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: `Schema.isStartsWith(${format(startsWith)})` }), + arbitrary: { + constraint: { + patterns: [regExp.source] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isStartsWith` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isStartsWith}. + * + * @see {@link isStartsWith} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isStartsWithReviver: SchemaRepresentation.FilterReviver<{ + readonly startsWith: string +}> = InternalSchema.makeFilterReviver( + "effect/schema/isStartsWith", + Struct({ startsWith: String }), + ({ annotations, payload }) => isStartsWith(payload.startsWith, annotations) +) + +/** + * Validates at runtime that a string ends with the specified literal suffix. + * + * **Details** + * + * RegExp metacharacters in the suffix are escaped in JSON Schema and arbitrary + * metadata so that the generated patterns retain literal `endsWith` semantics. + * + * @category validation + * @since 4.0.0 + */ +export function isEndsWith(endsWith: string, annotations?: Annotations.Filter) { + const formatted = JSON.stringify(endsWith) + const regExp = new globalThis.RegExp(`${RegExp_.escape(endsWith)}$`) + return makeFilter( + (s: string) => s.endsWith(endsWith), + { + expected: `a string ending with ${formatted}`, + representation: { + id: "effect/schema/isEndsWith", + payload: { endsWith } + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: `Schema.isEndsWith(${format(endsWith)})` }), + arbitrary: { + constraint: { + patterns: [regExp.source] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isEndsWith` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isEndsWith}. + * + * @see {@link isEndsWith} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isEndsWithReviver: SchemaRepresentation.FilterReviver<{ + readonly endsWith: string +}> = InternalSchema.makeFilterReviver( + "effect/schema/isEndsWith", + Struct({ endsWith: String }), + ({ annotations, payload }) => isEndsWith(payload.endsWith, annotations) +) + +/** + * Validates at runtime that a string contains the specified literal substring. + * + * **Details** + * + * RegExp metacharacters in the substring are escaped in JSON Schema and + * arbitrary metadata so that the generated patterns retain literal `includes` + * semantics. + * + * @category validation + * @since 4.0.0 + */ +export function isIncludes(includes: string, annotations?: Annotations.Filter) { + const formatted = JSON.stringify(includes) + const regExp = new globalThis.RegExp(RegExp_.escape(includes)) + return makeFilter( + (s: string) => s.includes(includes), + { + expected: `a string including ${formatted}`, + representation: { + id: "effect/schema/isIncludes", + payload: { includes } + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: `Schema.isIncludes(${format(includes)})` }), + arbitrary: { + constraint: { + patterns: [regExp.source] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isIncludes` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isIncludes}. + * + * @see {@link isIncludes} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isIncludesReviver: SchemaRepresentation.FilterReviver<{ + readonly includes: string +}> = InternalSchema.makeFilterReviver( + "effect/schema/isIncludes", + Struct({ includes: String }), + ({ annotations, payload }) => isIncludes(payload.includes, annotations) +) + +const UPPERCASED_PATTERN = "^[^a-z]*$" + +/** + * Validates that a string is unchanged by JavaScript's `toUpperCase()`. + * + * **Details** + * + * This accepts empty strings and characters that do not have lowercase forms, + * such as digits, punctuation, and whitespace. It rejects strings that would + * change when uppercased. + * + * @category validation + * @since 4.0.0 + */ +export function isUppercased(annotations?: Annotations.Filter) { + const regExp = new globalThis.RegExp(UPPERCASED_PATTERN) + return makeFilter( + (s: string) => s.toUpperCase() === s, + { + expected: "a string with all characters in uppercase", + representation: { + id: "effect/schema/isUppercased", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isUppercased()" }), + arbitrary: { + constraint: { + patterns: [UPPERCASED_PATTERN] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isUppercased` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isUppercased}. + * + * @see {@link isUppercased} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isUppercasedReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isUppercased", + Null, + ({ annotations }) => isUppercased(annotations) +) + +const LOWERCASED_PATTERN = "^[^A-Z]*$" + +/** + * Validates that a string is unchanged by JavaScript's `toLowerCase()`. + * + * **Details** + * + * This accepts empty strings and characters that do not have uppercase forms, + * such as digits, punctuation, and whitespace. It rejects strings that would + * change when lowercased. + * + * @category validation + * @since 4.0.0 + */ +export function isLowercased(annotations?: Annotations.Filter) { + const regExp = new globalThis.RegExp(LOWERCASED_PATTERN) + return makeFilter( + (s: string) => s.toLowerCase() === s, + { + expected: "a string with all characters in lowercase", + representation: { + id: "effect/schema/isLowercased", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isLowercased()" }), + arbitrary: { + constraint: { + patterns: [LOWERCASED_PATTERN] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isLowercased` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLowercased}. + * + * @see {@link isLowercased} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLowercasedReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isLowercased", + Null, + ({ annotations }) => isLowercased(annotations) +) + +const CAPITALIZED_PATTERN = "^[^a-z]?.*$" + +/** + * Validates that the first character of a string is unchanged by + * `toUpperCase()`. + * + * **Details** + * + * Empty strings pass. Strings whose first character has no lowercase form, such + * as a digit, punctuation mark, or whitespace, also pass. + * + * @category validation + * @since 4.0.0 + */ +export function isCapitalized(annotations?: Annotations.Filter) { + const regExp = new globalThis.RegExp(CAPITALIZED_PATTERN) + return makeFilter( + (s: string) => s.charAt(0).toUpperCase() === s.charAt(0), + { + expected: "a string with the first character in uppercase", + representation: { + id: "effect/schema/isCapitalized", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isCapitalized()" }), + arbitrary: { + constraint: { + patterns: [CAPITALIZED_PATTERN] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isCapitalized` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isCapitalized}. + * + * @see {@link isCapitalized} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isCapitalizedReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isCapitalized", + Null, + ({ annotations }) => isCapitalized(annotations) +) + +const UNCAPITALIZED_PATTERN = "^[^A-Z]?.*$" + +/** + * Validates that the first character of a string is unchanged by + * `toLowerCase()`. + * + * **Details** + * + * Empty strings pass. Strings whose first character has no uppercase form, such + * as a digit, punctuation mark, or whitespace, also pass. + * + * @category validation + * @since 4.0.0 + */ +export function isUncapitalized(annotations?: Annotations.Filter) { + const regExp = new globalThis.RegExp(UNCAPITALIZED_PATTERN) + return makeFilter( + (s: string) => s.charAt(0).toLowerCase() === s.charAt(0), + { + expected: "a string with the first character in lowercase", + representation: { + id: "effect/schema/isUncapitalized", + payload: null + }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isUncapitalized()" }), + arbitrary: { + constraint: { + patterns: [UNCAPITALIZED_PATTERN] + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isUncapitalized` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isUncapitalized}. + * + * @see {@link isUncapitalized} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isUncapitalizedReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isUncapitalized", + Null, + ({ annotations }) => isUncapitalized(annotations) +) + +/** + * Type-level representation of {@link Finite}. + * + * @category models + * @since 3.10.0 + */ +export interface Finite extends Number { + readonly "Rebuild": Finite +} + +/** + * Schema for finite numbers, rejecting `NaN`, `Infinity`, and `-Infinity`. + * + * @category schemas + * @since 3.10.0 + */ +export const Finite: Finite = make(SchemaAST.finite) + +/** + * Validates that a number is finite (not `Infinity`, `-Infinity`, or `NaN`). + * + * **Details** + * + * JSON Schema: + * + * This check does not have a direct JSON Schema equivalent, but ensures the + * number is valid and finite. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies `noNaN: true` and + * `noInfinity: true` constraints to ensure generated numbers are finite. + * + * @category validation + * @since 4.0.0 + */ +export const isFinite: (annotations?: Annotations.Filter) => SchemaAST.Filter = SchemaAST.isFinite + +/** + * Reviver for persisted `isFinite` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isFinite}. + * + * @see {@link isFinite} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isFiniteReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isFinite", + Null, + ({ annotations }) => isFinite(annotations) +) + +/** + * Creates a greater-than (`>`) check for any ordered type from an + * `Order.Order` instance. + * + * @category validation + * @since 4.0.0 + */ +export function makeIsGreaterThan(options: { + readonly order: Order.Order + readonly annotate?: ((exclusiveMinimum: T) => Annotations.Filter) | undefined + readonly formatter?: Formatter | undefined +}) { + const gt = Order.isGreaterThan(options.order) + const formatter = options.formatter ?? format + return (exclusiveMinimum: T, annotations?: Annotations.Filter) => { + return makeFilter( + (input) => gt(input, exclusiveMinimum), + { + expected: `a value greater than ${formatter(exclusiveMinimum)}`, + arbitrary: { + constraint: { + ordered: { + order: options.order, + minimum: exclusiveMinimum, + exclusiveMinimum: true + } + } + }, + ...options.annotate?.(exclusiveMinimum), + ...annotations + } + ) + } +} + +/** + * Creates a greater-than-or-equal-to (`>=`) check for any ordered type from an + * `Order.Order` instance. + * + * @category validation + * @since 4.0.0 + */ +export function makeIsGreaterThanOrEqualTo(options: { + readonly order: Order.Order + readonly annotate?: ((exclusiveMinimum: T) => Annotations.Filter) | undefined + readonly formatter?: Formatter | undefined +}) { + const gte = Order.isGreaterThanOrEqualTo(options.order) + const formatter = options.formatter ?? format + return (minimum: T, annotations?: Annotations.Filter) => { + return makeFilter( + (input) => gte(input, minimum), + { + expected: `a value greater than or equal to ${formatter(minimum)}`, + arbitrary: { + constraint: { + ordered: { + order: options.order, + minimum + } + } + }, + ...options.annotate?.(minimum), + ...annotations + } + ) + } +} + +/** + * Creates a less-than (`<`) check for any ordered type from an `Order.Order` + * instance. + * + * @category validation + * @since 4.0.0 + */ +export function makeIsLessThan(options: { + readonly order: Order.Order + readonly annotate?: ((exclusiveMaximum: T) => Annotations.Filter) | undefined + readonly formatter?: Formatter | undefined +}) { + const lt = Order.isLessThan(options.order) + const formatter = options.formatter ?? format + return (exclusiveMaximum: T, annotations?: Annotations.Filter) => { + return makeFilter( + (input) => lt(input, exclusiveMaximum), + { + expected: `a value less than ${formatter(exclusiveMaximum)}`, + arbitrary: { + constraint: { + ordered: { + order: options.order, + maximum: exclusiveMaximum, + exclusiveMaximum: true + } + } + }, + ...options.annotate?.(exclusiveMaximum), + ...annotations + } + ) + } +} + +/** + * Creates a less-than-or-equal-to (`<=`) check for any ordered type from an + * `Order.Order` instance. + * + * @category validation + * @since 4.0.0 + */ +export function makeIsLessThanOrEqualTo(options: { + readonly order: Order.Order + readonly annotate?: ((exclusiveMaximum: T) => Annotations.Filter) | undefined + readonly formatter?: Formatter | undefined +}) { + const lte = Order.isLessThanOrEqualTo(options.order) + const formatter = options.formatter ?? format + return (maximum: T, annotations?: Annotations.Filter) => { + return makeFilter( + (input) => lte(input, maximum), + { + expected: `a value less than or equal to ${formatter(maximum)}`, + arbitrary: { + constraint: { + ordered: { + order: options.order, + maximum + } + } + }, + ...options.annotate?.(maximum), + ...annotations + } + ) + } +} + +/** + * Creates an inclusive or exclusive range check for any ordered type from an + * `Order.Order` instance. + * + * @category validation + * @since 4.0.0 + */ +export function makeIsBetween(deriveOptions: { + readonly order: Order.Order + readonly annotate?: + | ((options: { + readonly minimum: T + readonly maximum: T + readonly exclusiveMinimum?: boolean | undefined + readonly exclusiveMaximum?: boolean | undefined + }) => Annotations.Filter) + | undefined + readonly formatter?: Formatter | undefined +}) { + const greaterThanOrEqualTo = Order.isGreaterThanOrEqualTo(deriveOptions.order) + const greaterThan = Order.isGreaterThan(deriveOptions.order) + const lessThanOrEqualTo = Order.isLessThanOrEqualTo(deriveOptions.order) + const lessThan = Order.isLessThan(deriveOptions.order) + const formatter = deriveOptions.formatter ?? format + return (options: { + readonly minimum: T + readonly maximum: T + readonly exclusiveMinimum?: boolean | undefined + readonly exclusiveMaximum?: boolean | undefined + }, annotations?: Annotations.Filter) => { + const gte = options.exclusiveMinimum ? greaterThan : greaterThanOrEqualTo + const lte = options.exclusiveMaximum ? lessThan : lessThanOrEqualTo + return makeFilter( + (input) => gte(input, options.minimum) && lte(input, options.maximum), + { + expected: `a value between ${formatter(options.minimum)}${options.exclusiveMinimum ? " (excluded)" : ""} and ${ + formatter(options.maximum) + }${options.exclusiveMaximum ? " (excluded)" : ""}`, + arbitrary: { + constraint: { + ordered: { + order: deriveOptions.order, + minimum: options.minimum, + maximum: options.maximum, + ...(options.exclusiveMinimum && { exclusiveMinimum: true }), + ...(options.exclusiveMaximum && { exclusiveMaximum: true }) + } + } + }, + ...deriveOptions.annotate?.(options), + ...annotations + } + ) + } +} + +/** + * Creates a divisibility check for any numeric type from a remainder function + * and a zero value. + * + * @category validation + * @since 4.0.0 + */ +export function makeIsMultipleOf(options: { + readonly remainder: (input: T, divisor: T) => T + readonly zero: NoInfer + readonly annotate?: ((divisor: T) => Annotations.Filter) | undefined + readonly formatter?: Formatter | undefined +}) { + return (divisor: T, annotations?: Annotations.Filter) => { + const formatter = options.formatter ?? format + return makeFilter( + (input) => options.remainder(input, divisor) === options.zero, + { + expected: `a value that is a multiple of ${formatter(divisor)}`, + ...options.annotate?.(divisor), + ...annotations + } + ) + } +} + +function encodeNumberPayload(number: number): number { + if (!globalThis.Number.isFinite(number)) { + throw new globalThis.RangeError(`Expected a finite number, got ${format(number)}`) + } + return number +} + +/** + * Validates that a number is greater than the specified value (exclusive). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `exclusiveMinimum` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies an + * `exclusiveMinimum` constraint to ensure generated numbers are greater than + * the specified value. + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThan = makeIsGreaterThan({ + order: Order.Number, + annotate: (exclusiveMinimum) => ({ + representation: { + id: "effect/schema/isGreaterThan", + payload: { exclusiveMinimum: encodeNumberPayload(exclusiveMinimum) } + }, + toJsonSchema: () => ({ exclusiveMinimum }), + toCode: () => ({ runtime: `Schema.isGreaterThan(${format(exclusiveMinimum)})` }) + }) +}) + +/** + * Reviver for persisted `isGreaterThan` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isGreaterThan}. + * + * @see {@link isGreaterThan} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanReviver: SchemaRepresentation.FilterReviver<{ + readonly exclusiveMinimum: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isGreaterThan", + Struct({ exclusiveMinimum: Finite }), + ({ annotations, payload }) => isGreaterThan(payload.exclusiveMinimum, annotations) +) + +/** + * Validates that a number is greater than or equal to the specified value + * (inclusive). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `minimum` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `minimum` constraint + * to ensure generated numbers are greater than or equal to the specified value. + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo = makeIsGreaterThanOrEqualTo({ + order: Order.Number, + annotate: (minimum) => ({ + representation: { + id: "effect/schema/isGreaterThanOrEqualTo", + payload: { minimum: encodeNumberPayload(minimum) } + }, + toJsonSchema: () => ({ minimum }), + toCode: () => ({ runtime: `Schema.isGreaterThanOrEqualTo(${format(minimum)})` }) + }) +}) + +/** + * Reviver for persisted `isGreaterThanOrEqualTo` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isGreaterThanOrEqualTo}. + * + * @see {@link isGreaterThanOrEqualTo} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanOrEqualToReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isGreaterThanOrEqualTo", + Struct({ minimum: Finite }), + ({ annotations, payload }) => isGreaterThanOrEqualTo(payload.minimum, annotations) +) + +/** + * Validates that a number is less than the specified value (exclusive). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `exclusiveMaximum` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies an + * `exclusiveMaximum` constraint to ensure generated numbers are less than the + * specified value. + * + * @category validation + * @since 4.0.0 + */ +export const isLessThan = makeIsLessThan({ + order: Order.Number, + annotate: (exclusiveMaximum) => ({ + representation: { + id: "effect/schema/isLessThan", + payload: { exclusiveMaximum: encodeNumberPayload(exclusiveMaximum) } + }, + toJsonSchema: () => ({ exclusiveMaximum }), + toCode: () => ({ runtime: `Schema.isLessThan(${format(exclusiveMaximum)})` }) + }) +}) + +/** + * Reviver for persisted `isLessThan` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLessThan}. + * + * @see {@link isLessThan} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanReviver: SchemaRepresentation.FilterReviver<{ + readonly exclusiveMaximum: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isLessThan", + Struct({ exclusiveMaximum: Finite }), + ({ annotations, payload }) => isLessThan(payload.exclusiveMaximum, annotations) +) + +/** + * Validates that a number is less than or equal to the specified value + * (inclusive). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `maximum` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `maximum` constraint + * to ensure generated numbers are less than or equal to the specified value. + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanOrEqualTo = makeIsLessThanOrEqualTo({ + order: Order.Number, + annotate: (maximum) => ({ + representation: { + id: "effect/schema/isLessThanOrEqualTo", + payload: { maximum: encodeNumberPayload(maximum) } + }, + toJsonSchema: () => ({ maximum }), + toCode: () => ({ runtime: `Schema.isLessThanOrEqualTo(${format(maximum)})` }) + }) +}) + +/** + * Reviver for persisted `isLessThanOrEqualTo` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLessThanOrEqualTo}. + * + * @see {@link isLessThanOrEqualTo} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanOrEqualToReviver: SchemaRepresentation.FilterReviver<{ + readonly maximum: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isLessThanOrEqualTo", + Struct({ maximum: Finite }), + ({ annotations, payload }) => isLessThanOrEqualTo(payload.maximum, annotations) +) + +/** + * Validates that a number is within a specified range. The range boundaries can + * be inclusive or exclusive based on the provided options. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to `minimum`/`maximum` or `exclusiveMinimum`/`exclusiveMaximum` + * constraints in JSON Schema, depending on the options provided. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies `minimum` and + * `maximum` constraints with optional `exclusiveMinimum` and + * `exclusiveMaximum` flags to ensure generated numbers fall within the + * specified range. + * + * @category validation + * @since 4.0.0 + */ +export const isBetween = makeIsBetween({ + order: Order.Number, + annotate: (options) => { + const exclusiveMinimum = options.exclusiveMinimum ? true : undefined + const exclusiveMaximum = options.exclusiveMaximum ? true : undefined + const payload = { + minimum: encodeNumberPayload(options.minimum), + maximum: encodeNumberPayload(options.maximum), + ...(exclusiveMinimum && { exclusiveMinimum }), + ...(exclusiveMaximum && { exclusiveMaximum }) + } + return { + representation: { + id: "effect/schema/isBetween", + payload + }, + toJsonSchema: () => ({ + [exclusiveMinimum ? "exclusiveMinimum" : "minimum"]: options.minimum, + [exclusiveMaximum ? "exclusiveMaximum" : "maximum"]: options.maximum + }), + toCode: () => ({ + runtime: `Schema.isBetween({ minimum: ${format(options.minimum)}, maximum: ${ + format(options.maximum) + }, exclusiveMinimum: ${format(exclusiveMinimum)}, exclusiveMaximum: ${format(exclusiveMaximum)} })` + }) + } + } +}) + +/** + * Reviver for persisted `isBetween` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isBetween}. + * + * @see {@link isBetween} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isBetweenReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: number + readonly maximum: number + readonly exclusiveMinimum?: true | undefined + readonly exclusiveMaximum?: true | undefined +}> = InternalSchema.makeFilterReviver( + "effect/schema/isBetween", + Struct({ + minimum: Finite, + maximum: Finite, + exclusiveMinimum: optional(Literal(true)), + exclusiveMaximum: optional(Literal(true)) + }), + ({ annotations, payload }) => isBetween(payload, annotations) +) + +/** + * Validates that a number is a multiple of the specified divisor. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `multipleOf` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies constraints to ensure + * generated numbers are multiples of the specified divisor. + * + * @category validation + * @since 4.0.0 + */ +export const isMultipleOf = makeIsMultipleOf({ + remainder, + zero: 0, + annotate: (divisor) => ({ + expected: `a value that is a multiple of ${divisor}`, + representation: { + id: "effect/schema/isMultipleOf", + payload: { divisor } + }, + toJsonSchema: () => ({ multipleOf: divisor }), + toCode: () => ({ runtime: `Schema.isMultipleOf(${format(divisor)})` }) + }) +}) + +/** + * Reviver for persisted `isMultipleOf` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isMultipleOf}. + * + * @see {@link isMultipleOf} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isMultipleOfReviver: SchemaRepresentation.FilterReviver<{ + readonly divisor: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isMultipleOf", + Struct({ divisor: Finite }), + ({ annotations, payload }) => isMultipleOf(payload.divisor, annotations) +) + +/** + * Validates that a number is a safe integer (within the safe integer range + * that can be exactly represented in JavaScript). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `type: "integer"` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies an `integer: true` + * constraint to ensure generated numbers are integers. + * + * @category validation + * @since 4.0.0 + */ +export function isInt(annotations?: Annotations.Filter) { + return makeFilter( + (n: number) => globalThis.Number.isSafeInteger(n), + { + expected: "an integer", + representation: { + id: "effect/schema/isInt", + payload: null + }, + toJsonSchema: () => ({ type: "integer" }), + toCode: () => ({ runtime: "Schema.isInt()" }), + arbitrary: { + constraint: { + integer: true + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isInt` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isInt}. + * + * @see {@link isInt} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isIntReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isInt", + Null, + ({ annotations }) => isInt(annotations) +) + +/** + * Type-level representation of {@link Int}. + * + * @category models + * @since 3.10.0 + */ +export interface Int extends Number { + readonly "Rebuild": Int +} + +/** + * Schema for integers, rejecting `NaN`, `Infinity`, and `-Infinity`. + * + * @category schemas + * @since 3.10.0 + */ +export const Int: Int = Number.check(isInt()) + +/** + * Type-level representation of {@link Natural}. + * + * @category models + * @since 4.0.0 + */ +export interface Natural extends Int { + readonly "Rebuild": Natural +} + +/** + * Schema for non-negative safe integers, including zero. + * + * **When to use** + * + * Use when you need a count, index, or size that cannot be negative. + * + * @see {@link Int} for safe integers that may be negative + * + * @category schemas + * @since 4.0.0 + */ +export const Natural: Natural = Int.check(isGreaterThanOrEqualTo(0)) + +/** + * Validates that a number is a 32-bit signed integer (range: -2,147,483,648 to + * 2,147,483,647). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `format: "int32"` constraint in OpenAPI 3.1, + * or `minimum`/`maximum` constraints in other JSON Schema targets. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies integer and range + * constraints to ensure generated numbers are 32-bit signed integers. + * + * @category validation + * @since 4.0.0 + */ +export function isInt32(annotations?: Annotations.Filter) { + return new SchemaAST.FilterGroup( + [ + isInt(), + isBetween({ minimum: -2147483648, maximum: 2147483647 }) + ], + { + expected: "a 32-bit integer", + ...annotations + } + ) +} + +/** + * Validates that a number is a 32-bit unsigned integer (range: 0 to + * 4,294,967,295). + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `format: "uint32"` constraint in OpenAPI 3.1, + * or `minimum`/`maximum` constraints in other JSON Schema targets. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies integer and range + * constraints to ensure generated numbers are 32-bit unsigned integers. + * + * @category validation + * @since 4.0.0 + */ +export function isUint32(annotations?: Annotations.Filter) { + return new SchemaAST.FilterGroup( + [ + isInt(), + isBetween({ minimum: 0, maximum: 4294967295 }) + ], + { + expected: "a 32-bit unsigned integer", + ...annotations + } + ) +} + +function encodeDatePayload(date: globalThis.Date): string { + if (globalThis.Number.isNaN(date.getTime())) { + throw new globalThis.RangeError(`Expected a valid Date, got ${format(date)}`) + } + return date.toISOString() +} + +function formatDateRuntime(date: globalThis.Date): string { + return `new Date(${format(date.getTime())})` +} + +/** + * Validates that a Date is greater than the specified value (exclusive). + * + * **Details** + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `min` constraint of + * one millisecond after the specified value to ensure generated Date objects are + * greater than it. + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanDate = makeIsGreaterThan({ + order: Order.Date, + annotate: (exclusiveMinimum) => { + const encoded = encodeDatePayload(exclusiveMinimum) + return { + representation: { + id: "effect/schema/isGreaterThanDate", + payload: { exclusiveMinimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanDate(${formatDateRuntime(exclusiveMinimum)})` }) + } + } +}) + +/** + * Validates that a Date is greater than or equal to the specified date + * (inclusive). + * + * **Details** + * + * JSON Schema: + * + * This check does not have a direct JSON Schema equivalent, as JSON Schema + * validates date strings, not Date objects. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `min` constraint + * to ensure generated Date objects are greater than or equal to the specified + * date. + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanOrEqualToDate = makeIsGreaterThanOrEqualTo({ + order: Order.Date, + annotate: (minimum) => { + const encoded = encodeDatePayload(minimum) + return { + representation: { + id: "effect/schema/isGreaterThanOrEqualToDate", + payload: { minimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanOrEqualToDate(${formatDateRuntime(minimum)})` }) + } + } +}) + +/** + * Validates that a Date is less than the specified value (exclusive). + * + * **Details** + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `max` constraint of + * one millisecond before the specified value to ensure generated Date objects + * are less than it. + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanDate = makeIsLessThan({ + order: Order.Date, + annotate: (exclusiveMaximum) => { + const encoded = encodeDatePayload(exclusiveMaximum) + return { + representation: { + id: "effect/schema/isLessThanDate", + payload: { exclusiveMaximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanDate(${formatDateRuntime(exclusiveMaximum)})` }) + } + } +}) + +/** + * Validates that a Date is less than or equal to the specified date + * (inclusive). + * + * **Details** + * + * JSON Schema: + * + * This check does not have a direct JSON Schema equivalent, as JSON Schema + * validates date strings, not Date objects. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `max` constraint + * to ensure generated Date objects are less than or equal to the specified + * date. + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanOrEqualToDate = makeIsLessThanOrEqualTo({ + order: Order.Date, + annotate: (maximum) => { + const encoded = encodeDatePayload(maximum) + return { + representation: { + id: "effect/schema/isLessThanOrEqualToDate", + payload: { maximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanOrEqualToDate(${formatDateRuntime(maximum)})` }) + } + } +}) + +/** + * Validates that a Date is within a specified range. The range boundaries can + * be inclusive or exclusive based on the provided options. + * + * **Details** + * + * JSON Schema: + * + * This check does not have a direct JSON Schema equivalent, as JSON Schema + * validates date strings, not Date objects. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies `min` and `max` + * constraints to ensure generated Date objects fall within the specified range, + * shifting exclusive bounds by one millisecond. + * + * @category validation + * @since 4.0.0 + */ +export const isBetweenDate = makeIsBetween({ + order: Order.Date, + annotate: (options) => { + const exclusiveMinimum = options.exclusiveMinimum ? true : undefined + const exclusiveMaximum = options.exclusiveMaximum ? true : undefined + const payload = { + minimum: encodeDatePayload(options.minimum), + maximum: encodeDatePayload(options.maximum), + ...(exclusiveMinimum && { exclusiveMinimum }), + ...(exclusiveMaximum && { exclusiveMaximum }) + } + return { + representation: { + id: "effect/schema/isBetweenDate", + payload + }, + toJsonSchema: () => ({}), + toCode: () => ({ + runtime: `Schema.isBetweenDate({ minimum: ${formatDateRuntime(options.minimum)}, maximum: ${ + formatDateRuntime(options.maximum) + }, exclusiveMinimum: ${format(exclusiveMinimum)}, exclusiveMaximum: ${format(exclusiveMaximum)} })` + }) + } + } +}) + +/** + * Validates that a BigInt is greater than the specified value (exclusive). + * + * **Details** + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `min` constraint of + * `exclusiveMinimum + 1n` to ensure generated BigInts are greater than the + * specified value. + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanBigInt = makeIsGreaterThan({ + order: Order.BigInt, + annotate: (exclusiveMinimum) => { + const encoded = exclusiveMinimum.toString(10) + return { + representation: { + id: "effect/schema/isGreaterThanBigInt", + payload: { exclusiveMinimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanBigInt(${format(exclusiveMinimum)})` }) + } + } +}) + +/** + * Validates that a BigInt is greater than or equal to the specified value + * (inclusive). + * + * **Details** + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `min` constraint + * to ensure generated BigInt values are greater than or equal to the specified + * value. + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanOrEqualToBigInt = makeIsGreaterThanOrEqualTo({ + order: Order.BigInt, + annotate: (minimum) => { + const encoded = minimum.toString(10) + return { + representation: { + id: "effect/schema/isGreaterThanOrEqualToBigInt", + payload: { minimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanOrEqualToBigInt(${format(minimum)})` }) + } + } +}) + +/** + * Validates that a BigInt is less than the specified value (exclusive). + * + * **Details** + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `max` constraint of + * `exclusiveMaximum - 1n` to ensure generated BigInts are less than the + * specified value. + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanBigInt = makeIsLessThan({ + order: Order.BigInt, + annotate: (exclusiveMaximum) => { + const encoded = exclusiveMaximum.toString(10) + return { + representation: { + id: "effect/schema/isLessThanBigInt", + payload: { exclusiveMaximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanBigInt(${format(exclusiveMaximum)})` }) + } + } +}) + +/** + * Validates that a BigInt is less than or equal to the specified value + * (inclusive). + * + * **Details** + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `max` constraint + * to ensure generated BigInt values are less than or equal to the specified + * value. + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanOrEqualToBigInt = makeIsLessThanOrEqualTo({ + order: Order.BigInt, + annotate: (maximum) => { + const encoded = maximum.toString(10) + return { + representation: { + id: "effect/schema/isLessThanOrEqualToBigInt", + payload: { maximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanOrEqualToBigInt(${format(maximum)})` }) + } + } +}) + +/** + * Validates that a BigInt is within a specified range. The range boundaries can + * be inclusive or exclusive based on the provided options. + * + * **Details** + * + * Arbitrary: + * + * When generating test data with fast-check, this applies `min` and `max` + * constraints to ensure generated BigInt values fall within the specified + * range. + * + * @category validation + * @since 4.0.0 + */ +export const isBetweenBigInt = makeIsBetween({ + order: Order.BigInt, + annotate: (options) => { + const exclusiveMinimum = options.exclusiveMinimum ? true : undefined + const exclusiveMaximum = options.exclusiveMaximum ? true : undefined + const payload = { + minimum: options.minimum.toString(10), + maximum: options.maximum.toString(10), + ...(exclusiveMinimum && { exclusiveMinimum }), + ...(exclusiveMaximum && { exclusiveMaximum }) + } + return { + representation: { + id: "effect/schema/isBetweenBigInt", + payload + }, + toJsonSchema: () => ({}), + toCode: () => ({ + runtime: `Schema.isBetweenBigInt({ minimum: ${format(options.minimum)}, maximum: ${ + format(options.maximum) + }, exclusiveMinimum: ${format(exclusiveMinimum)}, exclusiveMaximum: ${format(exclusiveMaximum)} })` + }) + } + } +}) + +/** + * Validates that a BigDecimal is greater than the specified value (exclusive). + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanBigDecimal = makeIsGreaterThan({ + order: BigDecimal_.Order, + formatter: (bd) => BigDecimal_.format(bd) +}) + +/** + * Validates that a BigDecimal is greater than or equal to the specified value + * (inclusive). + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanOrEqualToBigDecimal = makeIsGreaterThanOrEqualTo({ + order: BigDecimal_.Order, + formatter: (bd) => BigDecimal_.format(bd) +}) + +/** + * Validates that a BigDecimal is less than the specified value (exclusive). + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanBigDecimal = makeIsLessThan({ + order: BigDecimal_.Order, + formatter: (bd) => BigDecimal_.format(bd) +}) + +/** + * Validates that a BigDecimal is less than or equal to the specified value + * (inclusive). + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanOrEqualToBigDecimal = makeIsLessThanOrEqualTo({ + order: BigDecimal_.Order, + formatter: (bd) => BigDecimal_.format(bd) +}) + +/** + * Validates that a `BigDecimal` is within a specified range. + * + * **Details** + * + * The minimum and maximum boundaries are inclusive by default. Pass + * `exclusiveMinimum` or `exclusiveMaximum` to exclude either boundary. + * + * @category validation + * @since 4.0.0 + */ +export const isBetweenBigDecimal = makeIsBetween({ + order: BigDecimal_.Order, + formatter: (bd) => BigDecimal_.format(bd) +}) + +/** + * Validates that a value has at least the specified length. Works with strings + * and arrays. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `minLength` constraint for strings or the + * `minItems` constraint for arrays in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `minLength` + * constraint to ensure generated strings or arrays have at least the required + * length. + * + * **Example** (Checking minimum length) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const NonEmptyStringSchema = Schema.String.check(Schema.isMinLength(1)) + * const NonEmptyArraySchema = Schema.Array(Schema.Number).check(Schema.isMinLength(1)) + * Schema.is(NonEmptyStringSchema)("a") // => true + * Schema.is(NonEmptyArraySchema)([1]) // => true + * ``` + * + * @category validation + * @since 4.0.0 + */ +export function isMinLength(minLength: number, annotations?: Annotations.Filter) { + minLength = Math.max(0, Math.floor(minLength)) + return makeFilter<{ readonly length: number }>( + (input) => input.length >= minLength, + { + expected: `a value with a length of at least ${minLength}`, + representation: { + id: "effect/schema/isMinLength", + payload: { minLength } + }, + toJsonSchema: ({ type }) => type === "array" ? { minItems: minLength } : { minLength }, + toCode: () => ({ runtime: `Schema.isMinLength(${minLength})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + minLength + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isMinLength` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isMinLength}. + * + * @see {@link isMinLength} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isMinLengthReviver: SchemaRepresentation.FilterReviver<{ + readonly minLength: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isMinLength", + Struct({ minLength: Natural }), + ({ annotations, payload }) => isMinLength(payload.minLength, annotations) +) + +/** + * Validates that a value has at least one element. Works with strings and arrays. + * This is equivalent to `isMinLength(1)`. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `minLength: 1` constraint for strings or the + * `minItems: 1` constraint for arrays in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `minLength: 1` + * constraint to ensure generated strings or arrays are non-empty. + * + * @category validation + * @since 4.0.0 + */ +export function isNonEmpty(annotations?: Annotations.Filter) { + return isMinLength(1, annotations) +} + +/** + * Validates that a value has at most the specified length. Works with strings + * and arrays. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `maxLength` constraint for strings or the + * `maxItems` constraint for arrays in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a `maxLength` + * constraint to ensure generated strings or arrays have at most the required + * length. + * + * @category validation + * @since 4.0.0 + */ +export function isMaxLength(maxLength: number, annotations?: Annotations.Filter) { + maxLength = Math.max(0, Math.floor(maxLength)) + return makeFilter<{ readonly length: number }>( + (input) => input.length <= maxLength, + { + expected: `a value with a length of at most ${maxLength}`, + representation: { + id: "effect/schema/isMaxLength", + payload: { maxLength } + }, + toJsonSchema: ({ type }) => type === "array" ? { maxItems: maxLength } : { maxLength }, + toCode: () => ({ runtime: `Schema.isMaxLength(${maxLength})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + maxLength + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isMaxLength` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isMaxLength}. + * + * @see {@link isMaxLength} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isMaxLengthReviver: SchemaRepresentation.FilterReviver<{ + readonly maxLength: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isMaxLength", + Struct({ maxLength: Natural }), + ({ annotations, payload }) => isMaxLength(payload.maxLength, annotations) +) + +/** + * Validates that a value's length is within the specified range. Works with + * strings and arrays. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to `minLength`/`maxLength` constraints for strings + * or `minItems`/`maxItems` constraints for arrays in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies `minLength` and + * `maxLength` constraints to ensure generated strings or arrays have a length + * within the specified range. + * + * @category validation + * @since 4.0.0 + */ +export function isLengthBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) { + minimum = Math.max(0, Math.floor(minimum)) + maximum = Math.max(0, Math.floor(maximum)) + return makeFilter<{ readonly length: number }>( + (input) => input.length >= minimum && input.length <= maximum, + { + expected: minimum === maximum + ? `a value with a length of ${minimum}` + : `a value with a length between ${minimum} and ${maximum}`, + + representation: { + id: "effect/schema/isLengthBetween", + payload: { minimum, maximum } + }, + toJsonSchema: ({ type }) => + type === "array" + ? { allOf: [{ minItems: minimum }, { maxItems: maximum }] } + : { allOf: [{ minLength: minimum }, { maxLength: maximum }] }, + toCode: () => ({ runtime: `Schema.isLengthBetween(${minimum}, ${maximum})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + minLength: minimum, + maxLength: maximum + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isLengthBetween` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLengthBetween}. + * + * @see {@link isLengthBetween} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLengthBetweenReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: number + readonly maximum: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isLengthBetween", + Struct({ minimum: Natural, maximum: Natural }), + ({ annotations, payload }) => isLengthBetween(payload.minimum, payload.maximum, annotations) +) + +/** + * Validates that a value has at least the specified size. Works with values + * that have a `size` property, such as `Set` or `Map`. + * + * **Details** + * + * JSON Schema: + * + * This check does not have a direct JSON Schema equivalent, as it applies to + * values with a `size` property rather than standard JSON Schema types. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a node-local + * `minLength` constraint. Generators for values with a final `.size`, such as + * sets and maps, interpret it as final cardinality. + * + * @category validation + * @since 4.0.0 + */ +export function isMinSize(minSize: number, annotations?: Annotations.Filter) { + minSize = Math.max(0, Math.floor(minSize)) + return makeFilter<{ readonly size: number }>( + (input) => input.size >= minSize, + { + expected: `a value with a size of at least ${minSize}`, + representation: { + id: "effect/schema/isMinSize", + payload: { minSize } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isMinSize(${minSize})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + minLength: minSize + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isMinSize` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isMinSize}. + * + * @see {@link isMinSize} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isMinSizeReviver: SchemaRepresentation.FilterReviver<{ + readonly minSize: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isMinSize", + Struct({ minSize: Natural }), + ({ annotations, payload }) => isMinSize(payload.minSize, annotations) +) + +/** + * Validates that a value has at most the specified size. Works with values + * that have a `size` property, such as `Set` or `Map`. + * + * **Details** + * + * JSON Schema: + * + * This check does not have a direct JSON Schema equivalent, as it applies to + * values with a `size` property rather than standard JSON Schema types. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a node-local + * `maxLength` constraint. Generators for values with a final `.size`, such as + * sets and maps, interpret it as final cardinality. + * + * @category validation + * @since 4.0.0 + */ +export function isMaxSize(maxSize: number, annotations?: Annotations.Filter) { + maxSize = Math.max(0, Math.floor(maxSize)) + return makeFilter<{ readonly size: number }>( + (input) => input.size <= maxSize, + { + expected: `a value with a size of at most ${maxSize}`, + representation: { + id: "effect/schema/isMaxSize", + payload: { maxSize } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isMaxSize(${maxSize})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + maxLength: maxSize + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isMaxSize` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isMaxSize}. + * + * @see {@link isMaxSize} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isMaxSizeReviver: SchemaRepresentation.FilterReviver<{ + readonly maxSize: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isMaxSize", + Struct({ maxSize: Natural }), + ({ annotations, payload }) => isMaxSize(payload.maxSize, annotations) +) + +/** + * Validates that a value's size is within the specified range. Works with + * values that have a `size` property, such as `Set` or `Map`. + * + * **Details** + * + * JSON Schema: + * + * This check does not have a direct JSON Schema equivalent, as it applies to + * values with a `size` property rather than standard JSON Schema types. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies node-local + * `minLength` and `maxLength` constraints. Generators for values with a final + * `.size`, such as sets and maps, interpret them as final cardinality. + * + * @category validation + * @since 4.0.0 + */ +export function isSizeBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) { + minimum = Math.max(0, Math.floor(minimum)) + maximum = Math.max(0, Math.floor(maximum)) + return makeFilter<{ readonly size: number }>( + (input) => input.size >= minimum && input.size <= maximum, + { + expected: minimum === maximum + ? `a value with a size of ${minimum}` + : `a value with a size between ${minimum} and ${maximum}`, + + representation: { + id: "effect/schema/isSizeBetween", + payload: { minimum, maximum } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isSizeBetween(${minimum}, ${maximum})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + minLength: minimum, + maxLength: maximum + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isSizeBetween` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isSizeBetween}. + * + * @see {@link isSizeBetween} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isSizeBetweenReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: number + readonly maximum: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isSizeBetween", + Struct({ minimum: Natural, maximum: Natural }), + ({ annotations, payload }) => isSizeBetween(payload.minimum, payload.maximum, annotations) +) + +/** + * Validates that an object contains at least the specified number of + * properties. This includes both string and symbol keys when counting + * properties. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `minProperties` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a node-local + * `minLength` constraint. Object generators interpret it as the final number + * of own properties. + * + * @category validation + * @since 4.0.0 + */ +export function isMinProperties(minProperties: number, annotations?: Annotations.Filter) { + minProperties = Math.max(0, Math.floor(minProperties)) + return makeFilter( + (input) => Reflect.ownKeys(input).length >= minProperties, + { + expected: `a value with at least ${minProperties === 1 ? "1 entry" : `${minProperties} entries`}`, + representation: { + id: "effect/schema/isMinProperties", + payload: { minProperties } + }, + toJsonSchema: () => ({ minProperties }), + toCode: () => ({ runtime: `Schema.isMinProperties(${minProperties})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + minLength: minProperties + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isMinProperties` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isMinProperties}. + * + * @see {@link isMinProperties} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isMinPropertiesReviver: SchemaRepresentation.FilterReviver<{ + readonly minProperties: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isMinProperties", + Struct({ minProperties: Natural }), + ({ annotations, payload }) => isMinProperties(payload.minProperties, annotations) +) + +/** + * Validates that an object contains at most the specified number of properties. + * This includes both string and symbol keys when counting properties. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to the `maxProperties` constraint in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies a node-local + * `maxLength` constraint. Object generators interpret it as the final number + * of own properties. + * + * @category validation + * @since 4.0.0 + */ +export function isMaxProperties(maxProperties: number, annotations?: Annotations.Filter) { + maxProperties = Math.max(0, Math.floor(maxProperties)) + return makeFilter( + (input) => Reflect.ownKeys(input).length <= maxProperties, + { + expected: `a value with at most ${maxProperties === 1 ? "1 entry" : `${maxProperties} entries`}`, + representation: { + id: "effect/schema/isMaxProperties", + payload: { maxProperties } + }, + toJsonSchema: () => ({ maxProperties }), + toCode: () => ({ runtime: `Schema.isMaxProperties(${maxProperties})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + maxLength: maxProperties + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isMaxProperties` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isMaxProperties}. + * + * @see {@link isMaxProperties} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isMaxPropertiesReviver: SchemaRepresentation.FilterReviver<{ + readonly maxProperties: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isMaxProperties", + Struct({ maxProperties: Natural }), + ({ annotations, payload }) => isMaxProperties(payload.maxProperties, annotations) +) + +/** + * Validates that an object contains between `minimum` and `maximum` properties (inclusive). + * This includes both string and symbol keys when counting properties. + * + * **Details** + * + * JSON Schema: + * + * This check corresponds to `minProperties` and `maxProperties` + * constraints in JSON Schema. + * + * Arbitrary: + * + * When generating test data with fast-check, this applies node-local + * `minLength` and `maxLength` constraints. Object generators interpret them as + * the final number of own properties. + * + * @category validation + * @since 4.0.0 + */ +export function isPropertiesLengthBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) { + minimum = Math.max(0, Math.floor(minimum)) + maximum = Math.max(0, Math.floor(maximum)) + return makeFilter( + (input) => Reflect.ownKeys(input).length >= minimum && Reflect.ownKeys(input).length <= maximum, + { + expected: minimum === maximum + ? `a value with exactly ${minimum === 1 ? "1 entry" : `${minimum} entries`}` + : `a value with between ${minimum} and ${maximum} entries`, + + representation: { + id: "effect/schema/isPropertiesLengthBetween", + payload: { minimum, maximum } + }, + toJsonSchema: () => ({ minProperties: minimum, maxProperties: maximum }), + toCode: () => ({ runtime: `Schema.isPropertiesLengthBetween(${minimum}, ${maximum})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + arbitrary: { + constraint: { + minLength: minimum, + maxLength: maximum + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isPropertiesLengthBetween` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isPropertiesLengthBetween}. + * + * @see {@link isPropertiesLengthBetween} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isPropertiesLengthBetweenReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: number + readonly maximum: number +}> = InternalSchema.makeFilterReviver( + "effect/schema/isPropertiesLengthBetween", + Struct({ minimum: Natural, maximum: Natural }), + ({ annotations, payload }) => isPropertiesLengthBetween(payload.minimum, payload.maximum, annotations) +) + +/** + * Validates that every own property key of an object satisfies the encoded side + * of the provided key schema. + * + * **Details** + * + * This check uses `Reflect.ownKeys`, so symbol keys are validated in addition to + * string property names. + * + * JSON Schema: + * For string property names, this corresponds to the `propertyNames` constraint + * in JSON Schema. + * + * @category validation + * @since 4.0.0 + */ +export function isPropertyNames(keySchema: Constraint, annotations?: Annotations.Filter) { + const propertyNames = toEncoded(keySchema) + const parser = SchemaParser._issue(propertyNames.ast) + return makeFilter( + (input, ast, options) => { + const keys = Reflect.ownKeys(input) + const issues: Array = [] + for (const key of keys) { + const issue = parser(key, options) + if (issue !== undefined) { + issues.push(new SchemaIssue.Pointer([key], issue)) + if (options.errors === "first") break + } + } + if (Arr.isArrayNonEmpty(issues)) { + return new SchemaIssue.Composite(ast, issues, input, options) + } + return true + }, + { + expected: "an object with property names matching the schema", + representation: { + id: "effect/schema/isPropertyNames", + payload: null, + schemas: [propertyNames.ast] + }, + toJsonSchema: ({ schemas }) => ({ propertyNames: schemas[0] }), + toCode: ({ schemas }) => ({ runtime: `Schema.isPropertyNames(${schemas[0].runtime})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isPropertyNames` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isPropertyNames}. + * + * @see {@link isPropertyNames} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isPropertyNamesReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isPropertyNames", + Null, + ({ annotations, schemas }) => isPropertyNames(schemas[0], annotations) +) + +/** + * Validates that all items in an array are unique according to Effect equality. + * + * **Details** + * + * JSON Schema: + * This check corresponds to the `uniqueItems: true` constraint in JSON Schema. + * + * Arbitrary: + * When generating test data with fast-check, this applies a node-local + * `unique: true` constraint. Array generators translate it to `fast-check` + * `uniqueArray` using Effect equality. + * + * @category validation + * @since 4.0.0 + */ +export function isUnique(annotations?: Annotations.Filter) { + return makeFilter>( + (input) => Arr.dedupe(input).length === input.length, + { + expected: "an array with unique items", + representation: { + id: "effect/schema/isUnique", + payload: null + }, + toJsonSchema: () => ({ uniqueItems: true }), + toCode: () => ({ runtime: "Schema.isUnique()" }), + arbitrary: { + constraint: { + unique: true + } + }, + ...annotations + } + ) +} + +/** + * Reviver for persisted `isUnique` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isUnique}. + * + * @see {@link isUnique} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isUniqueReviver: SchemaRepresentation.FilterReviver = InternalSchema.makeFilterReviver( + "effect/schema/isUnique", + Null, + ({ annotations }) => isUnique(annotations) +) + +// ----------------------------------------------------------------------------- +// Built-in Schemas +// ----------------------------------------------------------------------------- + +/** + * Type-level representation of {@link NonEmptyString}. + * + * @category models + * @since 3.10.0 + */ +export interface NonEmptyString extends String { + readonly "Rebuild": NonEmptyString +} + +/** + * Schema for non-empty strings. Validates that a string has at least one + * character. + * + * @category schemas + * @since 3.10.0 + */ +export const NonEmptyString: NonEmptyString = String.check(isNonEmpty()) + +/** + * Type-level representation of {@link Char}. + * + * @category models + * @since 3.10.0 + */ +export interface Char extends String { + readonly "Rebuild": Char +} + +/** + * Schema for strings whose JavaScript `length` is exactly `1`. + * + * **When to use** + * + * Use to validate string values that must have `length === 1`. + * + * **Gotchas** + * + * This schema uses JavaScript `String.length`, so visible characters made from + * multiple UTF-16 code units do not satisfy `length === 1`. + * + * @see {@link String} for unconstrained string values + * @see {@link NonEmptyString} for strings with length greater than zero + * @see {@link isLengthBetween} for the underlying length check + * + * @category schemas + * @since 3.10.0 + */ +export const Char: Char = String.check(isLengthBetween(1, 1)) + +/** + * Type-level representation returned by {@link Option}. + * + * @category models + * @since 3.10.0 + */ +export interface Option extends + declareConstructor< + Option_.Option, + Option_.Option, + readonly [A], + OptionIso + > +{ + readonly "Rebuild": Option + readonly value: A +} + +/** + * Iso representation used for `Option` schemas. + * + * **Details** + * + * `None` is represented as `{ _tag: "None" }`, while `Some` is represented as + * `{ _tag: "Some", value }` using the wrapped schema's `Iso` type. + * + * @category utility types + * @since 4.0.0 + */ +export type OptionIso = + | { readonly _tag: "None" } + | { readonly _tag: "Some"; readonly value: A["Iso"] } + +/** + * Schema for `Option` values. + * + * @category schemas + * @since 3.10.0 + */ +export function Option(value: A): Option { + const schema = declareConstructor< + Option_.Option, + Option_.Option, + OptionIso + >()( + [value], + ([value]) => (input, ast, options) => { + if (Option_.isOption(input)) { + if (Option_.isNone(input)) { + return Effect.succeedNone + } + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(value)(input.value, options), + { + onSuccess: Option_.some, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "value", issue, input, options) + } + ) + } + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + }, + { + representation: { + id: "effect/schema/Option", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.Option(${typeParameters[0].runtime})`, + Type: `Option.Option<${typeParameters[0].Type}>`, + importDeclarations: [`import * as Option from "effect/Option"`] + }), + expected: "Option", + toCodec: ([value]) => + link>()( + Union([ + Struct({ _tag: Literal("Some"), value }), + Struct({ _tag: Literal("None") }) + ]), + SchemaTransformation.transform({ + decode: (e) => e._tag === "None" ? Option_.none() : Option_.some(e.value), + encode: (o) => (Option_.isSome(o) ? { _tag: "Some", value: o.value } as const : { _tag: "None" } as const) + }) + ), + toArbitrary: ([value]) => (fc, ctx) => { + const terminal = fc.constant(Option_.none()) + const arbitrary = fc.oneof( + terminal, + value.arbitrary.map(Option_.some) + ) + return withRecursion(fc, ctx, terminal, arbitrary) + }, + toEquivalence: ([value]) => Option_.makeEquivalence(value), + toFormatter: ([value]) => + Option_.match({ + onNone: () => "none()", + onSome: (t) => `some(${value(t)})` + }) + } + ) + return make(schema.ast, { value }) +} + +/** + * Reviver for persisted `Option` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link Option}. + * + * @see {@link Option} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const OptionReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Option", + Null, + ({ annotations, typeParameters }) => { + const schema = Option(typeParameters[0]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link OptionFromNullOr}. + * + * @category models + * @since 3.10.0 + */ +export interface OptionFromNullOr extends decodeTo>, NullOr> { + readonly "Rebuild": OptionFromNullOr +} + +/** + * Decodes a nullable, required value `T` to a required `Option` value. + * + * **Details** + * + * Decoding maps `null` to `None` and all other values to `Some`. Encoding maps + * `None` to `null` and maps `Some` to its value. + * + * @category schemas + * @since 3.10.0 + */ +export function OptionFromNullOr(schema: S): OptionFromNullOr { + return NullOr(schema).pipe(decodeTo( + Option(toType(schema)), + SchemaTransformation.optionFromNullOr() + )) +} + +/** + * Type-level representation returned by {@link OptionFromUndefinedOr}. + * + * @category models + * @since 3.10.0 + */ +export interface OptionFromUndefinedOr extends decodeTo>, UndefinedOr> { + readonly "Rebuild": OptionFromUndefinedOr +} + +/** + * Decodes a required value that may be `undefined` to a required `Option` + * value. + * + * **Details** + * + * Decoding maps `undefined` to `None` and all other values to `Some`. Encoding + * maps `None` to `undefined` and maps `Some` to its value. + * + * @category schemas + * @since 3.10.0 + */ +export function OptionFromUndefinedOr(schema: S): OptionFromUndefinedOr { + return UndefinedOr(schema).pipe(decodeTo( + Option(toType(schema)), + SchemaTransformation.optionFromUndefinedOr() + )) +} + +/** + * Type-level representation returned by {@link OptionFromNullishOr}. + * + * @category models + * @since 3.10.0 + */ +export interface OptionFromNullishOr extends decodeTo>, NullishOr> { + readonly "Rebuild": OptionFromNullishOr +} + +/** + * Decodes a nullish value `T` to a required `Option` value. + * + * **Details** + * + * Decoding maps `null` and `undefined` to `None` and all other values to + * `Some`. Encoding maps `None` to `null` or `undefined` depending on + * `options.onNoneEncoding`, which defaults to `undefined`, and maps `Some` to + * its value. + * + * @category schemas + * @since 3.10.0 + */ +export function OptionFromNullishOr( + schema: S, + options?: { + onNoneEncoding: null | undefined + } +): OptionFromNullishOr { + return NullishOr(schema).pipe(decodeTo( + Option(toType(schema)), + SchemaTransformation.optionFromNullishOr(options) + )) +} + +/** + * Type-level representation returned by {@link OptionFromOptionalKey}. + * + * @category models + * @since 4.0.0 + */ +export interface OptionFromOptionalKey extends decodeTo>, optionalKey> { + readonly "Rebuild": OptionFromOptionalKey +} + +/** + * Decodes an optional value `A` to a required `Option` value. + * + * **Details** + * + * Decoding maps a missing key to `None` and a present value to `Some`. + * Encoding maps `None` to a missing key and maps `Some` to its value. + * + * @category schemas + * @since 4.0.0 + */ +export function OptionFromOptionalKey(schema: S): OptionFromOptionalKey { + return optionalKey(schema).pipe(decodeTo( + Option(toType(schema)), + SchemaTransformation.optionFromOptionalKey() + )) +} + +/** + * Type-level representation returned by {@link OptionFromOptional}. + * + * @category models + * @since 4.0.0 + */ +export interface OptionFromOptional extends decodeTo>, optional> { + readonly "Rebuild": OptionFromOptional +} + +/** + * Decodes an optional or `undefined` value `A` to a required `Option` + * value. + * + * **Details** + * + * Decoding maps a missing key or a present `undefined` value to `None`, and + * maps all other values to `Some`. Encoding maps `None` to a missing key and + * maps `Some` to its value. + * + * @category schemas + * @since 4.0.0 + */ +export function OptionFromOptional(schema: S): OptionFromOptional { + return optional(schema).pipe(decodeTo( + Option(toType(schema)), + SchemaTransformation.optionFromOptional() + )) +} + +/** + * Type-level representation returned by {@link OptionFromOptionalNullOr}. + * + * @category models + * @since 4.0.0 + */ +export interface OptionFromOptionalNullOr + extends decodeTo>, optional>> +{ + readonly "Rebuild": OptionFromOptionalNullOr +} + +/** + * Decodes an optional or `null` or `undefined` value `A` to a required `Option` + * value. + * + * **Details** + * + * Decoding maps a missing key, `undefined`, or `null` to `None`, and maps all + * other values to `Some`. Encoding maps `Some` to its value. `None` is encoded + * according to `options.onNoneEncoding`: `"omit"` encodes a missing key, + * `null` encodes `null`, and `undefined` encodes `undefined`. + * + * @category schemas + * @since 4.0.0 + */ +export function OptionFromOptionalNullOr( + schema: S, + options?: { + readonly onNoneEncoding: "omit" | null | undefined + } +): OptionFromOptionalNullOr { + const onNoneEncoding = options === undefined ? "omit" : options.onNoneEncoding + const noneValue = onNoneEncoding === null + ? null as S["Type"] | null | undefined + : undefined as S["Type"] | null | undefined + return optional(NullOr(schema)).pipe(decodeTo( + Option(toType(schema)), + SchemaTransformation.transformOptional, S["Type"] | null | undefined>({ + decode: (oe) => oe.pipe(Option_.filter(Predicate.isNotNullish), Option_.some), + encode: onNoneEncoding === "omit" + ? Option_.flatten + : (ot) => Option_.some(Option_.getOrElse(Option_.flatten(ot), () => noneValue)) + }) + )) +} + +/** + * Type-level representation returned by {@link Result}. + * + * @category models + * @since 4.0.0 + */ +export interface Result extends + declareConstructor< + Result_.Result, + Result_.Result, + readonly [A, E], + ResultIso + > +{ + readonly "Rebuild": Result + readonly success: A + readonly failure: E +} + +/** + * Iso representation used for `Result` schemas. + * + * **Details** + * + * Successful results are represented as `{ _tag: "Success", success }`, while + * failed results are represented as `{ _tag: "Failure", failure }`. + * + * @category utility types + * @since 4.0.0 + */ +export type ResultIso = + | { readonly _tag: "Success"; readonly success: A["Iso"] } + | { readonly _tag: "Failure"; readonly failure: E["Iso"] } + +/** + * Schema for `Result` values. + * + * @category schemas + * @since 4.0.0 + */ +export function Result( + success: A, + failure: E +): Result { + const schema = declareConstructor< + Result_.Result, + Result_.Result, + ResultIso + >()( + [success, failure], + ([success, failure]) => (input, ast, options) => { + if (!Result_.isResult(input)) { + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + switch (input._tag) { + case "Success": + return Effect.mapBothEager(SchemaParser.decodeEffect(success)(input.success, options), { + onSuccess: Result_.succeed, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "success", issue, input, options) + }) + case "Failure": + return Effect.mapBothEager(SchemaParser.decodeEffect(failure)(input.failure, options), { + onSuccess: Result_.fail, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "failure", issue, input, options) + }) + } + }, + { + representation: { + id: "effect/schema/Result", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.Result(${typeParameters[0].runtime}, ${typeParameters[1].runtime})`, + Type: `Result.Result<${typeParameters[0].Type}, ${typeParameters[1].Type}>`, + importDeclarations: [`import * as Result from "effect/Result"`] + }), + expected: "Result", + toCodec: ([success, failure]) => + link>()( + Union([ + Struct({ _tag: Literal("Success"), success }), + Struct({ _tag: Literal("Failure"), failure }) + ]), + SchemaTransformation.transform({ + decode: (e): Result_.Result => + e._tag === "Success" ? Result_.succeed(e.success) : Result_.fail(e.failure), + encode: (r) => + Result_.isSuccess(r) + ? { _tag: "Success", success: r.success } as const + : { _tag: "Failure", failure: r.failure } as const + }) + ), + toArbitrary: ([success, failure]) => (fc, ctx) => { + const terminal = oneOfArbitraries( + fc, + success.terminal?.map((a): Result_.Result => Result_.succeed(a)), + failure.terminal?.map((e): Result_.Result => Result_.fail(e)) + ) + const arbitrary = fc.oneof( + success.arbitrary.map((a): Result_.Result => Result_.succeed(a)), + failure.arbitrary.map((e): Result_.Result => Result_.fail(e)) + ) + return withRecursion(fc, ctx, terminal, arbitrary) + }, + toEquivalence: ([success, failure]) => Result_.makeEquivalence(success, failure), + toFormatter: ([success, failure]) => + Result_.match({ + onSuccess: (t) => `success(${success(t)})`, + onFailure: (t) => `failure(${failure(t)})` + }) + } + ) + return make(schema.ast, { success, failure }) +} + +/** + * Reviver for persisted {@link Result} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link Result}. + * + * @see {@link Result} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const ResultReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Result", + Null, + ({ annotations, typeParameters }) => { + const schema = Result(typeParameters[0], typeParameters[1]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link Redacted}. + * + * @category models + * @since 3.10.0 + */ +export interface Redacted extends + declareConstructor< + Redacted_.Redacted, + Redacted_.Redacted, + readonly [S] + > +{ + readonly "Rebuild": Redacted + readonly value: S +} + +type RedactedRepresentationOptions = { + readonly label?: string | undefined + readonly disallowJsonEncode?: true | undefined +} + +type NormalizedRedactedOptions = + | { readonly label: string } + | { readonly disallowJsonEncode: true } + | { readonly label: string; readonly disallowJsonEncode: true } + +type RedactedRepresentationPayload = RedactedRepresentationOptions | null + +const RedactedOptionsPayload = declare((input): input is RedactedRepresentationOptions => { + if (!Predicate.isObject(input)) { + return false + } + const keys = globalThis.Object.keys(input) + return keys.length > 0 && keys.every((key) => { + switch (key) { + case "label": + return typeof input[key] === "string" + case "disallowJsonEncode": + return input[key] === true + default: + return false + } + }) +}) + +const RedactedRepresentationPayload: Decoder = Union([Null, RedactedOptionsPayload]) + +/** + * Schema for `Redacted` values, which hide their contents from inspection. + * + * Options: + * + * - `label`: When provided, the schema will behave as follows: + * - Values will be validated against the label in addition to the wrapped schema + * - The default JSON serializer will deserialize into a `Redacted` instance with the label + * - The arbitrary generator will produce a `Redacted` instance with the label + * - The formatter will return the label + * - `disallowJsonEncode`: When set to `true`, when attempting to encode a `Redacted` instance + * into JSON, it will fail with an error. This is useful when the wrapped schema is + * sensitive and should not be exposed in JSON. + * + * @see {@link RedactedFromValue} for decoding raw values and wrapping them in `Redacted`. + * @category schemas + * @since 3.10.0 + */ +export function Redacted(value: S, options?: { + readonly label?: string | undefined + readonly disallowJsonEncode?: boolean | undefined +}): Redacted { + const label = typeof options?.label === "string" ? options.label : undefined + const disallowJsonEncode = options?.disallowJsonEncode === true + const normalizedOptions: NormalizedRedactedOptions | undefined = label !== undefined + ? disallowJsonEncode ? { label, disallowJsonEncode: true } : { label } + : disallowJsonEncode + ? { disallowJsonEncode: true } + : undefined + const decodeLabel = label !== undefined + ? SchemaParser.decodeUnknownEffect(Literal(label)) + : undefined + const schema = declareConstructor, Redacted_.Redacted>()( + [value], + ([value]) => (input, ast, poptions) => { + if (Redacted_.isRedacted(input)) { + const label: Effect.Effect = decodeLabel !== undefined + ? Effect.mapErrorEager( + decodeLabel(input.label, poptions), + (issue) => new SchemaIssue.Pointer(["label"], issue) + ) + : Effect.void + return Effect.flatMapEager( + label, + () => + Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(value)(Redacted_.value(input), poptions), + { + onSuccess: () => input, + onFailure: (/** ignore the issue because of security reasons */) => { + return new SchemaIssue.Composite( + ast, + [ + new SchemaIssue.Pointer( + ["value"], + new SchemaIssue.InvalidValue(undefined, input, poptions) + ) + ], + input, + poptions + ) + } + } + ) + ) + } + return Effect.fail(new SchemaIssue.InvalidType(ast, input, poptions)) + }, + { + representation: { + id: "effect/schema/Redacted", + payload: normalizedOptions ?? null + }, + toCode: ({ typeParameters }) => ({ + runtime: normalizedOptions !== undefined + ? `Schema.Redacted(${typeParameters[0].runtime}, ${format(normalizedOptions)})` + : `Schema.Redacted(${typeParameters[0].runtime})`, + Type: `Redacted.Redacted<${typeParameters[0].Type}>`, + importDeclarations: [`import * as Redacted from "effect/Redacted"`] + }), + expected: "Redacted", + toCodecJson: ([value]) => + link>()( + value, + { + decode: SchemaGetter.transform((e) => Redacted_.make(e, { label })), + encode: disallowJsonEncode ? + SchemaGetter.forbidden((oe) => + "Cannot serialize Redacted" + + (Option_.isSome(oe) && typeof oe.value.label === "string" ? ` with label: "${oe.value.label}"` : "") + ) : + SchemaGetter.transform(Redacted_.value) + } + ), + toArbitrary: ([value]) => () => ({ + arbitrary: value.arbitrary.map((a) => Redacted_.make(a, { label })), + terminal: value.terminal?.map((a) => Redacted_.make(a, { label })) + }), + toFormatter: () => globalThis.String, + toEquivalence: ([value]) => Redacted_.makeEquivalence(value) + } + ) + return make(schema.ast, { value }) +} + +/** + * Reviver for persisted {@link Redacted} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link Redacted}. + * + * @see {@link Redacted} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const RedactedReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Redacted", + RedactedRepresentationPayload, + ({ annotations, payload, typeParameters }) => { + const schema = Redacted(typeParameters[0], payload ?? undefined) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link RedactedFromValue}. + * + * @category models + * @since 4.0.0 + */ +export interface RedactedFromValue extends decodeTo>, S> { + readonly "Rebuild": RedactedFromValue +} + +/** + * Decodes a value and wraps it in `Redacted`. Unlike {@link Redacted} which + * expects the input to already be a `Redacted` instance, this schema decodes + * the raw value and wraps it. + * + * @see {@link Redacted} for schemas whose input is already a `Redacted` value. + * @category schemas + * @since 4.0.0 + */ +export function RedactedFromValue(value: S, options?: { + readonly label?: string | undefined + readonly disallowEncode?: boolean | undefined +}): RedactedFromValue { + return decodeTo>, S>( + Redacted(toType(value), { + label: options?.label, + disallowJsonEncode: options?.disallowEncode + }), + { + decode: SchemaGetter.transform((t) => Redacted_.make(t, { label: options?.label })), + encode: options?.disallowEncode ? + SchemaGetter.forbidden((oe) => + "Cannot encode Redacted" + + (Option_.isSome(oe) && typeof oe.value.label === "string" ? ` with label: "${oe.value.label}"` : "") + ) : + SchemaGetter.transform(Redacted_.value) + } + )(value) +} + +/** + * Type-level representation returned by {@link CauseReason}. + * + * @category models + * @since 4.0.0 + */ +export interface CauseReason extends + declareConstructor< + Cause_.Reason, + Cause_.Reason, + readonly [E, D], + CauseReasonIso + > +{ + readonly "Rebuild": CauseReason + readonly error: E + readonly defect: D +} + +/** + * Iso representation used for `CauseReason` schemas. + * + * **Details** + * + * Failures are represented with a `Fail` tag and encoded error, defects with a + * `Die` tag and encoded defect, and interrupts with an optional `fiberId`. + * + * @category utility types + * @since 4.0.0 + */ +export type CauseReasonIso = { + readonly _tag: "Fail" + readonly error: E["Iso"] +} | { + readonly _tag: "Die" + readonly error: D["Iso"] +} | { + readonly _tag: "Interrupt" + readonly fiberId: number | undefined +} + +/** + * Creates a schema for `Cause.Reason` values using separate schemas for typed + * failures and unexpected defects. + * + * **When to use** + * + * Use when serializing or decoding individual cause reasons separately from a + * full failure cause, with distinct schemas for typed errors and defects. + * + * **Details** + * + * `Fail` reasons use the `error` schema, `Die` reasons use the `defect` schema, + * and `Interrupt` reasons carry only an optional fiber id. + * + * @see {@link Cause} for constructing schemas for full Cause values + * @see {@link CauseReasonIso} for the ISO shape of each cause reason + * + * @category schemas + * @since 4.0.0 + */ +export function CauseReason(error: E, defect: D): CauseReason { + const schema = declareConstructor, Cause_.Reason, CauseReasonIso>()( + [error, defect], + ([error, defect]) => (input, ast, options) => { + if (!Cause_.isReason(input)) { + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + switch (input._tag) { + case "Fail": + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(error)(input.error, options), + { + onSuccess: Cause_.makeFailReason, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "error", issue, input, options) + } + ) + case "Die": + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(defect)(input.defect, options), + { + onSuccess: Cause_.makeDieReason, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "defect", issue, input, options) + } + ) + case "Interrupt": + return Effect.succeed(input) + } + }, + { + representation: { + id: "effect/schema/CauseReason", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.CauseReason(${typeParameters[0].runtime}, ${typeParameters[1].runtime})`, + Type: `Cause.Failure<${typeParameters[0].Type}, ${typeParameters[1].Type}>`, + importDeclarations: [`import * as Cause from "effect/Cause"`] + }), + expected: "Cause.Failure", + toCodec: ([error, defect]) => + link>()( + Union([ + Struct({ _tag: Literal("Fail"), error }), + Struct({ _tag: Literal("Die"), defect }), + Struct({ _tag: Literal("Interrupt"), fiberId: UndefinedOr(Finite) }) + ]), + SchemaTransformation.transform({ + decode: (e) => { + switch (e._tag) { + case "Fail": + return Cause_.makeFailReason(e.error) + case "Die": + return Cause_.makeDieReason(e.defect) + case "Interrupt": + return Cause_.makeInterruptReason(e.fiberId) + } + }, + encode: identity + }) + ), + toArbitrary: ([error, defect]) => causeReasonToArbitrary(error, defect), + toEquivalence: ([error, defect]) => causeReasonToEquivalence(error, defect), + toFormatter: ([error, defect]) => causeReasonToFormatter(error, defect) + } + ) + return make(schema.ast, { error, defect }) +} + +/** + * Reviver for persisted `CauseReason` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link CauseReason}. + * + * @see {@link CauseReason} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const CauseReasonReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/CauseReason", + Null, + ({ annotations, typeParameters }) => { + const schema = CauseReason(typeParameters[0], typeParameters[1]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +function causeReasonToArbitrary( + error: Annotations.ToArbitrary.TypeParameter, + defect: Annotations.ToArbitrary.TypeParameter +) { + return (fc: typeof FastCheck, ctx: Annotations.ToArbitrary.Context) => { + const terminal = fc.constant(Cause_.makeInterruptReason()) + const arbitrary = fc.oneof( + terminal, + fc.integer({ min: 1 }).map(Cause_.makeInterruptReason), + error.arbitrary.map((e) => Cause_.makeFailReason(e)), + defect.arbitrary.map((d) => Cause_.makeDieReason(d)) + ) + return withRecursion(fc, ctx, terminal, arbitrary) + } +} + +function causeReasonToEquivalence(error: Equivalence.Equivalence, defect: Equivalence.Equivalence) { + return (a: Cause_.Reason, b: Cause_.Reason) => { + if (a._tag !== b._tag) return false + switch (a._tag) { + case "Fail": + return error(a.error, (b as Cause_.Fail).error) + case "Die": + return defect(a.defect, (b as Cause_.Die).defect) + case "Interrupt": + return a.fiberId === (b as Cause_.Interrupt).fiberId + } + } +} + +function causeReasonToFormatter(error: Formatter, defect: Formatter) { + return (t: Cause_.Reason) => { + switch (t._tag) { + case "Fail": + return `Fail(${error(t.error)})` + case "Die": + return `Die(${defect(t.defect)})` + case "Interrupt": + return "Interrupt" + } + } +} + +/** + * Type-level representation returned by {@link Cause}. + * + * @category models + * @since 3.10.0 + */ +export interface Cause extends + declareConstructor< + Cause_.Cause, + Cause_.Cause, + readonly [E, D], + CauseIso + > +{ + readonly "Rebuild": Cause + readonly error: E + readonly defect: D +} + +/** + * Iso representation used for `Cause` schemas: an ordered array of + * `CauseReasonIso` values. + * + * **When to use** + * + * Use when working with the ISO shape of a `Cause` schema, such as `toIso` + * optics or codecs that expose a cause as its ordered array of encoded reasons. + * + * @see {@link Cause} for constructing schemas for full Cause values + * @see {@link CauseReasonIso} for the ISO shape of each array element + * + * @category utility types + * @since 4.0.0 + */ +export type CauseIso = ReadonlyArray> + +/** + * Creates a schema for `Cause` values using separate schemas for typed failures + * and unexpected defects. + * + * **When to use** + * + * Use to validate, transform, or serialize Effect failure causes when typed + * failures and unexpected defects need separate schemas. + * + * **Details** + * + * The `error` schema is applied to `Fail` reasons and the `defect` schema is + * applied to `Die` reasons. Interrupt reasons do not use either schema and + * carry only an optional fiber id. + * + * @see {@link CauseReason} for the schema used by each individual cause reason + * @see {@link CauseIso} for the ordered array representation used by the schema ISO + * + * @category schemas + * @since 3.10.0 + */ +export function Cause(error: E, defect: D): Cause { + const schema = declareConstructor, Cause_.Cause, CauseIso>()( + [error, defect], + ([error, defect]) => { + const failures = ArraySchema(CauseReason(error, defect)) + return (input, ast, options) => { + if (!Cause_.isCause(input)) { + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + return Effect.mapBothEager(SchemaParser.decodeUnknownEffect(failures)(input.reasons, options), { + onSuccess: Cause_.fromReasons, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "failures", issue, input, options) + }) + } + }, + { + representation: { + id: "effect/schema/Cause", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.Cause(${typeParameters[0].runtime}, ${typeParameters[1].runtime})`, + Type: `Cause.Cause<${typeParameters[0].Type}, ${typeParameters[1].Type}>`, + importDeclarations: [`import * as Cause from "effect/Cause"`] + }), + expected: "Cause", + toCodec: ([error, defect]) => + link>()( + ArraySchema(CauseReason(error, defect)), + SchemaTransformation.transform({ + decode: Cause_.fromReasons, + encode: ({ reasons: failures }) => failures + }) + ), + toArbitrary: ([error, defect]) => causeToArbitrary(error, defect), + toEquivalence: ([error, defect]) => causeToEquivalence(error, defect), + toFormatter: ([error, defect]) => causeToFormatter(error, defect) + } + ) + return make(schema.ast, { error, defect }) +} + +/** + * Reviver for persisted `Cause` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link Cause}. + * + * @see {@link Cause} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const CauseReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Cause", + Null, + ({ annotations, typeParameters }) => { + const schema = Cause(typeParameters[0], typeParameters[1]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +function causeToArbitrary( + error: Annotations.ToArbitrary.TypeParameter, + defect: Annotations.ToArbitrary.TypeParameter +) { + return (fc: typeof FastCheck, ctx: Annotations.ToArbitrary.Context) => { + const reason = causeReasonToArbitrary(error, defect)(fc, ctx) + const terminal = fc.constant(Cause_.empty) + const arbitrary = fc.array(reason.arbitrary).map(Cause_.fromReasons) + return withRecursion(fc, ctx, terminal, arbitrary) + } +} + +function causeToEquivalence(error: Equivalence.Equivalence, defect: Equivalence.Equivalence) { + const failures = Equivalence.Array(causeReasonToEquivalence(error, defect)) + return (a: Cause_.Cause, b: Cause_.Cause) => failures(a.reasons, b.reasons) +} + +function causeToFormatter(error: Formatter, defect: Formatter) { + const causeReason = causeReasonToFormatter(error, defect) + return (t: Cause_.Cause) => `Cause([${t.reasons.map(causeReason).join(", ")}])` +} + +/** + * Type-level representation of {@link ErrorInstance}. + * + * @category models + * @since 4.0.0 + */ +export interface ErrorInstance extends instanceOf { + readonly "Rebuild": ErrorInstance +} + +/** + * Options for {@link ErrorInstance} and {@link Defect}. + * + * @category options + * @since 4.0.0 + */ +export interface ErrorOptions { + /** + * Includes string stack traces in encoded `Error` values when set to `true`. + * + * @default false + */ + readonly includeStack?: boolean | undefined + /** + * Excludes `Error.cause` values from encoded `Error` values when set to + * `true`. + * + * @default false + */ + readonly excludeCause?: boolean | undefined +} + +type ErrorRepresentationOptions = { + readonly includeStack?: true | undefined + readonly excludeCause?: true | undefined +} + +type NormalizedErrorOptions = + | { readonly includeStack: true } + | { readonly excludeCause: true } + | { readonly includeStack: true; readonly excludeCause: true } + +type ErrorRepresentationPayload = ErrorRepresentationOptions | null + +const ErrorOptionsPayload = declare((input): input is ErrorRepresentationOptions => { + if (!Predicate.isObject(input)) { + return false + } + const keys = globalThis.Object.keys(input) + return keys.length > 0 && + keys.every((key) => (key === "includeStack" || key === "excludeCause") && input[key] === true) +}) + +const ErrorRepresentationPayload: Decoder = Union([Null, ErrorOptionsPayload]) + +type ErrorOptionsKey = 0 | 1 | 2 | 3 + +const getErrorOptionsKey = (options?: ErrorOptions): ErrorOptionsKey => + ((options?.includeStack === true ? 1 : 0) | + (options?.excludeCause === true ? 2 : 0)) as ErrorOptionsKey + +const getErrorOptions = (key: ErrorOptionsKey): NormalizedErrorOptions | undefined => { + switch (key) { + case 0: + return undefined + case 1: + return { includeStack: true } + case 2: + return { excludeCause: true } + case 3: + return { includeStack: true, excludeCause: true } + } +} + +const errorSchemaCache: Array = [] + +/** + * Schema for JavaScript `Error` objects. + * + * **Details** + * + * Default JSON serializer: + * + * Encodes an `Error` as an object with `message`, optional `name`, and optional + * `cause` properties, and decodes that object back into an `Error`. Stack + * traces are omitted by default for security. Pass `{ includeStack: true }` to + * include stack traces, or `{ excludeCause: true }` to omit causes. + * + * @category schemas + * @since 4.0.0 + */ +export function ErrorInstance(options?: ErrorOptions): ErrorInstance { + const key = getErrorOptionsKey(options) + const cached = errorSchemaCache[key] + if (cached !== undefined) { + return cached + } + const normalizedOptions = getErrorOptions(key) + const schema = instanceOf(globalThis.Error, { + representation: { + id: "effect/schema/Error", + payload: normalizedOptions ?? null + }, + toCode: () => ({ + runtime: normalizedOptions !== undefined + ? `Schema.ErrorInstance(${format(normalizedOptions)})` + : `Schema.ErrorInstance()`, + Type: `globalThis.Error` + }), + expected: "Error", + toCodecJson: () => link()(JsonError, SchemaTransformation.errorFromJsonError(normalizedOptions)), + toArbitrary: () => (fc) => fc.string().map((message) => new globalThis.Error(message)) + }) + errorSchemaCache[key] = schema + return schema +} + +/** + * Reviver for persisted {@link ErrorInstance} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link ErrorInstance}. + * + * @see {@link ErrorInstance} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const ErrorInstanceReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Error", + ErrorRepresentationPayload, + ({ annotations, payload }) => { + const schema = ErrorInstance(payload ?? undefined) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation of {@link Defect}. + * + * @category models + * @since 3.10.0 + */ +export interface Defect extends decodeTo { + readonly "Rebuild": Defect +} + +const defectSchemaCache: Array = [] + +/** + * Schema for unexpected defect values represented as `unknown` with a JSON + * encoded form. + * + * **When to use** + * + * Use when you need a schema for `Cause` defects or other unexpected failures + * whose runtime value may be any value. + * + * **Details** + * + * The encoded side is {@link Json}. During decoding, JSON objects with a string + * `message` property are decoded into JavaScript `Error` values, preserving a + * non-default `name` and any string `stack`. Other JSON values decode + * unchanged. + * + * During encoding, JavaScript `Error` values encode to JSON objects with + * `name`, `message`, and optional `cause` properties. Pass + * `{ includeStack: true }` to include string stack traces in encoded `Error` + * defects, or `{ excludeCause: true }` to omit causes. Other values are + * serialized through Effect's JSON formatter and then parsed back into JSON + * when possible. + * + * **Gotchas** + * + * This schema is for carrying defects across JSON boundaries, not for + * preserving every JavaScript value exactly. Some values cannot round-trip + * unchanged: + * + * - A non-`Error` object such as `{ message: "boom" }` encodes as an + * error-shaped JSON object and decodes back as an `Error`. + * - JSON serialization normalizes unsupported values. For example, + * `undefined` array elements encode as `null`, unsupported object properties + * are omitted, and circular references are dropped. + * - Values that cannot be represented as JSON fall back to Effect's formatted + * string representation. + * + * @see {@link ErrorInstance} for a schema that only accepts JavaScript `Error` values. + * @category schemas + * @since 4.0.0 + */ +export function Defect(options?: ErrorOptions): Defect { + const key = getErrorOptionsKey(options) + const cached = defectSchemaCache[key] + if (cached !== undefined) { + return cached + } + const schema = Json.pipe(decodeTo(Unknown, SchemaTransformation.defectFromJson(getErrorOptions(key)))) + defectSchemaCache[key] = schema + return schema +} + +/** + * Type-level representation returned by {@link Exit}. + * + * @category models + * @since 3.10.0 + */ +export interface Exit extends + declareConstructor< + Exit_.Exit, + Exit_.Exit, + readonly [A, E, D], + ExitIso + > +{ + readonly "Rebuild": Exit + readonly value: A + readonly error: E + readonly defect: D +} + +/** + * Iso representation used for `Exit` schemas. + * + * **Details** + * + * Successful exits are represented as `{ _tag: "Success", value }`, while failed + * exits are represented as `{ _tag: "Failure", cause }`. + * + * @category utility types + * @since 4.0.0 + */ +export type ExitIso = { + readonly _tag: "Success" + readonly value: A["Iso"] +} | { + readonly _tag: "Failure" + readonly cause: CauseIso +} + +/** + * Creates a schema for `Exit` values using schemas for the success value, typed + * failure, and unexpected defect channels. + * + * **When to use** + * + * Use when serializing or validating an effect outcome where success, typed + * failure, and defects each need their own schema. + * + * @category schemas + * @since 3.10.0 + */ +export function Exit( + value: A, + error: E, + defect: D +): Exit { + const schema = declareConstructor< + Exit_.Exit, + Exit_.Exit, + ExitIso + >()( + [value, error, defect], + ([value, error, defect]) => { + const cause = Cause(error, defect) + return (input, ast, options) => { + if (!Exit_.isExit(input)) { + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + switch (input._tag) { + case "Success": + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(value)(input.value, options), + { + onSuccess: Exit_.succeed, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "value", issue, input, options) + } + ) + case "Failure": + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(cause)(input.cause, options), + { + onSuccess: Exit_.failCause, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "cause", issue, input, options) + } + ) + } + } + }, + { + representation: { + id: "effect/schema/Exit", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.Exit(${typeParameters[0].runtime}, ${typeParameters[1].runtime}, ${ + typeParameters[2].runtime + })`, + Type: `Exit.Exit<${typeParameters[0].Type}, ${typeParameters[1].Type}, ${typeParameters[2].Type}>`, + importDeclarations: [`import * as Exit from "effect/Exit"`] + }), + expected: "Exit", + toCodec: ([value, error, defect]) => + link>()( + Union([ + Struct({ _tag: Literal("Success"), value }), + Struct({ _tag: Literal("Failure"), cause: Cause(error, defect) }) + ]), + SchemaTransformation.transform({ + decode: (e): Exit_.Exit => + e._tag === "Success" ? Exit_.succeed(e.value) : Exit_.failCause(e.cause), + encode: (exit) => + Exit_.isSuccess(exit) + ? { _tag: "Success", value: exit.value } as const + : { _tag: "Failure", cause: exit.cause } as const + }) + ), + toArbitrary: ([value, error, defect]) => (fc, ctx) => { + const cause = causeToArbitrary(error, defect)(fc, ctx) + const terminal = oneOfArbitraries( + fc, + value.terminal?.map((v): Exit_.Exit => Exit_.succeed(v)), + cause.terminal?.map((cause): Exit_.Exit => Exit_.failCause(cause)) + ) + const arbitrary = fc.oneof( + value.arbitrary.map((v): Exit_.Exit => Exit_.succeed(v)), + cause.arbitrary.map((cause): Exit_.Exit => Exit_.failCause(cause)) + ) + return withRecursion(fc, ctx, terminal, arbitrary) + }, + toEquivalence: ([value, error, defect]) => { + const cause = causeToEquivalence(error, defect) + return (a, b) => { + if (a._tag !== b._tag) return false + switch (a._tag) { + case "Success": + return value(a.value, (b as Exit_.Success).value) + case "Failure": + return cause(a.cause, (b as Exit_.Failure).cause) + } + } + }, + toFormatter: ([value, error, defect]) => { + const cause = causeToFormatter(error, defect) + return (t) => { + switch (t._tag) { + case "Success": + return `Exit.Success(${value(t.value)})` + case "Failure": + return `Exit.Failure(${cause(t.cause)})` + } + } + } + } + ) + return make(schema.ast, { value, error, defect }) +} + +/** + * Reviver for persisted `Exit` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link Exit}. + * + * @see {@link Exit} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const ExitReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Exit", + Null, + ({ annotations, typeParameters }) => { + const schema = Exit(typeParameters[0], typeParameters[1], typeParameters[2]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link ReadonlyMap}. + * + * @category models + * @since 4.0.0 + */ +export interface $ReadonlyMap extends + declareConstructor< + globalThis.ReadonlyMap, + globalThis.ReadonlyMap, + readonly [Key, Value], + ReadonlyMapIso + > +{ + readonly "Rebuild": $ReadonlyMap + readonly key: Key + readonly value: Value +} + +/** + * Iso representation used for `ReadonlyMap` schemas: an array of readonly + * `[key, value]` tuples using each entry schema's `Iso` type. + * + * @category utility types + * @since 4.0.0 + */ +export type ReadonlyMapIso = ReadonlyArray< + readonly [Key["Iso"], Value["Iso"]] +> + +function oneOfArbitraries( + fc: typeof FastCheck, + a: FastCheck.Arbitrary | undefined, + b: FastCheck.Arbitrary | undefined +) { + return a === undefined ? b : b === undefined ? a : fc.oneof(a, b) +} + +function withRecursion( + fc: typeof FastCheck, + ctx: Annotations.ToArbitrary.Context, + terminal: FastCheck.Arbitrary | undefined, + arbitrary: FastCheck.Arbitrary +) { + return { + arbitrary: terminal === undefined || ctx.recursion === undefined + ? arbitrary + : fc.oneof(ctx.recursion, terminal, arbitrary), + terminal + } +} + +function arrayFromItems( + fc: typeof FastCheck, + item: FastCheck.Arbitrary, + constraints: FastCheck.ArrayConstraints | undefined, + comparator?: ((a: T, b: T) => boolean) | undefined +) { + return comparator === undefined + ? fc.array(item, constraints) + : fc.uniqueArray(item, { ...constraints, comparator }) +} + +function collectionArbitrary( + fc: typeof FastCheck, + ctx: Annotations.ToArbitrary.Context, + item: FastCheck.Arbitrary, + terminalItem: FastCheck.Arbitrary | undefined, + fromIterable: (items: Array) => Out, + comparator?: ((a: T, b: T) => boolean) | undefined +) { + const constraint = ctx.constraint + const constraints = constraint === undefined || + (constraint.minLength === undefined && constraint.maxLength === undefined) + ? undefined + : { + ...(constraint.minLength !== undefined ? { minLength: constraint.minLength } : {}), + ...(constraint.maxLength !== undefined ? { maxLength: constraint.maxLength } : {}) + } + if ( + constraints?.minLength !== undefined && constraints.maxLength !== undefined && + constraints.minLength > constraints.maxLength + ) { + throw new globalThis.Error("Unable to derive an arbitrary for size constraints") + } + const minLength = constraints?.minLength ?? 0 + const terminal = minLength === 0 + ? fc.constant>([]) + : terminalItem === undefined + ? undefined + : arrayFromItems(fc, terminalItem, { ...constraints, maxLength: minLength }, comparator) + const arrays = withRecursion( + fc, + ctx, + terminal, + arrayFromItems(fc, item, constraints, comparator) + ) + return { + arbitrary: arrays.arbitrary.map(fromIterable), + terminal: arrays.terminal?.map(fromIterable) + } +} + +function entriesArbitrary( + fc: typeof FastCheck, + ctx: Annotations.ToArbitrary.Context, + key: Annotations.ToArbitrary.TypeParameter, + value: Annotations.ToArbitrary.TypeParameter, + fromIterable: (items: Array<[K, V]>) => Out +) { + return collectionArbitrary( + fc, + ctx, + fc.tuple(key.arbitrary, value.arbitrary), + key.terminal === undefined || value.terminal === undefined ? undefined : fc.tuple(key.terminal, value.terminal), + fromIterable, + ([a], [b]) => Equal.equals(a, b) + ) +} + +/** + * Schema for readonly maps whose keys and values conform to the provided + * schemas. + * + * @category schemas + * @since 3.10.0 + */ +export function ReadonlyMap( + key: Key, + value: Value +): $ReadonlyMap { + const schema = declareConstructor< + globalThis.ReadonlyMap, + globalThis.ReadonlyMap, + ReadonlyMapIso + >()( + [key, value], + ([key, value]) => { + const array = ArraySchema(Tuple([key, value])) + return (input, ast, options) => { + if (input instanceof globalThis.Map) { + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(array)([...input], options), + { + onSuccess: (array: ReadonlyArray) => new globalThis.Map(array), + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "entries", issue, input, options) + } + ) + } + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + }, + { + representation: { + id: "effect/schema/ReadonlyMap", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.ReadonlyMap(${typeParameters[0].runtime}, ${typeParameters[1].runtime})`, + Type: `globalThis.ReadonlyMap<${typeParameters[0].Type}, ${typeParameters[1].Type}>` + }), + expected: "ReadonlyMap", + toCodec: ([key, value]) => + link>()( + ArraySchema(Tuple([key, value])), + SchemaTransformation.transform({ + decode: (e) => new globalThis.Map(e), + encode: (map) => [...map.entries()] + }) + ), + toArbitrary: ([key, value]) => (fc, ctx) => entriesArbitrary(fc, ctx, key, value, (as) => new globalThis.Map(as)), + toEquivalence: ([key, value]) => Equal.makeCompareMap(key, value), + toFormatter: ([key, value]) => (t) => { + const size = t.size + if (size === 0) { + return "ReadonlyMap(0) {}" + } + const entries = globalThis.Array.from(t.entries()).sort().map(([k, v]) => `${key(k)} => ${value(v)}`) + return `ReadonlyMap(${size}) { ${entries.join(", ")} }` + } + } + ) + return make(schema.ast, { key, value }) +} + +/** + * Reviver for persisted {@link ReadonlyMap} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link ReadonlyMap}. + * + * @see {@link ReadonlyMap} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const ReadonlyMapReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/ReadonlyMap", + Null, + ({ annotations, typeParameters }) => { + const schema = ReadonlyMap(typeParameters[0], typeParameters[1]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Encoded representation of an immutable Effect graph. + * + * @category models + * @since 4.0.0 + */ +export type EncodedGraph = Graph_.Snapshot + +/** + * Iso representation used for {@link Graph} schemas. + * + * @category utility types + * @since 4.0.0 + */ +export type GraphIso = EncodedGraph< + Node["Iso"], + Edge["Iso"], + T +> + +/** + * Type-level representation returned by {@link Graph}. + * + * @category models + * @since 4.0.0 + */ +export interface Graph + extends + declareConstructor< + Graph_.Graph, + Graph_.Graph, + readonly [Node, Edge], + GraphIso + > +{ + readonly "Rebuild": Graph + readonly type: T + readonly node: Node + readonly edge: Edge +} + +function graphEncodedSchema( + type: T, + node: Node, + edge: Edge +) { + return Struct({ + type: Literal(type), + nodes: ArraySchema(Struct({ index: Natural, data: node })), + edges: ArraySchema(Struct({ index: Natural, source: Natural, target: Natural, data: edge })) + }) +} + +function graphDecode( + input: EncodedGraph, + options: SchemaAST.ParseOptions +): Effect.Effect, SchemaIssue.Issue> { + let previous = -1 + const indexes = new Set() + for (let i = 0; i < input.nodes.length; i++) { + const index = input.nodes[i].index + if (index <= previous) { + return Effect.fail( + new SchemaIssue.Pointer( + ["nodes", i, "index"], + new SchemaIssue.InvalidValue({ expected: "a strictly increasing node index" }, index, options) + ) + ) + } + previous = index + indexes.add(index) + } + + previous = -1 + for (let i = 0; i < input.edges.length; i++) { + const edge = input.edges[i] + if (edge.index <= previous) { + return Effect.fail( + new SchemaIssue.Pointer( + ["edges", i, "index"], + new SchemaIssue.InvalidValue({ expected: "a strictly increasing edge index" }, edge.index, options) + ) + ) + } + previous = edge.index + if (!indexes.has(edge.source)) { + return Effect.fail( + new SchemaIssue.Pointer( + ["edges", i, "source"], + new SchemaIssue.InvalidValue({ expected: "an encoded node index" }, edge.source, options) + ) + ) + } + if (!indexes.has(edge.target)) { + return Effect.fail( + new SchemaIssue.Pointer( + ["edges", i, "target"], + new SchemaIssue.InvalidValue({ expected: "an encoded node index" }, edge.target, options) + ) + ) + } + } + + return Effect.succeed(InternalGraph.hydrate(input)) +} + +function graphEncode( + input: Graph_.Graph, + type: T, + options: SchemaAST.ParseOptions +): Effect.Effect, SchemaIssue.Issue> { + if (!Graph_.isGraph(input) || input.mutable || input.type !== type) { + return Effect.fail(new SchemaIssue.InvalidValue({ expected: `an immutable ${type} Graph` }, input, options)) + } + return Effect.succeed(InternalGraph.snapshot(input)) +} + +function graphToEquivalence( + node: Equivalence.Equivalence, + edge: Equivalence.Equivalence +) { + return (self: Graph_.Graph, that: Graph_.Graph): boolean => { + const a = InternalGraph.snapshot(self) + const b = InternalGraph.snapshot(that) + if (a.type !== b.type || a.nodes.length !== b.nodes.length || a.edges.length !== b.edges.length) return false + for (let i = 0; i < a.nodes.length; i++) { + if (a.nodes[i].index !== b.nodes[i].index || !node(a.nodes[i].data, b.nodes[i].data)) return false + } + for (let i = 0; i < a.edges.length; i++) { + const ae = a.edges[i] + const be = b.edges[i] + const sameEndpoints = a.type === "directed" + ? ae.source === be.source && ae.target === be.target + : (ae.source === be.source && ae.target === be.target) || + (ae.source === be.target && ae.target === be.source) + if ( + ae.index !== be.index || !sameEndpoints || !edge(ae.data, be.data) + ) return false + } + return true + } +} + +function graphToArbitrary( + type: T, + node: Annotations.ToArbitrary.TypeParameter, + edge: Annotations.ToArbitrary.TypeParameter +) { + return (fc: typeof FastCheck, ctx: Annotations.ToArbitrary.Context) => { + const empty = InternalGraph.hydrate({ type, nodes: [], edges: [] }) + const terminal = fc.constant(empty) + const arbitrary = fc.array(node.arbitrary).chain((values) => { + const nodes = values.map((data, index) => ({ index, data })) + if (nodes.length === 0) return terminal + const endpoint = fc.integer({ min: 0, max: nodes.length - 1 }) + return fc.array(fc.tuple(endpoint, endpoint, edge.arbitrary)).map((values) => + InternalGraph.hydrate({ + type, + nodes, + edges: values.map(([source, target, data], index) => ({ index, source, target, data })) + }) + ) + }) + return withRecursion(fc, ctx, terminal, arbitrary) + } +} + +/** + * Creates a schema for immutable directed or undirected Effect graphs. + * + * Encoding preserves active node and edge indexes, payloads, endpoints, + * isolated nodes, self-loops, parallel edges, and stored edge orientation. It + * does not encode removed-ID allocator history; after decoding, future allocation starts + * after the highest active decoded index. Encoding rejects mutable graphs. + * `Graph.toJSON()` remains an inspection summary and is not this wire format. + * + * **Example** (Encoding a directed graph as JSON) + * + * ```ts import.meta.vitest + * import { Graph, Schema } from "effect" + * + * const codec = Schema.toCodecJson(Schema.Graph("directed", Schema.String, Schema.Number)) + * const graph = Graph.directed((mutable) => { + * const source = Graph.addNode(mutable, "A") + * const target = Graph.addNode(mutable, "B") + * Graph.addEdge(mutable, source, target, 1) + * }) + * + * const encoded = Schema.encodeSync(codec)(graph) + * + * encoded.type // => "directed" + * encoded.nodes // => [{ index: 0, data: "A" }, { index: 1, data: "B" }] + * encoded.edges // => [{ index: 0, source: 0, target: 1, data: 1 }] + * ``` + * + * @category schemas + * @since 4.0.0 + */ +export function Graph( + type: "directed", + node: Node, + edge: Edge +): Graph<"directed", Node, Edge> +export function Graph( + type: "undirected", + node: Node, + edge: Edge +): Graph<"undirected", Node, Edge> +export function Graph( + type: T, + node: Node, + edge: Edge +): Graph +export function Graph( + type: T, + node: Node, + edge: Edge +): Graph { + const schema = declareConstructor< + Graph_.Graph, + Graph_.Graph, + GraphIso + >()( + [node, edge], + ([node, edge]) => { + const encoded = graphEncodedSchema(type, node, edge) + return (input, ast, options) => { + if (!Graph_.isGraph(input) || input.mutable || input.type !== type) { + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + return Effect.flatMap( + SchemaParser.decodeUnknownEffect(encoded)(InternalGraph.snapshot(input), options), + (snapshot) => graphDecode(snapshot, options) + ) + } + }, + { + representation: { + id: "effect/schema/Graph", + payload: type + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.Graph(${format(type)}, ${typeParameters[0].runtime}, ${typeParameters[1].runtime})`, + Type: `Graph.Graph<${typeParameters[0].Type}, ${typeParameters[1].Type}, ${format(type)}>`, + importDeclarations: [`import * as Graph from "effect/Graph"`] + }), + expected: `an immutable ${type} Graph`, + toCodec: ([node, edge]) => + link>()( + graphEncodedSchema(type, node, edge), + SchemaTransformation.transformOrFail({ + decode: graphDecode, + encode: (graph, options) => graphEncode(graph, type, options) + }) + ), + toArbitrary: ([node, edge]) => graphToArbitrary(type, node, edge), + toEquivalence: ([node, edge]) => graphToEquivalence(node, edge), + toFormatter: () => globalThis.String + } + ) + return make(schema.ast, { type, node, edge }) +} + +/** + * Reviver for persisted {@link Graph} declarations. + * + * @category schemas + * @since 4.0.0 + */ +export const GraphReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Graph", + Literals(["directed", "undirected"]), + ({ annotations, payload, typeParameters }) => { + const schema = Graph(payload, typeParameters[0], typeParameters[1]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link HashMap}. + * + * @category models + * @since 3.10.0 + */ +export interface HashMap extends + declareConstructor< + HashMap_.HashMap, + HashMap_.HashMap, + readonly [Key, Value], + HashMapIso + > +{ + readonly "Rebuild": HashMap + readonly key: Key + readonly value: Value +} + +/** + * Iso representation used for `HashMap` schemas: an array of readonly + * `[key, value]` tuples using each entry schema's `Iso` type. + * + * @category utility types + * @since 4.0.0 + */ +export type HashMapIso = ReadonlyArray< + readonly [Key["Iso"], Value["Iso"]] +> + +/** + * Schema for hash maps whose keys and values conform to the provided schemas. + * + * @category schemas + * @since 3.10.0 + */ +export function HashMap(key: Key, value: Value): HashMap { + const schema = declareConstructor< + HashMap_.HashMap, + HashMap_.HashMap, + HashMapIso + >()( + [key, value], + ([key, value]) => { + const entries = ArraySchema(Tuple([key, value])) + return (input, ast, options) => { + if (HashMap_.isHashMap(input)) { + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(entries)(HashMap_.toEntries(input), options), + { + onSuccess: HashMap_.fromIterable, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "entries", issue, input, options) + } + ) + } + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + }, + { + representation: { + id: "effect/schema/HashMap", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.HashMap(${typeParameters[0].runtime}, ${typeParameters[1].runtime})`, + Type: `HashMap.HashMap<${typeParameters[0].Type}, ${typeParameters[1].Type}>`, + importDeclarations: [`import * as HashMap from "effect/HashMap"`] + }), + expected: "HashMap", + toCodec: ([key, value]) => + link>()( + ArraySchema(Tuple([key, value])), + SchemaTransformation.transform({ + decode: HashMap_.fromIterable, + encode: HashMap_.toEntries + }) + ), + toArbitrary: ([key, value]) => (fc, ctx) => entriesArbitrary(fc, ctx, key, value, HashMap_.fromIterable), + toEquivalence: ([key, value]) => Equal.makeCompareMap(key, value), + toFormatter: ([key, value]) => (t) => { + const size = HashMap_.size(t) + if (size === 0) { + return "HashMap(0) {}" + } + const entries = HashMap_.toEntries(t).sort().map(([k, v]) => `${key(k)} => ${value(v)}`) + return `HashMap(${size}) { ${entries.join(", ")} }` + } + } + ) + return make(schema.ast, { key, value }) +} + +/** + * Reviver for persisted `HashMap` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link HashMap}. + * + * @see {@link HashMap} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const HashMapReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/HashMap", + Null, + ({ annotations, typeParameters }) => { + const schema = HashMap(typeParameters[0], typeParameters[1]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link ReadonlySet}. + * + * @category models + * @since 4.0.0 + */ +export interface $ReadonlySet extends + declareConstructor< + globalThis.ReadonlySet, + globalThis.ReadonlySet, + readonly [Value], + ReadonlySetIso + > +{ + readonly "Rebuild": $ReadonlySet + readonly value: Value +} + +/** + * Iso representation used for `ReadonlySet` schemas: an array of element values + * using the element schema's `Iso` type. + * + * @category utility types + * @since 4.0.0 + */ +export type ReadonlySetIso = ReadonlyArray + +/** + * Schema for readonly sets whose values conform to the provided element schema. + * + * @category schemas + * @since 3.10.0 + */ +export function ReadonlySet(value: Value): $ReadonlySet { + const schema = declareConstructor< + globalThis.ReadonlySet, + globalThis.ReadonlySet, + ReadonlySetIso + >()( + [value], + ([value]) => { + const array = ArraySchema(value) + return (input, ast, options) => { + if (input instanceof globalThis.Set) { + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(array)([...input], options), + { + onSuccess: (array: ReadonlyArray) => new globalThis.Set(array), + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "values", issue, input, options) + } + ) + } + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + }, + { + representation: { + id: "effect/schema/ReadonlySet", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.ReadonlySet(${typeParameters[0].runtime})`, + Type: `globalThis.ReadonlySet<${typeParameters[0].Type}>` + }), + expected: "ReadonlySet", + toCodec: ([value]) => + link>()( + ArraySchema(value), + SchemaTransformation.transform({ + decode: (e) => new globalThis.Set(e), + encode: (set) => [...set.values()] + }) + ), + toArbitrary: ([value]) => (fc, ctx) => + collectionArbitrary(fc, ctx, value.arbitrary, value.terminal, (as) => new globalThis.Set(as), Equal.equals), + toEquivalence: ([value]) => Equal.makeCompareSet(value), + toFormatter: ([value]) => (t) => { + const size = t.size + if (size === 0) { + return "ReadonlySet(0) {}" + } + const values = globalThis.Array.from(t.values()).sort().map((v) => `${value(v)}`) + return `ReadonlySet(${size}) { ${values.join(", ")} }` + } + } + ) + return make(schema.ast, { value }) +} + +/** + * Reviver for persisted {@link ReadonlySet} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link ReadonlySet}. + * + * @see {@link ReadonlySet} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const ReadonlySetReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/ReadonlySet", + Null, + ({ annotations, typeParameters }) => { + const schema = ReadonlySet(typeParameters[0]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link HashSet}. + * + * @category models + * @since 3.10.0 + */ +export interface HashSet extends + declareConstructor< + HashSet_.HashSet, + HashSet_.HashSet, + readonly [Value], + HashSetIso + > +{ + readonly "Rebuild": HashSet + readonly value: Value +} + +/** + * Iso representation used for `HashSet` schemas: an array of element values + * using the element schema's `Iso` type. + * + * @category utility types + * @since 4.0.0 + */ +export type HashSetIso = ReadonlyArray + +/** + * Schema for hash sets whose values conform to the provided element schema. + * + * @category schemas + * @since 3.10.0 + */ +export function HashSet(value: Value): HashSet { + const schema = declareConstructor< + HashSet_.HashSet, + HashSet_.HashSet, + HashSetIso + >()( + [value], + ([value]) => { + const values = ArraySchema(value) + return (input, ast, options) => { + if (HashSet_.isHashSet(input)) { + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(values)(Arr.fromIterable(input), options), + { + onSuccess: HashSet_.fromIterable, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "values", issue, input, options) + } + ) + } + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + }, + { + representation: { + id: "effect/schema/HashSet", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.HashSet(${typeParameters[0].runtime})`, + Type: `HashSet.HashSet<${typeParameters[0].Type}>` + }), + expected: "HashSet", + toCodec: ([value]) => + link>()( + ArraySchema(value), + SchemaTransformation.transform({ + decode: HashSet_.fromIterable, + encode: Arr.fromIterable + }) + ), + toArbitrary: ([value]) => (fc, ctx) => + collectionArbitrary(fc, ctx, value.arbitrary, value.terminal, HashSet_.fromIterable, Equal.equals), + toEquivalence: ([value]) => Equal.makeCompareSet(value), + toFormatter: ([value]) => (t) => { + const size = HashSet_.size(t) + if (size === 0) { + return "HashSet(0) {}" + } + const values = globalThis.Array.from(t).sort().map((v) => `${value(v)}`) + return `HashSet(${size}) { ${values.join(", ")} }` + } + } + ) + return make(schema.ast, { value }) +} + +/** + * Reviver for persisted `HashSet` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link HashSet}. + * + * @see {@link HashSet} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const HashSetReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/HashSet", + Null, + ({ annotations, typeParameters }) => { + const schema = HashSet(typeParameters[0]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation returned by {@link Chunk}. + * + * @category models + * @since 3.10.0 + */ +export interface Chunk extends + declareConstructor< + Chunk_.Chunk, + Chunk_.Chunk, + readonly [Value], + ChunkIso + > +{ + readonly "Rebuild": Chunk + readonly value: Value +} + +/** + * Iso representation used for `Chunk` schemas: an array of element values using + * the element schema's `Iso` type. + * + * **When to use** + * + * Use when annotating type-level helpers that work with the readonly-array ISO + * shape of a `Chunk` schema. + * + * @see {@link Chunk} for the schema interface and constructor that use this ISO representation + * + * @category utility types + * @since 4.0.0 + */ +export type ChunkIso = ReadonlyArray + +/** + * Schema for chunks whose values conform to the provided element schema. + * + * @category schemas + * @since 3.10.0 + */ +export function Chunk(value: Value): Chunk { + const schema = declareConstructor< + Chunk_.Chunk, + Chunk_.Chunk, + ChunkIso + >()( + [value], + ([value]) => { + const values = ArraySchema(value) + return (input, ast, options) => { + if (Chunk_.isChunk(input)) { + return Effect.mapBothEager( + SchemaParser.decodeUnknownEffect(values)(Arr.fromIterable(input), options), + { + onSuccess: Chunk_.fromIterable, + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "values", issue, input, options) + } + ) + } + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + }, + { + representation: { + id: "effect/schema/Chunk", + payload: null + }, + toCode: ({ typeParameters }) => ({ + runtime: `Schema.Chunk(${typeParameters[0].runtime})`, + Type: `Chunk.Chunk<${typeParameters[0].Type}>` + }), + expected: "Chunk", + toCodec: ([value]) => + link>()( + ArraySchema(value), + SchemaTransformation.transform({ + decode: Chunk_.fromIterable, + encode: Arr.fromIterable + }) + ), + toArbitrary: ([value]) => (fc, ctx) => + collectionArbitrary(fc, ctx, value.arbitrary, value.terminal, Chunk_.fromIterable), + toEquivalence: ([value]) => Chunk_.makeEquivalence(value), + toFormatter: ([value]) => (t) => { + const size = Chunk_.size(t) + if (size === 0) { + return "Chunk(0) {}" + } + const values = globalThis.Array.from(t).sort().map((v) => `${value(v)}`) + return `Chunk(${size}) { ${values.join(", ")} }` + } + } + ) + return make(schema.ast, { value }) +} + +/** + * Reviver for persisted {@link Chunk} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain schemas created by {@link Chunk}. + * + * @see {@link Chunk} for creating the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const ChunkReviver = InternalSchema.makeDeclarationReviver( + "effect/schema/Chunk", + Null, + ({ annotations, typeParameters }) => { + const schema = Chunk(typeParameters[0]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + +/** + * Type-level representation of {@link RegExp}. + * + * @category models + * @since 4.0.0 + */ +export interface RegExp extends instanceOf { + readonly "Rebuild": RegExp +} + +/** + * Schema for JavaScript `RegExp` objects. + * + * **Details** + * + * The default JSON serializer encodes a `RegExp` as `{ source, flags }`. + * + * @category schemas + * @since 4.0.0 + */ +export const RegExp: RegExp = instanceOf( + globalThis.RegExp, + { + representation: { + id: "effect/schema/RegExp", + payload: null + }, + toCode: () => ({ + runtime: `Schema.RegExp`, + Type: `globalThis.RegExp` + }), + expected: "RegExp", + toCodecJson: () => + link()( + Struct({ + source: String, + flags: String + }), + SchemaTransformation.transformOrFail({ + decode: (e, options) => + Effect.try({ + try: () => new globalThis.RegExp(e.source, e.flags), + catch: () => + new SchemaIssue.InvalidValue( + { expected: "valid RegExp source and flags" }, + e, + options + ) + }), + encode: (regExp) => + Effect.succeed({ + source: regExp.source, + flags: regExp.flags + }) + }) + ), + toArbitrary: () => (fc) => + fc + .tuple( + fc.constantFrom( + ".", + ".*", + "\\d+", + "\\w+", + "[a-z]+", + "[A-Z]+", + "[0-9]+", + "^[a-zA-Z0-9]+$", + "^\\d{4}-\\d{2}-\\d{2}$" // date pattern + ), + fc + .uniqueArray(fc.constantFrom("g", "i", "m", "s", "u", "y"), { + minLength: 0, + maxLength: 6 + }) + .map((flags) => flags.join("")) + ) + .map(([source, flags]) => new globalThis.RegExp(source, flags)), + toEquivalence: () => (a, b) => a.source === b.source && a.flags === b.flags + } +) + +/** + * Reviver for persisted `RegExp` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link RegExp} schema. + * + * @see {@link RegExp} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const RegExpReviver = makeFixedDeclarationReviver( + "effect/schema/RegExp", + RegExp +) + +/** + * Type-level representation of {@link URL}. + * + * @category models + * @since 4.0.0 + */ +export interface URL extends instanceOf { + readonly "Rebuild": URL +} + +const URLString = String.annotate({ expected: "a string that will be decoded as a URL" }) + +/** + * Schema for JavaScript `URL` objects. + * + * **Details** + * + * Default JSON serializer: + * + * - encodes `URL` as a `string` + * + * @category schemas + * @since 4.0.0 + */ +export const URL: URL = instanceOf( + globalThis.URL, + { + representation: { + id: "effect/schema/URL", + payload: null + }, + toCode: () => ({ + runtime: `Schema.URL`, + Type: `globalThis.URL` + }), + expected: "URL", + toCodecJson: () => + link()( + URLString, + SchemaTransformation.urlFromString + ), + toArbitrary: () => (fc) => fc.webUrl().map((s) => new globalThis.URL(s)), + toEquivalence: () => (a, b) => a.toString() === b.toString() + } +) + +/** + * Reviver for persisted `URL` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link URL} schema. + * + * @see {@link URL} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const URLReviver = makeFixedDeclarationReviver( + "effect/schema/URL", + URL +) + +/** + * Type-level representation of {@link URLFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface URLFromString extends decodeTo { + readonly "Rebuild": URLFromString +} + +/** + * Schema that decodes a `string` into a `URL`. + * + * **Details** + * + * Decoding: + * - A **valid** URL `string` is decoded as a `URL` + * + * Encoding: + * - A `URL` is encoded as a `string` + * + * @category schemas + * @since 4.0.0 + */ +export const URLFromString: URLFromString = URLString.pipe(decodeTo(URL, SchemaTransformation.urlFromString)) + +/** + * Type-level representation of {@link Date}. + * + * @category models + * @since 4.0.0 + */ +export interface Date extends declare { + readonly "Rebuild": Date +} + +function dateArbitraryConstraints( + ordered: Annotations.ToArbitrary.OrderedConstraint | undefined, + base?: FastCheck.DateConstraints | undefined, + toDate?: (value: T) => globalThis.Date +): FastCheck.DateConstraints { + const out: FastCheck.DateConstraints = { ...base } + if (ordered?.minimum !== undefined) { + const minimum = toDate === undefined ? ordered.minimum as globalThis.Date : toDate(ordered.minimum) + const nextMin = ordered.exclusiveMinimum ? new globalThis.Date(minimum.getTime() + 1) : minimum + if (out.min === undefined || nextMin.getTime() > out.min.getTime()) { + out.min = nextMin + } + } + if (ordered?.maximum !== undefined) { + const maximum = toDate === undefined ? ordered.maximum as globalThis.Date : toDate(ordered.maximum) + const nextMax = ordered.exclusiveMaximum ? new globalThis.Date(maximum.getTime() - 1) : maximum + if (out.max === undefined || nextMax.getTime() < out.max.getTime()) { + out.max = nextMax + } + } + return out +} + +const DateString = String.annotate({ expected: "a string that will be decoded as a Date" }) + +/** + * Schema for valid JavaScript `Date` objects. + * + * **When to use** + * + * Use to validate in-memory values that must already be valid JavaScript date + * objects. + * + * **Details** + * + * This schema accepts `Date` instances whose timestamp is not `NaN`. The + * default JSON serializer encodes dates as ISO 8601 strings. + * + * **Example** (Defining a Date schema) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const date = Schema.decodeUnknownSync(Schema.Date)(new Date("2024-01-01")) + * date.toISOString() // => "2024-01-01T00:00:00.000Z" + * ``` + * + * @see {@link DateFromString} for decoding strings into Date instances + * @see {@link DateFromMillis} for decoding epoch milliseconds into Date instances + * + * @category schemas + * @since 4.0.0 + */ +export const Date: Date = declare( + (input): input is globalThis.Date => input instanceof globalThis.Date && !globalThis.Number.isNaN(input.getTime()), + { + representation: { + id: "effect/schema/Date", + payload: null + }, + toCode: () => ({ + runtime: `Schema.Date`, + Type: `globalThis.Date` + }), + expected: "a valid Date", + toCodecJson: () => + link()( + DateString, + SchemaTransformation.dateFromString + ), + toArbitrary: () => (fc, ctx) => + fc.date(dateArbitraryConstraints( + ctx?.constraint?.ordered?.order === Order.Date ? ctx.constraint.ordered : undefined, + { noInvalidDate: true } + )) + } +) + +/** + * Reviver for persisted `Date` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link Date} schema. + * + * @see {@link Date} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const DateReviver = makeFixedDeclarationReviver( + "effect/schema/Date", + Date +) + +/** + * Type-level representation of {@link DateFromString}. + * + * @category models + * @since 3.10.0 + */ +export interface DateFromString extends decodeTo { + readonly "Rebuild": DateFromString +} + +/** + * Schema that decodes a string into a JavaScript `Date`. + * + * **When to use** + * + * Use to model string-encoded dates that decode to JavaScript `Date` objects + * and encode back to strings. + * + * **Details** + * + * Decoding: + * The string is passed to JavaScript `Date` construction. + * + * Encoding: + * A `Date` is encoded as an ISO string. + * + * Invalid date strings fail decoding. + * + * @see {@link DateFromMillis} for decoding epoch milliseconds into Date instances + * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values + * @see {@link Date} for accepting Date instances directly + * + * @category schemas + * @since 3.10.0 + */ +export const DateFromString: DateFromString = DateString.pipe(decodeTo(Date, SchemaTransformation.dateFromString)) + +/** + * Type-level representation of {@link DateFromMillis}. + * + * @category models + * @since 4.0.0 + */ +export interface DateFromMillis extends decodeTo { + readonly "Rebuild": DateFromMillis +} + +/** + * Schema that decodes epoch milliseconds into a JavaScript `Date`. + * + * **When to use** + * + * Use to model numeric millisecond timestamps that decode to JavaScript `Date` + * objects and encode back to numbers. + * + * **Details** + * + * Decoding: + * A safe integer number of milliseconds since the Unix epoch is decoded as a + * `Date`. + * + * Encoding: + * A `Date` is encoded as its millisecond timestamp. + * + * **Gotchas** + * + * JavaScript `Date` supports a narrower range than safe integers, so integers + * outside the supported `Date` range fail decoding. + * + * @see {@link DateFromString} for decoding string-encoded dates + * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values + * + * @category schemas + * @since 4.0.0 + */ +export const DateFromMillis: DateFromMillis = Int.pipe( + decodeTo(Date, SchemaTransformation.dateFromMillis) +) + +/** + * Type-level representation of {@link Duration}. + * + * @category models + * @since 3.10.0 + */ +export interface Duration extends declare { + readonly "Rebuild": Duration +} + +/** + * Schema for `Duration` values. + * + * **Details** + * + * The default JSON serializer encodes `Duration` as a tagged object with the + * duration type and value. + * + * **Example** (Defining a Duration schema) + * + * ```ts import.meta.vitest + * import { Duration, Schema } from "effect" + * + * Schema.decodeUnknownSync(Schema.Duration)(Duration.seconds(5)) // => Duration.seconds(5) + * ``` + * + * @category schemas + * + * @since 3.10.0 + */ +export const Duration: Duration = declare( + Duration_.isDuration, + { + representation: { + id: "effect/schema/Duration", + payload: null + }, + toCode: () => ({ + runtime: `Schema.Duration`, + Type: `Duration.Duration`, + importDeclarations: [`import * as Duration from "effect/Duration"`] + }), + expected: "Duration", + toCodecJson: () => + link()( + Union([ + Struct({ _tag: Literal("Infinity") }), + Struct({ _tag: Literal("NegativeInfinity") }), + Struct({ _tag: Literal("Nanos"), value: BigInt }), + Struct({ _tag: Literal("Millis"), value: Int }) + ]), + SchemaTransformation.transform({ + decode: (e) => { + switch (e._tag) { + case "Infinity": + return Duration_.infinity + case "NegativeInfinity": + return Duration_.negativeInfinity + case "Nanos": + return Duration_.nanos(e.value) + case "Millis": + return Duration_.millis(e.value) + } + }, + encode: (duration) => { + switch (duration.value._tag) { + case "Infinity": + return { _tag: "Infinity" } as const + case "NegativeInfinity": + return { _tag: "NegativeInfinity" } as const + case "Nanos": + return { _tag: "Nanos", value: duration.value.nanos } as const + case "Millis": + return { _tag: "Millis", value: duration.value.millis } as const + } + } + }) + ), + toArbitrary: () => (fc) => + fc.oneof( + fc.constant(Duration_.infinity), + fc.constant(Duration_.negativeInfinity), + fc.bigInt().map(Duration_.nanos), + fc.maxSafeInteger().map(Duration_.millis) + ), + toFormatter: () => globalThis.String, + toEquivalence: () => Duration_.Equivalence + } +) + +/** + * Reviver for persisted {@link Duration} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link Duration} schema. + * + * @see {@link Duration} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const DurationReviver = makeFixedDeclarationReviver( + "effect/schema/Duration", + Duration +) + +const DurationString = String.annotate({ expected: "a string that will be decoded as a Duration" }) + +/** + * Type-level representation of {@link DurationFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface DurationFromString extends decodeTo { + readonly "Rebuild": DurationFromString +} + +/** + * Schema that parses a string into a `Duration`. + * + * **Details** + * + * Decoding: + * - A `string` is decoded as a `Duration`, accepting any format that + * `Duration.fromInput` can parse. + * + * Encoding: + * - A `Duration` is encoded as a parseable `string`. + * + * @category schemas + * @since 4.0.0 + */ +export const DurationFromString: DurationFromString = DurationString.pipe( + decodeTo(Duration, SchemaTransformation.durationFromString) +) + +/** + * Type-level representation of {@link DurationFromNanos}. + * + * @category models + * @since 3.10.0 + */ +export interface DurationFromNanos extends decodeTo { + readonly "Rebuild": DurationFromNanos +} + +/** + * Schema that decodes a `bigint` into a `Duration`, treating the bigint as + * nanoseconds. + * + * **Details** + * + * Decoding: + * A `bigint` representing nanoseconds is decoded as a `Duration`. + * + * Encoding: + * Finite durations are encoded as a `bigint` number of nanoseconds. Encoding + * fails when the duration cannot be represented as nanoseconds, such as + * `Duration.infinity` or `Duration.negativeInfinity`. + * + * @category schemas + * @since 3.10.0 + */ +export const DurationFromNanos: DurationFromNanos = BigInt.pipe( + decodeTo(Duration, SchemaTransformation.durationFromNanos) +) + +/** + * Type-level representation of {@link DurationFromMillis}. + * + * @category models + * @since 3.10.0 + */ +export interface DurationFromMillis extends decodeTo { + readonly "Rebuild": DurationFromMillis +} + +/** + * Schema that decodes a number into a `Duration`, treating the number as + * milliseconds. + * + * **Details** + * + * Decoding: + * - A finite or infinite number is decoded as a `Duration` + * + * Encoding: + * - A `Duration` is encoded to a finite or infinite number of milliseconds + * + * **Gotchas** + * + * `NaN` is decoded as `Duration.zero`, matching `Duration.millis`. + * + * @category schemas + * @since 3.10.0 + */ +export const DurationFromMillis: DurationFromMillis = Number.pipe( + decodeTo(Duration, SchemaTransformation.durationFromMillis) +) + +/** + * Type-level representation of {@link BigDecimal}. + * + * @category models + * @since 3.10.0 + */ +export interface BigDecimal extends declare { + readonly "Rebuild": BigDecimal +} + +const BigDecimalString = String.annotate({ expected: "a string that will be decoded as a BigDecimal" }) + +const bigDecimalDefaultMaxScale = 20 +const bigDecimalInvalidOrderedConstraintsError = "Unable to derive an arbitrary for the ordered BigDecimal constraints" + +function bigDecimalScaleValueAtScale(bd: BigDecimal_.BigDecimal, scale: number): bigint { + return BigDecimal_.scale(bd, scale).value +} + +function bigDecimalMinValueAtScale( + minimum: BigDecimal_.BigDecimal, + scale: number, + excluded: boolean +): bigint { + return excluded + ? bigDecimalScaleValueAtScale(BigDecimal_.floor(minimum, scale), scale) + globalThis.BigInt(1) + : bigDecimalScaleValueAtScale(BigDecimal_.ceil(minimum, scale), scale) +} + +function bigDecimalMaxValueAtScale( + maximum: BigDecimal_.BigDecimal, + scale: number, + excluded: boolean +): bigint { + return excluded + ? bigDecimalScaleValueAtScale(BigDecimal_.ceil(maximum, scale), scale) - globalThis.BigInt(1) + : bigDecimalScaleValueAtScale(BigDecimal_.floor(maximum, scale), scale) +} + +function bigDecimalMaxScale(ordered: Annotations.ToArbitrary.OrderedConstraint): number { + return Math.max( + bigDecimalDefaultMaxScale, + ordered.minimum?.scale ?? 0, + ordered.maximum?.scale ?? 0, + ordered.exclusiveMinimum && ordered.minimum !== undefined ? ordered.minimum.scale + 1 : 0, + ordered.exclusiveMaximum && ordered.maximum !== undefined ? ordered.maximum.scale + 1 : 0 + ) +} + +function bigDecimalValueConstraintsAtScale( + ordered: Annotations.ToArbitrary.OrderedConstraint, + scale: number +): FastCheck.BigIntConstraints | undefined { + const constraints: FastCheck.BigIntConstraints = {} + if (ordered.minimum !== undefined) { + constraints.min = bigDecimalMinValueAtScale(ordered.minimum, scale, ordered.exclusiveMinimum === true) + } + if (ordered.maximum !== undefined) { + constraints.max = bigDecimalMaxValueAtScale(ordered.maximum, scale, ordered.exclusiveMaximum === true) + } + if (constraints.min !== undefined && constraints.max !== undefined && constraints.min > constraints.max) { + return undefined + } + return constraints +} + +function bigDecimalScaleConstraints( + ordered: Annotations.ToArbitrary.OrderedConstraint +): FastCheck.IntegerConstraints { + const max = bigDecimalMaxScale(ordered) + if (bigDecimalValueConstraintsAtScale(ordered, max) === undefined) { + throw new globalThis.Error(bigDecimalInvalidOrderedConstraintsError) + } + + let min = 0 + let high = max + while (min < high) { + const scale = min + Math.floor((high - min) / 2) + if (bigDecimalValueConstraintsAtScale(ordered, scale) === undefined) { + min = scale + 1 + } else { + high = scale + } + } + return { min, max } +} + +/** + * Schema for `BigDecimal` values. + * + * **When to use** + * + * Use when you already have Effect decimal instances and need schema + * validation, formatting, equivalence, and JSON string serialization. + * + * **Details** + * + * Default JSON serializer: + * + * - encodes `BigDecimal` as a `string` + * + * @see {@link BigDecimalFromString} for parsing string input into a BigDecimal + * + * @category schemas + * @since 3.10.0 + */ +export const BigDecimal: BigDecimal = declare( + BigDecimal_.isBigDecimal, + { + representation: { + id: "effect/schema/BigDecimal", + payload: null + }, + toCode: () => ({ + runtime: `Schema.BigDecimal`, + Type: `BigDecimal.BigDecimal`, + importDeclarations: [`import * as BigDecimal from "effect/BigDecimal"`] + }), + expected: "BigDecimal", + toCodecJson: () => + link()( + BigDecimalString, + SchemaTransformation.bigDecimalFromString + ), + toArbitrary: () => (fc, ctx) => { + const ordered = ctx.constraint?.ordered?.order === BigDecimal_.Order + ? ctx.constraint.ordered as Annotations.ToArbitrary.OrderedConstraint + : undefined + if (ordered === undefined) { + return fc.tuple(fc.bigInt(), fc.integer({ min: 0, max: bigDecimalDefaultMaxScale })) + .map(([value, scale]) => BigDecimal_.make(value, scale)) + } + + return fc.integer(bigDecimalScaleConstraints(ordered)).chain((scale) => { + const constraints = bigDecimalValueConstraintsAtScale(ordered, scale) + if (constraints === undefined) { + throw new globalThis.Error(bigDecimalInvalidOrderedConstraintsError) + } + return fc.bigInt(constraints).map((value) => BigDecimal_.make(value, scale)) + }) + }, + toFormatter: () => (bd) => BigDecimal_.format(bd), + toEquivalence: () => BigDecimal_.Equivalence + } +) + +/** + * Reviver for persisted {@link BigDecimal} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link BigDecimal} schema. + * + * @see {@link BigDecimal} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const BigDecimalReviver = makeFixedDeclarationReviver( + "effect/schema/BigDecimal", + BigDecimal +) + +/** + * Type-level representation of {@link BigDecimalFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface BigDecimalFromString extends decodeTo { + readonly "Rebuild": BigDecimalFromString +} + +/** + * Schema that parses a string into a `BigDecimal`. + * + * **When to use** + * + * Use to parse decimal or exponent-notation strings into arbitrary-precision + * BigDecimal values while encoding them back to strings. + * + * **Details** + * + * Decoding: + * - A `string` is decoded with `BigDecimal.fromString`. + * + * Encoding: + * - A `BigDecimal` is encoded with `BigDecimal.format`. + * + * **Gotchas** + * + * An empty string decodes as zero. + * + * @see {@link BigDecimal} for validating values that are already BigDecimal values + * @see {@link BigIntFromString} for parsing base-10 integer strings into bigint values + * @see {@link NumberFromString} for parsing JavaScript number strings + * + * @category schemas + * @since 4.0.0 + */ +export const BigDecimalFromString: BigDecimalFromString = BigDecimalString.pipe( + decodeTo(BigDecimal, SchemaTransformation.bigDecimalFromString) +) + +const JsonString = String.annotate({ + expected: "a string that will be decoded as JSON", + contentMediaType: "application/json" +}) + +/** + * Type-level representation returned by {@link fromJsonString}. + * + * @category models + * @since 4.0.0 + */ +export interface fromJsonString extends decodeTo { + readonly "Rebuild": fromJsonString +} + +/** + * Returns a schema that decodes a JSON string and then decodes the parsed value + * using the given schema. + * + * **Details** + * + * This is useful when working with JSON-encoded strings where the actual + * structure of the value is known and described by an existing schema. + * + * During decoding, the resulting schema first parses the input string as JSON, + * using `reviver` when provided, and then runs the provided schema on the + * parsed result. During encoding, it first encodes with the provided schema and + * then passes the result to `JSON.stringify` with the optional `replacer` and + * `space`. + * + * **Example** (Formatting encoded JSON) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.Struct({ a: Schema.Number }) + * const schemaFromJsonString = Schema.fromJsonString(schema, { space: 2 }) + * + * Schema.encodeSync(schemaFromJsonString)({ a: 1 }) // => "{\n \"a\": 1\n}" + * ``` + * + * @category schemas + * @since 4.0.0 + */ +export function fromJsonString( + schema: S, + options?: { + readonly reviver?: Parameters[1] | undefined + readonly replacer?: SchemaGetter.JsonReplacer | undefined + readonly space?: Parameters[2] | undefined + } +): fromJsonString { + return JsonString.pipe(decodeTo(schema, SchemaTransformation.fromJsonString(options))) +} + +/** @internal */ +export const UnknownFromJsonString: fromJsonString = fromJsonString(Unknown) + +/** + * Type-level representation of {@link File}. + * + * @category models + * @since 4.0.0 + */ +export interface File extends instanceOf { + readonly "Rebuild": File +} + +/** + * Schema for JavaScript `File` objects. + * + * **Details** + * + * The default JSON serializer encodes a `File` as `{ data, type, name, lastModified }` + * where `data` is base64-encoded. + * + * @category schemas + * @since 4.0.0 + */ +export const File: File = instanceOf(globalThis.File, { + representation: { + id: "effect/schema/File", + payload: null + }, + toCode: () => ({ + runtime: `Schema.File`, + Type: `globalThis.File` + }), + expected: "File", + toCodecJson: () => + link()( + Struct({ + data: String.check(isBase64()), + type: String, + name: String, + lastModified: Int + }), + SchemaTransformation.transformOrFail({ + decode: (e, options) => + Result_.match(Encoding.decodeBase64(e.data), { + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Base64 string" }, + e.data, + options + ) + ), + onSuccess: (bytes) => { + const buffer = new globalThis.Uint8Array(bytes) + return Effect.succeed( + new globalThis.File([buffer], e.name, { type: e.type, lastModified: e.lastModified }) + ) + } + }), + encode: (file, options) => + Effect.tryPromise({ + try: async () => { + const bytes = new globalThis.Uint8Array(await file.arrayBuffer()) + return { + data: Encoding.encodeBase64(bytes), + type: file.type, + name: file.name, + lastModified: file.lastModified + } + }, + catch: () => + new SchemaIssue.InvalidValue( + { expected: "a readable File" }, + file, + options + ) + }) + }) + ) +}) + +/** + * Reviver for persisted `File` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link File} schema. + * + * @see {@link File} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const FileReviver = makeFixedDeclarationReviver( + "effect/schema/File", + File +) + +/** + * Type-level representation of {@link FormData}. + * + * @category models + * @since 4.0.0 + */ +export interface FormData extends instanceOf { + readonly "Rebuild": FormData +} + +/** + * Schema for JavaScript `FormData` objects. + * + * **Details** + * + * The default JSON serializer encodes a `FormData` as an array of `[key, entry]` + * pairs where each entry is tagged as `"String"` or `"File"`. + * + * @category schemas + * @since 4.0.0 + */ +export const FormData: FormData = instanceOf(globalThis.FormData, { + representation: { + id: "effect/schema/FormData", + payload: null + }, + toCode: () => ({ + runtime: `Schema.FormData`, + Type: `globalThis.FormData` + }), + expected: "FormData", + toCodecJson: () => + link()( + ArraySchema( + Tuple([ + String, + Union([ + Struct({ _tag: tag("String"), value: String }), + Struct({ _tag: tag("File"), value: File }) + ]) + ]) + ), + SchemaTransformation.transformOrFail({ + decode: (e) => { + const out = new globalThis.FormData() + for (const [key, entry] of e) { + out.append(key, entry.value) + } + return Effect.succeed(out) + }, + encode: (formData) => { + return Effect.succeed( + globalThis.Array.from(formData.entries()).map(([key, value]) => { + if (typeof value === "string") { + return [key, { _tag: "String", value }] as const + } else { + return [key, { _tag: "File", value }] as const + } + }) + ) + } + }) + ) +}) + +/** + * Reviver for persisted `FormData` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link FormData} schema. + * + * @see {@link FormData} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const FormDataReviver = makeFixedDeclarationReviver( + "effect/schema/FormData", + FormData +) + +/** + * Type-level representation returned by {@link fromFormData}. + * + * @category models + * @since 4.0.0 + */ +export interface fromFormData extends decodeTo { + readonly "Rebuild": fromFormData +} + +/** + * Schema for decoding `FormData` through a bracket-notation tree. + * + * **When to use** + * + * Use to decode browser or multipart form data into a structured schema value. + * + * **Details** + * + * The decoding process has two steps: + * + * 1. Parse `FormData` into a nested tree record. + * 2. Decode the parsed value with the given schema. + * + * You can express nested values using bracket notation. + * + * If you want to decode string fields into non-string primitive values, use + * `Schema.toCodecStringTree`. + * + * **Example** (Decoding a flat structure) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.fromFormData( + * Schema.Struct({ + * a: Schema.String + * }) + * ) + * + * const formData = new FormData() + * formData.append("a", "1") + * formData.append("b", "2") + * + * Schema.decodeUnknownSync(schema)(formData) // => { a: "1" } + * ``` + * + * **Example** (Decoding nested fields) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.fromFormData( + * Schema.Struct({ + * a: Schema.String, + * b: Schema.Struct({ + * c: Schema.String, + * d: Schema.String + * }) + * }) + * ) + * + * const formData = new FormData() + * formData.append("a", "1") + * formData.append("b[c]", "2") + * formData.append("b[d]", "3") + * + * Schema.decodeUnknownSync(schema)(formData) // => { a: "1", b: { c: "2", d: "3" } } + * ``` + * + * **Example** (Parsing non-string values) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.fromFormData( + * Schema.toCodecStringTree( + * Schema.Struct({ + * a: Schema.Int + * }) + * ) + * ) + * + * const formData = new FormData() + * formData.append("a", "1") + * + * Schema.decodeUnknownSync(schema)(formData) // => { a: 1 } + * ``` + * + * @category decoding + * @since 4.0.0 + */ +export function fromFormData(schema: S): fromFormData { + return FormData.pipe(decodeTo(schema, SchemaTransformation.fromFormData)) +} + +/** + * Type-level representation of {@link URLSearchParams}. + * + * @category models + * @since 4.0.0 + */ +export interface URLSearchParams extends instanceOf { + readonly "Rebuild": URLSearchParams +} + +/** + * Schema for JavaScript `URLSearchParams` objects. + * + * **Details** + * + * The default JSON serializer encodes a `URLSearchParams` as a query string. + * + * @category schemas + * @since 4.0.0 + */ +export const URLSearchParams: URLSearchParams = instanceOf(globalThis.URLSearchParams, { + representation: { + id: "effect/schema/URLSearchParams", + payload: null + }, + toCode: () => ({ + runtime: `Schema.URLSearchParams`, + Type: `globalThis.URLSearchParams` + }), + expected: "URLSearchParams", + toCodecJson: () => + link()( + String.annotate({ expected: "a query string that will be decoded as URLSearchParams" }), + SchemaTransformation.transform({ + decode: (e) => new globalThis.URLSearchParams(e), + encode: (params) => params.toString() + }) + ) +}) + +/** + * Reviver for persisted `URLSearchParams` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link URLSearchParams} schema. + * + * @see {@link URLSearchParams} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const URLSearchParamsReviver = makeFixedDeclarationReviver( + "effect/schema/URLSearchParams", + URLSearchParams +) + +/** + * Type-level representation returned by {@link fromURLSearchParams}. + * + * @category models + * @since 4.0.0 + */ +export interface fromURLSearchParams extends decodeTo { + readonly "Rebuild": fromURLSearchParams +} + +/** + * Schema for decoding `URLSearchParams` through a bracket-notation tree. + * + * **When to use** + * + * Use to decode query parameters into a structured schema value. + * + * **Details** + * + * The decoding process has two steps: + * + * 1. Parse `URLSearchParams` into a nested tree record. + * 2. Decode the parsed value with the given schema. + * + * You can express nested values using bracket notation. + * + * If you want to decode values that are not strings, use + * `Schema.toCodecStringTree`. This serializer preserves values such as + * numbers when compatible with the schema. + * + * **Example** (Decoding a flat structure) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.fromURLSearchParams( + * Schema.Struct({ + * a: Schema.String + * }) + * ) + * + * const urlSearchParams = new URLSearchParams("a=1&b=2") + * + * Schema.decodeUnknownSync(schema)(urlSearchParams) // => { a: "1" } + * ``` + * + * **Example** (Decoding nested fields) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.fromURLSearchParams( + * Schema.Struct({ + * a: Schema.String, + * b: Schema.Struct({ + * c: Schema.String, + * d: Schema.String + * }) + * }) + * ) + * + * const urlSearchParams = new URLSearchParams("a=1&b[c]=2&b[d]=3") + * + * Schema.decodeUnknownSync(schema)(urlSearchParams) // => { a: "1", b: { c: "2", d: "3" } } + * ``` + * + * **Example** (Parsing non-string values) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.fromURLSearchParams( + * Schema.toCodecStringTree( + * Schema.Struct({ + * a: Schema.Int + * }) + * ) + * ) + * + * const urlSearchParams = new URLSearchParams("a=1&b=2") + * + * Schema.decodeUnknownSync(schema)(urlSearchParams) // => { a: 1 } + * ``` + * + * @category decoding + * @since 4.0.0 + */ +export function fromURLSearchParams(schema: S): fromURLSearchParams { + return URLSearchParams.pipe(decodeTo(schema, SchemaTransformation.fromURLSearchParams)) +} + +/** + * Type-level representation of {@link NumberFromString}. + * + * @category models + * @since 3.10.0 + */ +export interface NumberFromString extends decodeTo { + readonly "Rebuild": NumberFromString +} + +/** + * Schema that parses a string into a `number` using JavaScript + * number coercion. + * + * **Details** + * + * Decoding: + * A `string` is decoded as a number, including possible non-finite values such as + * `NaN`, `Infinity`, and `-Infinity`. Use `FiniteFromString` to reject non-finite + * numbers. + * + * Encoding: + * A number is encoded as a `string`. + * + * @category schemas + * @since 3.10.0 + */ +export const NumberFromString: NumberFromString = String.annotate({ + expected: "a string that will be decoded as a number" +}).pipe(decodeTo(Number, SchemaTransformation.numberFromString)) + +/** + * Type-level representation of {@link FiniteFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface FiniteFromString extends decodeTo { + readonly "Rebuild": FiniteFromString +} + +/** + * Schema that parses a string into a finite number. + * + * **Details** + * + * Decoding: + * - A `string` is decoded as a finite number, rejecting `NaN`, `Infinity`, and + * `-Infinity` values. + * + * Encoding: + * - A finite number is encoded as a `string`. + * + * @category schemas + * @since 4.0.0 + */ +export const FiniteFromString: FiniteFromString = String.annotate({ + expected: "a string that will be decoded as a finite number" +}).pipe(decodeTo(Finite, SchemaTransformation.numberFromString)) + +/** + * Type-level representation of {@link BigIntFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface BigIntFromString extends decodeTo { + readonly "Rebuild": BigIntFromString +} + +/** + * Schema that parses a string into a `bigint`. + * + * **When to use** + * + * Use to parse signed base-10 integer strings into bigint values while encoding + * bigint values back to decimal strings. + * + * **Details** + * + * Decoding: + * - A `string` is decoded as a `bigint`. + * + * Encoding: + * - A `bigint` is encoded as a `string`. + * + * **Gotchas** + * + * Decoding accepts only strings matching `^-?\d+$`. + * + * @see {@link isStringBigInt} for the string predicate used by this schema + * @see {@link BigInt} for validating values that are already bigint values + * @see {@link NumberFromString} for parsing JavaScript number strings, including non-finite values + * @see {@link BigDecimalFromString} for parsing decimal number strings + * + * @category schemas + * @since 4.0.0 + */ +export const BigIntFromString: BigIntFromString = make(SchemaAST.bigIntString).pipe( + decodeTo(BigInt, SchemaTransformation.bigintFromString) +) + +/** + * Type-level representation of {@link Trimmed}. + * + * @category models + * @since 3.10.0 + */ +export interface Trimmed extends String { + readonly "Rebuild": Trimmed +} + +/** + * Schema for strings that contains no leading or trailing whitespaces. + * + * @category schemas + * @since 3.10.0 + */ +export const Trimmed: Trimmed = String.check(isTrimmed()) + +/** + * Type-level representation of {@link Trim}. + * + * @category models + * @since 3.10.0 + */ +export interface Trim extends decodeTo { + readonly "Rebuild": Trim +} + +/** + * Schema that trims whitespace from a string. + * + * **Details** + * + * Decoding: + * - A `string` is decoded as a string with no leading or trailing whitespaces. + * + * Encoding: + * - The trimmed string is encoded as is. + * + * @category schemas + * @since 3.10.0 + */ +export const Trim: Trim = String.annotate({ + expected: "a string that will be decoded as a trimmed string" +}).pipe(decodeTo(Trimmed, SchemaTransformation.trim())) + +/** + * Type-level representation of {@link StringFromBase64}. + * + * @category models + * @since 3.10.0 + */ +export interface StringFromBase64 extends decodeTo { + readonly "Rebuild": StringFromBase64 +} + +/** + * Decodes a base64 (RFC4648) encoded string into a UTF-8 string. + * + * **Details** + * + * Decoding: + * - A **valid** base64 encoded string is decoded as a UTF-8 `string`. + * + * Encoding: + * - A `string` is encoded as a base64-encoded string. + * + * @category schemas + * @since 3.10.0 + */ +export const StringFromBase64: StringFromBase64 = String.annotate({ + expected: "a base64 encoded string that will be decoded as a UTF-8 string" +}).pipe( + decodeTo(String, SchemaTransformation.stringFromBase64String) +) + +/** + * Type-level representation of {@link StringFromBase64Url}. + * + * @category models + * @since 3.10.0 + */ +export interface StringFromBase64Url extends decodeTo { + readonly "Rebuild": StringFromBase64Url +} + +/** + * Decodes a base64 (URL) encoded string into a UTF-8 string. + * + * **Details** + * + * Decoding: + * - A **valid** base64 (URL) encoded string is decoded as a UTF-8 `string`. + * + * Encoding: + * - A `string` is encoded as a base64 (URL) encoded string. + * + * @category schemas + * @since 3.10.0 + */ +export const StringFromBase64Url: StringFromBase64Url = String.annotate({ + expected: "a base64 (URL) encoded string that will be decoded as a UTF-8 string" +}).pipe( + decodeTo(String, SchemaTransformation.stringFromBase64UrlString) +) + +/** + * Type-level representation of {@link StringFromHex}. + * + * @category models + * @since 3.10.0 + */ +export interface StringFromHex extends decodeTo { + readonly "Rebuild": StringFromHex +} + +/** + * Decodes a hex encoded string into a UTF-8 string. + * + * **Details** + * + * Decoding: + * - A **valid** hex encoded string is decoded as a UTF-8 `string`. + * + * Encoding: + * - A `string` is encoded as a hex string. + * + * @category schemas + * @since 3.10.0 + */ +export const StringFromHex: StringFromHex = String.annotate({ + expected: "a hex encoded string that will be decoded as a UTF-8 string" +}).pipe( + decodeTo(String, SchemaTransformation.stringFromHexString) +) + +/** + * Type-level representation of {@link StringFromUriComponent}. + * + * @category models + * @since 3.12.0 + */ +export interface StringFromUriComponent extends decodeTo { + readonly "Rebuild": StringFromUriComponent +} + +/** + * Decodes a URI component encoded string into a UTF-8 string. + * Can be used to store data in a URL. + * + * **Details** + * + * Decoding: + * - A **valid** URI component encoded string is decoded as a UTF-8 `string`. + * + * Encoding: + * - A `string` is encoded as a URI component encoded string. + * + * **Example** (Decoding URI component strings) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const PaginationSchema = Schema.Struct({ + * maxItemPerPage: Schema.Number, + * page: Schema.Number + * }) + * + * const UrlSchema = Schema.StringFromUriComponent.pipe( + * Schema.decodeTo(Schema.fromJsonString(PaginationSchema)) + * ) + * + * Schema.encodeSync(UrlSchema)({ maxItemPerPage: 10, page: 1 }) // => "%7B%22maxItemPerPage%22%3A10%2C%22page%22%3A1%7D" + * ``` + * + * @category schemas + * @since 3.12.0 + */ +export const StringFromUriComponent: StringFromUriComponent = String.annotate({ + expected: "a URI component encoded string that will be decoded as a UTF-8 string" +}).pipe( + decodeTo(String, SchemaTransformation.stringFromUriComponent) +) + +/** + * Schema for property keys accepted by Effect schemas: finite `number`, + * `symbol`, or `string`. + * + * @category schemas + * @since 4.0.0 + */ +export const PropertyKey = Union([Finite, Symbol, String]) + +/** + * Schema for a Standard Schema v1 failure result. + * + * **Details** + * + * The result contains an `issues` array where each issue has a message and an + * optional path made of property keys or keyed path segments. + * + * @category schemas + * @since 4.0.0 + */ +export const StandardSchemaV1FailureResult = Struct({ + issues: ArraySchema(Struct({ + message: String, + path: optional(ArraySchema(Union([PropertyKey, Struct({ key: PropertyKey })]))) + })) +}) + +/** + * Type-level representation of {@link BooleanFromBit}. + * + * @category models + * @since 4.0.0 + */ +export interface BooleanFromBit extends decodeTo> { + readonly "Rebuild": BooleanFromBit +} + +/** + * Schema for a boolean parsed from 0 or 1. + * + * **When to use** + * + * Use when decoding data sources that represent booleans as `0 | 1` while + * keeping boolean values in the decoded model. + * + * **Details** + * + * Decoding accepts only `0 | 1`, maps `1` to `true`, and maps `0` to `false`. + * Encoding maps `true` to `1` and `false` to `0`. + * + * @see {@link Boolean} for validating values that are already booleans + * @see {@link Literals} for keeping bit literals instead of decoding them + * + * @category schemas + * @since 4.0.0 + */ +export const BooleanFromBit: BooleanFromBit = Literals([0, 1]).pipe( + decodeTo( + Boolean, + SchemaTransformation.transform({ + decode: (bit) => bit === 1, + encode: (bool) => bool ? 1 : 0 + }) + ) +) + +/** + * Type-level representation of {@link Uint8Array}. + * + * @category models + * @since 4.0.0 + */ +export interface Uint8Array extends instanceOf> { + readonly "Rebuild": Uint8Array +} + +const Base64String = String.annotate({ + expected: "a base64 encoded string that will be decoded as Uint8Array", + format: "byte", + contentEncoding: "base64" +}) + +/** + * Schema for JavaScript `Uint8Array` objects. + * + * **Details** + * + * Default JSON serializer: + * + * The default JSON serializer encodes Uint8Array as a Base64 encoded string. + * + * @category schemas + * @since 4.0.0 + */ +export const Uint8Array: Uint8Array = instanceOf(globalThis.Uint8Array, { + representation: { + id: "effect/schema/Uint8Array", + payload: null + }, + toCode: () => ({ + runtime: `Schema.Uint8Array`, + Type: `globalThis.Uint8Array` + }), + expected: "Uint8Array", + toCodecJson: () => + link>()( + Base64String, + SchemaTransformation.uint8ArrayFromBase64String + ), + toArbitrary: () => (fc) => fc.uint8Array() +}) + +/** + * Reviver for persisted `Uint8Array` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link Uint8Array} schema. + * + * @see {@link Uint8Array} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const Uint8ArrayReviver = makeFixedDeclarationReviver( + "effect/schema/Uint8Array", + Uint8Array +) + +/** + * Type-level representation of {@link Uint8ArrayFromBase64}. + * + * @category models + * @since 3.10.0 + */ +export interface Uint8ArrayFromBase64 extends decodeTo { + readonly "Rebuild": Uint8ArrayFromBase64 +} + +/** + * Schema that decodes a base64 encoded string into a + * `Uint8Array`. + * + * **Details** + * + * Decoding: + * - A **valid** base64 encoded string is decoded as a `Uint8Array`. + * + * Encoding: + * - A `Uint8Array` is encoded as a base64-encoded string. + * + * @category schemas + * @since 3.10.0 + */ +export const Uint8ArrayFromBase64: Uint8ArrayFromBase64 = Base64String.pipe( + decodeTo(Uint8Array, SchemaTransformation.uint8ArrayFromBase64String) +) + +/** + * Type-level representation of {@link Uint8ArrayFromBase64Url}. + * + * @category models + * @since 3.10.0 + */ +export interface Uint8ArrayFromBase64Url extends decodeTo { + readonly "Rebuild": Uint8ArrayFromBase64Url +} + +/** + * Schema that decodes a base64 (URL) encoded string into a + * `Uint8Array`. + * + * **Details** + * + * Decoding: + * - A **valid** base64 (URL) encoded string is decoded as a `Uint8Array`. + * + * Encoding: + * - A `Uint8Array` is encoded as a base64 (URL) encoded string. + * + * @category schemas + * @since 3.10.0 + */ +export const Uint8ArrayFromBase64Url: Uint8ArrayFromBase64Url = String.annotate({ + expected: "a base64 (URL) encoded string that will be decoded as a Uint8Array" +}).pipe( + decodeTo(Uint8Array, { + decode: SchemaGetter.decodeBase64Url(), + encode: SchemaGetter.encodeBase64Url() + }) +) + +/** + * Type-level representation of {@link Uint8ArrayFromHex}. + * + * @category models + * @since 3.10.0 + */ +export interface Uint8ArrayFromHex extends decodeTo { + readonly "Rebuild": Uint8ArrayFromHex +} + +/** + * Schema that decodes a hex encoded string into a + * `Uint8Array`. + * + * **Details** + * + * Decoding: + * - A **valid** hex encoded string is decoded as a `Uint8Array`. + * + * Encoding: + * - A `Uint8Array` is encoded as a hex encoded string. + * + * @category schemas + * @since 3.10.0 + */ +export const Uint8ArrayFromHex: Uint8ArrayFromHex = String.annotate({ + expected: "a hex encoded string that will be decoded as a Uint8Array" +}).pipe( + decodeTo(Uint8Array, { + decode: SchemaGetter.decodeHex(), + encode: SchemaGetter.encodeHex() + }) +) + +/** + * Type-level representation of {@link DateTimeUtc}. + * + * @category models + * @since 3.10.0 + */ +export interface DateTimeUtc extends declare { + readonly "Rebuild": DateTimeUtc +} + +/** + * Schema for `DateTime.Utc` values. + * + * **When to use** + * + * Use to validate existing `DateTime.Utc` schema values and use the default JSON + * codec that represents them as UTC ISO strings. + * + * **Details** + * + * The default JSON codec decodes UTC ISO strings into `DateTime.Utc` values and + * encodes `DateTime.Utc` values as UTC ISO strings. + * + * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values + * @see {@link DateTimeUtcFromDate} for decoding JavaScript Date values into UTC values + * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values + * @see {@link DateTimeZoned} for preserving zoned DateTime values + * + * @category schemas + * @since 3.10.0 + */ +export const DateTimeUtc: DateTimeUtc = declare( + (u) => DateTime.isDateTime(u) && DateTime.isUtc(u), + { + representation: { + id: "effect/schema/DateTimeUtc", + payload: null + }, + toCode: () => ({ + runtime: `Schema.DateTimeUtc`, + Type: `DateTime.Utc`, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), + expected: "DateTime.Utc", + toCodecJson: () => + link()( + String, + SchemaTransformation.dateTimeUtcFromString + ), + toArbitrary: () => (fc, ctx) => + fc.date(dateArbitraryConstraints( + ctx?.constraint?.ordered?.order === DateTime.Order ? ctx.constraint.ordered : undefined, + { noInvalidDate: true }, + DateTime.toDateUtc + )) + .map((date) => DateTime.fromDateUnsafe(date)), + toFormatter: () => (utc) => utc.toString(), + toEquivalence: () => DateTime.Equivalence + } +) + +/** + * Reviver for persisted {@link DateTimeUtc} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link DateTimeUtc} schema. + * + * @see {@link DateTimeUtc} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const DateTimeUtcReviver = makeFixedDeclarationReviver( + "effect/schema/DateTimeUtc", + DateTimeUtc +) + +/** + * Type-level representation of {@link DateTimeUtcFromDate}. + * + * @category models + * @since 3.12.0 + */ +export interface DateTimeUtcFromDate extends decodeTo { + readonly "Rebuild": DateTimeUtcFromDate +} + +/** + * Schema that decodes a `Date` into a `DateTime.Utc`. + * + * **When to use** + * + * Use when you need to decode valid JavaScript `Date` objects into + * `DateTime.Utc` values. + * + * **Details** + * + * Decoding: + * - A **valid** `Date` is decoded as a `DateTime.Utc` + * + * Encoding: + * - A `DateTime.Utc` is encoded as a `Date` + * + * @see {@link DateTimeUtc} for validating values that are already `DateTime.Utc` + * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values + * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values + * @see {@link Date} for validating Date instances without converting them + * + * @category schemas + * @since 3.12.0 + */ +export const DateTimeUtcFromDate: DateTimeUtcFromDate = Date.pipe( + decodeTo(DateTimeUtc, { + decode: SchemaGetter.dateTimeUtcFromInput(), + encode: SchemaGetter.transform(DateTime.toDateUtc) + }) +) + +/** + * Type-level representation of {@link DateTimeUtcFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface DateTimeUtcFromString extends decodeTo { + readonly "Rebuild": DateTimeUtcFromString +} + +/** + * Schema that decodes a date-time string into a `DateTime.Utc`. + * + * **Details** + * + * Decoding: + * + * - A string accepted by `DateTime.make` is parsed and normalized to UTC. Strings + * without an explicit zone are interpreted as UTC. + * + * Encoding: + * + * - A `DateTime.Utc` is encoded as a UTC ISO 8601 string. + * + * @see {@link DateTimeUtcFromDate} for decoding JavaScript Date values into UTC values + * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values + * @see {@link DateFromString} for decoding strings into JavaScript Date instances + * + * @category schemas + * @since 4.0.0 + */ +export const DateTimeUtcFromString: DateTimeUtcFromString = String.annotate({ + expected: "a string that will be decoded as a DateTime.Utc" +}).pipe( + decodeTo( + DateTimeUtc, + SchemaTransformation.dateTimeUtcFromString + ) +) + +/** + * Type-level representation of {@link DateTimeUtcFromMillis}. + * + * @category models + * @since 4.0.0 + */ +export interface DateTimeUtcFromMillis extends decodeTo, Int> { + readonly "Rebuild": DateTimeUtcFromMillis +} + +/** + * Schema that decodes a number into a `DateTime.Utc`. + * + * **Details** + * + * Decoding: + * - A number of milliseconds since the Unix epoch is decoded as a `DateTime.Utc` + * + * Encoding: + * - A `DateTime.Utc` is encoded as a number of milliseconds since the Unix epoch. + * + * @see {@link DateTimeUtcFromDate} for decoding JavaScript Date values into UTC values + * @see {@link DateTimeUtcFromString} for decoding date-time strings into UTC values + * @see {@link DateFromMillis} for decoding epoch milliseconds into JavaScript Date instances + * + * @category schemas + * @since 4.0.0 + */ +export const DateTimeUtcFromMillis: DateTimeUtcFromMillis = Int.pipe( + decodeTo(DateTimeUtc, { + decode: SchemaGetter.dateTimeUtcFromInput(), + encode: SchemaGetter.transform(DateTime.toEpochMillis) + }) +) + +/** + * Type-level representation of {@link TimeZoneOffset}. + * + * @category models + * @since 3.10.0 + */ +export interface TimeZoneOffset extends declare { + readonly "Rebuild": TimeZoneOffset +} + +/** + * Schema for `DateTime.TimeZone.Offset` values. + * + * **Details** + * + * Default JSON serializer: + * + * - encodes `DateTime.TimeZone.Offset` as a number (offset in milliseconds) + * + * @category schemas + * @since 3.10.0 + */ +export const TimeZoneOffset: TimeZoneOffset = declare( + DateTime.isTimeZoneOffset, + { + representation: { + id: "effect/schema/TimeZoneOffset", + payload: null + }, + toCode: () => ({ + runtime: `Schema.TimeZoneOffset`, + Type: `DateTime.TimeZone.Offset`, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), + expected: "DateTime.TimeZone.Offset", + toCodecJson: () => + link()( + Int, + SchemaTransformation.timeZoneOffsetFromNumber + ), + toArbitrary: () => (fc) => + fc.integer({ min: -12 * 60 * 60 * 1000, max: 14 * 60 * 60 * 1000 }).map((n) => DateTime.zoneMakeOffset(n)), + toFormatter: () => (tz) => DateTime.zoneToString(tz), + toEquivalence: () => (a, b) => a.offset === b.offset + } +) + +/** + * Reviver for persisted {@link TimeZoneOffset} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link TimeZoneOffset} schema. + * + * @see {@link TimeZoneOffset} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const TimeZoneOffsetReviver = makeFixedDeclarationReviver( + "effect/schema/TimeZoneOffset", + TimeZoneOffset +) + +/** + * Type-level representation of {@link TimeZoneNamed}. + * + * @category models + * @since 3.10.0 + */ +export interface TimeZoneNamed extends declare { + readonly "Rebuild": TimeZoneNamed +} + +const TimeZoneNamedString = String.annotate({ expected: "an IANA time zone identifier" }) + +/** + * Schema for `DateTime.TimeZone.Named` values. + * + * **Details** + * + * Default JSON serializer: + * + * - encodes `DateTime.TimeZone.Named` as a string (IANA time zone identifier) + * + * @category schemas + * @since 3.10.0 + */ +export const TimeZoneNamed: TimeZoneNamed = declare( + DateTime.isTimeZoneNamed, + { + representation: { + id: "effect/schema/TimeZoneNamed", + payload: null + }, + toCode: () => ({ + runtime: `Schema.TimeZoneNamed`, + Type: `DateTime.TimeZone.Named`, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), + expected: "DateTime.TimeZone.Named", + toCodecJson: () => + link()( + TimeZoneNamedString, + SchemaTransformation.timeZoneNamedFromString + ), + toArbitrary: () => (fc) => + fc.constantFrom( + ...["UTC", "Europe/London", "America/New_York", "Asia/Tokyo", "Australia/Sydney"].map( + DateTime.zoneMakeNamedUnsafe + ) + ), + toFormatter: () => (tz) => DateTime.zoneToString(tz), + toEquivalence: () => (a, b) => a.id === b.id + } +) + +/** + * Reviver for persisted {@link TimeZoneNamed} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link TimeZoneNamed} schema. + * + * @see {@link TimeZoneNamed} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const TimeZoneNamedReviver = makeFixedDeclarationReviver( + "effect/schema/TimeZoneNamed", + TimeZoneNamed +) + +/** + * Type-level representation of {@link TimeZoneNamedFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface TimeZoneNamedFromString extends decodeTo { + readonly "Rebuild": TimeZoneNamedFromString +} + +/** + * Schema that parses an IANA time zone identifier string into a `DateTime.TimeZone.Named`. + * + * **Details** + * + * Decoding: + * - A `string` is decoded as a `DateTime.TimeZone.Named`. + * + * Encoding: + * - A `DateTime.TimeZone.Named` is encoded as a `string`. + * + * @category schemas + * @since 4.0.0 + */ +export const TimeZoneNamedFromString: TimeZoneNamedFromString = TimeZoneNamedString.pipe( + decodeTo(TimeZoneNamed, SchemaTransformation.timeZoneNamedFromString) +) + +/** + * Type-level representation of {@link TimeZone}. + * + * @category models + * @since 3.10.0 + */ +export interface TimeZone extends declare { + readonly "Rebuild": TimeZone +} + +const TimeZoneString = String.annotate({ + expected: "a time zone string (IANA identifier or offset like +03:00)" +}) + +/** + * Schema for `DateTime.TimeZone` values. + * + * **Details** + * + * Default JSON serializer: + * + * - encodes `DateTime.TimeZone` as a string (IANA identifier or offset like + * `+03:00`) + * + * @category schemas + * @since 3.10.0 + */ +export const TimeZone: TimeZone = declare( + DateTime.isTimeZone, + { + representation: { + id: "effect/schema/TimeZone", + payload: null + }, + toCode: () => ({ + runtime: `Schema.TimeZone`, + Type: `DateTime.TimeZone`, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), + expected: "DateTime.TimeZone", + toCodecJson: () => + link()( + TimeZoneString, + SchemaTransformation.timeZoneFromString + ), + toArbitrary: () => (fc) => + fc.oneof( + fc.integer({ min: -12 * 60 * 60 * 1000, max: 14 * 60 * 60 * 1000 }).map((n) => DateTime.zoneMakeOffset(n)), + fc.constantFrom( + ...["UTC", "Europe/London", "America/New_York", "Asia/Tokyo", "Australia/Sydney"].map( + DateTime.zoneMakeNamedUnsafe + ) + ) + ), + toFormatter: () => (tz) => DateTime.zoneToString(tz), + toEquivalence: () => (a, b) => DateTime.zoneToString(a) === DateTime.zoneToString(b) + } +) + +/** + * Reviver for persisted {@link TimeZone} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link TimeZone} schema. + * + * @see {@link TimeZone} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const TimeZoneReviver = makeFixedDeclarationReviver( + "effect/schema/TimeZone", + TimeZone +) + +/** + * Type-level representation of {@link TimeZoneFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface TimeZoneFromString extends decodeTo { + readonly "Rebuild": TimeZoneFromString +} + +/** + * Schema that parses a time zone string into a `DateTime.TimeZone`. + * + * **Details** + * + * Decoding: + * - A `string` (IANA identifier or offset like `+03:00`) is decoded as a `DateTime.TimeZone`. + * + * Encoding: + * - A `DateTime.TimeZone` is encoded as a `string`. + * + * @category schemas + * @since 4.0.0 + */ +export const TimeZoneFromString: TimeZoneFromString = TimeZoneString.pipe( + decodeTo(TimeZone, SchemaTransformation.timeZoneFromString) +) + +/** + * Type-level representation of {@link DateTimeZoned}. + * + * @category models + * @since 3.10.0 + */ +export interface DateTimeZoned extends declare { + readonly "Rebuild": DateTimeZoned +} + +const DateTimeZonedString = String.annotate({ + expected: "a zoned DateTime string (e.g. 2024-01-01T00:00:00.000+00:00[Europe/London])" +}) + +/** + * Schema for `DateTime.Zoned` values. + * + * **Details** + * + * Default JSON serializer: + * + * - encodes offset zones as an ISO date-time with a numeric offset, such as + * `YYYY-MM-DDTHH:mm:ss.sss+HH:MM` + * - encodes named zones by appending the IANA identifier in brackets, such as + * `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]` + * + * @category schemas + * @since 3.10.0 + */ +export const DateTimeZoned: DateTimeZoned = declare( + (u) => DateTime.isDateTime(u) && DateTime.isZoned(u), + { + representation: { + id: "effect/schema/DateTimeZoned", + payload: null + }, + toCode: () => ({ + runtime: `Schema.DateTimeZoned`, + Type: `DateTime.Zoned`, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), + expected: "DateTime.Zoned", + toCodecJson: () => + link()( + DateTimeZonedString, + SchemaTransformation.dateTimeZonedFromString + ), + toArbitrary: () => (fc, ctx) => + fc.tuple( + fc.date(dateArbitraryConstraints( + ctx?.constraint?.ordered?.order === DateTime.Order ? ctx.constraint.ordered : undefined, + { + max: new globalThis.Date(8640000000000000 - 14 * 60 * 60 * 1000), + min: new globalThis.Date(-8640000000000000 + 14 * 60 * 60 * 1000), + noInvalidDate: true + }, + DateTime.toDateUtc + )), + fc.constantFrom("UTC", "Europe/London", "America/New_York", "Asia/Tokyo", "Australia/Sydney") + ).map(([date, zone]) => DateTime.makeZonedUnsafe(date, { timeZone: zone })), + toFormatter: () => (zoned) => DateTime.formatIsoZoned(zoned), + toEquivalence: () => DateTime.Equivalence + } +) + +/** + * Reviver for persisted {@link DateTimeZoned} declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link DateTimeZoned} schema. + * + * @see {@link DateTimeZoned} for the corresponding schema + * + * @category schemas + * @since 4.0.0 + */ +export const DateTimeZonedReviver = makeFixedDeclarationReviver( + "effect/schema/DateTimeZoned", + DateTimeZoned +) + +/** + * Type-level representation of {@link DateTimeZonedFromString}. + * + * @category models + * @since 4.0.0 + */ +export interface DateTimeZonedFromString extends decodeTo { + readonly "Rebuild": DateTimeZonedFromString +} + +/** + * Schema that parses a zoned DateTime string into a `DateTime.Zoned`. + * + * **Details** + * + * Decoding: + * - A `string` (e.g. `2024-01-01T00:00:00.000+00:00[Europe/London]`) is decoded as a `DateTime.Zoned`. + * + * Encoding: + * - A `DateTime.Zoned` is encoded as a `string`. + * + * @category schemas + * @since 4.0.0 + */ +export const DateTimeZonedFromString: DateTimeZonedFromString = DateTimeZonedString.pipe( + decodeTo(DateTimeZoned, SchemaTransformation.dateTimeZonedFromString) +) + +// ----------------------------------------------------------------------------- +// Class +// ----------------------------------------------------------------------------- + +/** + * Type-level representation returned by {@link Class}. + * + * @category models + * @since 3.10.0 + */ +export interface Class + extends + BottomLazyWithoutNew< + SchemaAST.Declaration, + decodeTo, S>, + readonly [S], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": Self + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": RequiredKeys extends never ? void | S["~type.make.in"] + : S["~type.make.in"] + readonly "~type.make": Self + readonly "Iso": S["Iso"] + new( + ...args: {} extends S["~type.make.in"] ? [props?: S["~type.make.in"], options?: MakeOptions] + : [props: S["~type.make.in"], options?: MakeOptions] + ): S["Type"] & Inherited + readonly identifier: string + readonly fields: S["fields"] + + /** + * Returns a new struct with the fields modified by the provided function. + * + * **Details** + * + * Options: + * + * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints + * that were attached to the original struct. Defaults to `false`. + * + * **Warning**: This is an unsafe operation. Since `mapFields` + * transformations change the schema type, the original refinement functions + * may no longer be valid or safe to apply to the transformed schema. Only + * use this option if you have verified that your refinements remain correct + * after the transformation. + */ + mapFields( + f: (fields: S["fields"]) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Struct>> + + /** + * Returns a function that creates a schema-backed subclass with this class's + * fields plus additional fields. + * + * **When to use** + * + * Use when you need a subclass whose constructor validates both inherited + * fields and newly added fields. + * + * **Details** + * + * The returned function accepts either a field map or a `Struct`. When you + * pass a `Struct`, checks attached to that extension schema are preserved and + * combined with checks from the base class schema. + * + * **Gotchas** + * + * Checks from a `Struct` argument are evaluated against the full subclass + * value after inherited and extension fields are merged. Object-wide checks + * such as `isMaxProperties` count inherited fields too. + */ + extend( + identifier: string + ): { + ( + fields: NewFields, + annotations?: Annotations.Declaration>>]> + ): [Extended] extends [never] ? MissingSelfGeneric<"Base.extend"> : InheritStaticMembers< + Class>>, Self & Brand>, + Static + > + >( + schema: Extension, + annotations?: Annotations.Declaration< + Extended, + readonly [Struct>>] + > + ): [Extended] extends [never] ? MissingSelfGeneric<"Base.extend"> : InheritStaticMembers< + Class>>, Self & Brand>, + Static + > + } +} + +// Merges custom static members from a parent class onto the extended class, +// giving priority to the extended class's own members (e.g. schema-generated statics). +type InheritStaticMembers = C & Pick> + +const immerable: unique symbol = globalThis.Symbol.for("immer-draftable") as any + +const payloadToken = {} + +function makeClass< + Self, + S extends Struct, + Inherited extends new(...args: ReadonlyArray) => any +>( + Inherited: Inherited, + identifier: string, + struct: S, + annotations: Annotations.Declaration | undefined, + proto: ((identifier: string) => object) | undefined +): any { + const getClassSchema = getClassSchemaFactory(struct, identifier, annotations) + const ClassTypeId = getClassTypeId(identifier) // HMR support + + const out = class extends Inherited { + constructor(...[input, options]: ReadonlyArray) { + const internalOptions = options as MakeOptions | undefined + const payload = internalOptions?.["~payload"] + const value = payload?.token === payloadToken + ? payload.value + : struct.make(input ?? {}, options) + super(value, { ...options, disableChecks: true, "~payload": { token: payloadToken, value } }) + } + + static readonly [TypeId] = TypeId + + get [ClassTypeId]() { + return ClassTypeId + } + + static readonly [immerable] = true + + static readonly identifier = identifier + static readonly fields = struct.fields + + static get ast(): SchemaAST.Declaration { + return getClassSchema(this).ast + } + static pipe() { + return Pipeable.pipeArguments(this, arguments) + } + static rebuild(ast: SchemaAST.Declaration) { + return getClassSchema(this).rebuild(ast) + } + static make(input: S["~type.make.in"], options?: MakeOptions): Self { + return new this(input, options) + } + static makeOption(input: S["~type.make.in"], options?: MakeOptions): Option_.Option { + return SchemaParser.makeOption(getClassSchema(this) as any)(input ?? {}, options) as any + } + static makeEffect(input: S["~type.make.in"], options?: MakeOptions): Effect.Effect { + return (getClassSchema(this) as any).makeEffect(input ?? {}, options) + } + static annotate(annotations: Annotations.Declaration) { + return this.rebuild(SchemaAST.annotate(this.ast, annotations)) + } + static annotateKey(annotations: Annotations.Key) { + return this.rebuild(SchemaAST.annotateKey(this.ast, annotations)) + } + static check(...checks: readonly [SchemaAST.Check, ...Array>]) { + return this.rebuild(SchemaAST.appendChecks(this.ast, checks)) + } + static extend( + identifier: string + ) { + return ( + schema: Struct.Fields | Struct, + annotations?: Annotations.Declaration + ) => { + const extension = isStruct(schema) ? schema : Struct(schema) + const fields = { ...struct.fields, ...extension.fields } + const ast = SchemaAST.struct(fields, struct.ast.checks, { identifier }) + return makeClass( + this, + identifier, + makeStruct(SchemaAST.appendChecks(ast, extension.ast.checks), fields), + annotations, + proto + ) + } + } + static mapFields( + f: (fields: S["fields"]) => To, + options?: { + readonly unsafePreserveChecks?: boolean | undefined + } | undefined + ): Struct>> { + return struct.mapFields(f, options) + } + } + + if (proto !== undefined) { + Object.assign(out.prototype, proto(identifier)) + } + + return out +} + +function getClassTransformation(self: new(...args: ReadonlyArray) => any) { + return new SchemaTransformation.Transformation( + SchemaGetter.transform((input) => + new self(input, { + "~payload": { + token: payloadToken, + value: input + } + }) + ), + SchemaGetter.passthrough() + ) +} + +function getClassTypeId(identifier: string) { + return `~effect/Schema/Class/${identifier}` +} + +function getClassSchemaFactory( + from: S, + identifier: string, + annotations: Annotations.Declaration | undefined +) { + let memo: decodeTo, S> | undefined + return ) => any) & { readonly identifier: string }>( + self: Self + ): decodeTo, S> => { + if (memo !== undefined) { + return memo + } + const ClassTypeId = getClassTypeId(identifier) + const isClassValue: Predicate.Predicate = (input) => + input instanceof self || Predicate.hasProperty(input, ClassTypeId) + const transformation = getClassTransformation(self) + const to = make>( + new SchemaAST.Declaration( + [from.ast], + () => (input, ast, options) => { + return isClassValue(input) ? + Effect.succeed(input) : + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + }, + { + identifier, + [InternalAnnotations.CONSTRUCTOR_ANNOTATION_KEY]: ( + [from]: readonly [SchemaAST.AST] + ): SchemaAST.ConstructorDescriptor => ({ + isConstructed: isClassValue, + link: new SchemaAST.Link(from, transformation) + }), + toCodec: ([from]: readonly [ConstraintCodec]) => + new SchemaAST.Link(from.ast, transformation), + toArbitrary: ([from]: readonly [Annotations.ToArbitrary.TypeParameter]) => () => ({ + arbitrary: from.arbitrary.map((args: S["Type"]) => new self(args)), + terminal: from.terminal?.map((args: S["Type"]) => new self(args)) + }), + toFormatter: ([from]: readonly [Formatter]) => (t: Self) => `${self.identifier}(${from(t)})`, + [InternalAnnotations.SENTINELS_ANNOTATION_KEY]: SchemaAST.collectSentinels(from.ast), + ...annotations + } + ) + ) + return memo = decodeTo, S>(to, transformation)(from) + } +} + +function isStruct(schema: Struct.Fields | Struct): schema is Struct { + return isSchema(schema) +} + +type MissingSelfGeneric = + `Missing \`Self\` generic - use \`class Self extends ${Usage}(...)\`` + +/** + * Creates a schema-backed class whose constructor validates input against a + * {@link Struct} schema. Construction throws an `Error` with a + * `SchemaIssue.Issue` in its `cause` on invalid input. + * + * **When to use** + * + * Use when you need a schema-backed data class with validated construction, + * schema-derived decoding/encoding, and class-style methods or inheritance. + * + * **Details** + * + * Pass the desired class type as the first type parameter. The second optional + * type parameter can be used to add nominal brands. + * + * The `identifier` is the schema's stable runtime name. It is exposed on the + * class, stored in the schema AST, and used to label diagnostics and generated + * references as well as to format class instances. + * + * It also derives a runtime marker that recognizes instances across hot module + * reloads, where `instanceof` can fail because the constructor has been + * replaced. The identifier is explicit because the outer JavaScript class name + * is not available while the `extends` expression is evaluated and may change + * through renaming or minification. + * + * **Gotchas** + * + * Passing `disableChecks` in the options skips constructor validation. + * + * **Example** (Defining a basic class) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * class Person extends Schema.Class("Person")({ + * name: Schema.String, + * age: Schema.Number + * }) {} + * + * const alice = new Person({ name: "Alice", age: 30 }) + * alice.name // => "Alice" + * String(alice) // => "Person({\"name\":\"Alice\",\"age\":30})" + * ``` + * + * **Example** (Extending a class) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * class Animal extends Schema.Class("Animal")({ + * name: Schema.String + * }) {} + * + * class Dog extends Animal.extend("Dog")({ + * breed: Schema.String + * }) {} + * + * const dog = new Dog({ name: "Rex", breed: "Labrador" }) + * dog.name // => "Rex" + * dog.breed // => "Labrador" + * ``` + * + * @see {@link TaggedClass} for adding a `_tag` literal field to the class schema + * @see {@link Error} for defining schema-backed error classes + * @see {@link TaggedError} for defining tagged schema-backed error classes + * + * @category constructors + * @since 3.10.0 + */ +export const Class: { + (identifier: string): { + ( + fields: Fields, + annotations?: Annotations.Declaration]> + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Class"> : Class, Brand> + >( + schema: S, + annotations?: Annotations.Declaration + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Class"> : Class + } +} = (identifier: string) => +( + schema: Struct.Fields | Struct, + annotations?: Annotations.Declaration]> +): [Self] extends [never] ? MissingSelfGeneric<"Schema.Class"> : Class, Brand> => { + const struct = isStruct(schema) ? schema : Struct(schema) + return makeClass( + Data.Class, + identifier, + struct, + annotations, + (identifier) => ({ + toString() { + return `${identifier}(${format({ ...this })})` + } + }) + ) +} + +/** + * Defines a schema-backed class with an automatically populated `_tag` field. + * + * **When to use** + * + * Use to define class instances that are validated by a schema and participate + * in tagged union matching. + * + * **Details** + * + * The optional `identifier` parameter overrides the schema identifier; + * it defaults to the `tag` value. + * + * **Example** (Defining a tagged class) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * class Circle extends Schema.TaggedClass()("Circle", { + * radius: Schema.Number + * }) {} + * + * const c = new Circle({ radius: 5 }) + * c._tag // => "Circle" + * c.radius // => 5 + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export const TaggedClass: { + (identifier?: string): { + ( + tag: Tag, + fields: Fields, + annotations?: Annotations.Declaration]> + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedClass"> : Class, Brand> + >( + tag: Tag, + schema: S, + annotations?: Annotations.Declaration< + Self, + readonly [Struct } & S["fields"]>>] + > + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedClass"> + : Class } & S["fields"]>>, Brand> + } +} = (identifier?: string) => { + return ( + tagValue: string, + schema: Struct.Fields | Struct, + annotations?: Annotations.Declaration]> + ): any => { + const struct = isStruct(schema) ? + schema.mapFields((fields) => ({ _tag: tag(tagValue), ...fields }), { + unsafePreserveChecks: true + }) : + TaggedStruct(tagValue, schema) + return Class(identifier ?? tagValue)( + struct, + annotations as Annotations.Declaration + ) + } +} + +/** + * Creates a schema-backed error class that can be used as a typed, + * yieldable error in Effect programs. Combines {@link Class} validation with + * the `YieldableError` interface so instances can be yielded directly inside + * `Effect.gen`. + * + * **Example** (Schema-backed error) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * class NotFound extends Schema.Error("NotFound")({ + * id: Schema.Number + * }) {} + * + * const program = Effect.gen(function*() { + * yield* new NotFound({ id: 1 }) + * }) + * const error = await Effect.runPromise(Effect.flip(program)) + * error.id // => 1 + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const Error: { + (identifier: string): { + ( + fields: Fields, + annotations?: Annotations.Declaration]> + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Error"> + : Class, Cause_.YieldableError & Brand> + >( + schema: S, + annotations?: Annotations.Declaration + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Error"> : Class + } +} = (identifier: string) => +( + schema: Struct.Fields | Struct, + annotations?: Annotations.Declaration]> +): [Self] extends [never] ? MissingSelfGeneric<"Schema.Error"> + : Class, Cause_.YieldableError & Brand> => +{ + const struct = isStruct(schema) ? schema : Struct(schema) + const self = makeClass( + core.Error, + identifier, + struct, + annotations, + (identifier) => ({ + name: identifier + }) + ) + return self +} + +/** + * Defines a schema-backed yieldable error class with an automatically populated + * `_tag` field. + * + * **When to use** + * + * Use to define typed errors that are schema validated, yielded in `Effect.gen`, + * and matched as tagged union members. + * + * **Example** (Defining a tagged error class) + * + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" + * + * class NotFound extends Schema.TaggedError()("NotFound", { + * id: Schema.Number + * }) {} + * + * const program = Effect.gen(function*() { + * yield* new NotFound({ id: 42 }) + * }) + * const error = await Effect.runPromise(Effect.flip(program)) + * error._tag // => "NotFound" + * error.id // => 42 + * ``` + * + * @category constructors + * @since 3.10.0 + */ +export const TaggedError: { + (identifier?: string): { + ( + tag: Tag, + fields: Fields, + annotations?: Annotations.Declaration]> + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedError"> + : Class, Cause_.YieldableError & Brand> + >( + tag: Tag, + schema: S, + annotations?: Annotations.Declaration< + Self, + readonly [Struct } & S["fields"]>>] + > + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedError"> + : Class } & S["fields"]>>, Cause_.YieldableError & Brand> + } +} = (identifier?: string) => { + return ( + tagValue: string, + schema: Struct.Fields | Struct, + annotations?: Annotations.Declaration]> + ): any => { + const struct = isStruct(schema) ? + schema.mapFields((fields) => ({ _tag: tag(tagValue), ...fields }), { + unsafePreserveChecks: true + }) : + TaggedStruct(tagValue, schema) + return Error(identifier ?? tagValue)( + struct, + annotations as Annotations.Declaration + ) + } +} + +// ----------------------------------------------------------------------------- +// Arbitrary +// ----------------------------------------------------------------------------- + +/** + * Represents a function that builds a fast-check `Arbitrary` from the + * `fast-check` module. + * + * **When to use** + * + * Use as the result type of schema arbitrary derivation. + * + * @category utility types + * @since 4.0.0 + */ +export type Arbitrary = (fc: typeof FastCheck) => FastCheck.Arbitrary + +/** + * Returns an {@link Arbitrary} factory derived from a schema. The generated + * values satisfy the schema and use its decoded `Type`. + * + * **When to use** + * + * Use when you need a fast-check generator for values accepted by a schema. + * + * **Details** + * + * Constraints refine base generators; candidates add weighted sources while + * filters still validate every value. Recursive schemas use terminal branches + * and fail when no finite terminal path exists. The result is memoized so + * repeated calls with the same schema are cheap. + * + * **Example** (Generating arbitrary values) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * import * as FastCheck from "fast-check" + * + * const makePersonArbitrary = Schema.toArbitrary( + * Schema.Struct({ name: Schema.String, age: Schema.Number }) + * ) + * + * const PersonArbitrary = makePersonArbitrary(FastCheck) + * FastCheck.sample(PersonArbitrary, 1) + * ``` + * + * @category generators + * @since 4.0.0 + */ +export function toArbitrary(schema: S): Arbitrary { + const lawc = InternalArbitrary.memoized(schema.ast) + return (fc) => lawc(fc, {}) +} + +// ----------------------------------------------------------------------------- +// Formatter +// ----------------------------------------------------------------------------- + +/** + * Attaches a custom formatter used by `toFormatter`. + * + * **Details** + * + * Use this when the formatter derived from the schema structure is not suitable. + * The annotation is applied through this helper because adding it directly to + * `Annotations.Bottom` would make schemas invariant. + * + * @category formatting + * @since 4.0.0 + */ +export function overrideToFormatter(toFormatter: () => Formatter) { + return (self: S): S["Rebuild"] => { + return self.annotate({ toFormatter }) + } +} + +/** + * Derives a string formatter function from a schema. The formatter converts + * a value to its human-readable string representation, recursing into structs, + * arrays, and unions. + * + * **Details** + * + * The optional `onBefore` hook lets you intercept specific AST nodes before + * the default formatting logic runs. + * + * @category formatting + * @since 4.0.0 + */ +export function toFormatter(schema: S, options?: { + readonly onBefore?: + | ((ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => Formatter) => Formatter | undefined) + | undefined +}): Formatter { + return recur(schema.ast) + + function recur(ast: SchemaAST.AST): Formatter { + // --------------------------------------------- + // handle annotation + // --------------------------------------------- + const annotation = InternalAnnotations.resolve(ast)?.["toFormatter"] + if (typeof annotation === "function") { + return annotation(SchemaAST.isDeclaration(ast) ? ast.typeParameters.map(recur) : []) + } + // --------------------------------------------- + // handle onBefore + // --------------------------------------------- + if (options?.onBefore) { + const onBefore = options.onBefore(ast, recur) + if (onBefore !== undefined) { + return onBefore + } + } + // --------------------------------------------- + // handle base case + // --------------------------------------------- + return on(ast) + } + + function on(ast: SchemaAST.AST): Formatter { + switch (ast._tag) { + default: + return format + case "Never": + return () => "never" + case "Void": + return () => "void" + case "Arrays": { + const elements = ast.elements.map(recur) + const rest = ast.rest.map(recur) + return (t) => { + const out: Array = [] + let i = 0 + // --------------------------------------------- + // handle elements + // --------------------------------------------- + for (; i < elements.length; i++) { + if (t.length < i + 1) { + if (SchemaAST.isOptional(ast.elements[i])) { + continue + } + } else { + out.push(elements[i](t[i])) + } + } + // --------------------------------------------- + // handle rest element + // --------------------------------------------- + if (rest.length > 0) { + const [head, ...tail] = rest + for (; i < t.length - tail.length; i++) { + out.push(head(t[i])) + } + // --------------------------------------------- + // handle post rest elements + // --------------------------------------------- + for (let j = 0; j < tail.length; j++) { + out.push(tail[j](t[i + j])) + } + } + + return "[" + out.join(", ") + "]" + } + } + case "Objects": { + const propertySignatures = ast.propertySignatures.map((ps) => recur(ps.type)) + const indexSignatures = ast.indexSignatures.map((is) => recur(is.type)) + if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) { + return format + } + return (t) => { + const out: Array = [] + const visited = new Set() + // --------------------------------------------- + // handle property signatures + // --------------------------------------------- + for (let i = 0; i < propertySignatures.length; i++) { + const ps = ast.propertySignatures[i] + const name = ps.name + visited.add(name) + if (SchemaAST.isOptional(ps.type) && !Object.hasOwn(t, name)) { + continue + } + out.push(`${formatPropertyKey(name)}: ${propertySignatures[i](t[name])}`) + } + // --------------------------------------------- + // handle index signatures + // --------------------------------------------- + for (let i = 0; i < indexSignatures.length; i++) { + const keys = SchemaAST.getIndexSignatureKeys(t, ast.indexSignatures[i].parameter) + for (const key of keys) { + if (visited.has(key)) { + continue + } + visited.add(key) + out.push(`${formatPropertyKey(key)}: ${indexSignatures[i](t[key])}`) + } + } + + return out.length > 0 ? "{ " + out.join(", ") + " }" : "{}" + } + } + case "Union": { + const types = SchemaAST.toType(ast).types + const getCandidates = (t: any) => SchemaAST.getCandidates(t, types) + const compiled = new Map( + types.map((candidate, i) => [candidate, [SchemaParser._is(candidate), recur(ast.types[i])] as const] as const) + ) + return (t) => { + const candidates = getCandidates(t) + for (let i = 0; i < candidates.length; i++) { + const [is, formatter] = compiled.get(candidates[i])! + if (is(t)) { + return formatter(t) + } + } + return format(t) + } + } + case "Suspend": { + const get = SchemaAST.memoizeThunk(() => recur(ast.thunk())) + return (t) => get()(t) + } + } + } +} + +// ----------------------------------------------------------------------------- +// Equivalence +// ----------------------------------------------------------------------------- + +/** + * Overrides the equivalence derivation for a schema by supplying a custom + * `Equivalence`. + * + * **When to use** + * + * Use when you need a custom equivalence instead of the default structural + * equivalence derived by {@link toEquivalence}. + * + * @category instances + * @since 4.0.0 + */ +export function overrideToEquivalence(toEquivalence: () => Equivalence.Equivalence) { + return (self: S): S["Rebuild"] => self.annotate({ toEquivalence }) +} + +/** + * Derives an `Equivalence` from a schema. Two values are considered equal when + * every field (and nested field) compares equal according to the schema + * structure. + * + * **Example** (Comparing structs) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const eq = Schema.toEquivalence(Schema.Struct({ id: Schema.Number, name: Schema.String })) + * + * eq({ id: 1, name: "Alice" }, { id: 1, name: "Alice" }) // => true + * eq({ id: 1, name: "Alice" }, { id: 2, name: "Alice" }) // => false + * ``` + * + * @category instances + * @since 4.0.0 + */ +export function toEquivalence(schema: Schema): Equivalence.Equivalence { + return InternalEquivalence.toEquivalence(schema.ast) +} + +// ----------------------------------------------------------------------------- +// Representation +// ----------------------------------------------------------------------------- + +/** + * Derives an intermediate `SchemaRepresentation.Document` from the encoded + * side of a schema. + * + * **When to use** + * + * Use when you have a `Schema` and need its live structural representation for inspection, persistence, or compilation. + * + * **Details** + * + * Use {@link toType} before this function to represent the type side instead. + * The optional reference policy controls which candidates are extracted into the document's reference table. By + * default, only candidates with a resolved identifier become references; recursive candidates always require one. + * + * @see {@link SchemaRepresentation.toRepresentation} for converting a `SchemaAST.AST` directly + * + * @category converting + * @since 4.0.0 + */ +export function toRepresentation( + schema: Constraint, + options?: SchemaRepresentation.ToRepresentationOptions +): SchemaRepresentation.Document { + return InternalToRepresentation.toRepresentation(schema.ast, options) +} + +// ----------------------------------------------------------------------------- +// JsonSchema +// ----------------------------------------------------------------------------- + +/** + * Options for reference allocation and JSON Schema generation in {@link toJsonSchemaDocument}. + * + * **Details** + * + * The inherited `referencePolicy` runs after the input schema is converted to its canonical JSON codec, so it receives + * canonical JSON-encoded ASTs. The remaining options control compilation of the resulting live representation. + * + * **Gotchas** + * + * When these options are passed directly to `SchemaRepresentation.toJsonSchemaDocument` or + * `SchemaRepresentation.toJsonSchemaMultiDocument`, reference allocation has already happened and `referencePolicy` + * has no effect. + * + * @category options + * @since 4.0.0 + */ +export interface ToJsonSchemaOptions extends SchemaRepresentation.ToRepresentationOptions { + /** + * Controls how additional properties are handled while resolving the JSON + * schema. + * + * **Details** + * + * Possible values include: + * - `false`: Disallow additional properties (default) + * - `true`: Allow additional properties + * - `JsonSchema`: Use the provided JSON Schema for additional properties + */ + readonly additionalProperties?: boolean | JsonSchema.JsonSchema | undefined + /** + * Controls whether to generate descriptions for checks (if the user has not + * provided them) based on the `expected` annotation of the check. + */ + readonly generateDescriptions?: boolean | undefined + /** + * A predicate that controls which additional annotation keys (beyond the + * standard JSON Schema keys) are included in the generated output. + * + * **When to use** + * + * Use when you need to include non-standard annotation keys in the generated + * JSON Schema, such as Monaco Editor properties (`markdownDescription`, + * `defaultSnippets`) or vendor extensions (`x-*`). + * + * **Details** + * + * Standard JSON Schema keys (`title`, `description`, `default`, `examples`, + * `readOnly`, `writeOnly`, `format`, `contentEncoding`, `contentMediaType`, + * `contentSchema`) are always included. This predicate is checked for any + * *other* annotation key. + * + * **Gotchas** + * + * Prefer whitelisting the custom annotation keys you want to emit instead of + * using a broad predicate such as `() => true`, because broad predicates can + * include Effect-specific annotations that are preserved for internal schema + * generation. + * + * **Example** (Including custom annotations) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * const schema = Schema.String.annotate({ + * description: "A name", + * markdownDescription: "The **name** field" + * }) + * + * const doc = Schema.toJsonSchemaDocument(schema, { + * includeAnnotationKey: (key) => + * key === "markdownDescription" || key.startsWith("x-") + * }) + * + * doc.schema // => { type: "string", description: "A name", markdownDescription: "The **name** field" } + * ``` + */ + readonly includeAnnotationKey?: ((key: string) => boolean) | undefined +} + +/** + * Returns a JSON Schema document using draft 2020-12. + * + * **When to use** + * + * Use when you need a draft-2020-12 description of the canonical JSON form of a runtime schema. + * + * **Details** + * + * The `options` parameter controls reference extraction and generation details + * such as additional properties and synthesized check descriptions; it does + * not change the draft target. The reference policy receives canonical JSON + * encoded ASTs. By default, anonymous non-recursive candidates remain inline, while candidates with resolved identifiers + * become definitions. Declarations are lowered through their `toCodecJson` or `toCodec` + * annotation when available before the representation document is compiled. + * For schemas whose codec JSON AST can be represented exactly in JSON Schema, + * importing the emitted document reconstructs a schema that accepts the same + * JSON values. This is a semantic round-trip guarantee; the reconstructed AST + * may have a different shape. + * + * **Gotchas** + * + * JSON Schema generation is best-effort. Some Effect schema semantics cannot + * be represented exactly in JSON Schema, and importing an emitted JSON Schema + * may produce an equivalent approximation rather than the original schema + * shape. Such schemas are outside the exact round-trip subset. When canonical + * JSON derivation adds an artificial transformation, checks and annotations on + * its source node are not copied to the JSON target, so they do not appear in + * the emitted document. Opaque declarations without a structural codec are + * represented by an unconstrained JSON Schema. Effect decoding may discard + * excess object properties by default; use `onExcessProperty: "error"` when + * comparing validation semantics with an emitted JSON Schema. + * + * @see {@link SchemaRepresentation.toJsonSchemaDocument} for compiling an existing live representation document + * + * @category converting + * @since 4.0.0 + */ +export function toJsonSchemaDocument( + schema: Constraint, + options?: ToJsonSchemaOptions +): JsonSchema.Document<"draft-2020-12"> { + const document = InternalToRepresentation.toRepresentation( + toCodecJsonAST(schema.ast), + options + ) + return InternalToJsonSchemaDocument.toJsonSchemaDocument(document, options) +} + +// ----------------------------------------------------------------------------- +// Canonical Codecs +// ----------------------------------------------------------------------------- + +/** + * Type-level representation returned by {@link toCodecJson}. + * + * @category converting + * @since 4.0.0 + */ +export interface toCodecJson extends + BottomLazy< + S["ast"], + toCodecJson, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": Json + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] + readonly schema: S +} + +/** + * Derives a canonical JSON codec from a schema. The encoded form is `Json`, and + * decoding produces the schema's `Type`. + * + * **Details** + * + * Derivation does not run transformations. Annotation links may be asynchronous, + * may fail, and may use optional services; the consuming parser chooses the + * execution and failure handling. Because hooks do not widen the returned + * service types, links cannot require services not declared by the input schema. + * + * **Gotchas** + * + * Declarations without a `toCodecJson` or `toCodec` annotation use `Json` as + * their encoded schema. This keeps codec construction total, but encoding or + * decoding can still fail when declaration values are not JSON values. A + * `toCodecJson` callback can return `undefined` when the declaration is already + * in canonical JSON form. When derivation adds an artificial transformation, + * checks and annotations remain on its source node rather than being copied to + * the JSON target. Source checks still run after the transformation. + * + * @category converting + * @since 4.0.0 + */ +export function toCodecJson(schema: S): toCodecJson { + return make(toCodecJsonAST(schema.ast), { schema }) +} + +/** @internal */ +export const toCodecJsonAST = SchemaAST.applyToSelfOrLastLinkEncodingIdempotent((ast) => { + const out = toCodecJsonASTStep(ast, toCodecJsonAST) + const context = ast.context + if (out === ast || context === undefined) return out + return SchemaAST.replaceContextLastLink(out, withoutConstructorDefault(context)) +}) + +function withoutConstructorDefault(context: SchemaAST.Context): SchemaAST.Context { + return context.constructorDefault === undefined ? + context : + new SchemaAST.Context(context.isOptional, context.isMutable, undefined, context.annotations) +} + +function validateCanonicalObjectPropertyNames(ast: SchemaAST.Objects): void { + if (ast.propertySignatures.some((ps) => typeof ps.name !== "string")) { + throw new globalThis.Error("Objects property names must be strings", { cause: ast }) + } +} + +function makeReorder(getPriority: (ast: SchemaAST.AST) => number) { + return (types: ReadonlyArray): ReadonlyArray => { + // Create a map of original indices for O(1) lookup + const indexMap = new Map() + for (let i = 0; i < types.length; i++) { + indexMap.set(SchemaAST.toEncoded(types[i]), i) + } + + // Create a sorted copy of the types array + const sortedTypes = [...types].sort((a, b) => { + a = SchemaAST.toEncoded(a) + b = SchemaAST.toEncoded(b) + const pa = getPriority(a) + const pb = getPriority(b) + if (pa !== pb) return pa - pb + // If priorities are equal, maintain original order (stable sort) + return indexMap.get(a)! - indexMap.get(b)! + }) + + // Check if order changed by comparing arrays + const orderChanged = sortedTypes.some((ast, index) => ast !== types[index]) + + if (!orderChanged) return types + return sortedTypes + } +} + +const toCodecJsonReorder = makeReorder((ast: SchemaAST.AST) => { + switch (ast._tag) { + case "BigInt": + case "Symbol": + case "UniqueSymbol": + return 0 + default: + return 1 + } +}) + +function toCodecJsonASTStep(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST { + switch (ast._tag) { + case "Declaration": { + const getLink = ast.annotations?.toCodecJson ?? ast.annotations?.toCodec + if (!Predicate.isFunction(getLink)) { + return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToJson]) + } + const typeParameters = ast.typeParameters.map((tp) => InternalSchema.make(SchemaAST.toEncoded(tp))) + const link = getLink(typeParameters) + return link === undefined ? ast : SchemaAST.replaceEncoding(ast, [SchemaAST.mapLink(link, recur)]) + } + case "Unknown": + return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToJson]) + case "ObjectKeyword": + return SchemaAST.replaceEncoding(ast, [SchemaAST.objectKeywordToJson]) + case "Undefined": + case "Void": + case "Literal": + case "Number": + return ast.toCodecJson() + case "UniqueSymbol": + case "Symbol": + case "BigInt": + return ast.toCodecStringTree() + case "Objects": { + validateCanonicalObjectPropertyNames(ast) + return ast.recur(recur, SchemaAST.parameterFromString) + } + case "Union": { + const sortedTypes = toCodecJsonReorder(ast.types) + if (sortedTypes !== ast.types) { + return new SchemaAST.Union( + sortedTypes, + ast.mode, + ast.annotations, + ast.checks, + ast.encoding, + ast.context, + ast.encodingChecks + ).recur(recur) + } + return ast.recur(recur) + } + case "Arrays": + case "Suspend": + return ast.recur(recur) + } + // `Schema.Any` is used as an escape hatch + return ast +} + +/** + * Derives an isomorphism codec from a schema. The encoded form is the schema's + * `Iso` type — the intermediate representation used for round-tripping. + * + * **Details** + * + * Annotation links may be asynchronous, may fail, and may use optional services; + * the consuming parser chooses the execution and failure handling. + * + * **Gotchas** + * + * Links cannot require services because the returned `Codec` does not expose + * service requirements. + * + * @category converting + * @since 4.0.0 + */ +export function toCodecIso(schema: S): Codec { + return make(toCodecIsoAST(SchemaAST.toType(schema.ast))) +} + +const toCodecIsoAST = memoize((ast: SchemaAST.AST): SchemaAST.AST => { + const out = toCodecIsoASTStep(ast, toCodecIsoAST) + return out !== ast && ast.context !== undefined ? + SchemaAST.replaceContextLastLink(out, withoutConstructorDefault(ast.context)) : + out +}) + +function toCodecIsoASTStep(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST { + switch (ast._tag) { + case "Declaration": { + const getLink = ast.annotations?.toCodecIso ?? ast.annotations?.toCodec + if (Predicate.isFunction(getLink)) { + const link = getLink(ast.typeParameters.map((tp) => InternalSchema.make(tp))) + return SchemaAST.replaceEncoding(ast, [SchemaAST.mapLink(link, recur)]) + } + return ast + } + case "Arrays": + case "Objects": + case "Union": + case "Suspend": + return ast.recur(recur) + } + return ast +} + +/** + * A {@link Tree} of `string | undefined` nodes. Leaf values are either a + * string representation or `undefined` for opaque/declaration types. + * + * @category models + * @since 4.0.0 + */ +export type StringTree = Tree + +/** + * Type-level representation returned by {@link toCodecStringTree}. + * + * @category converting + * @since 4.0.0 + */ +export interface toCodecStringTree extends + BottomLazy< + S["ast"], + toCodecStringTree, + ReadonlyArray, + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": StringTree + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] + readonly schema: S +} + +/** + * Converts a schema to the StringTree canonical codec, where every leaf value + * becomes a string while preserving the original structure. + * + * **Details** + * + * Derivation does not run transformations. Annotation links may be asynchronous, + * may fail, and may use optional services; the consuming parser chooses the + * execution and failure handling. Links cannot require services not declared by + * the input schema because hooks do not widen the returned service types. + * + * **Gotchas** + * + * Declarations must provide a structural `toCodecStringTree`, `toCodecJson`, or + * `toCodec` encoding. A callback can return `undefined` when the declaration is + * already in canonical StringTree form. + * + * @category converting + * @since 4.0.0 + */ +export function toCodecStringTree(schema: S): toCodecStringTree { + return make(toCodecStringTreeAST(schema.ast), { schema }) +} + +/** + * Type-level representation returned by {@link toCodecArrayFromSingle}. + * + * @category converting + * @since 4.0.0 + */ +export interface toCodecArrayFromSingle extends + BottomLazy< + S["ast"], + toCodecArrayFromSingle, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": S["Iso"] +} + +/** + * Allows array schemas to decode from either an array input or a single value + * input. + * + * **When to use** + * + * Use when you need to accept transport formats that may represent a + * single-item array as a bare value, such as query-string or form-data adapters. + * + * **Gotchas** + * + * This combinator is intentionally not part of `toCodecStringTree`; it adds a + * decoding convenience rather than a canonical StringTree representation. It + * does not parse comma-separated strings. + * + * @category converting + * @since 4.0.0 + */ +export function toCodecArrayFromSingle(schema: S): toCodecArrayFromSingle { + return make(toCodecArrayFromSingleAST(schema.ast)) +} + +type XmlEncoderOptions = { + /** Root element name for the returned XML string. Default: "root" */ + readonly rootName?: string | undefined + /** When an array doesn't have a natural item name, use this. Default: "item" */ + readonly arrayItemName?: string | undefined + /** Pretty-print output. Default: true */ + readonly pretty?: boolean | undefined + /** Indentation used when pretty-printing. Default: " " (two spaces) */ + readonly indent?: string | undefined + /** Sort object keys for stable output. Default: true */ + readonly sortKeys?: boolean | undefined +} + +/** + * Derives an XML encoder from a codec. + * + * **Details** + * + * The returned function encodes a value through `toCodecStringTree` and returns + * an `Effect` that succeeds with the XML string or fails with `SchemaError` if + * codec encoding fails. + * + * @category encoding + * @since 4.0.0 + */ +export function toEncoderXml( + codec: ConstraintCodec, + options?: XmlEncoderOptions +) { + const rootName = InternalAnnotations.resolveIdentifier(codec.ast) ?? InternalAnnotations.resolveTitle(codec.ast) + const serialize = encodeEffect(toCodecStringTree(codec)) + return (t: T): Effect.Effect => + serialize(t).pipe(Effect.map((stringTree) => stringTreeToXml(stringTree, { rootName, ...options }))) +} + +function stringTreeToXml(value: StringTree, options: XmlEncoderOptions): string { + const rootName = options.rootName ?? "root" + const arrayItemName = options.arrayItemName ?? "item" + const pretty = options.pretty ?? true + const indent = options.indent ?? " " + const sortKeys = options.sortKeys ?? true + + const seen = new Set() + const lines: Array = [] + + recur(rootName, value, 0) + return lines.join(pretty ? "\n" : "") + + function push(depth: number, text: string): void { + lines.push(pretty ? indent.repeat(depth) + text : text) + } + + function recur(tagName: string, node: StringTree, depth: number, originalNameForMeta?: string): void { + const { attrs, safe } = xml.tagInfo(tagName, originalNameForMeta) + + if (node === undefined) { + push(depth, `<${safe}${attrs}/>`) + } else if (typeof node === "string") { + push(depth, `<${safe}${attrs}>${xml.escapeText(node)}`) + } else if (typeof node !== "object" || node === null) { + push(depth, `<${safe}${attrs}>${xml.escapeText(format(node))}`) + } else { + if (seen.has(node)) throw new globalThis.Error("Cycle detected while serializing to XML.", { cause: node }) + seen.add(node) + try { + if (globalThis.globalThis.Array.isArray(node)) { + if (node.length === 0) { + push(depth, `<${safe}${attrs}/>`) + return + } + push(depth, `<${safe}${attrs}>`) + for (const item of node) recur(arrayItemName, item, depth + 1) + push(depth, ``) + return + } + + const obj = node as Record + const keys = Object.keys(obj) + if (sortKeys) keys.sort() + + if (keys.length === 0) { + push(depth, `<${safe}${attrs}/>`) + return + } + + push(depth, `<${safe}${attrs}>`) + for (const k of keys) { + recur(xml.parseTagName(k).safe, obj[k], depth + 1, k) + } + push(depth, ``) + } finally { + seen.delete(node) + } + } + } +} + +const xml = { + escapeText(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">") + }, + escapeAttribute(s: string): string { + return s.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">") + }, + parseTagName(name: string): { safe: string; changed: boolean } { + const original = name + let safe = name + if (!/^[A-Za-z_]/.test(safe)) safe = "_" + safe + safe = safe.replace(/[^A-Za-z0-9._-]/g, "_") + if (/^xml/i.test(safe)) safe = "_" + safe + return { safe, changed: safe !== original } + }, + tagInfo(name: string, original?: string): { safe: string; attrs: string } { + const { changed, safe } = xml.parseTagName(name) + const needsMeta = changed || (original && original !== name) + const attrs = needsMeta ? ` data-name="${xml.escapeAttribute(original ?? name)}"` : "" + return { safe, attrs } + } +} + +const toStringTreeReorder = makeReorder((ast: SchemaAST.AST) => { + switch (ast._tag) { + case "Null": + case "Boolean": + case "Number": + case "BigInt": + case "Symbol": + case "UniqueSymbol": + return 0 + default: + return 1 + } +}) + +function toCodecStringTreeASTStep( + ast: SchemaAST.AST, + recur: (ast: SchemaAST.AST) => SchemaAST.AST, + onMissingAnnotation: (ast: SchemaAST.AST) => SchemaAST.AST +): SchemaAST.AST { + switch (ast._tag) { + case "Declaration": { + const typeParameters = ast.typeParameters.map((tp) => make(recur(SchemaAST.toEncoded(tp)))) + const getStringTreeLink = ast.annotations?.toCodecStringTree + if (Predicate.isFunction(getStringTreeLink)) { + const link = getStringTreeLink(typeParameters) + if (link === undefined) return ast + return SchemaAST.replaceEncoding(ast, [SchemaAST.mapLink(link, recur)]) + } + const getJsonLink = ast.annotations?.toCodecJson + const jsonLink = Predicate.isFunction(getJsonLink) ? getJsonLink(typeParameters) : undefined + const getLink = jsonLink === undefined ? ast.annotations?.toCodec : undefined + const link = jsonLink ?? (Predicate.isFunction(getLink) ? getLink(typeParameters) : undefined) + return link === undefined + ? onMissingAnnotation(ast) + : SchemaAST.replaceEncoding(ast, [SchemaAST.mapLink(link, recur)]) + } + case "Null": + return SchemaAST.replaceEncoding(ast, [nullToString]) + case "Boolean": + return SchemaAST.replaceEncoding(ast, [booleanToString]) + case "Unknown": + case "ObjectKeyword": + return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToStringTree]) + case "Enum": + case "Number": + case "Literal": + case "UniqueSymbol": + case "Symbol": + case "BigInt": + return ast.toCodecStringTree() + case "Objects": { + validateCanonicalObjectPropertyNames(ast) + return ast.recur(recur, SchemaAST.parameterFromString) + } + case "Union": { + const sortedTypes = toStringTreeReorder(ast.types) + if (sortedTypes !== ast.types) { + return new SchemaAST.Union( + sortedTypes, + ast.mode, + ast.annotations, + ast.checks, + ast.encoding, + ast.context, + ast.encodingChecks + ).recur(recur) + } + return ast.recur(recur) + } + case "Arrays": + case "Suspend": + return ast.recur(recur) + } + // `Schema.Any` is used as an escape hatch + return ast +} + +const nullToString = new SchemaAST.Link( + new SchemaAST.Literal("null"), + new SchemaTransformation.Transformation( + SchemaGetter.transform(() => null), + SchemaGetter.transform(() => "null") + ) +) + +const booleanToString = new SchemaAST.Link( + new SchemaAST.Union([new SchemaAST.Literal("true"), new SchemaAST.Literal("false")], "anyOf"), + new SchemaTransformation.Transformation( + SchemaGetter.transform((s) => s === "true"), + SchemaGetter.String() + ) +) + +const arrayFromSingleTransformation = new SchemaTransformation.Transformation( + SchemaGetter.transform((input: ReadonlyArray | string) => typeof input === "string" ? [input] : input), + SchemaGetter.passthrough() +) + +const isCodecArrayFromSingleLink = (link: SchemaAST.Link): boolean => + link.transformation === arrayFromSingleTransformation + +const toCodecStringTreeAST = SchemaAST.applyToSelfOrLastLinkEncodingIdempotent((ast) => { + const out = toCodecStringTreeASTStep(ast, toCodecStringTreeAST, (ast) => { + throw new globalThis.Error("Missing structural codec for StringTree", { cause: ast }) + }) + if (out !== ast && ast.context !== undefined) { + return SchemaAST.replaceContextLastLink(out, withoutConstructorDefault(ast.context)) + } + return out +}, { stopAt: isCodecArrayFromSingleLink }) + +const toArrayFromSingleInputElement = (ast: SchemaAST.AST): SchemaAST.AST => + SchemaAST.isOptional(ast) ? SchemaAST.optionalKey(SchemaAST.unknown) : SchemaAST.unknown + +const toCodecArrayFromSingleAST = SchemaAST.applyToSelfOrLastLinkEncodingIdempotent((ast) => { + const out = toCodecArrayFromSingleASTStep(ast) + if (SchemaAST.isArrays(out)) { + const ensure = SchemaAST.decodeTo( + new SchemaAST.Union( + [ + new SchemaAST.Arrays( + out.isMutable, + out.elements.map(toArrayFromSingleInputElement), + out.rest.map(toArrayFromSingleInputElement) + ), + SchemaAST.string + ], + "anyOf" + ), + out, + arrayFromSingleTransformation + ) + return SchemaAST.isOptional(ast) ? SchemaAST.optionalKey(ensure) : ensure + } + return out +}, { stopAt: isCodecArrayFromSingleLink }) + +function toCodecArrayFromSingleASTStep(ast: SchemaAST.AST): SchemaAST.AST { + return ast._tag === "Declaration" || ast._tag === "Arrays" || ast._tag === "Objects" || ast._tag === "Union" || + ast._tag === "Suspend" + ? ast.recur(toCodecArrayFromSingleAST) + : ast +} + +/** + * Reviver for persisted `isGreaterThanDate` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isGreaterThanDate}. + * + * @see {@link isGreaterThanDate} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanDateReviver: SchemaRepresentation.FilterReviver<{ + readonly exclusiveMinimum: globalThis.Date +}> = InternalSchema.makeFilterReviver( + "effect/schema/isGreaterThanDate", + Struct({ exclusiveMinimum: Date }), + ({ annotations, payload }) => isGreaterThanDate(payload.exclusiveMinimum, annotations) +) + +/** + * Reviver for persisted `isGreaterThanOrEqualToDate` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isGreaterThanOrEqualToDate}. + * + * @see {@link isGreaterThanOrEqualToDate} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanOrEqualToDateReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: globalThis.Date +}> = InternalSchema.makeFilterReviver( + "effect/schema/isGreaterThanOrEqualToDate", + Struct({ minimum: Date }), + ({ annotations, payload }) => isGreaterThanOrEqualToDate(payload.minimum, annotations) +) + +/** + * Reviver for persisted `isLessThanDate` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLessThanDate}. + * + * @see {@link isLessThanDate} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanDateReviver: SchemaRepresentation.FilterReviver<{ + readonly exclusiveMaximum: globalThis.Date +}> = InternalSchema.makeFilterReviver( + "effect/schema/isLessThanDate", + Struct({ exclusiveMaximum: Date }), + ({ annotations, payload }) => isLessThanDate(payload.exclusiveMaximum, annotations) +) + +/** + * Reviver for persisted `isLessThanOrEqualToDate` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLessThanOrEqualToDate}. + * + * @see {@link isLessThanOrEqualToDate} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanOrEqualToDateReviver: SchemaRepresentation.FilterReviver<{ + readonly maximum: globalThis.Date +}> = InternalSchema.makeFilterReviver( + "effect/schema/isLessThanOrEqualToDate", + Struct({ maximum: Date }), + ({ annotations, payload }) => isLessThanOrEqualToDate(payload.maximum, annotations) +) + +/** + * Reviver for persisted `isBetweenDate` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isBetweenDate}. + * + * @see {@link isBetweenDate} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isBetweenDateReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: globalThis.Date + readonly maximum: globalThis.Date + readonly exclusiveMinimum?: true | undefined + readonly exclusiveMaximum?: true | undefined +}> = InternalSchema.makeFilterReviver( + "effect/schema/isBetweenDate", + Struct({ + minimum: Date, + maximum: Date, + exclusiveMinimum: optional(Literal(true)), + exclusiveMaximum: optional(Literal(true)) + }), + ({ annotations, payload }) => isBetweenDate(payload, annotations) +) + +/** + * Reviver for persisted `isGreaterThanBigInt` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isGreaterThanBigInt}. + * + * @see {@link isGreaterThanBigInt} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanBigIntReviver: SchemaRepresentation.FilterReviver<{ + readonly exclusiveMinimum: bigint +}> = InternalSchema.makeFilterReviver( + "effect/schema/isGreaterThanBigInt", + Struct({ exclusiveMinimum: BigInt }), + ({ annotations, payload }) => isGreaterThanBigInt(payload.exclusiveMinimum, annotations) +) + +/** + * Reviver for persisted `isGreaterThanOrEqualToBigInt` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isGreaterThanOrEqualToBigInt}. + * + * @see {@link isGreaterThanOrEqualToBigInt} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isGreaterThanOrEqualToBigIntReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: bigint +}> = InternalSchema.makeFilterReviver( + "effect/schema/isGreaterThanOrEqualToBigInt", + Struct({ minimum: BigInt }), + ({ annotations, payload }) => isGreaterThanOrEqualToBigInt(payload.minimum, annotations) +) + +/** + * Reviver for persisted `isLessThanBigInt` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLessThanBigInt}. + * + * @see {@link isLessThanBigInt} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanBigIntReviver: SchemaRepresentation.FilterReviver<{ + readonly exclusiveMaximum: bigint +}> = InternalSchema.makeFilterReviver( + "effect/schema/isLessThanBigInt", + Struct({ exclusiveMaximum: BigInt }), + ({ annotations, payload }) => isLessThanBigInt(payload.exclusiveMaximum, annotations) +) + +/** + * Reviver for persisted `isLessThanOrEqualToBigInt` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isLessThanOrEqualToBigInt}. + * + * @see {@link isLessThanOrEqualToBigInt} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isLessThanOrEqualToBigIntReviver: SchemaRepresentation.FilterReviver<{ + readonly maximum: bigint +}> = InternalSchema.makeFilterReviver( + "effect/schema/isLessThanOrEqualToBigInt", + Struct({ maximum: BigInt }), + ({ annotations, payload }) => isLessThanOrEqualToBigInt(payload.maximum, annotations) +) + +/** + * Reviver for persisted `isBetweenBigInt` checks. + * + * **When to use** + * + * Use when reconstructing documents that may contain checks created by {@link isBetweenBigInt}. + * + * @see {@link isBetweenBigInt} for creating the corresponding check + * + * @category validation + * @since 4.0.0 + */ +export const isBetweenBigIntReviver: SchemaRepresentation.FilterReviver<{ + readonly minimum: bigint + readonly maximum: bigint + readonly exclusiveMinimum?: true | undefined + readonly exclusiveMaximum?: true | undefined +}> = InternalSchema.makeFilterReviver( + "effect/schema/isBetweenBigInt", + Struct({ + minimum: BigInt, + maximum: BigInt, + exclusiveMinimum: optional(Literal(true)), + exclusiveMaximum: optional(Literal(true)) + }), + ({ annotations, payload }) => isBetweenBigInt(payload, annotations) +) + +// ----------------------------------------------------------------------------- +// Optic APIs +// ----------------------------------------------------------------------------- + +/** + * Derives an `Iso` optic from a schema that isomorphically converts between + * the schema's `Type` and its `Iso` (intermediate / serialized form). + * + * **Details** + * + * Reading through the `Iso` encodes the schema value, while replacing through + * it decodes the new focus. + * + * **Gotchas** + * + * This API runs 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()`. Consume {@link toCodecIso} with an + * effectful parser for asynchronous execution or explicit failure handling. + * + * @category converting + * @since 4.0.0 + */ +export function toIso(schema: S): Optic_.Iso { + const serializer = toCodecIso(schema) + return Optic_.makeIso(SchemaParser.encodeSync(serializer), SchemaParser.decodeSync(serializer)) +} + +/** + * Returns an identity `Iso` over the schema's source (`Type`) side. + * + * @category constructors + * @since 4.0.0 + */ +export function toIsoSource(_: S): Optic_.Iso { + return Optic_.id() +} + +/** + * Returns an identity `Iso` over the schema's focus (`Iso`) side. + * + * @category constructors + * @since 4.0.0 + */ +export function toIsoFocus(_: S): Optic_.Iso { + return Optic_.id() +} + +/** + * Type-level representation returned by {@link overrideToCodecIso}. + * + * @category transforming + * @since 4.0.0 + */ +export interface overrideToCodecIso extends + BottomLazy< + S["ast"], + overrideToCodecIso, + S["~type.parameters"], + S["~type.mutability"], + S["~type.optionality"], + S["~type.constructor.default"], + S["~encoded.mutability"], + S["~encoded.optionality"] + > +{ + readonly "Type": S["Type"] + readonly "Encoded": S["Encoded"] + readonly "DecodingServices": S["DecodingServices"] + readonly "EncodingServices": S["EncodingServices"] + readonly "~type.make.in": S["~type.make.in"] + readonly "~type.make": S["~type.make"] + readonly "Iso": Iso + readonly schema: S +} + +/** + * Overrides a schema's derived ISO codec with an explicit target codec. + * + * **When to use** + * + * Use to provide a custom ISO transformation when the default derivation is not + * appropriate. + * + * **Details** + * + * The resulting schema carries a custom `Iso` type parameter and uses the + * provided `decode` and `encode` getters to transform between the schema type + * and the target codec. + * + * @category transforming + * @since 4.0.0 + */ +export function overrideToCodecIso( + to: ConstraintCodec, + transformation: { + readonly decode: SchemaGetter.Getter + readonly encode: SchemaGetter.Getter + } +) { + return (schema: S): overrideToCodecIso => { + return make( + SchemaAST.annotate(schema.ast, { + toCodecIso: () => new SchemaAST.Link(to.ast, SchemaTransformation.make(transformation)) + }), + { schema } + ) + } +} + +// ----------------------------------------------------------------------------- +// Differ APIs +// ----------------------------------------------------------------------------- + +/** + * Derives a JSON Patch differ from a codec. Serializes values to JSON (via + * {@link toCodecJson}), computes RFC 6902 JSON Patch operations between old + * and new values, and can apply patches back to the typed value. + * + * **Details** + * + * `diff` encodes both values before computing the patch. `patch` encodes the old + * value, applies the patch to its JSON representation, and decodes the result. + * + * **Gotchas** + * + * This API runs 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()`. Invalid patch operations instead produce + * {@link JsonPatch.apply} errors. + * + * @category converting + * @since 4.0.0 + */ +export function toDifferJsonPatch(schema: ConstraintCodec): Differ { + const serializer = toCodecJson(schema) + const get = SchemaParser.encodeSync(serializer) + const set = SchemaParser.decodeSync(serializer) + return { + empty: [], + diff: (oldValue, newValue) => JsonPatch.get(get(oldValue), get(newValue)), + combine: (first, second) => [...first, ...second], + patch: (oldValue, patch) => { + const value = get(oldValue) + const patched = JsonPatch.apply(patch, value) + return Object.is(patched, value) ? oldValue : set(patched) + } + } +} + +/** + * Recursive tree type whose leaves are `Node` values and whose branches are + * readonly arrays or string-keyed records of child trees. + * + * @category models + * @since 4.0.0 + */ +export type Tree = Node | TreeRecord | ReadonlyArray> + +/** + * A record node in a {@link Tree}: an object mapping string keys to child + * `Tree` nodes. + * + * @category models + * @since 4.0.0 + */ +export interface TreeRecord { + readonly [x: string]: Tree +} + +/** + * Creates a recursive schema for a {@link Tree} of values described by `node`. + * The resulting schema accepts a single node value, an array of trees, or an + * object whose values are trees. + * + * @category schemas + * @since 4.0.0 + */ +export function Tree(node: S) { + const Tree$ref = suspend((): Codec< + Tree, + Tree, + S["DecodingServices"], + S["EncodingServices"] + > => Tree) + const Tree = Union([ + node, + ArraySchema(Tree$ref), + Record(String, Tree$ref) + ]) + return Tree +} + +/** + * Recursive TypeScript type for any valid immutable JSON value: `null`, + * `number`, `boolean`, `string`, a readonly array of `Json` values, or a + * readonly record of `string → Json`. For the corresponding schema, see the + * {@link Json} const. + * + * @category models + * @since 4.0.0 + */ +export type Json = null | number | boolean | string | JsonArray | JsonObject + +/** + * A readonly array of {@link Json} values. + * + * @category models + * @since 4.0.0 + */ +export interface JsonArray extends ReadonlyArray {} + +/** + * A readonly record whose values are {@link Json} values. + * + * @category models + * @since 4.0.0 + */ +export interface JsonObject { + readonly [x: string]: Json +} + +/** + * Schema that accepts and validates any immutable JSON-compatible value. + * + * **Example** (Validating a JSON value) + * + * ```ts import.meta.vitest + * import { Option, Schema } from "effect" + * + * Schema.decodeUnknownOption(Schema.Json)({ key: [1, true, null] }) // => Option.some({ key: [1, true, null] }) + * ``` + * + * @category schemas + * @since 4.0.0 + */ +export const Json: Codec = make(SchemaAST.annotate(SchemaAST.Json, { + toCode: () => ({ + runtime: "Schema.Json", + Type: "Schema.Json" + }) +})) + +/** + * Schema for readonly string-keyed records whose values are JSON-compatible. + * + * **When to use** + * + * Use when you need to validate a JSON object rather than any JSON value. + * + * **Example** (Validating a JSON object) + * + * ```ts import.meta.vitest + * import { Option, Schema } from "effect" + * + * Schema.decodeUnknownOption(Schema.JsonObject)({ key: [1, true, null] }) // => Option.some({ key: [1, true, null] }) + * Schema.decodeUnknownOption(Schema.JsonObject)([1, 2, 3]) // => Option.none() + * ``` + * + * @see {@link Json} for a schema that also accepts JSON arrays and primitive values + * @category schemas + * @since 4.0.0 + */ +export const JsonObject = Record(String, Json) + +/** + * Reviver for persisted `Json` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link Json} schema. + * + * @see {@link Json} for the corresponding immutable JSON schema + * + * @category schemas + * @since 4.0.0 + */ +export const JsonReviver = makeFixedDeclarationReviver( + "effect/schema/Json", + Json +) + +const JsonError = Struct({ + message: String, + name: optionalKey(String), + stack: optionalKey(String), + cause: optionalKey(Json) +}) + +/** + * Recursive TypeScript type for mutable JSON values: `null`, `number`, + * `boolean`, `string`, mutable arrays, or mutable string-keyed records. + * + * @category models + * @since 4.0.0 + */ +export type MutableJson = null | number | boolean | string | MutableJsonArray | MutableJsonObject + +/** + * A mutable array of {@link MutableJson} values. + * + * @category models + * @since 4.0.0 + */ +export interface MutableJsonArray extends Array {} + +/** + * A mutable record whose values are {@link MutableJson} values. + * + * @category models + * @since 4.0.0 + */ +export interface MutableJsonObject { + [x: string]: MutableJson +} + +/** + * Schema that accepts any mutable JSON-compatible value. See {@link Json} for + * the immutable variant. + * + * @category schemas + * @since 4.0.0 + */ +export const MutableJson: Codec = make(SchemaAST.annotate(SchemaAST.MutableJson, { + toCode: () => ({ + runtime: "Schema.MutableJson", + Type: "Schema.MutableJson" + }) +})) + +/** + * Reviver for persisted `MutableJson` declarations. + * + * **When to use** + * + * Use when reconstructing documents that may contain the {@link MutableJson} schema. + * + * @see {@link MutableJson} for the corresponding mutable JSON schema + * + * @category schemas + * @since 4.0.0 + */ +export const MutableJsonReviver = makeFixedDeclarationReviver( + "effect/schema/MutableJson", + MutableJson +) + +// ----------------------------------------------------------------------------- +// Annotations +// ----------------------------------------------------------------------------- + +/** + * Resolves the typed annotations from a schema. The term "resolve" (rather + * than "get") reflects the lookup strategy: if the schema has checks, the + * annotations are taken from the last check; otherwise they are taken from + * the base schema instance. + * + * @category getters + * @since 4.0.0 + */ +export function resolveAnnotations( + schema: S +): Annotations.Bottom | undefined { + return InternalAnnotations.resolve(schema.ast) +} + +/** + * Resolves the context (key-level) annotations from a schema. Context + * annotations are those attached via `annotateKey` and live on the AST's + * `context` rather than on the schema node itself. + * + * @category getters + * @since 4.0.0 + */ +export function resolveAnnotationsKey(schema: S): Annotations.Key | undefined { + return schema.ast.context?.annotations +} + +/** + * The `Annotations` namespace groups all annotation interfaces used to attach + * metadata to schemas. Annotations control documentation, validation messages, + * JSON Schema generation, equivalence, arbitrary generation, and more. + * + * **Details** + * + * Use {@link resolveAnnotations} to read the annotations attached to a schema at + * runtime. + * + * @since 4.0.0 + */ +export declare namespace Annotations { + /** + * This interface is used to define the annotations that can be attached to a + * schema. You can extend this interface to define your own annotations. + * + * **Details** + * + * Note that both a missing key or `undefined` is used to indicate that the + * annotation is not present. + * + * This means that can remove any annotation by setting it to `undefined`. + * + * **Example** (Defining your own annotations) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * // Extend the Annotations interface with a custom `version` annotation + * declare module "effect/Schema" { + * namespace Annotations { + * interface Annotations { + * readonly version?: + * | readonly [major: number, minor: number, patch: number] + * | undefined + * } + * } + * } + * + * // The `version` annotation is now recognized by the TypeScript compiler + * const schema = Schema.String.annotate({ version: [1, 2, 0] }) + * + * // const version: readonly [major: number, minor: number, patch: number] | undefined + * const version = Schema.resolveAnnotations(schema)?.["version"] + * + * if (version) { + * // Access individual parts of the version + * version[1] // => 2 + * } + * ``` + * + * @category models + * @since 4.0.0 + */ + export interface Annotations { + readonly [x: string]: unknown + } + + /** + * Annotations shared by all schema nodes. These map to common JSON Schema / + * OpenAPI fields: `title`, `description`, `format`, etc. + * + * @category models + * @since 4.0.0 + */ + export interface Augment extends Annotations { + /** + * Human-readable description of what a value is expected to satisfy. + * + * **Details** + * + * For filter and refinement failures, the default formatter uses + * `message` first, then `expected`, and finally falls back to ``. + * + * Use this to name a failed filter in the default message: + * `Expected `. + */ + readonly expected?: string | undefined + readonly title?: string | undefined + readonly description?: string | undefined + readonly documentation?: string | undefined + readonly readOnly?: boolean | undefined + readonly writeOnly?: boolean | undefined + readonly format?: string | undefined + readonly contentEncoding?: string | undefined + readonly contentMediaType?: string | undefined + readonly contentSchema?: Json | undefined + } + + /** + * Extends {@link Augment} with type-parametric `default` and `examples` fields. + * + * @category models + * @since 4.0.0 + */ + export interface Documentation extends Augment { + readonly default?: T | undefined + readonly examples?: ReadonlyArray | undefined + } + + /** + * Annotations for struct property schemas. Extends {@link Documentation} + * with an optional `messageMissingKey` to override the error message when + * the property key is absent during decoding. + * + * @category models + * @since 4.0.0 + */ + export interface Key extends Documentation { + /** + * The message to use when a key is missing. + */ + readonly messageMissingKey?: string | undefined + } + + /** + * Base annotations shared by all composite schema nodes. Extends + * {@link Documentation} with error messages, branding, parse options, and + * arbitrary generation hooks. {@link Declaration} and other annotation + * interfaces build on top of this. + * + * @category models + * @since 4.0.0 + */ + export interface Bottom> extends Documentation { + /** + * Complete message to use when this schema node reports an issue. + * + * **Details** + * + * This replaces the default message for matching issue types instead of + * only changing the expected label. For a filter or refinement failure, + * annotate the filter with `message` to replace the whole filter failure + * message, or `expected` to keep the default + * `Expected ` shape. + */ + readonly message?: string | undefined + /** + * The message to use when a key is unexpected. + */ + readonly messageUnexpectedKey?: string | undefined + /** + * Stable identifier for this schema node. + * + * **Details** + * + * Identifiers are used by schema tooling, including JSON Schema + * generation, to name references. The default formatter also uses + * `identifier` as the expected label for type-level failures, such as + * `Expected UserId`. + * + * `identifier` does not name a failed filter or refinement. If the base + * type matches and a filter fails, put `expected` or `message` on the + * filter/refinement instead. + */ + readonly identifier?: string | undefined + readonly parseOptions?: SchemaAST.ParseOptions | undefined + /** + * Accumulated brands when multiple brands are added with `Schema.brand`. + */ + readonly brands?: ReadonlyArray | undefined + readonly toArbitrary?: + | ToArbitrary.Declaration + | undefined + } + + /** + * Helpers for projecting declaration type-parameter schemas into decoded or + * encoded codec arrays used by annotation hooks. + * + * @since 4.0.0 + */ + export namespace TypeParameters { + /** + * Maps declaration type-parameter schemas to codecs for their decoded `Type` + * values. + * + * @category utility types + * @since 4.0.0 + */ + export type Type> = { + readonly [K in keyof TypeParameters]: Codec + } + /** + * Maps declaration type-parameter schemas to codecs for their `Encoded` values. + * + * @category utility types + * @since 4.0.0 + */ + export type Encoded> = { + readonly [K in keyof TypeParameters]: Codec + } + } + + /** + * Full annotation set for `Declaration` schema nodes — used when defining + * custom, opaque schema types via `Schema.declare`. Extends {@link Bottom} + * with optional codec, arbitrary, equivalence, and formatter hooks so that + * derived capabilities (JSON encoding, property testing, etc.) can be + * provided for the custom type. + * + * @category models + * @since 4.0.0 + */ + export interface Declaration = readonly []> + extends Bottom + { + readonly representation?: + | SchemaRepresentation.RepresentationAnnotation + | undefined + /** + * Returns the fallback link used by canonical codec derivations. + * + * Transformations may be asynchronous, may fail, and may use optional + * services, but cannot require services absent from the derived codec type. + */ + readonly toCodec?: + | ((typeParameters: TypeParameters.Encoded) => SchemaAST.Link) + | undefined + /** + * Returns the link used to derive the declaration's JSON representation, or + * `undefined` when the declaration is already in canonical JSON form. + * + * Transformations follow the execution and service constraints of `toCodec`. + */ + readonly toCodecJson?: + | ((typeParameters: TypeParameters.Encoded) => SchemaAST.Link | undefined) + | undefined + /** + * Returns the link used to derive the declaration's StringTree + * representation, or `undefined` when it is already canonical. + * + * Transformations follow the execution and service constraints of `toCodec`. + */ + readonly toCodecStringTree?: + | ((typeParameters: TypeParameters.Encoded) => SchemaAST.Link | undefined) + | undefined + /** + * Returns the link used to derive the declaration's isomorphism + * representation. + * + * Transformations may be asynchronous, may fail, and may use optional + * services, but cannot require services because the derived `Codec` exposes + * none. + */ + readonly toCodecIso?: + | ((typeParameters: TypeParameters.Type) => SchemaAST.Link) + | undefined + readonly toArbitrary?: ToArbitrary.Declaration | undefined + readonly toEquivalence?: ToEquivalence.Declaration | undefined + readonly toFormatter?: ToFormatter.Declaration | undefined + readonly toCode?: SchemaRepresentation.Generation.Declaration | undefined + /** + * Used to collect sentinels from a Declaration SchemaAST. + * + * @internal + */ + readonly "~sentinels"?: ReadonlyArray | undefined + } + + /** + * Annotations for filter schema nodes (created via `Schema.filter`). Extends + * {@link Augment} with an optional error message, identifier, and metadata. + * Filters are intentionally non-parametric to keep them covariant. + * + * @category models + * @since 4.0.0 + */ + export interface Filter extends Augment { + readonly representation?: + | SchemaRepresentation.CheckRepresentationAnnotation + | undefined + /** + * Compiles this filter to a JSON Schema fragment. + * + * **Gotchas** + * + * Treat the input schemas as immutable. The returned value must be a valid JSON Schema object graph and must not be + * mutated after this function returns. Return a new object graph to produce different output during a later + * compilation. + */ + readonly toJsonSchema?: SchemaRepresentation.ToJsonSchema.Check | undefined + readonly toCode?: SchemaRepresentation.Generation.Check | undefined + /** + * Complete message to use when this filter or refinement fails. + * + * **Details** + * + * The default formatter checks filter annotations in this order: + * `message`, then `expected`, then ``. + */ + readonly message?: string | undefined + /** + * Stable identifier for the schema after this filter is attached. + * + * **Details** + * + * This can affect schema tooling such as JSON Schema generation and + * type-level failures before the filter runs, but it does not name the + * failed filter itself. For filter failure messages, use `expected` or + * `message`. + */ + readonly identifier?: string | undefined + /** + * Optional hints used by arbitrary derivation for this filter. + * + * **Details** + * + * The same annotation can be attached to a single filter or a + * `FilterGroup`. Group hints apply to the same schema node while child + * filters are still collected and checked normally. + */ + readonly arbitrary?: + | ToArbitrary.Filter + | undefined + /** + * Marks the filter as *structural*, meaning it applies to the shape or + * structure of the container (e.g., array length, object keys) rather than + * the contents. + * + * **Details** + * + * Reserved to internal use only. + * + * Example: `minLength` on an array is a structural filter. + */ + readonly "~structural"?: boolean | undefined + } + + /** + * Types used by arbitrary-derivation annotations to configure `toArbitrary` + * hooks, filter hints, candidate sources, and merged generation constraints. + * + * @since 4.0.0 + */ + export namespace ToArbitrary { + /** + * Arbitrary-generation hints attached to a filter or filter group. + * + * **Details** + * + * `constraint` refines the schema node's base generator. `candidate` adds a + * weighted source before all filters run. If neither hint is provided, the + * filter does not guide generation; generated values are still checked by + * the filter predicate. + * + * @category models + * @since 4.0.0 + */ + export interface Filter { + readonly constraint?: GenerationConstraint | undefined + readonly candidate?: Candidate | undefined + } + + /** + * Additional arbitrary source used before final filter checks run. + * + * **Details** + * + * The base generator keeps weight `1`; candidates default to weight `1` + * and must use a positive integer weight. `make` receives the merged + * constraint for the current node and may return `undefined` to opt out, + * including for recursive terminal branches. Candidate values are still + * checked by every schema filter, so invalid candidates affect efficiency but + * not validity. + * + * @category models + * @since 4.0.0 + */ + export interface Candidate { + readonly weight?: number | undefined + readonly make: ( + fc: typeof FastCheck, + context: Context + ) => FastCheck.Arbitrary | undefined + } + + /** + * Ordered constraint accumulated from range checks. + * + * **Details** + * + * Generators consume these constraints only when they recognize `order`, + * such as `Order.Number`, `Order.BigInt`, DateTime, or BigDecimal. Merging + * constraints with different `Order` instances fails fast. + * + * @category models + * @since 4.0.0 + */ + export interface OrderedConstraint { + readonly order: Order.Order + readonly minimum?: T | undefined + readonly exclusiveMinimum?: boolean | undefined + readonly maximum?: T | undefined + readonly exclusiveMaximum?: boolean | undefined + } + + /** + * Node-local arbitrary-generation constraint accumulated from schema checks. + * + * **Details** + * + * `GenerationConstraint` is a generation hint for the current schema AST + * node, not a self-describing validation contract. Each generator consumes + * the fields it understands for the current node and ignores the rest; + * final schema filters still validate every generated value. + * + * `minLength` and `maxLength` represent node-local cardinality: string + * length for strings, array length for arrays, final own-property count for + * objects, and final size/cardinality for sets, maps, hash collections, and + * chunks. `patterns` are concatenated and used by string generators. + * `integer`, `noNaN`, `noInfinity`, and `unique` are true when any + * contributing filter sets them. Range bounds live in `ordered` so ordered + * values can share the same representation. + * + * @category models + * @since 4.0.0 + */ + export interface GenerationConstraint { + readonly minLength?: number | undefined + readonly maxLength?: number | undefined + readonly patterns?: readonly [string, ...Array] + readonly integer?: boolean | undefined + readonly noInfinity?: boolean | undefined + readonly noNaN?: boolean | undefined + readonly unique?: boolean | undefined + readonly ordered?: OrderedConstraint | undefined + } + + /** + * Recursion budget passed to arbitrary-derivation hooks. + * + * **Details** + * + * Pass this object to `fc.oneof` when combining terminal and recursive + * branches. Put the terminal branch first because fast-check uses only the + * first branch once `maxDepth` is reached for `depthIdentifier`. + * + * @category models + * @since 4.0.0 + */ + export interface Recursion { + readonly maxDepth: number + readonly depthIdentifier: FastCheck.DepthIdentifier | string + } + + /** + * Context passed to arbitrary-derivation hooks and candidate factories. + * + * **Details** + * + * `constraint` contains the merged constraint for the current schema + * node. `recursion` is present while deriving through a suspended schema; + * hooks that build recursive alternatives should pass it to `fc.oneof` with + * the finite branch first. + * + * @category models + * @since 4.0.0 + */ + export interface Context { + readonly constraint?: ToArbitrary.GenerationConstraint | undefined + readonly recursion?: ToArbitrary.Recursion | undefined + } + + /** + * Arbitrary generators derived for a declaration type parameter. + * + * **Details** + * + * `arbitrary` is the normal generator. `terminal` is the finite generator + * used while building recursive terminal branches and is `undefined` when + * no finite path is known. Optional containers can ignore it; non-empty + * containers need it for their terminal branch. + * + * @category models + * @since 4.0.0 + */ + export interface TypeParameter { + readonly arbitrary: FastCheck.Arbitrary + readonly terminal: FastCheck.Arbitrary | undefined + } + + /** + * Arbitrary derivation returned by declaration hooks. + * + * **Details** + * + * `arbitrary` is the normal generator. `terminal` is an optional finite + * branch for recursive schemas. If omitted, it defaults to `arbitrary` only + * for declarations without type parameters. + * + * @category models + * @since 4.0.0 + */ + export interface Derivation { + readonly arbitrary: FastCheck.Arbitrary + readonly terminal?: FastCheck.Arbitrary | undefined + } + + /** + * Output accepted from declaration arbitrary hooks. + * + * **Details** + * + * A bare fast-check arbitrary is shorthand for `{ arbitrary }`, useful for + * atomic declarations such as URLs. Generic declarations that need precise + * recursive behavior should return a {@link Derivation} with `terminal`. + * + * @category models + * @since 4.0.0 + */ + export type Output = FastCheck.Arbitrary | Derivation + + /** + * Hook signature for declaration schema arbitrary annotations. + * + * **Details** + * + * Type parameters expose normal and terminal generators. A declaration with + * no type parameters can return a bare arbitrary; a generic declaration + * must return `terminal` explicitly when it has a finite branch depending on + * parameters. + * + * @category models + * @since 4.0.0 + */ + export interface Declaration> { + ( + /* Arbitrary derivations for any type parameters of the schema (if present) */ + typeParameters: { readonly [K in keyof TypeParameters]: TypeParameter } + ): (fc: typeof FastCheck, context: Context) => Output + } + } + + /** + * Types used by formatter annotations to customize formatter derivation for + * declaration schemas. + * + * @since 4.0.0 + */ + export namespace ToFormatter { + /** + * Hook signature for declaration schema formatter annotations. + * + * **Details** + * + * Given formatters for any type parameters, returns a formatter for `T`. + * + * @category models + * @since 4.0.0 + */ + export interface Declaration> { + ( + /* Formatters for any type parameters of the schema (if present) */ + typeParameters: { readonly [K in keyof TypeParameters]: Formatter } + ): Formatter + } + } + + /** + * Types used by equivalence annotations to customize equivalence derivation for + * declaration schemas. + * + * @since 4.0.0 + */ + export namespace ToEquivalence { + /** + * Hook signature for declaration schema equivalence annotations. + * + * **Details** + * + * Given equivalences for any type parameters, returns an `Equivalence` for `T`. + * + * @category models + * @since 4.0.0 + */ + export interface Declaration> { + ( + /* Equivalences for any type parameters of the schema (if present) */ + typeParameters: { readonly [K in keyof TypeParameters]: Equivalence.Equivalence } + ): Equivalence.Equivalence + } + } + + /** + * Annotations that can be attached to schema issues. + * + * **Details** + * + * For `InvalidValue` issues, `message` overrides the complete formatted + * message. When `message` is absent, `expected` uses the default expected + * value policy, including reported input when available. Other issue types + * ignore `expected`. + * + * @category models + * @since 4.0.0 + */ + export interface Issue extends Annotations { + /** + * The expected value description for an `InvalidValue` issue. + */ + readonly expected?: string | undefined + /** + * The complete formatted message for the issue. + */ + readonly message?: string | undefined + } +} diff --git a/.repos/effect/packages/effect/src/SchemaAST.ts b/.repos/effect/packages/effect/src/SchemaAST.ts new file mode 100644 index 000000000..e99d7f473 --- /dev/null +++ b/.repos/effect/packages/effect/src/SchemaAST.ts @@ -0,0 +1,4417 @@ +/** + * Represents Effect schemas as runtime trees. + * + * Every `Schema` has an AST made from nodes for declarations, primitives, + * literals, arrays, objects, unions, suspended schemas, checks, annotations, + * encoding links, and parsing context. Most users work with the higher-level + * `Schema` module. Use `SchemaAST` when you need to inspect schema nodes, build + * ASTs programmatically, change encoded or decoded views, collect issues, or + * run low-level schema checks. + * + * @since 4.0.0 + */ + +import * as Arr from "./Array.ts" +import * as Cause from "./Cause.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import { format, formatPropertyKey } from "./Formatter.ts" +import { identity, memoize, memoizeIdempotent } from "./Function.ts" +import { effectIsExit, iterateEager } from "./internal/effect.ts" +import * as InternalRecord from "./internal/record.ts" +import * as InternalAnnotations from "./internal/schema/annotations.ts" +import * as InternalSchemaCause from "./internal/schema/cause.ts" +import * as InternalParser from "./internal/schema/parser.ts" +import * as Pipeable from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import * as Result from "./Result.ts" +import type * as Schema from "./Schema.ts" +import * as SchemaGetter from "./SchemaGetter.ts" +import * as SchemaIssue from "./SchemaIssue.ts" +import type * as SchemaParser from "./SchemaParser.ts" +import * as SchemaTransformation from "./SchemaTransformation.ts" +import type * as FastCheck from "./testing/FastCheck.ts" + +/** + * Discriminated union of all AST node types. + * + * **Details** + * + * Every `Schema` has an `.ast` property of this type. Use the guard functions + * ({@link isString}, {@link isObjects}, etc.) to narrow to a specific variant, + * then access variant-specific fields. + * + * - All variants share the {@link Base} fields: `annotations`, `checks`, + * `encoding`, `context`. + * - Discriminate on the `_tag` field (e.g. `"String"`, `"Objects"`, `"Union"`). + * + * @see {@link Base} + * @see {@link isAST} + * @category models + * @since 3.10.0 + */ +export type AST = + | Declaration + | Null + | Undefined + | Void + | Never + | Unknown + | Any + | String + | Number + | Boolean + | BigInt + | Symbol + | Literal + | UniqueSymbol + | ObjectKeyword + | Enum + | TemplateLiteral + | Arrays + | Objects + | Union + | Suspend + +function makeGuard(tag: T) { + return (ast: AST): ast is Extract => ast._tag === tag +} + +/** + * Returns `true` if the value is an {@link AST} node (any variant). + * + * **Details** + * + * Uses the internal `TypeId` brand to distinguish AST nodes from arbitrary + * objects. + * + * @see {@link AST} + * @category guards + * @since 4.0.0 + */ +export function isAST(u: unknown): u is AST { + return Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId +} + +/** + * Narrows an {@link AST} to {@link Declaration}. + * + * **When to use** + * + * Use to recognize declaration AST nodes before running declaration-specific + * handling. + * + * @see {@link Declaration} for the AST node type narrowed by this guard + * + * @category guards + * @since 3.10.0 + */ +export const isDeclaration = makeGuard("Declaration") + +/** + * Narrows an {@link AST} to {@link Null}. + * + * **When to use** + * + * Use to recognize an AST node that represents exactly the `null` literal when + * inspecting, traversing, or transforming schema ASTs. + * + * @see {@link Null} for the AST node type narrowed by this guard + * @see {@link null_ null} for the singleton `Null` AST instance + * @see {@link isLiteral} for exact primitive literal AST nodes + * + * @category guards + * @since 4.0.0 + */ +export const isNull = makeGuard("Null") + +/** + * Narrows an {@link AST} to {@link Undefined}. + * + * **When to use** + * + * Use to identify AST nodes that represent exactly the JavaScript `undefined` + * value. + * + * @see {@link isVoid} for narrowing AST nodes that represent TypeScript `void` instead of exact `undefined` + * + * @category guards + * @since 4.0.0 + */ +export const isUndefined = makeGuard("Undefined") + +/** + * Narrows an {@link AST} to {@link Void}. + * + * **When to use** + * + * Use to identify AST nodes that represent the TypeScript `void` type before + * handling `Void`-specific schema behavior. + * + * @see {@link isUndefined} for narrowing AST nodes that represent the literal `undefined` value instead of TypeScript `void` + * + * @category guards + * @since 4.0.0 + */ +export const isVoid = makeGuard("Void") + +/** + * Narrows an {@link AST} to {@link Never}. + * + * **When to use** + * + * Use to detect the AST node for a schema that can never match before handling + * other schema variants. + * + * @see {@link Never} for the AST node type narrowed by this guard + * @see {@link never} for the singleton `Never` AST instance + * + * @category guards + * @since 4.0.0 + */ +export const isNever = makeGuard("Never") + +/** + * Narrows an {@link AST} to {@link Unknown}. + * + * **When to use** + * + * Use when you need to inspect a schema AST and handle the `Unknown` node + * variant specifically. + * + * @see {@link isAny} for the guard for the `Any` node, whose parsed result is typed as `any` rather than `unknown` + * + * @category guards + * @since 4.0.0 + */ +export const isUnknown = makeGuard("Unknown") + +/** + * Narrows an {@link AST} to {@link Any}. + * + * **When to use** + * + * Use when you need to inspect a schema AST and handle the `Any` node + * variant specifically. + * + * @see {@link isUnknown} for the guard for the `Unknown` node, whose parsed result is typed as `unknown` rather than `any` + * + * @category guards + * @since 4.0.0 + */ +export const isAny = makeGuard("Any") + +/** + * Narrows an {@link AST} to {@link String}. + * + * **When to use** + * + * Use to detect schema AST nodes that match any string value while inspecting + * or transforming a Schema AST. + * + * @see {@link String} for the AST node class narrowed by this guard + * @see {@link string} for the singleton `String` AST instance + * @see {@link isLiteral} for exact primitive literal AST nodes, including exact string literals + * + * @category guards + * @since 4.0.0 + */ +export const isString = makeGuard("String") + +/** + * Narrows an {@link AST} to {@link Number}. + * + * **When to use** + * + * Use to detect `Number` AST nodes while inspecting, traversing, or transforming + * schema ASTs. + * + * @category guards + * @since 4.0.0 + */ +export const isNumber = makeGuard("Number") + +/** + * Narrows an {@link AST} to {@link Boolean}. + * + * **When to use** + * + * Use to identify the `Boolean` AST variant while inspecting, traversing, or + * transforming schema definitions. + * + * @see {@link Boolean} for the AST node type matched by this guard + * @see {@link boolean} for the singleton instance to use when constructing a boolean AST directly + * + * @category guards + * @since 4.0.0 + */ +export const isBoolean = makeGuard("Boolean") + +/** + * Narrows an {@link AST} to {@link BigInt}. + * + * **When to use** + * + * Use to identify bigint AST nodes while inspecting or transforming schema ASTs. + * + * @see {@link BigInt} for the AST node matched by this guard + * @see {@link bigInt} for the singleton instance; use `isBigInt` when narrowing an existing `AST` value + * + * @category guards + * @since 4.0.0 + */ +export const isBigInt = makeGuard("BigInt") + +/** + * Narrows an {@link AST} to {@link Symbol}. + * + * **When to use** + * + * Use to narrow an `AST` node before handling the `Symbol` variant for schemas + * that accept any JavaScript symbol value. + * + * @see {@link isUniqueSymbol} for the sibling guard that narrows the `UniqueSymbol` variant for one exact symbol value + * + * @category guards + * @since 4.0.0 + */ +export const isSymbol = makeGuard("Symbol") + +/** + * Narrows an {@link AST} to {@link Literal}. + * + * **When to use** + * + * Use to recognize exact string, number, boolean, or bigint literal AST nodes. + * + * @see {@link Literal} for the AST node type narrowed by this guard + * @see {@link LiteralValue} for the values stored by literal nodes + * + * @category guards + * @since 3.10.0 + */ +export const isLiteral = makeGuard("Literal") + +/** + * Narrows an {@link AST} to {@link UniqueSymbol}. + * + * @category guards + * @since 3.10.0 + */ +export const isUniqueSymbol = makeGuard("UniqueSymbol") + +/** + * Narrows an {@link AST} to {@link ObjectKeyword}. + * + * **When to use** + * + * Use to identify the AST node for the TypeScript `object` keyword when + * inspecting or transforming a Schema AST. + * + * @see {@link ObjectKeyword} for the AST node matched by this guard + * @see {@link objectKeyword} for the singleton `ObjectKeyword` AST instance + * @see {@link isObjects} for struct and record AST nodes + * + * @category guards + * @since 3.10.0 + */ +export const isObjectKeyword = makeGuard("ObjectKeyword") + +/** + * Narrows an {@link AST} to {@link Enum}. + * + * **When to use** + * + * Use to recognize enum AST nodes before reading enum cases or running + * enum-specific handling. + * + * @see {@link Enum} for the AST node type narrowed by this guard + * + * @category guards + * @since 4.0.0 + */ +export const isEnum = makeGuard("Enum") + +/** + * Narrows an {@link AST} to {@link TemplateLiteral}. + * + * @category guards + * @since 3.10.0 + */ +export const isTemplateLiteral = makeGuard("TemplateLiteral") + +/** + * Narrows an {@link AST} to {@link Arrays}. + * + * **When to use** + * + * Use to recognize array-like AST nodes before reading their element, rest, or + * mutability metadata. + * + * @see {@link Arrays} for the AST node type narrowed by this guard + * + * @category guards + * @since 4.0.0 + */ +export const isArrays = makeGuard("Arrays") + +/** + * Narrows an {@link AST} to {@link Objects}. + * + * @category guards + * @since 4.0.0 + */ +export const isObjects = makeGuard("Objects") + +/** + * Narrows an {@link AST} to {@link Union}. + * + * @category guards + * @since 3.10.0 + */ +export const isUnion = makeGuard("Union") + +/** + * Narrows an {@link AST} to {@link Suspend}. + * + * @category guards + * @since 3.10.0 + */ +export const isSuspend = makeGuard("Suspend") + +/** + * Represents a single step in an {@link Encoding} chain. + * + * **Details** + * + * A link pairs a target {@link AST} with a `Transformation` or `Middleware` + * that converts values between the current node and the target. + * + * - `to` — the AST node on the other side of this transformation step. + * - `transformation` — the bidirectional conversion logic (decode/encode). + * + * Links are composed into a non-empty array ({@link Encoding}) attached to + * AST nodes that have a different encoded representation. + * + * @see {@link Encoding} + * @see {@link decodeTo} + * @category models + * @since 4.0.0 + */ +export class Link { + readonly to: AST + readonly transformation: + | SchemaTransformation.Transformation + | SchemaTransformation.Middleware + + constructor( + to: AST, + transformation: + | SchemaTransformation.Transformation + | SchemaTransformation.Middleware + ) { + this.to = to + this.transformation = transformation + } +} + +/** + * A non-empty chain of {@link Link} values representing the transformation + * steps between a schema's decoded (type) form and its encoded (wire) form. + * + * **Details** + * + * Stored on {@link Base.encoding}. When `undefined`, the node has no + * encoding transformation (type and encoded forms are identical). + * + * @see {@link Link} + * @see {@link toEncoded} + * @category models + * @since 4.0.0 + */ +export type Encoding = readonly [Link, ...Array] + +/** + * Options that control schema parsing, validation, transformation, and output behavior. + * + * **Details** + * + * Pass to `Schema.decodeUnknown`, `Schema.encode`, and related APIs to customize + * error reporting, excess property handling, output key ordering, check + * execution, and asynchronous parser concurrency. + * + * - `errors` — `"first"` (default) stops at the first error; `"all"` collects + * every error. + * - `onExcessProperty` — `"ignore"` (default) strips unknown object keys; + * `"error"` fails; `"preserve"` keeps them. + * - `propertyOrder` — `"none"` (default) lets the system choose key order; + * `"original"` preserves input key order. + * - `disableChecks` — skips validation checks while still applying defaults and + * transformations. + * - `concurrency` — maximum number of async parse effects to run concurrently; + * defaults to `1`, or use `"unbounded"`. + * - `reportInput` — includes rejected input values in value-bearing schema + * issues. + * + * @category options + * @since 3.10.0 + */ +export interface ParseOptions { + /** + * Controls how many parsing errors are reported. + * + * **Details** + * + * The default, `"first"`, stops at the first error. Set the option to `"all"` + * to collect every parsing error, which can help with debugging or with + * presenting more complete error messages to a user. + * + * @default "first" + */ + readonly errors?: "first" | "all" | undefined + + /** + * Controls how object parsing handles keys that are not declared by the schema. + * + * **Details** + * + * The default, `"ignore"`, strips unspecified properties from the output. Use + * `"error"` to fail when an excess property is present, or `"preserve"` to + * keep excess properties in the output. + * + * @default "ignore" + */ + readonly onExcessProperty?: "ignore" | "error" | "preserve" | undefined + + /** + * The `propertyOrder` option provides control over the order of object fields + * in the output. This feature is useful when the sequence of keys is + * important for the consuming processes or when maintaining the input order + * enhances readability and usability. + * + * **Details** + * + * By default, the `propertyOrder` option is set to `"none"`. This means that + * the internal system decides the order of keys to optimize parsing speed. + * + * Setting `propertyOrder` to `"original"` ensures that the keys are ordered + * as they appear in the input during the decoding/encoding process. + * + * **Gotchas** + * + * The key order for `"none"` should not be considered stable and may change + * in future updates without notice. + * + * @default "none" + */ + readonly propertyOrder?: "none" | "original" | undefined + + /** + * Whether to disable checks while still applying defaults and + * transformations. + */ + readonly disableChecks?: boolean | undefined + + /** + * The maximum number of async effects to run concurrently. + * + * @default 1 + */ + readonly concurrency?: number | "unbounded" | undefined + + /** + * Whether schema issues should retain and report rejected input values. + * + * **Details** + * + * When enabled, value-bearing issues created by the parser expose an `input` + * field. Built-in formatters may include reported input in default messages. + * The input is retained by reference rather than copied. + * + * **Gotchas** + * + * Enabling this option can retain or disclose secrets, personally + * identifiable information, and large object graphs. The `input` field is + * enumerable and may be included by object enumeration, spread, or + * serialization. Disabling it on a nested schema does not redact that value + * from an ancestor issue whose input reporting remains enabled. Issues + * returned directly by user-defined declarations, checks, transformations, + * and middleware are not modified; their authors decide whether to retain an + * input. To respect this option, pass the callback's input and parse options + * directly to a value-bearing issue constructor. Custom messages and + * annotations remain the caller's responsibility regardless of this option. + * Formatting an issue with `SchemaIssue.makeFormatterDefault()`, reading + * `SchemaError.message`, or formatting a Standard Schema failure can disclose + * retained input. + * + * @default false + */ + readonly reportInput?: boolean | undefined +} + +/** @internal */ +export const defaultParseOptions: ParseOptions = {} + +/** + * Represents per-property metadata attached to AST nodes via {@link Base.context}. + * + * **Details** + * + * Tracks whether a property key is optional, mutable, has a constructor + * default, or carries key-level annotations. Typically set by helpers like + * {@link optionalKey} and `Schema.mutableKey`. + * + * - `isOptional` — the property key may be absent from the input. + * - `isMutable` — the property is `readonly` when `false`. + * - `constructorDefault` — a {@link Link} applied during construction to + * supply missing values. + * - `annotations` — key-level annotations (e.g. description of the key + * itself). + * + * @see {@link optionalKey} + * @see {@link isOptional} + * @category models + * @since 4.0.0 + */ +export class Context { + readonly isOptional: boolean + readonly isMutable: boolean + /** Used for constructor default values (e.g. `withConstructorDefault` API) */ + readonly constructorDefault: Link | undefined + readonly annotations: Schema.Annotations.Key | undefined + + constructor( + isOptional: boolean, + isMutable: boolean, + /** Used for constructor default values (e.g. `withConstructorDefault` API) */ + constructorDefault: Link | undefined = undefined, + annotations: Schema.Annotations.Key | undefined = undefined + ) { + this.isOptional = isOptional + this.isMutable = isMutable + this.constructorDefault = constructorDefault + this.annotations = annotations + } +} + +/** + * Non-empty array of validation {@link Check} values attached to an AST node + * via {@link Base.checks}. + * + * **Details** + * + * Checks are run after basic type matching succeeds. They represent + * refinements like `minLength`, `pattern`, `int`, etc. + * + * @see {@link Check} + * @see {@link Filter} + * @see {@link FilterGroup} + * @category models + * @since 4.0.0 + */ +export type Checks = readonly [Check, ...Array>] + +const TypeId = "~effect/Schema" + +/** + * Represents the abstract base class for all {@link AST} node variants. + * + * **Details** + * + * Every AST node extends `Base` and inherits these fields: + * + * - `annotations` — user-supplied metadata (identifier, title, description, + * arbitrary keys). + * - `checks` — optional {@link Checks} for post-type-match validation. + * - `encoding` — optional {@link Encoding} chain for type ↔ wire + * transformations. + * - `context` — optional {@link Context} for per-property metadata. + * + * Subclasses add a `_tag` discriminant and variant-specific data. + * + * @see {@link AST} + * @category models + * @since 4.0.0 + */ +export abstract class Base { + readonly [TypeId] = TypeId + abstract readonly _tag: string + readonly annotations: Schema.Annotations.Annotations | undefined + readonly checks: Checks | undefined + readonly encoding: Encoding | undefined + readonly context: Context | undefined + + constructor( + annotations: Schema.Annotations.Annotations | undefined = undefined, + checks: Checks | undefined = undefined, + encoding: Encoding | undefined = undefined, + context: Context | undefined = undefined + ) { + this.annotations = annotations + this.checks = checks + this.encoding = encoding + this.context = context + } + toString() { + return `<${this._tag}>` + } +} + +/** + * Parser factory carried by a {@link Declaration}. + * + * @category models + * @since 4.0.0 + */ +export type DeclarationRun = ( + typeParameters: ReadonlyArray +) => (input: unknown, self: Declaration, options: ParseOptions) => Effect.Effect + +/** + * AST node for user-defined opaque types with custom parsing logic. + * + * **When to use** + * + * Use when you need a custom schema AST node because none of the built-in + * nodes fit. + * + * **Details** + * + * - `typeParameters` — inner schemas this declaration is parameterized over + * (e.g. the element type for a custom collection). + * - `run` — factory that receives `typeParameters` and returns a parser that + * validates or transforms raw input. + * + * @see {@link isDeclaration} + * @category models + * @since 3.10.0 + */ +export class Declaration extends Base { + readonly _tag = "Declaration" + readonly typeParameters: ReadonlyArray + readonly run: DeclarationRun + readonly encodingChecks: Checks | undefined + /** + * Parser factory {@link flip} swaps in, so a declaration can behave + * differently when encoding. `undefined` reuses {@link run}. + */ + readonly encodingRun: DeclarationRun | undefined + + constructor( + typeParameters: ReadonlyArray, + run: DeclarationRun, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context, + encodingChecks?: Checks, + encodingRun?: DeclarationRun + ) { + super(annotations, checks, encoding, context) + this.typeParameters = typeParameters + this.run = run + this.encodingChecks = encodingChecks + this.encodingRun = encodingRun + } + /** @internal */ + getParser(): SchemaParser.Parser { + let run: ReturnType + return (input, options) => { + if (input === InternalParser.missing) return InternalParser.missingExit + return (run ??= this.run(this.typeParameters))(input, this, options) + } + } + private _rebuild( + recur: (ast: AST) => AST, + checks: Checks | undefined, + encodingChecks: Checks | undefined, + run: DeclarationRun, + encodingRun: DeclarationRun | undefined + ) { + const tps = mapOrSame(this.typeParameters, recur) + return tps === this.typeParameters && checks === this.checks && encodingChecks === this.encodingChecks && + run === this.run && encodingRun === this.encodingRun ? + this : + new Declaration(tps, run, this.annotations, checks, undefined, this.context, encodingChecks, encodingRun) + } + /** @internal */ + recur(recur: (ast: AST) => AST) { + return this._rebuild(recur, this.checks, this.encodingChecks, this.run, this.encodingRun) + } + /** @internal */ + flip(recur: (ast: AST) => AST) { + return this._rebuild(recur, this.encodingChecks, this.checks, this.encodingRun ?? this.run, this.run) + } + /** @internal */ + getExpected(): string { + const expected = this.annotations?.expected + if (typeof expected === "string") return expected + return "" + } +} + +/** + * AST node matching the `null` literal value. + * + * **Details** + * + * Parsing succeeds only when the input is exactly `null`. + * + * @see {@link null_ null} + * @see {@link isNull} + * @category models + * @since 4.0.0 + */ +export class Null extends Base { + readonly _tag = "Null" + /** @internal */ + getParser() { + return fromConst(this, null) + } + /** @internal */ + getExpected(): string { + return "null" + } +} + +const null_ = new Null() +export { + /** + * Provides the singleton {@link Null} AST instance. + * + * **When to use** + * + * Use when you need the shared AST node for exact null values while inspecting + * or constructing schema ASTs. + * + * @category constants + * @since 4.0.0 + */ + null_ as null +} + +/** + * AST node matching the `undefined` value. + * + * **Details** + * + * Parsing succeeds only when the input is exactly `undefined`. + * + * @see {@link undefined} + * @see {@link isUndefined} + * @category models + * @since 4.0.0 + */ +export class Undefined extends Base { + readonly _tag = "Undefined" + /** @internal */ + getParser() { + return fromConst(this, undefined) + } + /** @internal */ + toCodecJson(): AST { + return replaceEncoding(this, [undefinedToNull]) + } + /** @internal */ + getExpected(): string { + return "undefined" + } +} + +const undefinedToNull = new Link( + null_, + new SchemaTransformation.Transformation( + SchemaGetter.transform(() => undefined), + SchemaGetter.transform(() => null) + ) +) + +const undefined_ = new Undefined() +export { + /** + * Provides the singleton {@link Undefined} AST instance. + * + * **When to use** + * + * Use when you need the shared AST node for exact undefined values while + * inspecting or constructing schema ASTs. + * + * @category constants + * @since 4.0.0 + */ + undefined_ as undefined +} + +/** + * AST node matching TypeScript `void` return-value semantics. + * + * **When to use** + * + * Use when you need an AST node for a value whose result is intentionally + * ignored. + * + * **Details** + * + * Parsers built from this node accept any present runtime input and map it to + * `undefined`. Public schemas built from it may still expose `void` as their + * typed decoded and encoded representation. + * + * @see {@link undefined} for the AST singleton that matches only exact `undefined` + * @see {@link void_ void} + * @see {@link isVoid} + * @category models + * @since 4.0.0 + */ +export class Void extends Base { + readonly _tag = "Void" + /** @internal */ + getParser() { + const succeed = InternalParser.succeed(undefined) + return (input: unknown) => input === InternalParser.missing ? InternalParser.missingExit : succeed + } + /** @internal */ + toCodecJson(): AST { + return replaceEncoding(this, [undefinedToNull]) + } + /** @internal */ + getExpected(): string { + return "void" + } +} + +const void_ = new Void() +export { + /** + * Provides the singleton {@link Void} AST instance. + * + * **When to use** + * + * Use when constructing or comparing AST nodes for TypeScript `void` return + * values whose result is intentionally ignored. + * + * **Details** + * + * The node parses any present runtime value as `undefined`; schemas may still + * expose `void` on their typed decoded and encoded sides. + * + * @see {@link Void} for the AST node class + * @see {@link undefined} for the sibling AST singleton that matches exactly `undefined` + * @see {@link isVoid} for narrowing an AST to a `Void` node + * + * @category constructors + * @since 4.0.0 + */ + void_ as void +} + +/** + * AST node representing the `never` type — no value matches. + * + * **Details** + * + * Parsing always fails. Useful as a placeholder in unions or as the result + * of narrowing that eliminates all options. + * + * @see {@link never} + * @see {@link isNever} + * @category models + * @since 4.0.0 + */ +export class Never extends Base { + readonly _tag = "Never" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isNever) + } + /** @internal */ + getExpected(): string { + return "never" + } +} + +/** + * Provides the singleton {@link Never} AST instance. + * + * **When to use** + * + * Use to reuse the canonical bottom-type AST node when constructing, + * comparing, or returning ASTs. + * + * @see {@link Never} for the AST node class + * @see {@link isNever} for narrowing an AST to a `Never` node + * + * @category constructors + * @since 4.0.0 + */ +export const never = new Never() + +/** + * AST node representing the `any` type — every value matches. + * + * @see {@link any} + * @see {@link isAny} + * + * @category models + * @since 4.0.0 + */ +export class Any extends Base { + readonly _tag = "Any" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isUnknown) + } + /** @internal */ + getExpected(): string { + return "any" + } +} + +/** + * Provides the singleton {@link Any} AST instance. + * + * **When to use** + * + * Use when you need the singleton AST node for the TypeScript `any` type and + * intentionally want parsing to accept every input value. + * + * @see {@link unknown} for the sibling AST singleton that also accepts every value while preserving the safer `unknown` type + * + * @category constructors + * @since 4.0.0 + */ +export const any = new Any() + +/** + * AST node representing the `unknown` type — every value matches. + * + * **Details** + * + * Unlike {@link Any}, this is type-safe: the parsed result is typed as + * `unknown` rather than `any`. + * + * @see {@link unknown} + * @see {@link isUnknown} + * @category models + * @since 4.0.0 + */ +export class Unknown extends Base { + readonly _tag = "Unknown" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isUnknown) + } + /** @internal */ + getExpected(): string { + return "unknown" + } +} + +/** + * Provides the singleton {@link Unknown} AST instance. + * + * **When to use** + * + * Use when you need the reusable AST singleton for a schema node that accepts + * every value while keeping parsed values opaque. + * + * @see {@link any} for the singleton that accepts every value as `any` + * + * @category constructors + * @since 4.0.0 + */ +export const unknown = new Unknown() + +/** + * AST node matching the TypeScript `object` type — accepts objects, arrays, + * and functions (anything non-primitive and non-null). + * + * @see {@link objectKeyword} + * @see {@link isObjectKeyword} + * + * @category models + * @since 3.10.0 + */ +export class ObjectKeyword extends Base { + readonly _tag = "ObjectKeyword" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isObjectKeyword) + } + /** @internal */ + getExpected(): string { + return "object | array | function" + } +} + +/** + * Provides the singleton {@link ObjectKeyword} AST instance. + * + * **When to use** + * + * Use to reuse the canonical AST node for the TypeScript `object` keyword when + * building or comparing `SchemaAST` values directly. + * + * @see {@link ObjectKeyword} for the AST node class + * @see {@link isObjectKeyword} for narrowing an AST to an `ObjectKeyword` node + * + * @category constructors + * @since 3.10.0 + */ +export const objectKeyword = new ObjectKeyword() + +/** + * AST node representing a TypeScript `enum`. + * + * **Details** + * + * Holds `enums` as an array of `[name, value]` pairs where values are + * `string | number`. Parsing succeeds when the input matches any enum value. + * + * @see {@link isEnum} + * @category models + * @since 4.0.0 + */ +export class Enum extends Base { + readonly _tag = "Enum" + readonly enums: ReadonlyArray + + constructor( + enums: ReadonlyArray, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context + ) { + super(annotations, checks, encoding, context) + this.enums = enums + } + /** @internal */ + getParser() { + const values = new Set(this.enums.map(([, v]) => v)) + return fromRefinement( + this, + (input): input is typeof this.enums[number][1] => values.has(input) + ) + } + /** @internal */ + toCodecStringTree(): AST { + if (this.enums.some(([_, v]) => typeof v === "number")) { + const coercions = Object.fromEntries(this.enums.map(([_, v]) => [globalThis.String(v), v])) + return replaceEncoding(this, [ + new Link( + new Union(Object.keys(coercions).map((k) => new Literal(k)), "anyOf"), + new SchemaTransformation.Transformation( + SchemaGetter.transform((s) => coercions[s]), + SchemaGetter.String() + ) + ) + ]) + } + return this + } + /** @internal */ + getExpected(): string { + return this.enums.map(([_, value]) => JSON.stringify(value)).join(" | ") + } +} + +type TemplateLiteralPart = + | String + | Number + | BigInt + | Literal + | TemplateLiteral + | Union + +function isTemplateLiteralPart(ast: AST): ast is TemplateLiteralPart { + switch (ast._tag) { + case "String": + case "Number": + case "BigInt": + return true + case "Literal": + case "TemplateLiteral": + return !ast.checks + case "Union": + return !ast.checks && ast.types.every(isTemplateLiteralPart) + default: + return false + } +} + +/** + * AST node representing a TypeScript template literal type + * (e.g. `` `user_${string}` ``). + * + * **Details** + * + * `parts` is an array of AST nodes; each part contributes to matching + * strings at runtime. + * + * @see {@link isTemplateLiteral} + * @category models + * @since 3.10.0 + */ +export class TemplateLiteral extends Base { + readonly _tag = "TemplateLiteral" + readonly parts: ReadonlyArray + /** @internal */ + readonly encodedParts: ReadonlyArray + /** @internal */ + readonly literals: ReadonlyArray + /** @internal */ + readonly suffixLengths: ReadonlyArray + + constructor( + parts: ReadonlyArray, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context + ) { + super(annotations, checks, encoding, context) + const encodedParts: Array = [] + const literals: Array = [] + for (const part of parts) { + const encoded = toEncoded(part) + if (isTemplateLiteralPart(encoded)) { + encodedParts.push(encoded) + literals.push(encoded._tag === "Literal" ? globalThis.String(encoded.literal) : undefined) + } else { + throw new Error(`Invalid TemplateLiteral part ${encoded._tag}`) + } + } + const suffixLengths = new Array(encodedParts.length + 1) + suffixLengths[encodedParts.length] = 0 + for (let i = encodedParts.length - 1; i >= 0; i--) { + suffixLengths[i] = suffixLengths[i + 1] + (literals[i]?.length ?? 0) + } + this.parts = parts + this.encodedParts = encodedParts + this.literals = literals + this.suffixLengths = suffixLengths + } + /** @internal */ + getParser(compile: SchemaParser.Compiler): SchemaParser.Parser { + const parser = compile(this.asTemplateLiteralParser()) + return (input, options) => { + if (input === InternalParser.missing) return InternalParser.missingExit + const result = parser(input, options) + if ((result as Exit.Exit)._tag === "Success") { + return InternalParser.sameExit + } + return Effect.mapBothEager(result, { + onSuccess: () => input, + onFailure: (issue) => new SchemaIssue.Composite(this, [issue], input, options) + }) + } + } + /** @internal */ + getExpected(): string { + return "string" + } + /** @internal */ + matchPart(s: string, options: ParseOptions): string | undefined { + return segmentTemplateLiteralParts(this, s, options) === undefined ? undefined : s + } + /** @internal */ + asTemplateLiteralParser(): Arrays { + const tuple = new Arrays(false, this.parts.map(partFromString), []) + return decodeTo( + string, + tuple, + new SchemaTransformation.Transformation( + SchemaGetter.transformOrFail((s: string, options) => { + const segments = segmentTemplateLiteralParts(this, s, options) + if (segments) return Effect.succeed(segments) + return Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a string matching template literal parts" }, + s, + options + ) + ) + }), + SchemaGetter.transform((parts) => parts.join("")) + ) + ) + } +} + +/** + * AST node matching a specific `unique symbol` value. + * + * **Details** + * + * Parsing succeeds only when the input is reference-equal to the stored + * `symbol`. + * + * @see {@link isUniqueSymbol} + * @category models + * @since 3.10.0 + */ +export class UniqueSymbol extends Base { + readonly _tag = "UniqueSymbol" + readonly symbol: symbol + + constructor( + symbol: symbol, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context + ) { + super(annotations, checks, encoding, context) + this.symbol = symbol + } + /** @internal */ + getParser() { + return fromConst(this, this.symbol) + } + /** @internal */ + toCodecStringTree(): AST { + return replaceEncoding(this, [symbolToString]) + } + /** @internal */ + getExpected(): string { + return globalThis.String(this.symbol) + } +} + +/** + * The set of primitive types that can appear as a {@link Literal} value. + * + * @see {@link Literal} + * + * @category models + * @since 3.10.0 + */ +export type LiteralValue = string | number | boolean | bigint + +/** + * AST node matching an exact primitive value (string, number, boolean, or + * bigint). + * + * **Details** + * + * Parsing succeeds only when the input is strictly equal (`===`) to the + * stored `literal`. Numeric literals must be finite — `Infinity`, `-Infinity`, + * and `NaN` are rejected at construction time. + * + * **Example** (Creating a literal AST) + * + * ```ts import.meta.vitest + * import { SchemaAST } from "effect" + * + * const ast = new SchemaAST.Literal("active") + * ast.literal // => "active" + * ``` + * + * @see {@link LiteralValue} + * @see {@link isLiteral} + * @category models + * @since 3.10.0 + */ +export class Literal extends Base { + readonly _tag = "Literal" + readonly literal: LiteralValue + + constructor( + literal: LiteralValue, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context + ) { + super(annotations, checks, encoding, context) + if (typeof literal === "number" && !globalThis.Number.isFinite(literal)) { + throw new Error(`A numeric literal must be finite, got ${format(literal)}`) + } + this.literal = literal + } + /** @internal */ + getParser() { + return fromConst(this, this.literal) + } + /** @internal */ + matchPart(s: string, _options: ParseOptions): LiteralValue | undefined { + return s === globalThis.String(this.literal) ? this.literal : undefined + } + /** @internal */ + toCodecJson(): AST { + return typeof this.literal === "bigint" ? literalToString(this) : this + } + /** @internal */ + toCodecStringTree(): AST { + return typeof this.literal === "string" ? this : literalToString(this) + } + /** @internal */ + getExpected(): string { + return typeof this.literal === "string" ? JSON.stringify(this.literal) : globalThis.String(this.literal) + } +} + +function literalToString(ast: Literal): Literal { + const literalAsString = globalThis.String(ast.literal) + return replaceEncoding(ast, [ + new Link( + new Literal(literalAsString), + new SchemaTransformation.Transformation( + SchemaGetter.transform(() => ast.literal), + SchemaGetter.transform(() => literalAsString) + ) + ) + ]) +} + +/** + * AST node matching any `string` value. + * + * @see {@link string} + * @see {@link isString} + * + * @category models + * @since 4.0.0 + */ +export class String extends Base { + readonly _tag = "String" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isString) + } + /** @internal */ + matchPart(s: string, options: ParseOptions): string | undefined { + const checks = this.checks + return checks && !options.disableChecks && collectIssues(checks, s, undefined, this, options) ? undefined : s + } + /** @internal */ + getExpected(): string { + return "string" + } +} + +/** + * Provides the singleton {@link String} AST instance. + * + * **When to use** + * + * Use as the shared `SchemaAST` node for unconstrained JavaScript strings. + * + * @see {@link String} for the AST node class + * @see {@link isString} for narrowing an AST to a string node + * + * @category constructors + * @since 4.0.0 + */ +export const string = new String() + +/** + * AST node matching any `number` value (including `NaN`, `Infinity`, + * `-Infinity`). + * + * **Details** + * + * Default JSON serialization: + * + * - Finite numbers are serialized as JSON numbers. + * - `Infinity`, `-Infinity`, and `NaN` are serialized as JSON strings. + * + * If the node has an `isFinite` or `isInt` check, the string fallback is + * skipped since non-finite values cannot occur. + * + * @see {@link number} + * @see {@link isNumber} + * @category models + * @since 4.0.0 + */ +export class Number extends Base { + readonly _tag = "Number" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isNumber) + } + /** @internal */ + matchKey(s: string, options: ParseOptions): number | undefined { + return this._match(isStringNumberRegExp, s, options) + } + /** @internal */ + matchPart(s: string, options: ParseOptions): number | undefined { + return this._match(isStringFiniteRegExp, s, options) + } + private _match(regexp: RegExp, s: string, options: ParseOptions): number | undefined { + if (!regexp.test(s)) return undefined + const value = globalThis.Number(s) + if (options.disableChecks || !this.checks) return value + return collectIssues(this.checks, value, undefined, this, options) ? undefined : value + } + /** @internal */ + toCodecJson(): AST { + if ( + this.checks && + (hasCheck(this.checks, "effect/schema/isFinite") || hasCheck(this.checks, "effect/schema/isInt")) + ) { + return this + } + return replaceEncoding(this, [numberToJson]) + } + /** @internal */ + toCodecStringTree(): AST { + if (this.toCodecJson() === this) { + return replaceEncoding(this, [finiteToString]) + } + return replaceEncoding(this, [numberToString]) + } + /** @internal */ + getExpected(): string { + return "number" + } +} + +function hasCheck(checks: ReadonlyArray>, id: string): boolean { + return checks.some((check) => + check.annotations?.representation?.id === id || + (check._tag === "FilterGroup" && hasCheck(check.checks, id)) + ) +} + +/** + * Provides the singleton {@link Number} AST instance. + * + * **When to use** + * + * Use when you need the canonical `SchemaAST` node for schemas that accept any + * JavaScript number value. + * + * @see {@link Number} for the AST node class and serialization behavior + * @see {@link Literal} for exact finite numeric literal AST nodes + * + * @category constructors + * @since 4.0.0 + */ +export const number = new Number() + +/** + * AST node matching any `boolean` value (`true` or `false`). + * + * @see {@link boolean} + * @see {@link isBoolean} + * + * @category models + * @since 4.0.0 + */ +export class Boolean extends Base { + readonly _tag = "Boolean" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isBoolean) + } + /** @internal */ + getExpected(): string { + return "boolean" + } +} + +/** + * Provides the singleton {@link Boolean} AST instance. + * + * **When to use** + * + * Use to reuse the standard AST node that accepts either `true` or `false` when + * constructing schema ASTs directly. + * + * @see {@link Boolean} for the AST node class + * @see {@link Literal} for exact boolean literal AST nodes + * + * @category constructors + * @since 4.0.0 + */ +export const boolean = new Boolean() + +/** + * AST node matching any `symbol` value. + * + * **When to use** + * + * Use when you need the AST node class for schemas that match any JavaScript + * symbol value. + * + * **Details** + * + * When serialized to a string-based codec, symbols are converted via + * `Symbol.keyFor` and must be registered with `Symbol.for`. + * + * @see {@link symbol} + * @see {@link isSymbol} + * @category models + * @since 4.0.0 + */ +export class Symbol extends Base { + readonly _tag = "Symbol" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isSymbol) + } + /** @internal */ + matchKey(s: symbol, options: ParseOptions): symbol | undefined { + if (options.disableChecks || !this.checks) return s + return collectIssues(this.checks, s, undefined, this, options) ? undefined : s + } + /** @internal */ + toCodecStringTree(): AST { + return replaceEncoding(this, [symbolToString]) + } + /** @internal */ + getExpected(): string { + return "symbol" + } +} + +/** + * Provides the singleton {@link Symbol} AST instance. + * + * **When to use** + * + * Use to reuse the singleton AST node for schemas that match any JavaScript + * symbol value. + * + * **Gotchas** + * + * String-based codecs can encode only symbols registered with `Symbol.for`, + * because the implementation uses `Symbol.keyFor`. + * + * @see {@link UniqueSymbol} for an AST node that matches one specific symbol + * + * @category constructors + * @since 4.0.0 + */ +export const symbol = new Symbol() + +/** + * AST node matching any `bigint` value. + * + * **Details** + * + * When serialized to a string-based codec, bigints are converted to/from + * their decimal string representation. + * + * @see {@link bigInt} + * @see {@link isBigInt} + * @category models + * @since 4.0.0 + */ +export class BigInt extends Base { + readonly _tag = "BigInt" + /** @internal */ + getParser() { + return fromRefinement(this, Predicate.isBigInt) + } + /** @internal */ + matchPart(s: string, options: ParseOptions): bigint | undefined { + if (!isStringBigIntRegExp.test(s)) return undefined + const value = globalThis.BigInt(s) + if (options.disableChecks || !this.checks) return value + return collectIssues(this.checks, value, undefined, this, options) ? undefined : value + } + /** @internal */ + toCodecStringTree(): AST { + return replaceEncoding(this, [bigIntToString]) + } + /** @internal */ + getExpected(): string { + return "bigint" + } +} + +/** + * Provides the singleton {@link BigInt} AST instance. + * + * **When to use** + * + * Use to reuse the canonical `BigInt` AST node when constructing, inspecting, + * or transforming schemas at the AST level. + * + * @see {@link BigInt} for the AST node class and string-codec behavior + * @see {@link isBigInt} for narrowing an AST to a `BigInt` node + * + * @category constructors + * @since 4.0.0 + */ +export const bigInt = new BigInt() + +/** + * AST node for array-like types — both tuples and arrays. + * + * **When to use** + * + * Use when constructing or inspecting AST nodes for tuple or array-like schemas, + * including rest elements. + * + * **Details** + * + * - `elements` — positional element types (tuple elements). An element is + * optional if its {@link Context.isOptional} is `true`. + * - `rest` — the rest/variadic element types. When non-empty, the first + * entry is the "spread" type (e.g. `...Array`), and subsequent + * entries are trailing positional elements after the spread. + * - `isMutable` — whether the resulting array is `readonly` (`false`) or + * mutable (`true`). + * + * **Gotchas** + * + * Construction enforces TypeScript ordering rules: a required element + * cannot follow an optional one, and an optional element cannot follow a + * rest element. + * + * **Example** (Inspecting a tuple AST) + * + * ```ts import.meta.vitest + * import { Schema, SchemaAST } from "effect" + * + * const schema = Schema.Tuple([Schema.String, Schema.Number]) + * const ast = schema.ast + * + * if (SchemaAST.isArrays(ast)) { + * [ast.elements.length, ast.rest.length] // => [2, 0] + * } + * ``` + * + * @see {@link isArrays} + * @see {@link Objects} + * @category models + * @since 4.0.0 + */ +export class Arrays extends Base { + readonly _tag = "Arrays" + readonly isMutable: boolean + readonly elements: ReadonlyArray + readonly rest: ReadonlyArray + readonly encodingChecks: Checks | undefined + + constructor( + isMutable: boolean, + elements: ReadonlyArray, + rest: ReadonlyArray, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context, + encodingChecks?: Checks + ) { + super(annotations, checks, encoding, context) + this.isMutable = isMutable + this.elements = elements + this.rest = rest + this.encodingChecks = encodingChecks + + let hasOptional = false + for (let i = 0; i < elements.length; i++) { + if (isOptional(elements[i])) { + hasOptional = true + } else if (hasOptional) { + throw new Error("A required element cannot follow an optional element. ts(1257)") + } + } + if (hasOptional && rest.length > 1) { + throw new Error("A required element cannot follow an optional element. ts(1257)") + } + + // An optional element cannot follow a rest element.ts(1266) + for (let i = 1; i < rest.length; i++) { + if (isOptional(rest[i])) { + throw new Error("An optional element cannot follow a rest element. ts(1266)") + } + } + } + /** @internal */ + getParser( + compile: SchemaParser.Compiler, + compileConstructorDefault: SchemaParser.Compiler = compile + ): SchemaParser.Parser { + // oxlint-disable-next-line @typescript-eslint/no-this-alias + const ast = this + type ElementParser = { readonly ast: AST; readonly parser: SchemaParser.Parser } + let elements: Array | undefined + let rest: Array | undefined + const elementLen = ast.elements.length + const tailLen = Math.max(0, ast.rest.length - 1) + + function getParser( + tailThreshold: number, + index: number + ): { readonly ast: AST; readonly parser: SchemaParser.Parser } { + if (index < elementLen) { + return elements![index] + } else if (index >= tailThreshold) { + return rest![index - tailThreshold + 1] + } + return rest![0] + } + + return Effect.fnUntracedEager(function*(input, options) { + if (input === InternalParser.missing) { + return InternalParser.missing + } + + // If the input is not an array, return early with an error + if (!Array.isArray(input)) { + return yield* Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + if (!elements) { + elements = ast.elements.map((ast) => ({ ast, parser: compileConstructorDefault(ast) })) + rest = ast.rest.map((ast) => ({ ast, parser: compileConstructorDefault(ast) })) + } + + const len = input.length + const state = { + ast, + getParser, + input, + len, + tailThreshold: Math.max(elementLen, len - tailLen), + output: new globalThis.Array(len), + issues: undefined as Arr.NonEmptyArray | undefined, + options + } + const concurrency = resolveConcurrency(options?.concurrency) + const eff = parseArray(state, input, { + concurrency: concurrency?.concurrency, + end: ast.rest.length === 0 ? elementLen : Math.max(len, elementLen + tailLen) + }) + if (eff) yield* eff + + // --------------------------------------------- + // handle excess indexes + // --------------------------------------------- + if (ast.rest.length === 0 && len > elementLen) { + for (let i = elementLen; i <= len - 1; i++) { + const unexpected = new SchemaIssue.UnexpectedKey(ast, input[i], options) + const issue = new SchemaIssue.Pointer([i], unexpected) + if (options.errors === "all") { + if (state.issues) state.issues.push(issue) + else state.issues = [issue] + } else { + return yield* Effect.fail( + new SchemaIssue.Composite(ast, [issue], input, options) + ) + } + } + } + if (state.issues) { + return yield* Effect.fail( + new SchemaIssue.Composite(ast, state.issues, input, options) + ) + } + return state.output + }) + } + private _rebuild(recur: (ast: AST) => AST, checks: Checks | undefined, encodingChecks: Checks | undefined) { + const elements = mapOrSame(this.elements, recur) + const rest = mapOrSame(this.rest, recur) + return elements === this.elements && rest === this.rest && checks === this.checks && + encodingChecks === this.encodingChecks ? + this : + new Arrays( + this.isMutable, + elements, + rest, + this.annotations, + checks, + undefined, + this.context, + encodingChecks + ) + } + /** @internal */ + recur(recur: (ast: AST) => AST) { + return this._rebuild(recur, this.checks, this.encodingChecks) + } + /** @internal */ + flip(recur: (ast: AST) => AST) { + return this._rebuild(recur, this.encodingChecks, this.checks) + } + /** @internal */ + getExpected(): string { + return "array" + } +} +const parseArray = iterateEager<{ + readonly ast: AST + readonly input: unknown + readonly len: number + readonly getParser: ( + tailThreshold: number, + index: number + ) => { readonly ast: AST; readonly parser: SchemaParser.Parser } + readonly tailThreshold: number + readonly options: ParseOptions + readonly output: Array + issues: Array | undefined +}, unknown>()({ + onItem(s, item, i) { + const value = i < s.len ? item : InternalParser.missing + return s.getParser(s.tailThreshold, i).parser(value, s.options) + }, + step(s, item, exit, i) { + if (exit._tag === "Failure") { + return wrapPropertyKeyIssue(s, s.ast, i, exit) + } + const value = exit === InternalParser.sameExit + ? item + : (exit as InternalParser.Success)[InternalParser.args] + if (value !== InternalParser.missing) { + s.output[i] = value + } else { + const p = s.getParser(s.tailThreshold, i) + if (isOptional(p.ast)) return + const issue = new SchemaIssue.Pointer([i], new SchemaIssue.MissingKey(p.ast.context?.annotations)) + if (s.options.errors === "all") { + if (s.issues) s.issues.push(issue) + else s.issues = [issue] + } else { + return Exit.fail( + new SchemaIssue.Composite(s.ast, [issue], s.input, s.options) + ) + } + } + } +}) + +const resolveConcurrency = (value: number | "unbounded" | undefined) => { + value = value === "unbounded" ? Infinity : value ?? 1 + return value > 1 ? { concurrency: value } : undefined +} + +const wrapPropertyKeyIssue = ( + s: { + readonly input: unknown + readonly options: ParseOptions + issues: Array | undefined + }, + ast: AST, + key: PropertyKey, + exit: Exit.Failure +) => { + if (exit.cause.reasons.length === 0) { + return exit + } + const issue = InternalSchemaCause.getSchemaIssue(exit.cause) + if (issue === undefined) { + return Exit.failCause( + Cause.map( + exit.cause, + (issue) => + new SchemaIssue.Composite( + ast, + [new SchemaIssue.Pointer([key], issue)], + s.input, + s.options + ) + ) + ) + } + const pointer = new SchemaIssue.Pointer([key], issue) + if (s.options.errors === "all") { + if (s.issues) s.issues.push(pointer) + else s.issues = [pointer] + } else { + return Exit.fail( + new SchemaIssue.Composite(ast, [pointer], s.input, s.options) + ) + } +} + +/** + * floating point or integer, with optional exponent + * @internal + */ +export const FINITE_PATTERN = "[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?" + +/** + * Returns the object keys that match the index signature parameter schema. + * @internal + */ +export function getIndexSignatureKeys( + input: { readonly [x: PropertyKey]: unknown }, + parameter: IndexSignatureParameter, + options: ParseOptions = defaultParseOptions +): ReadonlyArray { + let stringKeys: ReadonlyArray | undefined + let symbolKeys: ReadonlyArray | undefined + + function go(parameter: AST): ReadonlyArray { + switch (parameter._tag) { + case "String": + case "TemplateLiteral": + return (stringKeys ??= Object.keys(input)).filter((k) => parameter.matchPart(k, options) !== undefined) + case "Number": + return (stringKeys ??= Object.keys(input)).filter((k) => parameter.matchKey(k, options) !== undefined) + case "Symbol": + return (symbolKeys ??= Object.getOwnPropertySymbols(input)).filter((k) => + parameter.matchKey(k, options) !== undefined + ) + case "Union": + return [...new Set(parameter.types.flatMap(go))] + default: + return [] + } + } + return go(parameterFromPropertyKey(toEncoded(parameter))) +} + +/** + * Represents a named property within an {@link Objects} node. + * + * **Details** + * + * Pairs a `name` (any `PropertyKey`) with a `type` ({@link AST}). The + * property's optionality and mutability are determined by the `type`'s + * {@link Context}. + * + * @see {@link Objects} + * @category models + * @since 3.10.0 + */ +export class PropertySignature { + readonly name: PropertyKey + readonly type: AST + + constructor( + name: PropertyKey, + type: AST + ) { + this.name = name + this.type = type + } +} + +type IndexSignatureParameter = + | String + | Number + | Symbol + | TemplateLiteral + | Union + +function isIndexSignatureParameterSide(ast: AST): ast is IndexSignatureParameter { + switch (ast._tag) { + case "String": + case "Number": + case "Symbol": + case "TemplateLiteral": + return true + case "Union": + return ast.types.every(isIndexSignatureParameterSide) + default: + return false + } +} + +function isIndexSignatureParameter(ast: AST): ast is IndexSignatureParameter { + return isIndexSignatureParameterSide(ast) && isIndexSignatureParameterSide(toEncoded(ast)) +} + +/** + * Represents an index signature entry within an {@link Objects} node. + * + * **When to use** + * + * Use when constructing or inspecting object AST entries for record-like keys + * and values. + * + * **Details** + * + * - `parameter` — the key type AST (e.g. {@link String} for `string` keys, + * {@link TemplateLiteral} for patterned keys). + * - `type` — the value type SchemaAST. + * + * **Gotchas** + * + * Using `Schema.optionalKey` on the value type is not allowed for index + * signatures (throws at construction); use `Schema.optional` instead. + * + * @see {@link Objects} + * @see {@link PropertySignature} + * @category models + * @since 3.10.0 + */ +export class IndexSignature { + readonly parameter: IndexSignatureParameter + readonly type: AST + + constructor( + parameter: AST, + type: AST + ) { + if (!isIndexSignatureParameter(parameter)) { + throw new Error(`Invalid index signature parameter ${parameter._tag}`) + } + this.parameter = parameter + this.type = type + if (isOptional(type) && !containsUndefined(type)) { + throw new Error("Cannot use `Schema.optionalKey` with index signatures, use `Schema.optional` instead.") + } + } +} + +/** + * AST node for object-like schemas, including structs and records. + * + * **When to use** + * + * Use when constructing or inspecting AST nodes for structs or records rather + * than array-like schemas. + * + * **Details** + * + * - `propertySignatures` — named properties with their types (struct fields). + * - `indexSignatures` — index signature entries (record patterns), each with + * a `parameter` AST for matching keys and a `type` AST for values. + * + * An `Objects` node with no properties and no index signatures performs only a + * non-nullish check: it accepts any value except `null` and `undefined`, + * including primitive values. + * + * **Gotchas** + * + * Duplicate property names throw at construction time. + * + * **Example** (Inspecting a struct AST) + * + * ```ts import.meta.vitest + * import { Schema, SchemaAST } from "effect" + * + * const schema = Schema.Struct({ name: Schema.String }) + * const ast = schema.ast + * + * if (SchemaAST.isObjects(ast)) { + * ast.propertySignatures.map((ps) => [ps.name, ps.type._tag]) // => [["name", "String"]] + * } + * ``` + * + * @see {@link isObjects} + * @see {@link PropertySignature} + * @see {@link IndexSignature} + * @see {@link Arrays} + * @category models + * @since 4.0.0 + */ +export class Objects extends Base { + readonly _tag = "Objects" + readonly propertySignatures: ReadonlyArray + readonly indexSignatures: ReadonlyArray + readonly encodingChecks: Checks | undefined + + constructor( + propertySignatures: ReadonlyArray, + indexSignatures: ReadonlyArray, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context, + encodingChecks?: Checks + ) { + super(annotations, checks, encoding, context) + this.propertySignatures = propertySignatures + this.indexSignatures = indexSignatures + this.encodingChecks = encodingChecks + + // Duplicate property signatures + const duplicates = propertySignatures.map((ps) => ps.name).filter((name, i, arr) => arr.indexOf(name) !== i) + if (duplicates.length > 0) { + throw new Error(`Duplicate identifiers: ${JSON.stringify(duplicates)}. ts(2300)`) + } + } + /** @internal */ + getParser( + compile: SchemaParser.Compiler, + compileConstructorDefault: SchemaParser.Compiler = compile + ): SchemaParser.Parser { + // oxlint-disable-next-line @typescript-eslint/no-this-alias + const ast = this + const expectedKeys: Array = [] + for (const ps of ast.propertySignatures) { + expectedKeys.push(ps.name) + } + const hasProperties = expectedKeys.length + const indexCount = ast.indexSignatures.length + let expectedKeysSet = hasProperties && indexCount ? new Set(expectedKeys) : undefined + // --------------------------------------------- + // handle empty struct + // --------------------------------------------- + if (!hasProperties && !indexCount) { + return fromRefinement(ast, Predicate.isNotNullish) + } + + let properties: Array | undefined + let indexes: + | Array<{ + readonly is: IndexSignature + readonly parserKey: SchemaParser.Parser + readonly parserValue: SchemaParser.Parser + }> + | undefined + type Index = NonNullable[number] + const finishIndex = ( + s: ObjectParserState, + key: PropertyKey, + k2: PropertyKey | typeof InternalParser.missing, + inputValue: unknown, + exitValue: Exit.Exit + ): Effect.Effect => { + if (exitValue._tag === "Failure") { + return wrapPropertyKeyIssue(s, ast, key, exitValue) ?? Exit.void + } + const value = exitValue === InternalParser.sameExit + ? inputValue + : (exitValue as InternalParser.Success)[InternalParser.args] + if (k2 !== InternalParser.missing && value !== InternalParser.missing) { + if (hasProperties && (expectedKeysSet!.has(key) || expectedKeysSet!.has(k2))) return Exit.void + InternalRecord.assignProperty(s.out, k2, value) + } + return Exit.void + } + const parseIndex = ( + s: ObjectParserState, + key: PropertyKey, + index: Index, + exitKey?: Exit.Exit + ): Effect.Effect => { + if (!exitKey) { + const eff = index.parserKey(key, s.options) + if (!effectIsExit(eff)) { + return Effect.flatMap(Effect.exit(eff), (exit) => parseIndex(s, key, index, exit)) + } + exitKey = eff + } + if (exitKey._tag === "Failure") { + return wrapPropertyKeyIssue(s, ast, key, exitKey) ?? Exit.void + } + const k2 = exitKey === InternalParser.sameExit + ? key + : (exitKey as InternalParser.Success)[InternalParser.args] + const inputValue = s.input[key] + const result = index.parserValue(inputValue, s.options) + return effectIsExit(result) + ? finishIndex(s, key, k2, inputValue, result) + : Effect.flatMap(Effect.exit(result), (exit) => finishIndex(s, key, k2, inputValue, exit)) + } + const parseStringIndex = ( + s: ObjectParserState, + key: PropertyKey, + index: Index + ): Effect.Effect => { + const inputValue = s.input[key] + const result = index.parserValue(inputValue, s.options) + return effectIsExit(result) + ? finishIndex(s, key, key, inputValue, result) + : Effect.flatMap(Effect.exit(result), (exit) => finishIndex(s, key, key, inputValue, exit)) + } + const parseIndexes = indexCount ? + iterateEager()({ + onItem: (s, [key, index]) => parseIndex(s, key, index), + step: (_s, _, exit: Exit.Exit) => exit._tag === "Failure" ? exit : undefined + }) : + undefined + + const compileMembers = (): Array => { + if (!properties) { + properties = ast.propertySignatures.map((ps) => ({ + parser: compileConstructorDefault(ps.type), + name: ps.name, + type: ps.type + })) + indexes = indexCount + ? ast.indexSignatures.map((is) => ({ + is, + parserKey: compile(parameterFromPropertyKey(is.parameter)), + parserValue: compileConstructorDefault(is.type) + })) + : undefined + } + return properties + } + + const fallback: SchemaParser.Parser = Effect.fnUntracedEager(function*(input, options) { + if (input === InternalParser.missing) { + return InternalParser.missing + } + + // If the input is not a record, return early with an error + if (!(typeof input === "object" && input !== null && !Array.isArray(input))) { + return yield* Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + compileMembers() + + const record = input as Record + const out: Record = {} + const state = { + ast, + input: record, + out, + issues: undefined as Arr.NonEmptyArray | undefined, + options + } + const errorsAllOption = options.errors === "all" + const onExcessPropertyError = options.onExcessProperty === "error" + const onExcessPropertyPreserve = options.onExcessProperty === "preserve" + + // --------------------------------------------- + // handle excess properties + // --------------------------------------------- + let inputKeys: Array | undefined + if (!indexCount && (onExcessPropertyError || onExcessPropertyPreserve)) { + expectedKeysSet ??= new Set(expectedKeys) + inputKeys = Reflect.ownKeys(record) + for (let i = 0; i < inputKeys.length; i++) { + const key = inputKeys[i] + if (!expectedKeysSet.has(key)) { + // key is unexpected + if (onExcessPropertyError) { + const unexpected = new SchemaIssue.UnexpectedKey(ast, record[key], options) + const issue = new SchemaIssue.Pointer([key], unexpected) + if (errorsAllOption) { + if (state.issues) { + state.issues.push(issue) + } else { + state.issues = [issue] + } + continue + } else { + return yield* Effect.fail( + new SchemaIssue.Composite(ast, [issue], input, options) + ) + } + } else { + // preserve key + InternalRecord.assignProperty(out, key, record[key]) + } + } + } + } + + const concurrency = resolveConcurrency(options?.concurrency) + + // --------------------------------------------- + // handle property signatures + // --------------------------------------------- + if (hasProperties) { + const eff = parseProperties(state, properties!, concurrency) + if (eff) yield* eff + } + + // --------------------------------------------- + // handle index signatures + // --------------------------------------------- + if (indexCount && !concurrency) { + for (let i = 0; i < indexCount; i++) { + const index = indexes![i] + const parse = index.is.parameter === string ? parseStringIndex : parseIndex + const keys = index.is.parameter === string + ? Object.keys(record) + : getIndexSignatureKeys(record, index.is.parameter, options) + for (let j = 0; j < keys.length; j++) { + const eff = parse(state, keys[j], index) + if (!effectIsExit(eff)) yield* eff + else if (eff._tag === "Failure") return yield* eff as Exit.Exit + } + } + } else if (parseIndexes) { + const keyPairs = Arr.empty<[PropertyKey, Index]>() + for (let i = 0; i < indexCount; i++) { + const index = indexes![i] + const keys = getIndexSignatureKeys(record, index.is.parameter, options) + for (let j = 0; j < keys.length; j++) { + keyPairs.push([keys[j], index]) + } + } + const eff = parseIndexes(state, keyPairs, concurrency) + if (eff) yield* eff + } + + if (state.issues) { + return yield* Effect.fail( + new SchemaIssue.Composite(ast, state.issues, input, options) + ) + } + if (options.propertyOrder === "original") { + // preserve input keys order + const keys = (inputKeys ?? Reflect.ownKeys(record)).concat(expectedKeys) + const preserved: Record = {} + for (const key of keys) { + if (Object.hasOwn(out, key)) { + InternalRecord.assignProperty(preserved, key, out[key]) + } + } + return preserved + } + return out + }) + + if (indexCount) return fallback + + // Resumes at the property whose parser suspended, without replaying the + // properties already parsed. + const resume = ( + state: ObjectParserState, + index: number, + pending: Effect.Effect + ): Effect.Effect => { + const property = properties![index] + return Effect.flatMap(Effect.exit(pending), (exit) => { + const terminal = stepProperty(state, property, exit) + if (terminal) return terminal + const done = () => InternalParser.succeed(state.out) + const eff = parseProperties(state, properties!.slice(index + 1)) + return eff ? Effect.flatMapEager(eff, done) : done() + }) + } + + // Fast path: a struct without index signatures, under the default parse + // options, needs none of the generator the fallback runs per value. + return (input, options) => { + if (input === InternalParser.missing) return InternalParser.missingExit + if ( + options.errors === "all" || + options.onExcessProperty !== undefined || + options.propertyOrder === "original" || + options.concurrency !== undefined + ) { + return fallback(input, options) + } + if (!(typeof input === "object" && input !== null && !Array.isArray(input))) { + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + const props = compileMembers() + const record = input as Record + const out: Record = {} + const state: ObjectParserState = { ast, input: record, out, issues: undefined, options } + try { + for (let index = 0; index < props.length; index++) { + const property = props[index] + const name = property.name + const hasKey = Object.hasOwn(record, name) + const value = hasKey ? record[name] : InternalParser.missing + const exit = property.parser(value, options) + if (!effectIsExit(exit)) { + return resume(state, index, exit) + } + if (exit === InternalParser.sameExit) { + if (hasKey) InternalRecord.assignProperty(out, name, value) + continue + } + const terminal = stepProperty(state, property, exit) + if (terminal) return terminal + } + } catch (error) { + // `Effect.fnUntracedEager` turns a synchronous throw into a defect + return Effect.die(error) + } + return InternalParser.succeed(out) + } + } + private _rebuild( + recur: (ast: AST) => AST, + recurParameter: (ast: AST) => AST, + checks: Checks | undefined, + encodingChecks: Checks | undefined + ): Objects { + const props = mapOrSame(this.propertySignatures, (ps) => { + const t = recur(ps.type) + return t === ps.type ? ps : new PropertySignature(ps.name, t) + }) + + const indexes = mapOrSame(this.indexSignatures, (is) => { + const p = recurParameter(is.parameter) + const t = recur(is.type) + return p === is.parameter && t === is.type + ? is + : new IndexSignature(p, t) + }) + + return props === this.propertySignatures && indexes === this.indexSignatures && checks === this.checks && + encodingChecks === this.encodingChecks + ? this + : new Objects( + props, + indexes, + this.annotations, + checks, + undefined, + this.context, + encodingChecks + ) + } + /** @internal */ + flip(recur: (ast: AST) => AST): AST { + return this._rebuild(recur, recur, this.encodingChecks, this.checks) + } + /** @internal */ + recur(recur: (ast: AST) => AST, recurParameter: (ast: AST) => AST = recur): AST { + return this._rebuild(recur, recurParameter, this.checks, this.encodingChecks) + } + /** @internal */ + getExpected(): string { + if (this.propertySignatures.length === 0 && this.indexSignatures.length === 0) return "object | array" + return "object" + } +} + +type ObjectParserState = { + readonly ast: Objects + readonly input: Record + readonly options: ParseOptions + readonly out: Record + issues: Array | undefined +} + +type ParsedProperty = { + readonly parser: SchemaParser.Parser + readonly name: PropertyKey + readonly type: AST +} + +function stepProperty( + s: ObjectParserState, + p: ParsedProperty, + exit: Exit.Exit +): Exit.Exit | void { + if (exit._tag === "Failure") { + return wrapPropertyKeyIssue(s, s.ast, p.name, exit) + } + if (exit === InternalParser.sameExit) return + const value = (exit as InternalParser.Success)[InternalParser.args] + if (value !== InternalParser.missing) { + InternalRecord.assignProperty(s.out, p.name, value) + return + } + delete s.out[p.name] + if (!isOptional(p.type)) { + const issue = new SchemaIssue.Pointer([p.name], new SchemaIssue.MissingKey(p.type.context?.annotations)) + if (s.options.errors === "all") { + if (s.issues) s.issues.push(issue) + else s.issues = [issue] + return + } else { + return Exit.fail( + new SchemaIssue.Composite(s.ast, [issue], s.input, s.options) + ) + } + } +} + +const parseProperties = iterateEager()({ + onItem(s, p) { + if (!Object.hasOwn(s.input, p.name)) { + return p.parser(InternalParser.missing, s.options) + } + const value = s.input[p.name] + InternalRecord.assignProperty(s.out, p.name, value) + return p.parser(value, s.options) + }, + step: stepProperty +}) + +function combineChecks(a: Checks | undefined, b: Checks | undefined): Checks | undefined { + if (!a) return b + if (!b) return a + return [...a, ...b] +} + +/** @internal */ +export function struct( + fields: Fields, + checks: Checks | undefined, + annotations?: Schema.Annotations.Annotations +): Objects { + return new Objects( + Reflect.ownKeys(fields).map((key) => { + return new PropertySignature(key, fields[key].ast) + }), + [], + annotations, + checks + ) +} + +/** @internal */ +export function getAST(self: S): S["ast"] { + return self.ast +} + +/** @internal */ +export function tuple( + elements: Elements, + checks: Checks | undefined = undefined +): Arrays { + return new Arrays(false, elements.map((e) => e.ast), [], undefined, checks) +} + +/** @internal */ +export function union>( + members: Members, + mode: "anyOf" | "oneOf", + checks: Checks | undefined +): Union { + return new Union(members.map(getAST), mode, undefined, checks) +} + +/** @internal */ +export function structWithRest(ast: Objects, records: ReadonlyArray): Objects { + if (ast.encoding || records.some((r) => r.encoding)) { + throw new Error("StructWithRest does not support encodings") + } + let propertySignatures = ast.propertySignatures + let indexSignatures = ast.indexSignatures + let checks = ast.checks + for (const record of records) { + propertySignatures = propertySignatures.concat(record.propertySignatures) + indexSignatures = indexSignatures.concat(record.indexSignatures) + checks = combineChecks(checks, record.checks) + } + return new Objects(propertySignatures, indexSignatures, undefined, checks) +} + +/** @internal */ +export function tupleWithRest(ast: Arrays, rest: ReadonlyArray): Arrays { + if (ast.encoding) { + throw new Error("TupleWithRest does not support encodings") + } + return new Arrays(ast.isMutable, ast.elements, rest, undefined, ast.checks) +} + +type Type = + | "null" + | "array" + | "object" + | "string" + | "number" + | "boolean" + | "symbol" + | "undefined" + | "bigint" + | "function" + +/** @internal */ +export type Sentinel = { + readonly key: PropertyKey + readonly literal: LiteralValue | symbol +} + +const toCandidate = memoizeIdempotent((ast: AST): AST => { + while (true) { + if (isSuspend(ast)) return unknown + const encoding = ast.encoding + if (!encoding) { + // Index signature parameters do not participate in union selection. + return (ast as any).recur?.(toCandidate, identity) ?? ast + } + if ( + encoding.some((link) => link.transformation._tag === "Middleware" && link.transformation.decode !== identity) + ) return unknown + ast = encoding[encoding.length - 1].to + } +}) + +function getCandidateTypes(ast: AST): ReadonlyArray { + switch (ast._tag) { + case "Null": + return ["null"] + case "Undefined": + return ["undefined"] + case "String": + case "TemplateLiteral": + return ["string"] + case "Number": + return ["number"] + case "Boolean": + return ["boolean"] + case "Symbol": + case "UniqueSymbol": + return ["symbol"] + case "BigInt": + return ["bigint"] + case "Arrays": + return ["array"] + case "ObjectKeyword": + return ["object", "array", "function"] + case "Objects": + return ast.propertySignatures.length || ast.indexSignatures.length + ? ["object"] + : ["string", "number", "boolean", "symbol", "bigint", "object", "array", "function"] + case "Enum": + return Array.from(new Set(ast.enums.map(([, v]) => typeof v))) + case "Literal": + return [typeof ast.literal] + case "Union": + return Array.from(new Set(ast.types.flatMap(getCandidateTypes))) + default: + return [ + "null", + "undefined", + "string", + "number", + "boolean", + "symbol", + "bigint", + "object", + "array", + "function" + ] + } +} + +/** @internal */ +export function collectSentinels(ast: AST): ReadonlyArray { + switch (ast._tag) { + default: + return [] + case "Declaration": { + const s = ast.annotations?.[InternalAnnotations.SENTINELS_ANNOTATION_KEY] + return Array.isArray(s) ? s : [] + } + case "Objects": + return ast.propertySignatures.flatMap((ps): Array => { + const type = ps.type + if (!isOptional(type)) { + if (isLiteral(type)) { + return [{ key: ps.name, literal: type.literal }] + } + if (isUniqueSymbol(type)) { + return [{ key: ps.name, literal: type.symbol }] + } + } + return [] + }) + case "Arrays": + return ast.elements.flatMap((e, i): Array => { + if (!isOptional(e)) { + if (isLiteral(e)) { + return [{ key: i, literal: e.literal }] + } + if (isUniqueSymbol(e)) { + return [{ key: i, literal: e.symbol }] + } + } + return [] + }) + case "Union": { + if (ast.types.length === 0) return [] + const members = ast.types.map((type) => collectSentinels(toCandidate(type))) + return members[0].filter((s) => + members.every((sentinels) => sentinels.some((o) => o.key === s.key && o.literal === s.literal)) + ) + } + case "Suspend": + return collectSentinels(ast.thunk()) + } +} + +type CandidateIndex = (input: any, isConstructor: boolean) => ReadonlyArray +type SentinelEntry = readonly [ + byValue: Map>, + all: Set +] +type SentinelIndex = Map + +const candidateIndexCache = new WeakMap, CandidateIndex>() +const emptyCandidates: ReadonlyArray = Object.freeze([]) + +function getIndex(types: ReadonlyArray): CandidateIndex { + let index = candidateIndexCache.get(types) + if (index) return index + + let bySentinel: SentinelIndex | undefined + let sentinelCandidateCount = 0 + let otherwise: { [K in Type]?: Array } | undefined + let literalCandidates: Map> | undefined + let onlyLiterals = true + for (let i = 0; i < types.length; i++) { + const a = types[i] + const encoded = toCandidate(a) + if (isNever(encoded)) continue + + if (onlyLiterals) { + if (isLiteral(encoded) || isUniqueSymbol(encoded)) { + literalCandidates ??= new Map() + const literal = isLiteral(encoded) ? encoded.literal : encoded.symbol + let arr = literalCandidates.get(literal) + if (!arr) literalCandidates.set(literal, arr = []) + arr.push(a) + } else { + onlyLiterals = false + } + } + + const sentinels = collectSentinels(encoded) + + if (sentinels.length) { // discriminated variants + bySentinel ??= new Map() + sentinelCandidateCount++ + for (const { key, literal } of sentinels) { + let entry = bySentinel.get(key) + if (!entry) bySentinel.set(key, entry = [new Map(), new Set()]) + entry[1].add(i) + let indexes = entry[0].get(literal) + if (!indexes) entry[0].set(literal, indexes = new Set()) + indexes.add(i) + } + } else { // non-discriminated + otherwise ??= {} + const candidateTypes = getCandidateTypes(encoded) + for (const t of candidateTypes) (otherwise[t] ??= []).push(i) + } + } + + if (onlyLiterals && literalCandidates) { + literalCandidates.forEach(Object.freeze) + index = (input) => literalCandidates.get(input) ?? emptyCandidates + } else if (bySentinel?.size === 1 && !otherwise) { + const [key, [byValue]] = bySentinel.entries().next().value! + const candidates = byValue as unknown as Map> + for (const [literal, indexes] of byValue) { + candidates.set(literal, Object.freeze(Array.from(indexes, (index) => types[index]))) + } + index = (input, isConstructor) => { + if (Predicate.isObjectKeyword(input)) { + const value = Object.hasOwn(input, key) ? (input as any)[key] : undefined + if (value !== undefined) return candidates.get(value) ?? emptyCandidates + if (isConstructor) return types + } + return emptyCandidates + } + } else if (bySentinel) { + // A key owned by every discriminated candidate is safe to use as the initial selector: no candidate can + // be excluded merely because it uses a different sentinel key. Prefer the key with the most distinct values + // to minimize the matching bucket. + let commonSentinel: [PropertyKey, SentinelEntry] | undefined + for (const entry of bySentinel) { + if ( + (!commonSentinel || entry[1][0].size > commonSentinel[1][0].size) && + entry[1][1].size === sentinelCandidateCount + ) { + commonSentinel = entry + } + } + + index = (input, isConstructor) => { + const runtimeType: Type = input === null ? "null" : Array.isArray(input) ? "array" : typeof input + const base = otherwise?.[runtimeType] ?? emptyCandidates + if (!Predicate.isObjectKeyword(input)) return base.map((i) => types[i]) + + // Non-discriminated candidates are runtime-type fallbacks and are never removed by sentinel checks. + const selected = new Set(base) + let directKey: PropertyKey | undefined + // An observed common key can seed the selection directly; an unknown value rules out every + // discriminated candidate. + if (commonSentinel) { + const [key, [byValue]] = commonSentinel + const hasKey = Object.hasOwn(input, key) + const value = hasKey ? (input as any)[key] : undefined + if (hasKey && (!isConstructor || value !== undefined)) { + const match = byValue.get(value) + if (!match) return base.map((i) => types[i]) + for (const i of match) selected.add(i) + directKey = key + } + } + + // Without an observed common key, collect positive matches from every sentinel. Constructor mode treats + // absent and undefined keys as unconstrained and therefore selects every candidate that owns the key. + if (directKey === undefined) { + for (const [key, [byValue, all]] of bySentinel) { + const hasKey = Object.hasOwn(input, key) + const value = hasKey ? (input as any)[key] : undefined + if (hasKey && (!isConstructor || value !== undefined)) { + const match = byValue.get(value) + if (match) { + for (const i of match) selected.add(i) + } + } else if (isConstructor) { + for (const i of all) selected.add(i) + } + } + } + // Missing keys are neutral. An observed key rejects only selected candidates that own it and do not match. + for (const [key, [byValue, all]] of bySentinel) { + if (key === directKey) continue + const hasKey = Object.hasOwn(input, key) + const value = hasKey ? (input as any)[key] : undefined + if (hasKey && (!isConstructor || value !== undefined)) { + const match = byValue.get(value) + for (const i of selected) { + if (all.has(i) && !match?.has(i)) selected.delete(i) + } + } + } + return Array.from(selected).sort((a, b) => a - b).map((i) => types[i]) + } + } else { + index = (input) => { + const runtimeType: Type = input === null ? "null" : Array.isArray(input) ? "array" : typeof input + return (otherwise?.[runtimeType] ?? emptyCandidates).map((i) => types[i]).filter(filterLiterals(input)) + } + } + + candidateIndexCache.set(types, index) + return index +} + +function filterLiterals(input: any) { + return (ast: AST) => { + const encoded = toCandidate(ast) + return encoded._tag === "Literal" ? + encoded.literal === input + : encoded._tag === "UniqueSymbol" ? + encoded.symbol === input + : true + } +} + +/** + * The goal is to reduce the number of a union members that will be checked. + * This is useful to reduce the number of issues that will be returned. + * + * @internal + */ +export function getCandidates( + input: any, + types: ReadonlyArray, + isConstructor = false +): ReadonlyArray { + return getIndex(types)(input, isConstructor) +} + +/** + * AST node representing a union of schemas. + * + * **Details** + * + * - `types` — the member AST nodes. + * - `mode` — `"anyOf"` succeeds on the first match (like TypeScript unions); + * `"oneOf"` requires exactly one member to match (fails if multiple do). + * + * During parsing, members are tried in order. An internal candidate index + * narrows which members to try based on the runtime type of the input and + * discriminant ("sentinel") fields, making large unions efficient. + * + * **Example** (Inspecting a union AST) + * + * ```ts import.meta.vitest + * import { Schema, SchemaAST } from "effect" + * + * const schema = Schema.Union([Schema.String, Schema.Number]) + * const ast = schema.ast + * + * if (SchemaAST.isUnion(ast)) { + * [ast.types.length, ast.mode] // => [2, "anyOf"] + * } + * ``` + * + * @see {@link isUnion} + * @category models + * @since 3.10.0 + */ +export class Union extends Base { + readonly _tag = "Union" + readonly types: ReadonlyArray + readonly mode: "anyOf" | "oneOf" + readonly encodingChecks: Checks | undefined + + constructor( + types: ReadonlyArray, + mode: "anyOf" | "oneOf", + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context, + encodingChecks?: Checks + ) { + super(annotations, checks, encoding, context) + this.types = types + this.mode = mode + this.encodingChecks = encodingChecks + } + /** @internal */ + getParser( + compile: SchemaParser.Compiler, + compileConstructorDefault?: SchemaParser.Compiler + ): SchemaParser.Parser { + // oxlint-disable-next-line @typescript-eslint/no-this-alias + const ast = this + + return (input, options) => { + if (input === InternalParser.missing) { + return InternalParser.missingExit + } + const candidates = getCandidates(input, ast.types, compileConstructorDefault !== undefined) + + if (candidates.length === 1) { + const result = compile(candidates[0])(input, options) + if ((result as Exit.Exit)._tag === "Success") return result + return effectIsExit(result) + ? failSingleUnionCandidate(ast, (result as Exit.Failure).cause, input, options) + : Effect.catchCause(result, (cause) => failSingleUnionCandidate(ast, cause, input, options)) + } + + const state = { + ast, + compile, + input, + out: undefined, + successes: ast.mode === "oneOf" ? [] : undefined, + issues: undefined as Arr.NonEmptyArray | undefined, + options + } + const concurrency = resolveConcurrency(options?.concurrency) + const eff = parseUnion(state, candidates, concurrency ? { ...concurrency, orderedStep: true } : undefined) + if (!eff) { + if (state.out) return state.out + return Effect.fail(new SchemaIssue.AnyOf(ast, state.issues ?? [], input, options)) + } + return Effect.flatMapEager(eff, (_) => { + if (state.out === InternalParser.sameExit) return Effect.succeed(input) + if (state.out) return state.out + return Effect.fail(new SchemaIssue.AnyOf(ast, state.issues ?? [], input, options)) + }) + } + } + private _rebuild(recur: (ast: AST) => AST, checks: Checks | undefined, encodingChecks: Checks | undefined) { + const types = mapOrSame(this.types, recur) + return types === this.types && checks === this.checks && encodingChecks === this.encodingChecks ? + this : + new Union(types, this.mode, this.annotations, checks, undefined, this.context, encodingChecks) + } + /** @internal */ + recur(recur: (ast: AST) => AST) { + return this._rebuild(recur, this.checks, this.encodingChecks) + } + /** @internal */ + flip(recur: (ast: AST) => AST) { + return this._rebuild(recur, this.encodingChecks, this.checks) + } + /** @internal */ + matchPart(s: string, options: ParseOptions): LiteralValue | undefined { + for (const type of this.types) { + const out = (type as TemplateLiteralPart).matchPart(s, options) + if (out !== undefined) return out + } + return undefined + } + /** @internal */ + getExpected(getExpected: (ast: AST) => string): string { + const expected = this.annotations?.expected + if (typeof expected === "string") return expected + + if (this.types.length === 0) return "never" + + const types = this.types.map((type) => { + const encoded = toEncoded(type) + switch (encoded._tag) { + case "Arrays": { + const literals = encoded.elements.filter(isLiteral) + if (literals.length > 0) { + return `${formatIsMutable(encoded.isMutable)}[ ${ + literals.map((e) => getExpected(e) + formatIsOptional(e.context?.isOptional)).join(", ") + }, ... ]` + } + break + } + case "Objects": { + const literals = encoded.propertySignatures.filter((ps) => isLiteral(ps.type)) + if (literals.length > 0) { + return `{ ${ + literals.map((ps) => + `${formatIsMutable(ps.type.context?.isMutable)}${formatPropertyKey(ps.name)}${ + formatIsOptional(ps.type.context?.isOptional) + }: ${getExpected(ps.type)}` + ).join(", ") + }, ... }` + } + break + } + } + return getExpected(encoded) + }) + return Array.from(new Set(types)).join(" | ") + } +} + +function failSingleUnionCandidate( + ast: Union, + cause: Cause.Cause, + input: unknown, + options: ParseOptions +) { + const issue = InternalSchemaCause.getSchemaIssue(cause) + if (!issue) return Exit.failCause(cause) + return Exit.fail(new SchemaIssue.AnyOf(ast, [issue], input, options)) +} + +const parseUnion = iterateEager<{ + readonly compile: (ast: AST) => SchemaParser.Parser + readonly ast: Union + readonly input: unknown + readonly options: ParseOptions + out: Exit.Success | undefined + readonly successes: Array | undefined + issues: Array | undefined +}, AST>()({ + onItem(s, ast) { + const parser = s.compile(ast) + return parser(s.input, s.options) + }, + step(s, candidate, exit) { + if (exit._tag === "Failure") { + const issue = InternalSchemaCause.getSchemaIssue(exit.cause) + if (issue === undefined) { + return exit + } + if (s.issues) s.issues.push(issue) + else s.issues = [issue] + } else { + if (s.out && s.successes) { + s.successes.push(candidate) + return Exit.fail(new SchemaIssue.OneOf(s.ast, s.successes, s.input, s.options)) + } + s.out = exit + if (s.successes) { + s.successes.push(candidate) + } else { + return Exit.void + } + } + } +}) + +const nonFiniteLiterals = new Union([ + new Literal("Infinity"), + new Literal("-Infinity"), + new Literal("NaN") +], "anyOf") + +function formatIsMutable(isMutable: boolean | undefined): string { + return isMutable ? "" : "readonly " +} + +function formatIsOptional(isOptional: boolean | undefined): string { + return isOptional ? "?" : "" +} + +/** @internal */ +export function memoizeThunk(f: () => A): () => A { + let done = false + let a: A + return () => { + if (done) { + return a + } + a = f() + done = true + return a + } +} + +/** + * AST node for lazy/recursive schemas. + * + * **Details** + * + * Wraps a thunk (`() => AST`) that is memoized on first call. Use this to + * define recursive or mutually recursive schemas without infinite loops at + * construction time. + * + * **Example** (Defining recursive schema ASTs) + * + * ```ts import.meta.vitest + * import { Schema, SchemaAST } from "effect" + * + * interface Category { + * readonly name: string + * readonly children: ReadonlyArray + * } + * + * const Category = Schema.Struct({ + * name: Schema.String, + * children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) + * }) + * + * SchemaAST.isObjects(Category.ast) // => true + * ``` + * + * @see {@link isSuspend} + * @category models + * @since 3.10.0 + */ +export class Suspend extends Base { + readonly _tag = "Suspend" + readonly thunk: () => AST + + constructor( + thunk: () => AST, + annotations?: Schema.Annotations.Annotations, + checks?: Checks, + encoding?: Encoding, + context?: Context + ) { + if (checks) { + throw new Error("Cannot add checks to Suspend") + } + super(annotations, undefined, encoding, context) + this.thunk = memoizeThunk(thunk) + } + /** @internal */ + getParser(compile: SchemaParser.Compiler): SchemaParser.Parser { + let parser: SchemaParser.Parser + return (input, options) => (parser ??= compile(this.thunk()))(input, options) + } + /** @internal */ + recur(recur: (ast: AST) => AST) { + return new Suspend( + () => recur(this.thunk()), + this.annotations, + undefined, + undefined, + this.context + ) + } + /** @internal */ + getExpected(getExpected: (ast: AST) => string): string { + return getExpected(this.thunk()) + } +} + +// ----------------------------------------------------------------------------- +// Checks +// ----------------------------------------------------------------------------- + +/** + * Represents a single validation check attached to an AST node. + * + * **Details** + * + * - `run` — the validation function. Returns `undefined` on success, or an + * `Issue` on failure. + * - `annotations` — optional filter-level annotations (expected message, + * representation, arbitrary constraint hints). + * - `aborted` — when `true`, parsing stops immediately after this filter + * fails (no further checks run). + * + * Use `.annotate()` to add metadata and `.abort()` to mark as aborting. + * Combine with another check via `.and()` to form a {@link FilterGroup}. + * + * @see {@link FilterGroup} + * @see {@link Check} + * @see {@link isPattern} + * @category models + * @since 4.0.0 + */ +export class Filter extends Pipeable.Class { + readonly _tag = "Filter" + readonly run: (input: E, self: AST, options: ParseOptions) => SchemaIssue.Issue | undefined + readonly annotations: Schema.Annotations.Filter | undefined + /** + * Whether the parsing process should be aborted after this check has failed. + */ + readonly aborted: boolean + + constructor( + run: (input: E, self: AST, options: ParseOptions) => SchemaIssue.Issue | undefined, + annotations: Schema.Annotations.Filter | undefined = undefined, + /** + * Whether the parsing process should be aborted after this check has failed. + */ + aborted: boolean = false + ) { + super() + this.run = run + this.annotations = annotations + this.aborted = aborted + } + annotate(annotations: Schema.Annotations.Filter): Filter { + return new Filter(this.run, { ...this.annotations, ...annotations }, this.aborted) + } + abort(): Filter { + return new Filter(this.run, this.annotations, true) + } + and(other: Check, annotations?: Schema.Annotations.Filter): FilterGroup + and(other: Check, annotations?: Schema.Annotations.Filter): FilterGroup { + return new FilterGroup([this, other], annotations) + } +} + +/** + * Represents a composite validation check grouping multiple {@link Check} values. + * + * **Details** + * + * Created by calling `.and()` on a {@link Filter} or another `FilterGroup`. + * All inner checks are run; failures from aborted filters still stop + * evaluation. + * + * @see {@link Filter} + * @see {@link Check} + * @category models + * @since 4.0.0 + */ +export class FilterGroup extends Pipeable.Class { + readonly _tag = "FilterGroup" + readonly checks: readonly [Check, ...Array>] + readonly annotations: Schema.Annotations.Filter | undefined + + constructor( + checks: readonly [Check, ...Array>], + annotations: Schema.Annotations.Filter | undefined = undefined + ) { + super() + this.checks = checks + this.annotations = annotations + } + annotate(annotations: Schema.Annotations.Filter): FilterGroup { + return new FilterGroup(this.checks, { ...this.annotations, ...annotations }) + } + and(other: Check, annotations?: Schema.Annotations.Filter): FilterGroup + and(other: Check, annotations?: Schema.Annotations.Filter): FilterGroup { + return new FilterGroup([this, other], annotations) + } +} + +/** + * A validation check — either a single {@link Filter} or a composite + * {@link FilterGroup}. + * + * **Details** + * + * Stored in the {@link Checks} array on {@link Base.checks}. + * + * @see {@link Filter} + * @see {@link FilterGroup} + * @category models + * @since 4.0.0 + */ +export type Check = Filter | FilterGroup + +/** @internal */ +export function makeFilter( + filter: (input: T, ast: AST, options: ParseOptions) => Schema.FilterOutput, + annotations?: Schema.Annotations.Filter | undefined, + aborted: boolean = false +): Filter { + return new Filter( + (input, ast, options) => SchemaIssue.normalizeFilterOutput(ast, filter(input, ast, options), input, options), + annotations, + aborted + ) +} + +/** @internal */ +export function makeFilterByGuard( + is: (value: E) => value is T, + annotations?: Schema.Annotations.Filter +): Filter { + return new Filter( + (input: E, _ast, options) => is(input) ? undefined : new SchemaIssue.InvalidValue(undefined, input, options), + annotations, + true // after a guard, we always want to abort + ) +} + +/** @internal */ +export function isFinite(annotations?: Schema.Annotations.Filter) { + return makeFilter( + (n: number) => globalThis.Number.isFinite(n), + { + expected: "a finite number", + representation: { + id: "effect/schema/isFinite", + payload: null + }, + toJsonSchema: () => ({ type: "number" }), + toCode: () => ({ runtime: "Schema.isFinite()" }), + arbitrary: { + constraint: { + noInfinity: true, + noNaN: true + } + }, + ...annotations + } + ) +} + +/** @internal */ +export const finite = appendChecks(number, [isFinite()]) + +const numberToJson = new Link( + new Union([finite, nonFiniteLiterals], "anyOf"), + new SchemaTransformation.Transformation( + SchemaGetter.Number(), + SchemaGetter.transform((n) => globalThis.Number.isFinite(n) ? n : globalThis.String(n)) + ) +) + +/** + * Creates a {@link Filter} that validates strings by running `RegExp.test`. + * + * **When to use** + * + * Use when string validation should be represented as a schema `Filter` backed + * by a regular expression. + * + * **Details** + * + * The filter can be used with `Schema.filter` or attached directly to a + * `String` AST node through checks. The regular expression is cloned and its + * `lastIndex` is reset before each test, so global and sticky expressions are + * deterministic and the provided regular expression is not mutated. The + * regular expression source is stored in annotations for serialization and + * arbitrary generation. + * + * **Gotchas** + * + * When deriving an arbitrary, only `regExp.source` is used. Regular expression + * flags are ignored because fast-check does not support them. + * + * **Example** (Validating an email pattern) + * + * ```ts import.meta.vitest + * import { SchemaAST } from "effect" + * + * const emailFilter = SchemaAST.isPattern(/^[^@]+@[^@]+$/) + * emailFilter.run("alice@example.com", SchemaAST.string, {}) // => undefined + * emailFilter.run("invalid", SchemaAST.string, {})?._tag // => "InvalidValue" + * ``` + * + * @see {@link Filter} + * @category constructors + * @since 4.0.0 + */ +export function isPattern(regExp: globalThis.RegExp, annotations?: Schema.Annotations.Filter) { + const source = regExp.source + const pattern = new globalThis.RegExp(source, regExp.flags) + return makeFilter( + (s: string) => { + pattern.lastIndex = 0 + return pattern.test(s) + }, + { + expected: `a string matching the RegExp ${source}`, + representation: { + id: "effect/schema/isPattern", + payload: { source, flags: regExp.flags } + }, + toJsonSchema: () => ({ pattern: source }), + arbitrary: { + constraint: { + patterns: [regExp.source] + } + }, + ...annotations + } + ) +} + +function modifyOwnPropertyDescriptors( + ast: A, + f: ( + d: { [P in keyof A]: TypedPropertyDescriptor } + ) => void +): A { + const d = Object.getOwnPropertyDescriptors(ast) + f(d) + return Object.create(Object.getPrototypeOf(ast), d) +} + +const contextOwners = new WeakMap() + +/** @internal */ +export function getContextOwner(ast: AST): AST { + return contextOwners.get(ast) ?? ast +} + +/** @internal */ +export function replaceEncoding(ast: A, encoding: Encoding | undefined): A { + if (ast.encoding === encoding) { + return ast + } + return modifyOwnPropertyDescriptors(ast, (d) => { + d.encoding.value = encoding + }) +} + +/** @internal */ +export function replaceContext(ast: A, context: Context | undefined): A { + if (ast.context === context) { + return ast + } + const owner = getContextOwner(ast) + if (owner.context === context) { + return owner as A + } + const out = modifyOwnPropertyDescriptors(ast, (d) => { + d.context.value = context + }) + contextOwners.set(out, owner) + return out +} + +/** @internal */ +export function getLastEncoding(ast: AST): AST { + return ast.encoding ? getLastEncoding(ast.encoding[ast.encoding.length - 1].to) : ast +} + +/** @internal */ +export function annotate(ast: A, annotations: Schema.Annotations.Annotations): A { + if (ast.checks) { + const last = ast.checks[ast.checks.length - 1] + return replaceChecks(ast, Arr.append(ast.checks.slice(0, -1), last.annotate(annotations))) + } + return modifyOwnPropertyDescriptors(ast, (d) => { + d.annotations.value = { ...d.annotations.value, ...annotations } + }) +} + +/** @internal */ +export function replaceChecks(ast: A, checks: Checks | undefined): A { + if (ast._tag === "Suspend" && checks) { + throw new Error("Cannot add checks to Suspend") + } + if (ast.checks === checks) { + return ast + } + return modifyOwnPropertyDescriptors(ast, (d) => { + d.checks.value = checks + }) +} + +/** @internal */ +export function appendChecks(ast: A, checks: Checks | undefined): A { + return replaceChecks(ast, combineChecks(ast.checks, checks)) +} + +/** @internal */ +export function mapLink(link: Link, f: (ast: AST) => AST): Link { + const to = f(link.to) + return to === link.to ? link : new Link(to, link.transformation) +} + +function updateLastLink(encoding: Encoding, f: (ast: AST) => AST): Encoding { + const links = encoding + const last = links[links.length - 1] + const out = mapLink(last, f) + return out === last ? encoding : Arr.append(encoding.slice(0, encoding.length - 1), out) +} + +/** @internal */ +export function applyToLastLink(f: (ast: AST) => AST) { + return (ast: A): A => ast.encoding ? replaceEncoding(ast, updateLastLink(ast.encoding, f)) : ast +} + +/** @internal */ +export function replaceContextLastLink(ast: A, context: Context): A { + return applyToLastLink((ast) => replaceContext(ast, context))(ast) +} + +/** @internal */ +export function applyToSelfOrLastLinkEncoding(f: (ast: AST) => AST) { + function out(ast: AST): AST { + return ast.encoding ? replaceEncoding(ast, updateLastLink(ast.encoding, out)) : f(ast) + } + return memoize(out) +} + +/** @internal */ +export function applyToSelfOrLastLinkEncodingIdempotent( + f: (ast: AST) => AST, + options?: { readonly stopAt?: (link: Link) => boolean } +) { + function out(ast: AST): AST { + if (ast.encoding) { + const last = ast.encoding[ast.encoding.length - 1] + return options?.stopAt?.(last) ? ast : replaceEncoding(ast, updateLastLink(ast.encoding, out)) + } + return f(ast) + } + return memoizeIdempotent(out) +} + +/** @internal */ +export function middlewareDecoding( + ast: AST, + middleware: SchemaTransformation.Middleware +): AST { + return appendTransformation(ast, middleware, toType(ast)) +} + +/** @internal */ +export function middlewareEncoding( + ast: AST, + middleware: SchemaTransformation.Middleware +): AST { + return appendTransformation(toEncoded(ast), middleware, ast) +} + +function appendTransformation( + from: AST, + transformation: + | SchemaTransformation.Transformation + | SchemaTransformation.Middleware, + to: A +): A { + const link = new Link(from, transformation) + return replaceEncoding(to, to.encoding ? [...to.encoding, link] : [link]) +} + +/** @internal */ +export function brand(ast: AST, brand: string): AST { + const existing = InternalAnnotations.resolveBrands(ast) + const brands = existing ? [...existing, brand] : [brand] + return annotate(ast, { brands }) +} + +/** + * Maps over the array but will return the original array if no changes occur. + * @internal + */ +export function mapOrSame(as: Arr.NonEmptyReadonlyArray, f: (a: A) => A): Arr.NonEmptyReadonlyArray +export function mapOrSame(as: ReadonlyArray, f: (a: A) => A): ReadonlyArray +export function mapOrSame(as: ReadonlyArray, f: (a: A) => A): ReadonlyArray { + let changed = false + const out: Array = new Array(as.length) + for (let i = 0; i < as.length; i++) { + const a = as[i] + const fa = f(a) + if (fa !== a) { + changed = true + } + out[i] = fa + } + return changed ? out : as +} + +/** @internal */ +export function annotateKey(ast: A, annotations: Schema.Annotations.Key): A { + const context = ast.context ? + new Context( + ast.context.isOptional, + ast.context.isMutable, + ast.context.constructorDefault, + { ...ast.context.annotations, ...annotations } + ) : + new Context(false, false, undefined, annotations) + return replaceContext(ast, context) +} + +/** @internal */ +export const optionalKey: (ast: A) => A = memoizeIdempotent((ast: A): A => { + const context = ast.context ? + ast.context.isOptional === false ? + new Context(true, ast.context.isMutable, ast.context.constructorDefault, ast.context.annotations) : + ast.context : + new Context(true, false) + return optionalKeyLastLink(replaceContext(ast, context)) +}) + +const optionalKeyLastLink = applyToLastLink(optionalKey) + +/** @internal */ +export const optional = memoize((ast: A): Union => + optionalKey(new Union([ast, undefined_], "anyOf")) +) + +/** @internal */ +export const mutableKey = memoizeIdempotent((ast: A): A => { + const context = ast.context ? + ast.context.isMutable === false ? + new Context(ast.context.isOptional, true, ast.context.constructorDefault, ast.context.annotations) : + ast.context : + new Context(false, true) + return mutableKeyLastLink(replaceContext(ast, context)) +}) + +const mutableKeyLastLink = applyToLastLink(mutableKey) + +/** @internal */ +export function withConstructorDefault( + ast: A, + defaultValue: Effect.Effect +): A { + const transformation = new SchemaTransformation.Transformation( + SchemaGetter.withDefault(defaultValue), + SchemaGetter.passthrough() + ) + const constructorDefault = new Link(unknown, transformation) + const context = ast.context ? + new Context(ast.context.isOptional, ast.context.isMutable, constructorDefault, ast.context.annotations) : + new Context(false, false, constructorDefault) + return replaceContext(ast, context) +} + +/** + * Attaches a `Transformation` to the `to` AST, making it decode from the + * `from` AST and encode back to it. + * + * **Details** + * + * This is the low-level primitive behind `Schema.transform` and + * `Schema.transformOrFail`. It appends a {@link Link} to the `to` node's + * encoding chain. + * + * - Returns a new AST with the same type as `to`. + * + * @see {@link Link} + * @see {@link Encoding} + * @see {@link flip} + * @category transforming + * @since 4.0.0 + */ +export function decodeTo( + from: AST, + to: A, + transformation: SchemaTransformation.Transformation +): A { + return appendTransformation(from, transformation, to) +} + +function parseParameter(ast: AST): { + literals: ReadonlyArray + parameters: ReadonlyArray +} { + const literals: Array = [] + const parameters: Array = [] + function go(ast: AST) { + switch (ast._tag) { + case "Literal": + if (Predicate.isPropertyKey(ast.literal)) { + literals.push(ast.literal) + } + return + case "UniqueSymbol": + literals.push(ast.symbol) + return + case "Never": + return + case "Union": + for (let i = 0; i < ast.types.length; i++) { + go(ast.types[i]) + } + return + default: + parameters.push(ast) + } + } + go(ast) + return { literals, parameters } +} + +/** @internal */ +export function record(key: AST, value: AST): Objects { + const { literals, parameters: indexSignatures } = parseParameter(key) + return new Objects( + literals.map((literal) => new PropertySignature(literal, value)), + indexSignatures.map((parameter) => new IndexSignature(parameter, value)) + ) +} + +// ------------------------------------------------------------------------------------- +// Public APIs +// ------------------------------------------------------------------------------------- + +/** + * Returns `true` if the AST node represents an optional property. + * + * **Details** + * + * Checks `ast.context?.isOptional`. Defaults to `false` when no + * {@link Context} is set. + * + * @see {@link optionalKey} + * @see {@link Context} + * @category predicates + * @since 4.0.0 + */ +export function isOptional(ast: AST): boolean { + return ast.context?.isOptional ?? false +} + +/** @internal */ +export function isMutable(ast: AST): boolean { + return ast.context?.isMutable ?? false +} + +function isStructuralCheck(check: Check): boolean { + return check.annotations?.[InternalAnnotations.STRUCTURAL_ANNOTATION_KEY] === true || + check._tag === "FilterGroup" && check.checks.every(isStructuralCheck) +} + +function extractStructuralChecks(checks: Checks): Checks | undefined { + function extract(check: Check): Array> { + if (isStructuralCheck(check)) return [check] + return check._tag === "FilterGroup" ? check.checks.flatMap(extract) : [] + } + const out = checks.flatMap(extract) + return Arr.isArrayNonEmpty(out) ? out : undefined +} + +/** + * Strips all encoding transformations from an AST, returning the decoded + * (type-level) representation. + * + * **Details** + * + * - Memoized: same input reference → same output reference. + * - Recursively walks into composite nodes ({@link Arrays}, {@link Objects}, + * {@link Union}, {@link Suspend}). + * + * **Example** (Getting the type AST) + * + * ```ts import.meta.vitest + * import { Schema, SchemaAST } from "effect" + * + * const schema = Schema.NumberFromString + * const typeAst = SchemaAST.toType(schema.ast) + * typeAst._tag // => "Number" + * ``` + * + * @see {@link toEncoded} + * @see {@link flip} + * @category transforming + * @since 4.0.0 + */ +export const toType = memoizeIdempotent((ast: A): A => { + if (ast.encoding) { + return toType(replaceEncoding(ast, undefined)) + } + const out: any = ast + const type = out.recur?.(toType) ?? out + const encodingChecks: Checks | undefined = type.encodingChecks + if (encodingChecks) { + const checks = type === ast + ? encodingChecks + : isArrays(type) || isObjects(type) || isDeclaration(type) && type.typeParameters.length > 0 + ? extractStructuralChecks(encodingChecks) + : undefined + return modifyOwnPropertyDescriptors(type, (d) => { + d.encodingChecks.value = undefined + d.checks.value = combineChecks(type.checks, checks) + }) + } + return type +}) + +/** + * Returns the encoded (wire-format) AST by flipping and then stripping + * encodings. + * + * **Details** + * + * Equivalent to `toType(flip(ast))`. This gives you the AST that describes + * the shape of the serialized/encoded data. + * + * - Memoized: same input reference → same output reference. + * + * **Example** (Getting the encoded AST) + * + * ```ts import.meta.vitest + * import { Schema, SchemaAST } from "effect" + * + * const schema = Schema.NumberFromString + * const encodedAst = SchemaAST.toEncoded(schema.ast) + * encodedAst._tag // => "String" + * ``` + * + * @see {@link toType} + * @see {@link flip} + * @category transforming + * @since 4.0.0 + */ +export const toEncoded = memoizeIdempotent((ast: AST): AST => { + return toType(flip(ast)) +}) + +function flipEncoding(ast: AST, encoding: Encoding): AST { + const links = encoding + const len = links.length + const last = links[len - 1] + const ls: Arr.NonEmptyArray = [ + new Link(flip(replaceEncoding(ast, undefined)), links[0].transformation.flip()) + ] + for (let i = 1; i < len; i++) { + ls.unshift(new Link(flip(links[i - 1].to), links[i].transformation.flip())) + } + const to = flip(last.to) + if (to.encoding) { + return replaceEncoding(to, [...to.encoding, ...ls]) + } else { + return replaceEncoding(to, ls) + } +} + +/** + * Swaps the decode and encode directions of an AST's {@link Encoding} chain. + * + * **Details** + * + * After flipping, what was decoding becomes encoding and vice versa. This is + * the core operation behind `Schema.encode` — encoding a value is decoding + * with a flipped SchemaAST. + * + * - Memoized: same input reference → same output reference. + * - Recursively walks composite nodes. + * + * @see {@link toType} + * @see {@link toEncoded} + * @category transforming + * @since 4.0.0 + */ +export const flip = memoize((ast: AST): AST => { + if (ast.encoding) { + return flipEncoding(ast, ast.encoding) + } + const out: any = ast + return out.flip?.(flip) ?? out.recur?.(flip) ?? out +}) + +/** @internal */ +export function containsUndefined(ast: AST): boolean { + switch (ast._tag) { + case "Undefined": + return true + case "Union": + return ast.types.some(containsUndefined) + default: + return false + } +} + +function fromConst( + ast: AST, + value: T +): SchemaParser.Parser { + const succeed = InternalParser.succeed(value) + return (input, options) => { + if (input === InternalParser.missing) return InternalParser.missingExit + if (input === value) return succeed + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } +} + +function fromRefinement( + ast: AST, + refinement: (input: unknown) => input is T +): SchemaParser.Parser { + return (input, options) => { + if (input === InternalParser.missing) return InternalParser.missingExit + if (refinement(input)) return InternalParser.sameExit + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } +} + +function segmentTemplateLiteralParts( + ast: TemplateLiteral, + input: string, + options: ParseOptions +): Array | undefined { + const parts = ast.encodedParts + const literals = ast.literals + const inputLength = input.length + for (let i = 0; i < literals.length; i++) { + const literal = literals[i] + if (literal && !input.includes(literal)) return undefined + } + if (ast.suffixLengths[0] > inputLength) return undefined + + const out = new Array(parts.length) + let failures: Set | undefined + function go(i: number, pos: number): boolean { + if (i === parts.length) return pos === inputLength + if (failures?.has(i * (inputLength + 1) + pos)) return false + const part = parts[i] + if (i === parts.length - 1) { + const s = input.slice(pos) + if (part.matchPart(s, options) !== undefined) { + out[i] = s + return true + } + } else if (part._tag === "Literal") { + const s = literals[i]! + if (input.startsWith(s, pos) && go(i + 1, pos + s.length)) { + out[i] = s + return true + } + } else { + const maximumEnd = inputLength - ast.suffixLengths[i + 1] + // Splits preceding a literal only need to consider occurrences of that literal. + const anchor = literals[i + 1] + let end = anchor === undefined ? maximumEnd : input.lastIndexOf(anchor, maximumEnd) + while (end >= pos) { + const s = input.slice(pos, end) + if (part.matchPart(s, options) !== undefined && go(i + 1, end)) { + out[i] = s + return true + } + if (end === 0) break + end = anchor === undefined ? end - 1 : input.lastIndexOf(anchor, end - 1) + } + } + failures ??= new Set() + failures.add(i * (inputLength + 1) + pos) + return false + } + return go(0, 0) ? out : undefined +} + +/** @internal */ +export const enumsToLiterals = memoize((ast: Enum): Union => { + return new Union( + ast.enums.map((e) => new Literal(e[1], { title: e[0] })), + "anyOf" + ) +}) + +const parameterFromPropertyKey = applyToSelfOrLastLinkEncodingIdempotent((ast) => { + switch (ast._tag) { + default: + return ast + case "Number": + return ast.toCodecStringTree() + case "Union": + return ast.recur(parameterFromPropertyKey) + } +}) + +/** @internal */ +export const parameterFromString = applyToSelfOrLastLinkEncodingIdempotent((ast) => { + switch (ast._tag) { + default: + return ast + case "Symbol": + case "UniqueSymbol": + return ast.toCodecStringTree() + case "Union": + return ast.recur(parameterFromString) + } +}) + +const partFromString = applyToSelfOrLastLinkEncodingIdempotent((ast) => { + switch (ast._tag) { + default: + return ast + case "Number": + case "Literal": + case "BigInt": + return ast.toCodecStringTree() + case "Union": + return ast.recur(partFromString) + } +}) + +/** + * any string, including newlines + * @internal + */ +export const STRING_PATTERN = "[\\s\\S]*?" + +const isStringFiniteRegExp = new globalThis.RegExp(`^${FINITE_PATTERN}$`) + +const isStringNumberRegExp = new globalThis.RegExp(`^(?:${FINITE_PATTERN}|Infinity|-Infinity|NaN)$`) + +/** @internal */ +export function isStringFinite(annotations?: Schema.Annotations.Filter) { + return isPattern( + isStringFiniteRegExp, + { + expected: "a string representing a finite number", + representation: { + id: "effect/schema/isStringFinite", + payload: null + }, + toJsonSchema: () => ({ pattern: isStringFiniteRegExp.source }), + ...annotations + } + ) +} + +const finiteString = appendChecks(string, [isStringFinite()]) + +const finiteToString = new Link( + finiteString, + SchemaTransformation.numberFromString +) + +const numberToString = new Link( + new Union([finiteString, nonFiniteLiterals], "anyOf"), + SchemaTransformation.numberFromString +) + +/** + * signed integer only (no leading "+" because TypeScript doesn't support it) + */ +const BIGINT_PATTERN = "-?\\d+" + +const isStringBigIntRegExp = new globalThis.RegExp(`^${BIGINT_PATTERN}$`) + +/** @internal */ +export function isStringBigInt(annotations?: Schema.Annotations.Filter) { + return isPattern( + isStringBigIntRegExp, + { + expected: "a string representing a bigint", + representation: { + id: "effect/schema/isStringBigInt", + payload: null + }, + toJsonSchema: () => ({ pattern: isStringBigIntRegExp.source }), + ...annotations + } + ) +} + +/** @internal */ +export const bigIntString = appendChecks(string, [isStringBigInt({ + expected: "a string representing a bigint" +})]) + +const bigIntToString = new Link( + bigIntString, + SchemaTransformation.bigintFromString +) + +const REGEXP_PATTERN = "Symbol\\((.*)\\)" + +const isStringSymbolRegExp = new globalThis.RegExp(`^${REGEXP_PATTERN}$`) + +/** @internal */ +export const symbolString = appendChecks(string, [isStringSymbol()]) + +/** + * to distinguish between Symbol and String, we need to add a check to the string keyword + */ +const symbolToString = new Link( + symbolString, + new SchemaTransformation.Transformation( + SchemaGetter.transform((description) => globalThis.Symbol.for(isStringSymbolRegExp.exec(description)![1])), + SchemaGetter.transformOrFail((sym: symbol, options) => { + const key = globalThis.Symbol.keyFor(sym) + if (key !== undefined) { + return Effect.succeed(globalThis.String(sym)) + } + return Effect.fail( + new SchemaIssue.Forbidden( + { message: "cannot serialize to string, Symbol is not registered" }, + sym, + options + ) + ) + }) + ) +) + +/** @internal */ +export function isStringSymbol(annotations?: Schema.Annotations.Filter) { + return isPattern( + isStringSymbolRegExp, + { + expected: "a string representing a symbol", + representation: { + id: "effect/schema/isStringSymbol", + payload: null + }, + toJsonSchema: () => ({ pattern: isStringSymbolRegExp.source }), + ...annotations + } + ) +} + +/** @internal */ +export function collectIssues( + checks: ReadonlyArray>, + value: T, + issues: Arr.NonEmptyArray | undefined, + ast: AST, + options: ParseOptions +): Arr.NonEmptyArray | undefined { + for (let i = 0; i < checks.length; i++) { + const check = checks[i] + if (check._tag === "FilterGroup") { + issues = collectIssues(check.checks, value, issues, ast, options) + if ( + issues && + (options.errors !== "all" || (issues[issues.length - 1] as SchemaIssue.Filter).filter.aborted) + ) { + return issues + } + } else { + const issue = check.run(value, ast, options) + if (issue) { + const filter = new SchemaIssue.Filter(check, issue, value, options) + if (issues) issues.push(filter) + else issues = [filter] + if (options.errors !== "all" || check.aborted) { + return issues + } + } + } + } + return issues +} + +/** @internal */ +export function runChecks( + checks: readonly [Check, ...Array>], + s: T +): Result.Result { + const issues = collectIssues(checks, s, undefined, unknown, { errors: "all" }) + if (issues) { + const issue = new SchemaIssue.Composite(unknown, issues) + return Result.fail(issue) + } + return Result.succeed(s) +} + +/** @internal */ +export interface ConstructorDescriptor { + readonly isConstructed: Predicate.Predicate + readonly link: Link +} + +/** @internal */ +export function getConstructorDescriptor(ast: AST): ConstructorDescriptor | undefined { + if (!isDeclaration(ast)) return undefined + const getDescriptor = ast.annotations?.[InternalAnnotations.CONSTRUCTOR_ANNOTATION_KEY] + return Predicate.isFunction(getDescriptor) ? getDescriptor(ast.typeParameters) : undefined +} + +/** + * Returns all annotations from the AST node. + * + * **Details** + * + * If the node has {@link Checks}, returns annotations from the last check + * (which is where user-supplied annotations end up after `.pipe(Schema.annotations(...))`). + * Otherwise returns `Base.annotations` directly. + * + * **Example** (Reading annotations) + * + * ```ts import.meta.vitest + * import { Schema, SchemaAST } from "effect" + * + * const schema = Schema.String.annotate({ title: "Name" }) + * const annotations = SchemaAST.resolve(schema.ast) + * annotations?.title // => "Name" + * ``` + * + * @see {@link resolveAt} + * @see {@link resolveIdentifier} + * @see {@link resolveTitle} + * @see {@link resolveDescription} + * @category annotations + * @since 4.0.0 + */ +export const resolve: (ast: AST) => Schema.Annotations.Annotations | undefined = InternalAnnotations.resolve + +/** + * Returns a single annotation value by key from the AST node. + * + * **Details** + * + * Like {@link resolve}, reads from the last check's annotations when checks + * are present. Returns `undefined` if the key is not found. + * + * @see {@link resolve} + * @category annotations + * @since 4.0.0 + */ +export const resolveAt: (key: string) => (ast: AST) => A | undefined = InternalAnnotations.resolveAt + +/** + * Returns the `identifier` annotation from the AST node, if set. + * + * **Details** + * + * The identifier is typically set by `Schema.annotations({ identifier: "..." })` + * and is used for error messages and schema identification. + * + * @see {@link resolve} + * @see {@link resolveTitle} + * @category annotations + * @since 4.0.0 + */ +export const resolveIdentifier: (ast: AST) => string | undefined = InternalAnnotations.resolveIdentifier + +/** + * Returns the `title` annotation from the AST node, if set. + * + * @see {@link resolve} + * @see {@link resolveIdentifier} + * @see {@link resolveDescription} + * + * @category annotations + * @since 4.0.0 + */ +export const resolveTitle: (ast: AST) => string | undefined = InternalAnnotations.resolveTitle + +/** + * Returns the `description` annotation from the AST node, if set. + * + * @see {@link resolve} + * @see {@link resolveTitle} + * @see {@link resolveIdentifier} + * + * @category annotations + * @since 4.0.0 + */ +export const resolveDescription: (ast: AST) => string | undefined = InternalAnnotations.resolveDescription + +type TreeFrame = { + readonly value: object + // Object keys or an array length snapshot. + readonly keys: ReadonlyArray | number + index: number +} + +function isJsonLeaf(u: unknown): boolean { + return u === null || typeof u === "string" || typeof u === "boolean" || + typeof u === "number" && globalThis.Number.isFinite(u) +} + +function isStringTreeLeaf(u: unknown): boolean { + return u === undefined || typeof u === "string" +} + +function isTree(u: unknown, isLeaf: (u: unknown) => boolean): boolean { + const cache = new WeakMap() + const stack: Array = [] + outer: while (true) { + if (typeof u !== "object" || u === null) { + if (!isLeaf(u)) { + return false + } + } else { + const value = u + const cached = cache.get(value) + // `false` marks a node on the current path, while `true` marks a fully + // validated node that can be safely reused by a DAG. + if (cached === false) { + return false + } + if (cached === undefined) { + const isArray = Array.isArray(value) + if (!isArray) { + const prototype = Object.getPrototypeOf(value) + // A plain object from another realm has a different Object.prototype, + // but that prototype still has a null prototype. + if ( + prototype !== null && + prototype !== Object.prototype && + Object.getPrototypeOf(prototype) !== null + ) { + return false + } + } + cache.set(value, false) + stack.push({ + value, + keys: isArray ? value.length : Object.keys(value), + index: 0 + }) + } + } + + while (stack.length > 0) { + const frame = stack[stack.length - 1] + const keys = frame.keys + if (typeof keys === "number") { + if (frame.index < keys) { + // A sparse slot is read as `undefined`; the leaf predicate determines + // whether that is valid for the current tree. + u = (frame.value as ReadonlyArray)[frame.index++] + continue outer + } + } else if (frame.index < keys.length) { + u = (frame.value as Record)[keys[frame.index++]] + continue outer + } + cache.set(frame.value, true) + stack.pop() + } + return true + } +} + +/** + * Returns true if the value is a JSON value. + * + * When a cyclic reference is detected, returns false. + * + * @internal + */ +export function isJson(u: unknown): u is Schema.Json { + return isTree(u, isJsonLeaf) +} + +/** @internal */ +export const Json = new Declaration( + [], + () => (input, ast, options) => + isJson(input) ? + InternalParser.sameExit : + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)), + { + representation: { + id: "effect/schema/Json", + payload: null + }, + expected: "JSON value", + toCodecJson: () => undefined, + toCodecStringTree: () => unknownToStringTree, + toArbitrary: () => (fc: typeof FastCheck) => fc.jsonValue() + } +) + +/** @internal */ +export const MutableJson = annotate(Json, { + representation: { + id: "effect/schema/MutableJson", + payload: null + } +}) + +/** @internal */ +export const unknownToJson = new Link( + Json, + SchemaTransformation.passthrough() +) + +/** @internal */ +export const objectKeywordToJson = new Link( + new Union([ + new Arrays(false, [], [Json]), + new Objects([], [new IndexSignature(string, Json)]) + ], "anyOf"), + SchemaTransformation.passthrough() +) + +/** + * Returns true if the value is a StringTree value. + * + * When a cyclic reference is detected, returns false. + * + * @internal + */ +export function isStringTree(u: unknown): u is Schema.StringTree { + return isTree(u, isStringTreeLeaf) +} + +const StringTree = new Declaration( + [], + () => (input, ast, options) => + isStringTree(input) ? + InternalParser.sameExit : + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)), + { expected: "StringTree", toCodecStringTree: () => undefined } +) + +/** @internal */ +export const unknownToStringTree = new Link( + StringTree, + SchemaTransformation.passthrough() +) diff --git a/.repos/effect/packages/effect/src/SchemaGetter.ts b/.repos/effect/packages/effect/src/SchemaGetter.ts new file mode 100644 index 000000000..321096f0a --- /dev/null +++ b/.repos/effect/packages/effect/src/SchemaGetter.ts @@ -0,0 +1,1994 @@ +/** + * Builds one-way conversions used by schemas. + * + * A `Getter` receives an optional encoded value and returns an + * optional decoded value. It can also report a schema issue or require Effect + * services. Schema transformations use getters to describe one direction of a + * conversion, for example decoding a field from input data. This module + * includes basic getters, validation helpers, pure and effectful conversions, + * and ready-made conversions for common string, number, binary, date, form, and + * URL-related values. + * + * @since 4.0.0 + */ +import * as DateTime from "./DateTime.ts" +import * as Effect from "./Effect.ts" +import * as Encoding from "./Encoding.ts" +import * as InternalRecord from "./internal/record.ts" +import * as Option from "./Option.ts" +import * as Pipeable from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import * as Result from "./Result.ts" +import type * as Schema from "./Schema.ts" +import type * as SchemaAST from "./SchemaAST.ts" +import * as SchemaIssue from "./SchemaIssue.ts" +import * as Str from "./String.ts" + +/** + * Represents a composable transformation from an encoded type `E` to a decoded type `T`. + * + * **When to use** + * + * Use when you need a schema getter to build and compose custom transformations + * for `Schema.decodeTo` or `Schema.decode`. + * + * **Details** + * + * A getter wraps a function `Option -> Effect, Issue, R>`. It + * receives `Option.None` when the encoded key is absent, such as a missing + * struct field, and returns `Option.None` to omit the value from the decoded + * output. It fails with `Issue` on invalid input and may require Effect + * services via `R`. `.map(f)` applies `f` to the decoded value inside `Some` + * while leaving `None` unchanged. `.compose(other)` chains two getters by + * feeding the output of `this` into `other`; passthrough getters on either side + * are optimized away. + * + * **Example** (Creating and composing getters) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const parseNumber = SchemaGetter.transform((s) => Number(s)) + * const double = SchemaGetter.transform((n) => n * 2) + * const composed = parseNumber.compose(double) + * await Effect.runPromise(composed.run(Option.some("21"), {})) // => Option.some(42) + * ``` + * + * @see {@link transform} to create a getter from a pure function + * @see {@link passthrough} for the identity getter + * @see {@link transformOrFail} for fallible transformation + * + * @category models + * @since 4.0.0 + */ +export class Getter extends Pipeable.Class { + readonly run: ( + input: Option.Option, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, R> + + constructor( + run: ( + input: Option.Option, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, R> + ) { + super() + this.run = run + } + map(f: (t: T) => T2): Getter { + return new Getter((oe, options) => this.run(oe, options).pipe(Effect.mapEager(Option.map(f)))) + } + compose(other: Getter): Getter { + if (isPassthrough(this)) { + return other as any + } + if (isPassthrough(other)) { + return this as any + } + return new Getter((oe, options) => this.run(oe, options).pipe(Effect.flatMapEager((ot) => other.run(ot, options)))) + } +} + +/** + * Creates a getter that always produces the given constant value, ignoring the input. + * + * **When to use** + * + * Use when you need a schema getter that always decodes a field to a fixed + * value. + * + * **Details** + * + * The getter is pure and always returns `Option.some(t)` regardless of whether + * the input is `Some` or `None`. + * + * **Example** (Returning a constant getter) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const alwaysZero = SchemaGetter.succeed(0) + * await Effect.runPromise(alwaysZero.run(Option.none(), {})) // => Option.some(0) + * ``` + * + * @see {@link transform} when you need to use the input value + * @see {@link passthrough} when you want to keep the input as-is + * + * @category constructors + * @since 4.0.0 + */ +export function succeed(t: T): Getter { + return new Getter(() => Effect.succeedSome(t)) +} + +/** + * Creates a getter that always fails with the given issue. + * + * **When to use** + * + * Use when you need a schema getter that unconditionally rejects input. + * - Building custom validation getters that produce specific error types. + * + * **Details** + * + * - Always fails with the `Issue` returned by `f`. + * - The failure function receives the original `Option` input and the + * effective `ParseOptions` for error context. + * + * **Example** (Defining an always-failing getter) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter, SchemaIssue } from "effect" + * + * const rejectAll = SchemaGetter.fail( + * () => new SchemaIssue.InvalidValue({ message: "not allowed" }) + * ) + * const issue = await Effect.runPromise(Effect.flip(rejectAll.run(Option.some("x"), {}))) + * issue._tag // => "InvalidValue" + * ``` + * + * @see {@link forbidden} for a convenience helper for `Forbidden` issues + * @see {@link checkEffect} to fail conditionally based on input value + * + * @category constructors + * @since 4.0.0 + */ +export function fail( + f: (oe: Option.Option, options: SchemaAST.ParseOptions) => SchemaIssue.Issue +): Getter { + return new Getter((oe, options) => Effect.fail(f(oe, options))) +} + +/** + * Creates a getter that always fails with a `Forbidden` issue. + * + * **When to use** + * + * Use when you need a schema getter to disallow a field or direction + * (encode/decode) entirely. + * - You want a clear "forbidden" error message in schema validation output. + * + * **Details** + * + * - Always fails with `SchemaIssue.Forbidden`. + * - The message function receives the `Option` input for context. + * + * **Example** (Forbidding a decode direction) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const noEncode = SchemaGetter.forbidden( + * () => "encoding is not supported" + * ) + * const issue = await Effect.runPromise(Effect.flip(noEncode.run(Option.some(1), {}))) + * issue._tag // => "Forbidden" + * ``` + * + * @see {@link fail} to fail with a custom issue type + * + * @category constructors + * @since 4.0.0 + */ +export function forbidden(message: (oe: Option.Option) => string): Getter { + return fail((oe, options) => { + const annotations = { message: message(oe) } + return Option.isSome(oe) + ? new SchemaIssue.Forbidden(annotations, oe.value, options) + : new SchemaIssue.Forbidden(annotations) + }) +} + +const passthrough_ = new Getter(Effect.succeed) + +function isPassthrough(getter: Getter): getter is typeof passthrough_ { + return getter.run === passthrough_.run +} + +/** + * Returns the identity getter — passes the value through unchanged. + * + * **When to use** + * + * Use when you need a schema getter for one side of a `decodeTo` pair, either + * encode or decode, to pass values through unchanged. + * + * **Details** + * + * - Pure, no allocation (singleton instance). + * - Optimized away during `.compose()` — composing with a passthrough is free. + * - The default overload requires `T === E`. Pass `{ strict: false }` to opt + * out of the type constraint. + * + * **Example** (Passing through identity transformations) + * + * ```ts import.meta.vitest + * import { Schema, SchemaGetter } from "effect" + * + * // No transformation needed — types already match + * const StringToString = Schema.String.pipe( + * Schema.decodeTo(Schema.String, { + * decode: SchemaGetter.passthrough(), + * encode: SchemaGetter.passthrough() + * }) + * ) + * Schema.decodeSync(StringToString)("hello") // => "hello" + * ``` + * + * @see {@link passthroughSupertype} when `T extends E` + * @see {@link passthroughSubtype} when `E extends T` + * @see {@link transform} when you need to change the value + * + * @category constructors + * @since 4.0.0 + */ +export function passthrough(options: { readonly strict: false }): Getter +export function passthrough(): Getter +export function passthrough(): Getter { + return passthrough_ +} + +/** + * Returns the identity getter typed for the relationship `T extends E`. + * + * **When to use** + * + * Use when you need a schema getter that passes values through when the + * decoded/output type is narrower than the encoded/input type. + * + * **Details** + * + * - Same singleton as {@link passthrough} — no allocation, optimized in composition. + * + * **Example** (Passing through supertypes) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * // string extends string, so this is valid + * const g = SchemaGetter.passthroughSupertype() + * await Effect.runPromise(g.run(Option.some("hello"), {})) // => Option.some("hello") + * ``` + * + * @see {@link passthrough} when types are identical + * @see {@link passthroughSubtype} when `E extends T` + * + * @category constructors + * @since 4.0.0 + */ +export function passthroughSupertype(): Getter +export function passthroughSupertype(): Getter { + return passthrough_ +} + +/** + * Returns the identity getter, typed for when the encoded type `E` is a subtype of `T`. + * + * **When to use** + * + * Use when you need a schema getter that passes values through without + * `{ strict: false }` for an encoded type that narrows the decoded type. + * + * **Details** + * + * - Same singleton as {@link passthrough} — no allocation, optimized in composition. + * + * **Example** (Passing through subtypes) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * // "hello" extends string, so E extends T + * const g = SchemaGetter.passthroughSubtype() + * await Effect.runPromise(g.run(Option.some("hello"), {})) // => Option.some("hello") + * ``` + * + * @see {@link passthrough} when types are identical + * @see {@link passthroughSupertype} when `T extends E` + * + * @category constructors + * @since 4.0.0 + */ +export function passthroughSubtype(): Getter +export function passthroughSubtype(): Getter { + return passthrough_ +} + +/** + * Creates a getter that handles the case when the input is absent (`Option.None`). + * + * **When to use** + * + * Use when you need a schema getter to provide a fallback or computed value for + * missing struct keys. + * - Building custom "default value" logic more complex than {@link withDefault}. + * + * **Details** + * + * - When input is `None`, calls `f` to produce the result. + * - When input is `Some`, passes it through unchanged. + * - `f` receives the parse options and may return `None` to keep the value absent. + * + * **Example** (Providing a default timestamp for a missing field) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const withTimestamp = SchemaGetter.onNone(() => + * Effect.succeed(Option.some(0)) + * ) + * await Effect.runPromise(withTimestamp.run(Option.none(), {})) // => Option.some(0) + * ``` + * + * @see {@link required} when absent input should fail + * @see {@link withDefault} for a simpler default value for undefined inputs + * @see {@link onSome} to handle only present values + * + * @category transforming + * @since 4.0.0 + */ +export function onNone( + f: (options: SchemaAST.ParseOptions) => Effect.Effect, SchemaIssue.Issue, R> +): Getter { + return new Getter((ot, options) => Option.isNone(ot) ? f(options) : Effect.succeed(ot)) +} + +/** + * Creates a getter that fails with `MissingKey` if the input is absent (`Option.None`). + * + * **When to use** + * + * Use when you need a schema getter to require a struct field in the encoded + * input and report a missing key error when it is absent. + * + * **Details** + * + * - When input is `None`, fails with `SchemaIssue.MissingKey`. + * - When input is `Some`, passes it through unchanged. + * - Optional `annotations` customize the error message for the missing key. + * + * **Example** (Defining a required struct field) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const mustExist = SchemaGetter.required() + * const issue = await Effect.runPromise(Effect.flip(mustExist.run(Option.none(), {}))) + * issue._tag // => "MissingKey" + * ``` + * + * @see {@link onNone} to provide a fallback instead of failing + * @see {@link withDefault} to substitute a default for undefined values + * + * @category validation + * @since 4.0.0 + */ +export function required(annotations?: Schema.Annotations.Key): Getter { + return onNone(() => Effect.fail(new SchemaIssue.MissingKey(annotations))) +} + +/** + * Creates a getter that handles present values (`Option.Some`), passing `None` through. + * + * **When to use** + * + * Use when you need a schema getter to transform or validate only when a field + * value is present. + * - Missing keys should remain absent in the output. + * + * **Details** + * + * - When input is `None`, returns `None` (no-op). + * - When input is `Some(e)`, calls `f(e, options)` to produce the result. + * - `f` may return `None` to omit the value, or fail with an `Issue`. + * + * **Example** (Transforming only present values) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const parseIfPresent = SchemaGetter.onSome( + * (s) => Effect.succeed(Option.some(Number(s))) + * ) + * await Effect.runPromise(parseIfPresent.run(Option.some("42"), {})) // => Option.some(42) + * ``` + * + * @see {@link onNone} to handle only absent values + * @see {@link transform} for a simpler pure transformation of present values + * @see {@link transformOrFail} for fallible transformation of present values + * + * @category transforming + * @since 4.0.0 + */ +export function onSome( + f: (e: E, options: SchemaAST.ParseOptions) => Effect.Effect, SchemaIssue.Issue, R> +): Getter { + return new Getter((oe, options) => Option.isNone(oe) ? Effect.succeedNone : f(oe.value, options)) +} + +/** + * Creates a getter that validates a value using an effectful check function. + * + * **When to use** + * + * Use when you need a schema getter to validate a decoded value (e.g. check a + * constraint or call an external service). + * - The validation may be asynchronous or require Effect services. + * + * **Details** + * + * - Only runs when input is `Some` — `None` passes through. + * - The check function returns a validation result: + * - `undefined` or `true` — value is valid, passes through. + * - `false` or a `string` — value is invalid, fails with an `Issue`. + * - An `Issue` object — fails with that issue directly. + * - `{ path, issue }` — fails with a nested path issue (`issue` may be a + * message string or a full {@link SchemaIssue.Issue}). + * - Does not transform the value — input and output types are the same. + * + * **Example** (Validating effectfully) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const nonNegative = SchemaGetter.checkEffect((n) => + * Effect.succeed(n >= 0 ? undefined : "must be non-negative") + * ) + * await Effect.runPromise(nonNegative.run(Option.some(1), {})) // => Option.some(1) + * ``` + * + * @see {@link transform} when you need to change the value, not just validate + * @see {@link fail} for unconditional failure + * + * @category validation + * @since 4.0.0 + */ +export function checkEffect( + f: (input: T, options: SchemaAST.ParseOptions) => Effect.Effect< + undefined | boolean | Schema.FilterIssue, + never, + R + > +): Getter { + return onSome((t, options) => { + return f(t, options).pipe(Effect.flatMapEager((out) => { + const issue = SchemaIssue.makeSingle(out, t, options) + return issue ? + Effect.fail(issue) : + Effect.succeed(Option.some(t)) + })) + }) +} + +/** + * Creates a getter that applies a pure function to present values. + * + * **When to use** + * + * Use when you need a schema getter for a pure, infallible transformation + * between types. + * - Building encode/decode pairs for `Schema.decodeTo`. + * + * **Details** + * + * - This is the most commonly used constructor. + * - Transforms `Some(e)` to `Some(f(e))` and leaves `None` unchanged. + * - Skips `None` inputs — only called when a value is present. + * - Never fails. + * + * **Example** (Transforming strings to numbers) + * + * ```ts import.meta.vitest + * import { Schema, SchemaGetter } from "effect" + * + * const NumberFromString = Schema.String.pipe( + * Schema.decodeTo(Schema.Number, { + * decode: SchemaGetter.transform((s) => Number(s)), + * encode: SchemaGetter.transform((n) => String(n)) + * }) + * ) + * Schema.decodeSync(NumberFromString)("42") // => 42 + * ``` + * + * @see {@link transformOrFail} when the transformation can fail + * @see {@link transformOptional} when you need to handle `None` inputs + * @see {@link passthrough} when no transformation is needed + * + * @category transforming + * @since 4.0.0 + */ +export function transform(f: (e: E) => T): Getter { + return transformOptional(Option.map(f)) +} + +/** + * Creates a getter that applies a fallible, effectful transformation to present values. + * + * **When to use** + * + * Use when you need a schema getter for a transformation that may fail, require + * Effect services, or run asynchronously. + * + * **Details** + * + * - Skips `None` inputs — only called when a value is present. + * - On success, wraps the result in `Some`. + * - On failure, propagates the `Issue`. + * + * **Example** (Parsing with failure) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter, SchemaIssue } from "effect" + * + * const safeParseInt = SchemaGetter.transformOrFail( + * (s, options) => { + * const n = parseInt(s, 10) + * return isNaN(n) + * ? Effect.fail(new SchemaIssue.InvalidValue({ message: "not an integer" }, s, options)) + * : Effect.succeed(n) + * } + * ) + * await Effect.runPromise(safeParseInt.run(Option.some("42"), {})) // => Option.some(42) + * ``` + * + * @see {@link transform} when transformation cannot fail + * @see {@link onSome} when you need full `Option` control over the output + * + * @category transforming + * @since 4.0.0 + */ +export function transformOrFail( + f: (e: E, options: SchemaAST.ParseOptions) => Effect.Effect +): Getter { + return onSome((e, options) => f(e, options).pipe(Effect.mapEager(Option.some))) +} + +/** + * Creates a getter that transforms the full `Option` — both present and absent values. + * + * **When to use** + * + * Use when you need a schema getter to handle both `Some` and `None` cases. + * + * **Details** + * + * The getter is pure and never fails. It receives the full `Option` and + * must return `Option`, so it can turn a present value into absent or an + * absent value into present. + * + * **Example** (Filtering out empty strings) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const skipEmpty = SchemaGetter.transformOptional((o) => + * Option.filter(o, (s) => s.length > 0) + * ) + * await Effect.runPromise(skipEmpty.run(Option.some(""), {})) // => Option.none() + * ``` + * + * @see {@link transform} when you only need to transform present values + * @see {@link omit} when you always want `None` + * + * @category transforming + * @since 4.0.0 + */ +export function transformOptional(f: (oe: Option.Option) => Option.Option): Getter { + return new Getter((oe) => Effect.succeed(f(oe))) +} + +/** + * Creates a getter that always returns `None`, effectively omitting the value from output. + * + * **When to use** + * + * Use when you need a schema getter to exclude a field during decoding or + * encoding. + * + * **Details** + * + * - Always returns `Option.None` regardless of input. + * - Never fails. + * + * **Example** (Omitting a field during encoding) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const omitField = SchemaGetter.omit() + * await Effect.runPromise(omitField.run(Option.some("hidden"), {})) // => Option.none() + * ``` + * + * @see {@link transformOptional} when you want conditional omission + * @see {@link forbidden} when you want to fail instead of silently omit + * + * @category filtering + * @since 4.0.0 + */ +export function omit(): Getter { + return new Getter(() => Effect.succeedNone) +} + +/** + * Creates a getter that replaces `undefined` values with a default. + * + * **When to use** + * + * Use when you need a schema getter to provide a fallback for a field that may + * be `undefined` in the encoded input. + * + * **Details** + * + * - If the input is `Some(undefined)` or `None`, produces `Some(T)`. + * - If the input is `Some(value)` where value is not `undefined`, passes it through. + * - `defaultValue` is an `Effect` that will be executed each time a default is needed. + * + * **Example** (Providing a default value for an optional field) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const withZero = SchemaGetter.withDefault(Effect.succeed(0)) + * await Effect.runPromise(withZero.run(Option.some(undefined), {})) // => Option.some(0) + * ``` + * + * @see {@link onNone} to handle only absent keys (not `undefined` values) + * @see {@link required} when absent input should fail instead of using a default + * + * @category transforming + * @since 4.0.0 + */ +export function withDefault( + defaultValue: Effect.Effect +): Getter { + return new Getter((o) => { + const filtered = Option.filter(o, Predicate.isNotUndefined) + return Option.isSome(filtered) ? Effect.succeed(filtered) : Effect.mapEager(defaultValue, Option.some) + }) +} + +/** + * Coerces any value to a `string` using the global `String()` constructor. + * + * **When to use** + * + * Use when you need a schema getter to coerce a present encoded value to a + * string with `String()`. + * + * **Details** + * + * The getter is pure, never fails, and delegates to `globalThis.String`. + * + * **Example** (Coercing to a string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const toString = SchemaGetter.String() + * await Effect.runPromise(toString.run(Option.some(42), {})) // => Option.some("42") + * ``` + * + * @see {@link transform} for custom string conversions + * + * @category converting + * @since 4.0.0 + */ +export function String(): Getter { + return transform(globalThis.String) +} + +/** + * Coerces any value to a `number` using the global `Number()` constructor. + * + * **When to use** + * + * Use when you need a schema getter to coerce a present encoded value to a + * number with `Number()`. + * + * **Details** + * + * The getter is pure, never fails, and delegates to `globalThis.Number`. It may + * produce `NaN` for non-numeric inputs. + * + * **Example** (Coercing to a number) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const toNumber = SchemaGetter.Number() + * await Effect.runPromise(toNumber.run(Option.some("42"), {})) // => Option.some(42) + * ``` + * + * @see {@link transformOrFail} for validated number parsing + * + * @category converting + * @since 4.0.0 + */ +export function Number(): Getter { + return transform(globalThis.Number) +} + +/** + * Coerces any value to a `boolean` using the global `Boolean()` constructor. + * + * **When to use** + * + * Use when you need a schema getter to coerce a present encoded value to a + * boolean with `Boolean()`. + * + * **Details** + * + * The getter is pure, never fails, and delegates to `globalThis.Boolean`. + * + * **Example** (Coercing to a boolean) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const toBool = SchemaGetter.Boolean() + * await Effect.runPromise(toBool.run(Option.some("true"), {})) // => Option.some(true) + * ``` + * + * @category converting + * @since 4.0.0 + */ +export function Boolean(): Getter { + return transform(globalThis.Boolean) +} + +/** + * Coerces a value to `bigint` using the global `BigInt()` constructor. + * + * **When to use** + * + * Use when you need a schema getter to convert a present string, number, or + * boolean value to `bigint`. + * + * **Details** + * + * - Delegates to `globalThis.BigInt`. + * - Throws at runtime if the input cannot be converted (e.g. non-numeric string). + * + * **Example** (Coercing to a bigint) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const toBigInt = SchemaGetter.BigInt() + * await Effect.runPromise(toBigInt.run(Option.some("42"), {})) // => Option.some(42n) + * ``` + * + * @category converting + * @since 4.0.0 + */ +export function BigInt(): Getter { + return transform(globalThis.BigInt) +} + +/** + * Coerces a value to a `Date` using `new Date(input)`. + * + * **When to use** + * + * Use when you need a schema getter to coerce a present string, number, or + * existing date object into a new date object. + * + * **Details** + * + * - Delegates to `new globalThis.Date(input)`. + * - Does not validate the result — may produce an invalid Date. + * + * **Example** (Coercing to a Date) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const toDate = SchemaGetter.Date() + * const result = await Effect.runPromise(toDate.run(Option.some("1970-01-01"), {})) + * Option.map(result, (date) => date.toISOString()) // => Option.some("1970-01-01T00:00:00.000Z") + * ``` + * + * @see {@link dateTimeUtcFromInput} for validated DateTime parsing + * + * @category converting + * @since 4.0.0 + */ +export function Date(): Getter { + return transform((u) => new globalThis.Date(u)) +} + +/** + * Strips whitespace from both ends of a string. + * + * **Details** + * + * - Pure, delegates to `String.trim`. + * + * **Example** (Trimming whitespace) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const trimmed = SchemaGetter.trim() + * await Effect.runPromise(trimmed.run(Option.some(" hello "), {})) // => Option.some("hello") + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function trim(): Getter { + return transform(Str.trim) +} + +/** + * Capitalizes the first character of a string. + * + * **Details** + * + * - Pure, delegates to `String.capitalize`. + * + * **Example** (Capitalizing a string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const cap = SchemaGetter.capitalize() + * await Effect.runPromise(cap.run(Option.some("hello"), {})) // => Option.some("Hello") + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function capitalize(): Getter { + return transform(Str.capitalize) +} + +/** + * Uncapitalizes the first character of a string. + * + * **Details** + * + * - Pure, delegates to `String.uncapitalize`. + * + * **Example** (Uncapitalizing a string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const uncap = SchemaGetter.uncapitalize() + * await Effect.runPromise(uncap.run(Option.some("Hello"), {})) // => Option.some("hello") + * ``` + * + * @category transforming + * @since 4.0.0 + */ +export function uncapitalize(): Getter { + return transform(Str.uncapitalize) +} + +/** + * Converts a `snake_case` string to `camelCase`. + * + * **Details** + * + * - Pure, delegates to `String.snakeToCamel`. + * + * **Example** (Converting snake case to camel case) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const toCamel = SchemaGetter.snakeToCamel() + * await Effect.runPromise(toCamel.run(Option.some("user_name"), {})) // => Option.some("userName") + * ``` + * + * @see {@link camelToSnake} for the inverse operation + * + * @category transforming + * @since 4.0.0 + */ +export function snakeToCamel(): Getter { + return transform(Str.snakeToCamel) +} + +/** + * Converts a `camelCase` string to `snake_case`. + * + * **Details** + * + * - Pure, delegates to `String.camelToSnake`. + * + * **Example** (Converting camel case to snake case) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const toSnake = SchemaGetter.camelToSnake() + * await Effect.runPromise(toSnake.run(Option.some("userName"), {})) // => Option.some("user_name") + * ``` + * + * @see {@link snakeToCamel} for the inverse operation + * + * @category transforming + * @since 4.0.0 + */ +export function camelToSnake(): Getter { + return transform(Str.camelToSnake) +} + +/** + * Converts a string to lowercase. + * + * **Details** + * + * - Pure, delegates to `String.toLowerCase`. + * + * **Example** (Converting to lowercase) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const lower = SchemaGetter.toLowerCase() + * await Effect.runPromise(lower.run(Option.some("HELLO"), {})) // => Option.some("hello") + * ``` + * + * @see {@link toUpperCase} for the inverse operation + * + * @category transforming + * @since 4.0.0 + */ +export function toLowerCase(): Getter { + return transform(Str.toLowerCase) +} + +/** + * Converts a string to uppercase. + * + * **Details** + * + * - Pure, delegates to `String.toUpperCase`. + * + * **Example** (Converting to uppercase) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const upper = SchemaGetter.toUpperCase() + * await Effect.runPromise(upper.run(Option.some("hello"), {})) // => Option.some("HELLO") + * ``` + * + * @see {@link toLowerCase} for the inverse operation + * + * @category transforming + * @since 4.0.0 + */ +export function toUpperCase(): Getter { + return transform(Str.toUpperCase) +} + +type ParseJsonOptions = { + readonly reviver?: Parameters[1] +} + +/** + * Parses a JSON string into a value. + * + * **When to use** + * + * Use when you need a schema getter to parse a present encoded JSON string + * during decoding. + * + * **Details** + * + * - Skips `None` inputs. + * - Without `reviver`: returns `Schema.MutableJson` (typed JSON). + * - With `reviver`: returns `unknown` (reviver may produce arbitrary values). + * - On parse failure, fails with `SchemaIssue.InvalidValue` whose `expected` + * annotation is `"a valid JSON string"`. Its default message includes the + * reported input when `reportInput` is enabled. + * + * **Example** (Parsing JSON) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const parse = SchemaGetter.parseJson() + * await Effect.runPromise(parse.run(Option.some("{\"a\":1}"), {})) // => Option.some({ a: 1 }) + * ``` + * + * @see {@link stringifyJson} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function parseJson(): Getter +export function parseJson(options: ParseJsonOptions): Getter +export function parseJson(options?: ParseJsonOptions | undefined): Getter { + return onSome((input, parseOptions) => + Effect.try({ + try: () => Option.some(JSON.parse(input, options?.reviver)), + catch: () => + new SchemaIssue.InvalidValue( + { expected: "a valid JSON string" }, + input, + parseOptions + ) + }) + ) +} + +/** + * Replacer function or property allowlist accepted by `JSON.stringify`. + * + * @category utility types + * @since 4.0.0 + */ +export type JsonReplacer = + | ((this: any, key: string, value: any) => any) + | Array + | null + +type StringifyJsonOptions = { + readonly replacer?: JsonReplacer | undefined + readonly space?: Parameters[2] +} + +/** + * Stringifies a present value using `JSON.stringify`. + * + * **When to use** + * + * Use when you need a schema getter to serialize a present decoded value to + * JSON text during encoding. + * + * **Details** + * + * - Skips `None` inputs. + * - If `JSON.stringify` throws or returns `undefined`, fails with + * `SchemaIssue.InvalidValue`. + * - Supports optional `replacer` and `space` options, matching + * `JSON.stringify`. + * + * **Example** (Stringifying JSON) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const stringify = SchemaGetter.stringifyJson() + * await Effect.runPromise(stringify.run(Option.some({ a: 1 }), {})) // => Option.some("{\"a\":1}") + * ``` + * + * @see {@link parseJson} for the inverse operation + * + * @category encoding + * @since 4.0.0 + */ +export function stringifyJson(options?: StringifyJsonOptions): Getter { + return onSome((input, parseOptions) => + Effect.try({ + try: () => { + const output = JSON.stringify(input, options?.replacer as any, options?.space) + if (output === undefined) { + throw new TypeError("Value cannot be represented as JSON") + } + return Option.some(output) + }, + catch: () => + new SchemaIssue.InvalidValue( + { expected: "a JSON-serializable value" }, + input, + parseOptions + ) + }) + ) +} + +/** + * Parses a string into a record of key-value pairs. + * + * **When to use** + * + * Use when you need a schema getter to parse a present encoded string that + * contains delimited key-value pairs (e.g. `"a=1,b=2"`). + * + * **Details** + * + * The getter is pure and never fails. It splits the string by `separator` + * (default `,`) and then each pair by `keyValueSeparator` (default `=`). Pairs + * missing a key or value are silently skipped. + * + * **Example** (Parsing a key-value string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const parse = SchemaGetter.splitKeyValue() + * await Effect.runPromise(parse.run(Option.some("a=1,b=2"), {})) // => Option.some({ a: "1", b: "2" }) + * ``` + * + * @see {@link joinKeyValue} for the inverse operation + * @see {@link split} to split into an array of strings + * + * @category splitting + * @since 4.0.0 + */ +export function splitKeyValue(options?: { + readonly separator?: string | undefined + readonly keyValueSeparator?: string | undefined +}): Getter, E> { + const separator = options?.separator ?? "," + const keyValueSeparator = options?.keyValueSeparator ?? "=" + return transform((input) => + input.split(separator).reduce((acc, pair) => { + const [key, value] = pair.split(keyValueSeparator) + if (key && value) { + InternalRecord.assignProperty(acc, key, value) + } + return acc + }, {} as Record) + ) +} + +/** + * Joins a record of key-value pairs into a delimited string. + * + * **When to use** + * + * Use when you need a schema getter to serialize a present decoded record as a + * delimited key-value string. + * + * **Details** + * + * The getter is pure and never fails. It joins entries with `separator` + * (default `,`) and joins each key and value with `keyValueSeparator` (default + * `=`). + * + * **Example** (Joining key-value records) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const join = SchemaGetter.joinKeyValue() + * await Effect.runPromise(join.run(Option.some({ a: "1", b: "2" }), {})) // => Option.some("a=1,b=2") + * ``` + * + * @see {@link splitKeyValue} for the inverse operation + * + * @category combining + * @since 4.0.0 + */ +export function joinKeyValue>(options?: { + readonly separator?: string | undefined + readonly keyValueSeparator?: string | undefined +}): Getter { + const separator = options?.separator ?? "," + const keyValueSeparator = options?.keyValueSeparator ?? "=" + return transform((input) => + Object.entries(input).map(([key, value]) => `${key}${keyValueSeparator}${value}`).join(separator) + ) +} + +/** + * Splits a string into an array of strings by a separator. + * + * **When to use** + * + * Use when you need a schema getter to split a present encoded string + * containing a delimited list, such as CSV values. + * + * **Details** + * + * The getter is pure and never fails. It splits by `separator` (default `,`). + * An empty string produces an empty array, not `[""]`. + * + * **Example** (Splitting a comma-separated string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const splitComma = SchemaGetter.split() + * await Effect.runPromise(splitComma.run(Option.some("a,b,c"), {})) // => Option.some(["a", "b", "c"]) + * ``` + * + * @see {@link splitKeyValue} when values are key-value pairs + * + * @category splitting + * @since 4.0.0 + */ +export function split(options?: { + readonly separator?: string | undefined +}): Getter, E> { + const separator = options?.separator ?? "," + return transform((input) => input === "" ? [] : input.split(separator)) +} + +/** + * Encodes a `Uint8Array` or string to a Base64 string. + * + * **Details** + * + * The getter is pure and never fails. + * + * **Example** (Encoding to Base64) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const encode = SchemaGetter.encodeBase64() + * await Effect.runPromise(encode.run(Option.some(new Uint8Array([1, 2, 3])), {})) // => Option.some("AQID") + * ``` + * + * @see {@link decodeBase64} for the inverse operation to `Uint8Array` + * @see {@link decodeBase64String} for the inverse operation to `string` + * @see {@link encodeBase64Url} for the URL-safe variant + * + * @category encoding + * @since 4.0.0 + */ +export function encodeBase64(): Getter { + return transform(Encoding.encodeBase64) +} + +/** + * Encodes a `Uint8Array` or string to a URL-safe Base64 string. + * + * **Details** + * + * The getter is pure and never fails. + * + * **Example** (Encoding to Base64Url) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const encode = SchemaGetter.encodeBase64Url() + * await Effect.runPromise(encode.run(Option.some(new Uint8Array([251, 255])), {})) // => Option.some("-_8") + * ``` + * + * @see {@link decodeBase64Url} for the inverse operation to `Uint8Array` + * @see {@link decodeBase64UrlString} for the inverse operation to `string` + * @see {@link encodeBase64} for the standard Base64 variant + * + * @category encoding + * @since 4.0.0 + */ +export function encodeBase64Url(): Getter { + return transform(Encoding.encodeBase64Url) +} + +/** + * Encodes a `Uint8Array` or string to a hexadecimal string. + * + * **Details** + * + * The getter is pure and never fails. + * + * **Example** (Encoding to hex) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const encode = SchemaGetter.encodeHex() + * await Effect.runPromise(encode.run(Option.some(new Uint8Array([1, 2, 3])), {})) // => Option.some("010203") + * ``` + * + * @see {@link decodeHex} for the inverse operation to `Uint8Array` + * @see {@link decodeHexString} for the inverse operation to `string` + * + * @category encoding + * @since 4.0.0 + */ +export function encodeHex(): Getter { + return transform(Encoding.encodeHex) +} + +/** + * Decodes a Base64 string to a `Uint8Array`. + * + * **Details** + * + * - Fails with `SchemaIssue.InvalidValue` if the input is not valid Base64. + * + * **Example** (Decoding Base64 to bytes) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeBase64() + * const result = await Effect.runPromise(decode.run(Option.some("AQID"), {})) + * Option.map(result, Array.from) // => Option.some([1, 2, 3]) + * ``` + * + * @see {@link decodeBase64String} to decode to `string` instead + * @see {@link encodeBase64} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function decodeBase64(): Getter { + return transformOrFail((input, options) => + Effect.mapErrorEager( + Effect.fromResult(Encoding.decodeBase64(input)), + () => + new SchemaIssue.InvalidValue( + { expected: "a valid Base64 string" }, + input, + options + ) + ) + ) +} + +/** + * Decodes a Base64 string to a UTF-8 `string`. + * + * **Details** + * + * - Fails with `SchemaIssue.InvalidValue` if the input is not valid Base64. + * + * **Example** (Decoding Base64 to string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeBase64String() + * await Effect.runPromise(decode.run(Option.some("aGVsbG8="), {})) // => Option.some("hello") + * ``` + * + * @see {@link decodeBase64} to decode to `Uint8Array` instead + * @see {@link encodeBase64} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function decodeBase64String(): Getter { + return transformOrFail((input, options) => + Result.match(Encoding.decodeBase64String(input), { + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Base64 string" }, + input, + options + ) + ), + onSuccess: Effect.succeed + }) + ) +} + +/** + * Decodes a URL-safe Base64 string to a `Uint8Array`. + * + * **Details** + * + * - Fails with `SchemaIssue.InvalidValue` if the input is not valid Base64Url. + * + * **Example** (Decoding Base64Url to bytes) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeBase64Url() + * const result = await Effect.runPromise(decode.run(Option.some("-_8="), {})) + * Option.map(result, Array.from) // => Option.some([251, 255]) + * ``` + * + * @see {@link decodeBase64UrlString} to decode to `string` instead + * @see {@link encodeBase64Url} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function decodeBase64Url(): Getter { + return transformOrFail((input, options) => + Result.match(Encoding.decodeBase64Url(input), { + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Base64Url string" }, + input, + options + ) + ), + onSuccess: Effect.succeed + }) + ) +} + +/** + * Decodes a URL-safe Base64 string to a UTF-8 `string`. + * + * **Details** + * + * - Fails with `SchemaIssue.InvalidValue` if the input is not valid Base64Url. + * + * **Example** (Decoding Base64Url to string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeBase64UrlString() + * await Effect.runPromise(decode.run(Option.some("aGVsbG8"), {})) // => Option.some("hello") + * ``` + * + * @see {@link decodeBase64Url} to decode to `Uint8Array` instead + * @see {@link encodeBase64Url} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function decodeBase64UrlString(): Getter { + return transformOrFail((input, options) => + Result.match(Encoding.decodeBase64UrlString(input), { + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Base64Url string" }, + input, + options + ) + ), + onSuccess: Effect.succeed + }) + ) +} + +/** + * Decodes a hexadecimal string to a `Uint8Array`. + * + * **Details** + * + * - Fails with `SchemaIssue.InvalidValue` if the input is not valid hex. + * + * **Example** (Decoding hex to bytes) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeHex() + * const result = await Effect.runPromise(decode.run(Option.some("010203"), {})) + * Option.map(result, Array.from) // => Option.some([1, 2, 3]) + * ``` + * + * @see {@link decodeHexString} to decode to `string` instead + * @see {@link encodeHex} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function decodeHex(): Getter { + return transformOrFail((input, options) => + Result.match(Encoding.decodeHex(input), { + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid hexadecimal string" }, + input, + options + ) + ), + onSuccess: Effect.succeed + }) + ) +} + +/** + * Decodes a hexadecimal string to a UTF-8 `string`. + * + * **Details** + * + * - Fails with `SchemaIssue.InvalidValue` if the input is not valid hex. + * + * **Example** (Decoding hex to string) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeHexString() + * await Effect.runPromise(decode.run(Option.some("68656c6c6f"), {})) // => Option.some("hello") + * ``` + * + * @see {@link decodeHex} to decode to `Uint8Array` instead + * @see {@link encodeHex} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function decodeHexString(): Getter { + return transformOrFail((input, options) => + Result.match(Encoding.decodeHexString(input), { + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid hexadecimal string" }, + input, + options + ) + ), + onSuccess: Effect.succeed + }) + ) +} + +/** + * Encodes a present string using `encodeURIComponent`. + * + * **Details** + * + * - Skips `None` inputs. + * - May throw a `URIError` for malformed surrogate pairs; this exception is not + * converted into an `Issue`. + * + * **Example** (Encoding a URI component) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const encode = SchemaGetter.encodeUriComponent() + * await Effect.runPromise(encode.run(Option.some("hello world"), {})) // => Option.some("hello%20world") + * ``` + * + * @see {@link decodeUriComponent} for the inverse operation + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUriComponent(): Getter { + return transform(encodeURIComponent) +} + +/** + * Decodes a URI component encoded string using `decodeURIComponent`. + * + * **Details** + * + * - Fails with `SchemaIssue.InvalidValue` if the input contains malformed percent-encoding sequences. + * + * **Example** (Decoding a URI component) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeUriComponent() + * await Effect.runPromise(decode.run(Option.some("hello%20world"), {})) // => Option.some("hello world") + * ``` + * + * @see {@link encodeUriComponent} for the inverse operation + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUriComponent(): Getter { + return transformOrFail((input, options) => { + try { + return Effect.succeed(globalThis.decodeURIComponent(input)) + } catch { + return Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid URI component" }, + input, + options + ) + ) + } + }) +} + +/** + * Parses a `DateTime.Input` value into a `DateTime.Utc`. + * + * **When to use** + * + * Use when you need a schema getter to decode a present encoded date/time value + * to a `DateTime.Utc`. + * + * **Details** + * + * - Accepted input includes existing `DateTime` values, partial date/time parts, + * instant objects, zoned instant objects, JavaScript `Date` instances, epoch + * milliseconds, and date strings. + * - Converts successfully parsed values to UTC. + * - Fails with `SchemaIssue.InvalidValue` if the input cannot be parsed as a valid + * `DateTime`. + * + * **Example** (Parsing DateTime) + * + * ```ts import.meta.vitest + * import { DateTime, Effect, Option, SchemaGetter } from "effect" + * + * const parseDate = SchemaGetter.dateTimeUtcFromInput() + * const result = await Effect.runPromise(parseDate.run(Option.some("2024-01-01T00:00:00Z"), {})) + * Option.map(result, DateTime.toEpochMillis) // => Option.some(1704067200000) + * ``` + * + * @see {@link Date} for a simpler coercion to `Date` (no validation) + * + * @category converting + * @since 4.0.0 + */ +export function dateTimeUtcFromInput(): Getter { + return transformOrFail((input, options) => { + return Option.match(DateTime.make(input), { + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue({ message: "Invalid DateTime input" }, input, options) + ), + onSome: (dt) => Effect.succeed(DateTime.toUtc(dt)) + }) + }) +} + +/** + * Decodes a `FormData` object into a nested tree structure using bracket-path notation. + * + * **When to use** + * + * Use when you need a schema getter to parse `FormData` from HTTP requests into + * structured objects. + * + * **Details** + * + * The getter is pure and never fails. It interprets bracket-path keys such as + * `user[name]` and `items[0]` to build nested objects or arrays, and each leaf + * value is a `string` or `Blob`. + * + * **Example** (Decoding FormData) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeFormData() + * const formData = new FormData() + * formData.append("user[name]", "Alice") + * await Effect.runPromise(decode.run(Option.some(formData), {})) // => Option.some({ user: { name: "Alice" } }) + * ``` + * + * @see {@link encodeFormData} for the corresponding encoder + * @see {@link makeTreeRecord} for the underlying bracket-path parser + * @see {@link decodeURLSearchParams} for the URLSearchParams variant + * + * @category decoding + * @since 4.0.0 + */ +export function decodeFormData(): Getter, FormData> { + return transform((input) => makeTreeRecord(Array.from(input.entries()))) +} + +const collectFormDataEntries = collectBracketPathEntries((value): value is string | Blob => + typeof value === "string" || (typeof Blob !== "undefined" && value instanceof Blob) +) + +/** + * Encodes a nested object into a `FormData` instance using bracket-path notation. + * + * **When to use** + * + * Use when you need a schema getter to serialize structured data to `FormData` + * for HTTP requests. + * + * **Details** + * + * The getter is pure and never fails. It flattens nested objects or arrays into + * bracket-path keys such as `user[name]` and `items[0]`. Non-object inputs + * produce an empty `FormData`. + * + * **Example** (Encoding to FormData) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const encode = SchemaGetter.encodeFormData() + * const result = await Effect.runPromise(encode.run(Option.some({ name: "Alice" }), {})) + * Option.map(result, (formData) => formData.get("name")) // => Option.some("Alice") + * ``` + * + * @see {@link decodeFormData} for the corresponding decoder + * @see {@link collectBracketPathEntries} for the underlying flattener + * @see {@link encodeURLSearchParams} for the URLSearchParams variant + * + * @category encoding + * @since 4.0.0 + */ +export function encodeFormData(): Getter { + return transform((input) => { + const out = new FormData() + if (typeof input === "object" && input !== null) { + const entries = collectFormDataEntries(input) + entries.forEach(([key, value]) => { + out.append(key, value) + }) + } + return out + }) +} + +/** + * Decodes a `URLSearchParams` object into a nested tree structure using bracket-path notation. + * + * **When to use** + * + * Use when you need a schema getter to parse query parameters from URLs into + * structured objects. + * + * **Details** + * + * The getter is pure and never fails. It interprets bracket-path keys such as + * `user[name]` and `items[0]` to build nested objects or arrays, and each leaf + * value is a `string`. + * + * **Example** (Decoding URLSearchParams) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const decode = SchemaGetter.decodeURLSearchParams() + * const params = new URLSearchParams("user[name]=Alice") + * await Effect.runPromise(decode.run(Option.some(params), {})) // => Option.some({ user: { name: "Alice" } }) + * ``` + * + * @see {@link encodeURLSearchParams} for the corresponding encoder + * @see {@link makeTreeRecord} for the underlying bracket-path parser + * @see {@link decodeFormData} for the FormData variant + * + * @category decoding + * @since 4.0.0 + */ +export function decodeURLSearchParams(): Getter, URLSearchParams> { + return transform((input) => makeTreeRecord(Array.from(input.entries()))) +} + +const collectURLSearchParamsEntries = collectBracketPathEntries(Predicate.isString) + +/** + * Encodes a nested object into a `URLSearchParams` instance using bracket-path notation. + * + * **When to use** + * + * Use when you need a schema getter to serialize structured data to query + * parameters for URLs. + * + * **Details** + * + * The getter is pure and never fails. It flattens nested objects or arrays into + * bracket-path keys. Non-object inputs produce an empty `URLSearchParams`. + * + * **Example** (Encoding to URLSearchParams) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" + * + * const encode = SchemaGetter.encodeURLSearchParams() + * const result = await Effect.runPromise(encode.run(Option.some({ name: "Alice" }), {})) + * Option.map(result, (params) => params.toString()) // => Option.some("name=Alice") + * ``` + * + * @see {@link decodeURLSearchParams} for the corresponding decoder + * @see {@link collectBracketPathEntries} for the underlying flattener + * @see {@link encodeFormData} for the FormData variant + * + * @category encoding + * @since 4.0.0 + */ +export function encodeURLSearchParams(): Getter { + return transform((input) => { + if (typeof input === "object" && input !== null) { + return new URLSearchParams(collectURLSearchParamsEntries(input)) + } + return new URLSearchParams() + }) +} + +const INDEX_REGEXP = /^\d+$/ + +function bracketPathToTokens(bracketPath: string): Array { + // real empty path (from append("", value)) + if (bracketPath === "") { + return [""] + } + + const replaced = bracketPath.replace(/\[(.*?)\]/g, ".$1") + const parts = replaced.split(".") + // if bracket path started with "[...]" we get ".foo" => ["", "foo"]; drop the synthetic first "" + const start = replaced.startsWith(".") ? 1 : 0 + + return parts + .slice(start) + .map((part) => (INDEX_REGEXP.test(part) ? globalThis.Number(part) : part)) +} + +/** + * Builds a nested tree object from a list of bracket-path entries. + * + * **When to use** + * + * Use when you need a schema getter to parse FormData or URLSearchParams + * entries into structured objects. + * - You have flat key-value pairs with bracket-path keys that need nesting. + * + * **Details** + * + * - A bracket path is a string like `"user[address][city]"` that describes nested + * object/array structure. + * - Interprets bracket paths and constructs the corresponding nested object. + * - Builds and returns a nested object from the input entries. + * - Supported syntax: + * - `"foo"` → object key `"foo"` + * - `"foo[bar]"` → nested `{ foo: { bar: ... } }` + * - `"foo[0]"` → array index `{ foo: [value] }` + * - `"foo[]"` → append to array `foo` + * - `""` → real empty key + * - Duplicate keys for the same path are merged into arrays. + * - If a structural path conflicts with a previous leaf or a different container + * type, the later structural path replaces the conflicting value. + * - The notation has no escaping for `.`, `[` or `]`, so keys containing these + * delimiters cannot be round-tripped without changing their structure. + * + * **Example** (Building a tree from bracket paths) + * + * ```ts import.meta.vitest + * import { SchemaGetter } from "effect" + * + * SchemaGetter.makeTreeRecord([ + * ["user[name]", "Alice"], + * ["user[tags][]", "admin"], + * ["user[tags][]", "editor"] + * ]) // => { user: { name: "Alice", tags: ["admin", "editor"] } } + * ``` + * + * @see {@link collectBracketPathEntries} for flattening trees into bracket-path entries + * @see {@link decodeFormData} for a higher-level FormData decoder + * @see {@link decodeURLSearchParams} for a higher-level URLSearchParams decoder + * + * @category constructors + * @since 4.0.0 + */ +export function makeTreeRecord( + bracketPathEntries: ReadonlyArray +): Schema.TreeRecord { + const out: any = {} + const containers = new WeakSet() + + function getOrCreateContainer(self: any, key: PropertyKey, shouldBeArray: boolean): any { + const current = Object.hasOwn(self, key) ? self[key] : undefined + if (containers.has(current) && Array.isArray(current) === shouldBeArray) { + return current + } + const container = shouldBeArray ? [] : {} + containers.add(container) + InternalRecord.assignProperty(self, key, container) + return container + } + + bracketPathEntries.forEach(([key, value]) => { + const tokens = bracketPathToTokens(key) + let cur: any = out + tokens.forEach((token, i) => { + const isLast = i === tokens.length - 1 + + // We are inside an array and see "[]" (empty token) => append + if (Array.isArray(cur) && token === "") { + if (isLast) { + cur.push(value) + } else { + // bracket path: "foo[][bar]" => push a new element and descend into it + const next = tokens[i + 1] + const shouldBeArray = typeof next === "number" || next === "" + const index = cur.length + cur = getOrCreateContainer(cur, index, shouldBeArray) + } + } else if (isLast) { + // If we're setting a value at a path that already exists + // convert it to an array to support multiple values for the same key + const hasOwn = Object.hasOwn(cur, token) + if (hasOwn && Array.isArray(cur[token])) { + cur[token].push(value) + } else if (hasOwn) { + InternalRecord.assignProperty(cur, token, [cur[token], value]) + } else { + InternalRecord.assignProperty(cur, token, value) + } + } else { + const next = tokens[i + 1] + // if next is a number OR "" (from []), we are building an array + const shouldBeArray = typeof next === "number" || next === "" + cur = getOrCreateContainer(cur, token, shouldBeArray) + } + }) + }) + return out +} + +/** + * Flattens a nested object into bracket-path entries, filtering leaf values by a type guard. + * + * **When to use** + * + * Use when you need a schema getter to serialize structured objects to flat + * key-value entries. + * - Building custom `FormData` or `URLSearchParams` encoders. + * + * **Details** + * + * - Takes a nested object and produces flat `[bracketPath, value]` pairs suitable for + * `FormData` or `URLSearchParams`. + * - Returns a curried function: first call provides the leaf type guard, second call provides the object. + * - Recursively traverses objects and arrays. + * - If all elements of an array are leaves, encodes them as multiple entries with the same key + * (e.g. `tags=a&tags=b`). Otherwise uses indexed bracket paths (e.g. `items[0]`, `items[1]`). + * - Non-leaf values that aren't objects or arrays are silently skipped. + * - Empty arrays and objects produce no entries, and path delimiters in property + * names are not escaped. The resulting format is therefore lossy. + * + * **Example** (Flattening an object to bracket paths) + * + * ```ts import.meta.vitest + * import { Predicate, SchemaGetter } from "effect" + * + * const collectStrings = SchemaGetter.collectBracketPathEntries(Predicate.isString) + * const entries = collectStrings({ user: { name: "Alice", tags: ["admin", "editor"] } }) + * + * entries // => [["user[name]", "Alice"], ["user[tags]", "admin"], ["user[tags]", "editor"]] + * ``` + * + * @see {@link makeTreeRecord} for building trees from bracket-path entries + * @see {@link encodeFormData} for a higher-level FormData encoder + * @see {@link encodeURLSearchParams} for a higher-level URLSearchParams encoder + * + * @category converting + * @since 4.0.0 + */ +export function collectBracketPathEntries(isLeaf: (value: unknown) => value is A) { + return (input: object): Array<[bracketPath: string, value: A]> => { + const bracketPathEntries: Array<[string, A]> = [] + + function append(key: string, value: unknown): void { + if (isLeaf(value)) { + bracketPathEntries.push([key, value]) + } else if (Array.isArray(value)) { + // If all values are leaves, encode as multiple entries with the same key + const allLeaves = value.every(isLeaf) + if (allLeaves) { + value.forEach((v) => { + bracketPathEntries.push([key, v]) + }) + } else { + value.forEach((v, i) => { + append(`${key}[${i}]`, v) + }) + } + } else if (typeof value === "object" && value !== null) { + for (const [k, v] of Object.entries(value)) { + append(`${key}[${k}]`, v) + } + } + } + + for (const [key, value] of Object.entries(input)) { + append(key, value) + } + + return bracketPathEntries + } +} diff --git a/.repos/effect/packages/effect/src/SchemaIssue.ts b/.repos/effect/packages/effect/src/SchemaIssue.ts new file mode 100644 index 000000000..07be9d9dc --- /dev/null +++ b/.repos/effect/packages/effect/src/SchemaIssue.ts @@ -0,0 +1,1209 @@ +/** + * Describes problems found while decoding, encoding, or checking data with + * schemas. + * + * An `Issue` records what failed and, for nested data, where the failure + * happened. The Schema system uses these values for missing keys, unexpected + * keys, invalid types, invalid values, failed filters, failed transformations, + * and alternatives that did not match. This module also formats issues. + * + * @since 4.0.0 + */ +import * as Arr from "./Array.ts" +import { format, formatPath, type Formatter as FormatterI } from "./Formatter.ts" +import * as InternalAnnotations from "./internal/schema/annotations.ts" +import * as InternalParser from "./internal/schema/parser.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Schema from "./Schema.ts" +import type * as SchemaAST from "./SchemaAST.ts" +import type { StandardSchemaV1 } from "./StandardSchema.ts" + +const TypeId = "~effect/SchemaIssue/Issue" + +/** + * Returns `true` if the given value is an {@link Issue}. + * + * **When to use** + * + * Use when you need to narrow an `unknown` value to `Issue` in error-handling + * code, such as distinguishing an `Issue` from other error types in a catch-all + * handler. + * + * **Details** + * + * - Checks for the internal `TypeId` brand on the value. + * + * **Example** (Type-guarding an unknown error) + * + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" + * + * const issue = new SchemaIssue.MissingKey(undefined) + * SchemaIssue.isIssue(issue) // => true + * SchemaIssue.isIssue("not an issue") // => false + * ``` + * + * @see {@link Issue} + * + * @category guards + * @since 4.0.0 + */ +export function isIssue(u: unknown): u is Issue { + return hasProperty(u, TypeId) && u[TypeId] === TypeId +} + +/** + * Returns `true` when an issue contains an input reported by the schema parser. + * + * **When to use** + * + * Use when reading `Issue.input`, especially when `undefined` is a valid input + * value. + * + * **Details** + * + * Reported input is stored as an own property. This guard checks for that + * property and narrows `input` from optional to required. + * + * **Example** (Reading a reported input) + * + * ```ts import.meta.vitest + * import { Result, Schema, SchemaIssue } from "effect" + * + * const result = Schema.decodeUnknownResult(Schema.String)(1, { reportInput: true }) + * if (Result.isFailure(result) && SchemaIssue.hasInput(result.failure.issue)) { + * result.failure.issue.input // => 1 + * } + * ``` + * + * @see {@link Issue} for the complete issue model + * + * @category guards + * @since 4.0.0 + */ +export function hasInput(issue: Issue): issue is Issue & { readonly input: unknown } { + return Object.hasOwn(issue, "input") +} + +/** + * Union of all terminal (leaf) issue types that have no inner `Issue` children. + * + * **When to use** + * + * Use when constraining formatter hooks to only handle terminal nodes or when + * pattern matching on the `_tag` of an issue and only leaf nodes matter. + * + * **Details** + * + * Members: {@link InvalidType}, {@link InvalidValue}, {@link MissingKey}, + * {@link UnexpectedKey}, {@link Forbidden}, {@link OneOf}. + * + * @see {@link Issue} — the full union including composite nodes + * @see {@link LeafHook} — formatter hook that operates on `Leaf` values + * + * @category models + * @since 4.0.0 + */ +export type Leaf = + | InvalidType + | InvalidValue + | MissingKey + | UnexpectedKey + | Forbidden + | OneOf + +/** + * The root discriminated union of all validation error nodes. + * + * **When to use** + * + * Use when typing the error channel in `Effect` results from + * schema parsing, or when writing custom formatters or issue-tree walkers. + * + * **Details** + * + * Every node has a `_tag` field for pattern-matching. The union includes both + * terminal {@link Leaf} types and composite types that wrap inner issues: + * {@link Filter}, {@link Encoding}, {@link Pointer}, {@link Composite}, + * {@link AnyOf}. Use {@link makeFormatterDefault} when a human-readable + * representation is needed. When parsing with `reportInput: true`, + * value-bearing issues expose the rejected value through an enumerable `input` + * field. Built-in formatters may include reported input in default messages. This + * is not a general sanitization boundary: paths, ASTs, union successes, and + * custom annotations or messages are preserved as supplied and remain the + * caller's responsibility. + * + * @see {@link Leaf} — the terminal subset + * @see {@link isIssue} — type guard + * @see {@link hasInput} — checks whether an issue reports an input + * @category models + * @since 4.0.0 + */ +export type Issue = + | Leaf + // composite + | Filter + | Encoding + | Pointer + | Composite + | AnyOf + +class Base { + readonly [TypeId] = TypeId + /** + * The input reported by the schema parser, when input reporting is enabled + * and the issue is associated with a present value. + */ + declare readonly input?: unknown + constructor(input?: unknown, options?: SchemaAST.ParseOptions) { + if (options?.reportInput === true && input !== InternalParser.missing) { + this.input = input + } + } +} + +/** + * Represents a schema issue produced when a schema filter (refinement check) fails. + * + * **When to use** + * + * Use when you need to inspect a schema issue that records which refinement + * check rejected the value. + * + * **Details** + * + * - `filter` is the AST filter node that produced this issue. + * - `issue` is the inner issue describing the failure reason. + * + * **Example** (Matching a Filter issue) + * + * ```ts import.meta.vitest + * import { SchemaAST, SchemaIssue } from "effect" + * + * const formatIssue = SchemaIssue.makeFormatterDefault() + * + * function describe(issue: SchemaIssue.Issue): string { + * if (issue._tag === "Filter") { + * return `Filter failed: ${formatIssue(issue.issue)}` + * } + * return formatIssue(issue) + * } + * + * const issue = new SchemaIssue.Filter( + * SchemaAST.isPattern(/^valid$/), + * new SchemaIssue.InvalidValue() + * ) + * describe(issue) // => `Filter failed: Expected a valid value` + * ``` + * + * @see {@link Leaf} — terminal issue types that commonly appear as the inner `issue` + * @see {@link CheckHook} — formatter hook for `Filter` issues + * + * @category models + * @since 4.0.0 + */ +export class Filter extends Base { + readonly _tag = "Filter" + /** + * The filter that failed. + */ + readonly filter: SchemaAST.Filter + /** + * The issue that occurred. + */ + readonly issue: Issue + + constructor( + /** + * The filter that failed. + */ + filter: SchemaAST.Filter, + /** + * The issue that occurred. + */ + issue: Issue, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.filter = filter + this.issue = issue + } +} + +/** + * Represents a schema issue produced when a schema transformation (encode/decode step) fails. + * + * **When to use** + * + * Use when you need to inspect failures from `Schema.decodeTo` / `Schema.encodeTo` + * transformations. + * + * **Details** + * + * - `ast` is the AST node for the transformation that failed. + * - `issue` is the inner issue describing the failure. + * + * @see {@link Filter} — failure from a refinement check (not a transformation) + * @see {@link Composite} — multiple issues from a single schema node + * + * @category models + * @since 4.0.0 + */ +export class Encoding extends Base { + readonly _tag = "Encoding" + /** + * The schema that caused the issue. + */ + readonly ast: SchemaAST.AST + /** + * The issue that occurred. + */ + readonly issue: Issue + + constructor( + /** + * The schema that caused the issue. + */ + ast: SchemaAST.AST, + /** + * The issue that occurred. + */ + issue: Issue, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.ast = ast + this.issue = issue + } +} + +/** + * Wraps an inner {@link Issue} with a property-key path, indicating *where* in + * a nested structure the error occurred. + * + * **When to use** + * + * Use when you need to walk the issue tree to accumulate path segments for error + * reporting. + * + * **Details** + * + * - `path` is an array of property keys (strings, numbers, or symbols). + * - Formatters concatenate nested `Pointer` paths into a single path like + * `["a"]["b"][0]`. + * + * @see {@link Composite} — groups multiple issues under one schema node + * + * @category models + * @since 3.10.0 + */ +export class Pointer extends Base { + readonly _tag = "Pointer" + /** + * The path to the location in the input that caused the issue. + */ + readonly path: ReadonlyArray + /** + * The issue that occurred. + */ + readonly issue: Issue + + constructor( + /** + * The path to the location in the input that caused the issue. + */ + path: ReadonlyArray, + /** + * The issue that occurred. + */ + issue: Issue + ) { + super() + this.path = path + this.issue = issue + } +} + +/** + * Represents a schema issue produced when a required key or tuple index is missing from the input. + * + * **When to use** + * + * Use when you need to detect absent fields in struct/tuple validation. + * + * **Details** + * + * - `annotations` may contain a custom `messageMissingKey` for formatting. + * + * @see {@link Pointer} — wraps this issue with the missing key's path + * @see {@link UnexpectedKey} — the opposite case (extra key present) + * + * @category models + * @since 4.0.0 + */ +export class MissingKey extends Base { + readonly _tag = "MissingKey" + /** + * The metadata for the issue. + */ + readonly annotations: Schema.Annotations.Key | undefined + + constructor( + /** + * The metadata for the issue. + */ + annotations: Schema.Annotations.Key | undefined + ) { + super() + this.annotations = annotations + } +} + +/** + * Represents a schema issue produced when an input object or tuple contains a key/index not + * declared by the schema. + * + * **When to use** + * + * Use when you need to detect excess properties during strict struct/tuple + * validation. + * + * **Details** + * + * - `ast` is the schema that was being validated against. + * - `annotations` on `ast` may contain a custom `messageUnexpectedKey`. + * - The default formatter renders this as `"Expected no excess property"`, or + * `"Unexpected key with value "` when the issue reports an input. + * + * @see {@link MissingKey} — the opposite case (required key absent) + * @see {@link Pointer} — wraps this issue with the unexpected key's path + * + * @category models + * @since 4.0.0 + */ +export class UnexpectedKey extends Base { + readonly _tag = "UnexpectedKey" + /** + * The schema that caused the issue. + */ + readonly ast: SchemaAST.AST + constructor( + /** + * The schema that caused the issue. + */ + ast: SchemaAST.AST, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.ast = ast + } +} + +/** + * Represents a schema issue that groups multiple child issues under a single schema node. + * + * **When to use** + * + * Use when you need to walk the issue tree for struct/tuple schemas that collect + * all field errors rather than failing on the first. + * + * **Details** + * + * - `issues` is a non-empty readonly array (at least one child). + * - Formatters flatten `Composite` by recursing into each child. + * + * @see {@link AnyOf} — used for union no-match errors (similar but different semantics) + * @see {@link Pointer} — adds path context to individual issues + * + * @category models + * @since 3.10.0 + */ +export class Composite extends Base { + readonly _tag = "Composite" + /** + * The schema that caused the issue. + */ + readonly ast: SchemaAST.AST + /** + * The issues that occurred. + */ + readonly issues: readonly [Issue, ...Array] + + constructor( + /** + * The schema that caused the issue. + */ + ast: SchemaAST.AST, + /** + * The issues that occurred. + */ + issues: readonly [Issue, ...Array], + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.ast = ast + this.issues = issues + } +} + +/** + * Represents a schema issue produced when the runtime type of the input does not match the type + * expected by the schema. + * + * **When to use** + * + * Use when you need to detect basic type mismatches, such as a wrong primitive + * or `null` where an object was expected. + * + * **Details** + * + * - `ast` is the schema node that expected a different type. + * - The default formatter renders this as `"Expected "`, adding + * `", got "` when the issue reports an input. + * + * **Example** (Formatting a type mismatch) + * + * ```ts import.meta.vitest + * import { Schema, SchemaIssue } from "effect" + * + * const formatIssue = SchemaIssue.makeFormatterDefault() + * const issue = new SchemaIssue.InvalidType(Schema.String.ast) + * formatIssue(issue) // => "Expected string" + * ``` + * + * @see {@link InvalidValue} — the input has the right type but fails a value constraint + * + * @category models + * @since 4.0.0 + */ +export class InvalidType extends Base { + readonly _tag = "InvalidType" + /** + * The schema that caused the issue. + */ + readonly ast: SchemaAST.AST + constructor( + /** + * The schema that caused the issue. + */ + ast: SchemaAST.AST, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.ast = ast + } +} + +/** + * Represents a schema issue produced when the input has the correct type but its value violates a + * constraint (e.g. a string that is too short, a number out of range). + * + * **When to use** + * + * Use when you need to detect constraint violations from `Schema.filter`, + * `Schema.minLength`, `Schema.greaterThan`, or similar checks. + * + * **Details** + * + * - A `message` annotation is returned unchanged and takes precedence over all + * other default formatting. + * - Without `message`, an `expected` annotation is formatted as + * `"Expected "`, adding `", got "` when input is reported. + * - Without either annotation, the default formatter renders + * `"Expected a valid value"`, or `"Invalid data "` when input is + * reported. + * + * **Example** (Returning InvalidValue from a custom filter) + * + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" + * + * const formatIssue = SchemaIssue.makeFormatterDefault() + * const issue = new SchemaIssue.InvalidValue({ message: "must not be empty" }) + * formatIssue(issue) // => "must not be empty" + * ``` + * + * @see {@link InvalidType} — the input has the wrong type entirely + * @see {@link Filter} — composite wrapper when a schema filter produces this issue + * + * @category models + * @since 4.0.0 + */ +export class InvalidValue extends Base { + readonly _tag = "InvalidValue" + /** + * The metadata for the issue. + */ + readonly annotations: Schema.Annotations.Issue | undefined + + constructor( + /** + * The metadata for the issue. + */ + annotations?: Schema.Annotations.Issue | undefined, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.annotations = annotations + } +} + +/** @internal */ +export function makeCompositeAtKey( + compositeAst: SchemaAST.AST, + pointerKey: PropertyKey, + pointerIssue: Issue, + compositeInput: unknown, + parseOptions?: SchemaAST.ParseOptions +): Composite { + return new Composite( + compositeAst, + [new Pointer([pointerKey], pointerIssue)], + compositeInput, + parseOptions + ) +} + +/** + * Represents a schema issue produced when a forbidden operation is encountered during parsing, + * such as an asynchronous Effect running inside `Schema.decodeUnknownSync`. + * + * **When to use** + * + * Use when you need to detect that a schema requires async execution but was run + * synchronously. + * + * **Details** + * + * - `annotations` optionally carries a `message` string. + * - The default formatter renders this as `"Forbidden operation"`. + * + * **Example** (Creating a Forbidden issue) + * + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" + * + * const formatIssue = SchemaIssue.makeFormatterDefault() + * const issue = new SchemaIssue.Forbidden( + * { message: "async operation not allowed in sync context" } + * ) + * formatIssue(issue) // => "async operation not allowed in sync context" + * ``` + * + * @see {@link InvalidValue} — for value-constraint failures (not operation failures) + * + * @category models + * @since 3.10.0 + */ +export class Forbidden extends Base { + readonly _tag = "Forbidden" + /** + * The metadata for the issue. + */ + readonly annotations: Schema.Annotations.Issue | undefined + + constructor( + /** + * The metadata for the issue. + */ + annotations: Schema.Annotations.Issue | undefined, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.annotations = annotations + } +} + +/** + * Represents a schema issue produced when a value does not match *any* member of a union schema. + * + * **When to use** + * + * Use when you need to inspect which union members were attempted and why each + * failed. + * + * **Details** + * + * - `ast` is the `Union` AST node. + * - `issues` contains the per-member failures. + * + * **Gotchas** + * + * `issues` is empty when no union member was applicable. In that case, the + * default formatter reports the expected type for the union and appends + * `", got "` when input is reported. + * + * @see {@link OneOf} — the opposite: *too many* members matched + * @see {@link Composite} — groups multiple issues under a non-union schema + * + * @category models + * @since 4.0.0 + */ +export class AnyOf extends Base { + readonly _tag = "AnyOf" + /** + * The schema that caused the issue. + */ + readonly ast: SchemaAST.Union + /** + * The issues that occurred. + */ + readonly issues: ReadonlyArray + + constructor( + /** + * The schema that caused the issue. + */ + ast: SchemaAST.Union, + /** + * The issues that occurred. + */ + issues: ReadonlyArray, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.ast = ast + this.issues = issues + } +} + +/** + * Represents a schema issue produced when a value matches *multiple* members of a union that is + * configured to allow exactly one match (oneOf mode). + * + * **When to use** + * + * Use when you need to detect ambiguous union matches when `oneOf` validation is + * enabled. + * + * **Details** + * + * - `ast` is the `Union` AST node. + * - `successes` lists the AST nodes of each member that accepted the input. + * - The default formatter renders this as + * `"Expected exactly one member to match"`, or + * `"Expected exactly one member to match the input "` when input is + * reported. + * + * @see {@link AnyOf} — the opposite: *no* members matched + * + * @category models + * @since 4.0.0 + */ +export class OneOf extends Base { + readonly _tag = "OneOf" + /** + * The schema that caused the issue. + */ + readonly ast: SchemaAST.Union + /** + * The schemas that were successful. + */ + readonly successes: ReadonlyArray + + constructor( + /** + * The schema that caused the issue. + */ + ast: SchemaAST.Union, + /** + * The schemas that were successful. + */ + successes: ReadonlyArray, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. + */ + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions + ) { + super(input, options) + this.ast = ast + this.successes = successes + } +} + +function makeFilterIssue( + entry: Schema.FilterIssue, + input?: unknown, + options?: SchemaAST.ParseOptions +): Issue { + if (isIssue(entry)) { + return entry + } + if (typeof entry === "string") { + return new InvalidValue({ message: entry }, input, options) + } + const inner = typeof entry.issue === "string" + ? new InvalidValue({ message: entry.issue }, input, options) + : entry.issue + return new Pointer(entry.path, inner) +} + +/** @internal */ +export function makeSingle( + out: undefined | boolean | Schema.FilterIssue, + input?: unknown, + options?: SchemaAST.ParseOptions +): Issue | undefined { + if (out === undefined) { + return undefined + } + if (typeof out === "boolean") { + return out ? undefined : new InvalidValue(undefined, input, options) + } + return makeFilterIssue(out, input, options) +} + +/** @internal */ +export function normalizeFilterOutput( + ast: SchemaAST.AST, + out: Schema.FilterOutput, + input?: unknown, + options?: SchemaAST.ParseOptions +): Issue | undefined { + if (Array.isArray(out)) { + if (!Arr.isReadonlyArrayNonEmpty(out)) { + return undefined + } + return out.length === 1 + ? makeFilterIssue(out[0], input, options) + : new Composite(ast, Arr.map(out, (entry) => makeFilterIssue(entry, input, options)), input, options) + } + return makeSingle(out as undefined | boolean | Schema.FilterIssue, input, options) +} + +/** + * A function type that converts an {@link Issue} into a formatted + * representation. Specialisation of the generic `Formatter` from + * `Formatter.ts` with `Value` fixed to `Issue`. + * + * @see {@link makeFormatterDefault} — creates a `Formatter` + * @see {@link makeFormatterStandardSchemaV1} — creates a `Formatter` + * + * @category formatting + * @since 4.0.0 + */ +export interface Formatter extends FormatterI {} + +/** + * Callback type used to format {@link Leaf} issues into strings. + * + * **When to use** + * + * Use when customizing how {@link makeFormatterStandardSchemaV1} renders + * terminal issues. + * + * @see {@link defaultLeafHook} — the built-in implementation + * @see {@link Leaf} — the union of terminal issue types + * + * @category formatting + * @since 4.0.0 + */ +export type LeafHook = (issue: Leaf) => string + +/** + * Returns the built-in {@link LeafHook} used by default formatters. + * + * **When to use** + * + * Use as the default leaf renderer when customizing only the {@link CheckHook}. + * + * **Details** + * + * - Checks for a `message` annotation first; returns it if present. + * - For `InvalidValue`, an `expected` annotation uses the standard expected + * value message and includes reported input when available. + * - Otherwise generates a default message per `_tag`. When the issue reports + * input, the message includes its formatted value where applicable: + * - `InvalidType` → `"Expected "` or `"Expected , got "` + * - `InvalidValue` → `"Expected a valid value"` or `"Invalid data "` + * - `MissingKey` → `"Missing key"` + * - `UnexpectedKey` → `"Expected no excess property"` or + * `"Unexpected key with value "` + * - `Forbidden` → `"Forbidden operation"` + * - `OneOf` → `"Expected exactly one member to match"` or + * `"Expected exactly one member to match the input "` + * + * **Example** (Formatting Standard Schema issues with defaultLeafHook) + * + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" + * + * const formatter = SchemaIssue.makeFormatterStandardSchemaV1({ + * leafHook: SchemaIssue.defaultLeafHook + * }) + * formatter(new SchemaIssue.MissingKey(undefined)) // => { issues: [{ path: [], message: "Missing key" }] } + * ``` + * + * @see {@link LeafHook} + * @see {@link makeFormatterStandardSchemaV1} + * + * @category formatting + * @since 4.0.0 + */ +export const defaultLeafHook: LeafHook = (issue): string => { + const message = findMessage(issue) + if (message !== undefined) return message + switch (issue._tag) { + case "InvalidType": + return getExpectedMessage(InternalAnnotations.getExpected(issue.ast), issue) + case "InvalidValue": { + const expected = findExpected(issue) + if (expected !== undefined) return getExpectedMessage(expected, issue) + const input = formatInput(issue) + return input === undefined ? "Expected a valid value" : `Invalid data ${input}` + } + case "MissingKey": + return "Missing key" + case "UnexpectedKey": { + const input = formatInput(issue) + return input === undefined ? "Expected no excess property" : `Unexpected key with value ${input}` + } + case "Forbidden": + return "Forbidden operation" + case "OneOf": { + const input = formatInput(issue) + return input === undefined + ? "Expected exactly one member to match" + : `Expected exactly one member to match the input ${input}` + } + } +} + +/** + * Callback type used to format {@link Filter} issues into strings. + * + * **When to use** + * + * Use when customizing how {@link makeFormatterStandardSchemaV1} renders + * filter failures. + * + * **Details** + * + * - Returns `string` to override the message, or `undefined` to fall back to + * the default formatting. + * @see {@link defaultCheckHook} — the built-in implementation + * @see {@link Filter} — the issue type this hook formats + * + * @category formatting + * @since 4.0.0 + */ +export type CheckHook = (issue: Filter) => string | undefined + +/** + * Returns the built-in {@link CheckHook} used by default formatters. + * + * **When to use** + * + * Use as the default filter renderer when customizing only the {@link LeafHook}. + * + * **Details** + * + * - Looks for a `message` annotation on the inner issue first, then on the + * filter itself. + * - Returns `undefined` when no annotation is found, causing the formatter to + * fall back to `"Expected "` or, when the filter reports input, + * `"Expected , got "`. + * + * @see {@link CheckHook} + * @see {@link makeFormatterStandardSchemaV1} + * + * @category formatting + * @since 4.0.0 + */ +export const defaultCheckHook: CheckHook = (issue): string | undefined => findMessage(issue.issue) ?? findMessage(issue) + +/** + * Creates a {@link Formatter} that produces a `StandardSchemaV1.FailureResult`. + * + * **When to use** + * + * Use when you need schema parse errors in + * [Standard Schema V1](https://github.com/standard-schema/standard-schema) + * format, optionally customizing leaf or check issue rendering. + * + * **Details** + * + * - Returns a `Formatter`. + * - Each leaf issue is flattened into `{ message, path }` entries. + * - `Pointer` paths are accumulated to produce full property paths. + * - Falls back to {@link defaultLeafHook} / {@link defaultCheckHook} when no + * hooks are provided. + * - Default messages include reported input when the issue that produces the + * message has an `input` field. The returned Standard Schema issues do not + * receive an `input` field. + * + * **Gotchas** + * + * Reported input can appear inside the Standard Schema `message` string even + * though it is not exposed as a separate property. Custom hooks control their + * complete message and are not modified. + * + * **Example** (Creating a Standard Schema V1 formatter) + * + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" + * + * const formatter = SchemaIssue.makeFormatterStandardSchemaV1() + * formatter(new SchemaIssue.MissingKey(undefined)).issues[0].message // => "Missing key" + * ``` + * + * @see {@link makeFormatterDefault} — produces a plain string instead + * @see {@link LeafHook} + * @see {@link CheckHook} + * + * @category formatting + * @since 4.0.0 + */ +export function makeFormatterStandardSchemaV1(options?: { + readonly leafHook?: LeafHook | undefined + readonly checkHook?: CheckHook | undefined +}): Formatter { + return (issue) => ({ + issues: toDefaultIssues(issue, [], options?.leafHook ?? defaultLeafHook, options?.checkHook ?? defaultCheckHook) + }) +} + +// A subtype of StandardSchemaV1.Issue +type DefaultIssue = { + readonly message: string + readonly path: ReadonlyArray +} + +function formatInput(issue: Issue): string | undefined { + return hasInput(issue) ? format(issue.input) : undefined +} + +function findExpected(issue: InvalidValue): string | undefined { + const expected = issue.annotations?.expected + return typeof expected === "string" ? expected : undefined +} + +function getExpectedMessage(expected: string, issue: Issue): string { + const input = formatInput(issue) + return input === undefined ? `Expected ${expected}` : `Expected ${expected}, got ${input}` +} + +function toDefaultIssues( + issue: Issue, + path: ReadonlyArray, + leafHook: LeafHook, + checkHook: CheckHook +): Array { + switch (issue._tag) { + case "Filter": { + const message = checkHook(issue) + if (message !== undefined) { + return [{ path, message }] + } + if (issue.issue._tag !== "InvalidValue") { + return toDefaultIssues(issue.issue, path, leafHook, checkHook) + } + const expected = findExpected(issue.issue) + return [{ + path, + message: expected === undefined + ? getExpectedMessage(formatCheck(issue.filter), issue) + : getExpectedMessage(expected, issue.issue) + }] + } + case "Encoding": + return toDefaultIssues(issue.issue, path, leafHook, checkHook) + case "Pointer": + return toDefaultIssues(issue.issue, [...path, ...issue.path], leafHook, checkHook) + case "Composite": + return issue.issues.flatMap((issue) => toDefaultIssues(issue, path, leafHook, checkHook)) + case "AnyOf": { + if (issue.issues.length === 0) { + return [{ + path, + message: findMessage(issue) ?? getExpectedMessage(InternalAnnotations.getExpected(issue.ast), issue) + }] + } + return issue.issues.flatMap((issue) => toDefaultIssues(issue, path, leafHook, checkHook)) + } + default: + return [{ path, message: leafHook(issue) }] + } +} + +function formatCheck(check: SchemaAST.Check): string { + const expected = check.annotations?.expected + if (typeof expected === "string") return expected + + switch (check._tag) { + case "Filter": + return "" + case "FilterGroup": + return check.checks.map((check) => formatCheck(check)).join(" & ") + } +} + +/** + * Creates a {@link Formatter} that converts an {@link Issue} into a + * human-readable multi-line string. + * + * **When to use** + * + * Use when you need to format a `SchemaIssue.Issue` as error messages for + * logging, CLI output, or developer-facing diagnostics. + * + * **Details** + * + * - Flattens the issue tree into `{ message, path }` entries using + * {@link defaultLeafHook} and {@link defaultCheckHook}. + * - Includes reported input in default messages when the node producing the + * message has an `input` field. + * - Each entry is rendered as `""` or `"\n at "`. + * - Multiple entries are joined with newlines. + * + * **Gotchas** + * + * Formatting an issue can disclose input retained with `reportInput: true`. + * Wrapper inputs are not inherited by child messages, and custom messages are + * returned unchanged. + * + * **Example** (Formatting an issue as a string) + * + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" + * + * const formatter = SchemaIssue.makeFormatterDefault() + * formatter(new SchemaIssue.MissingKey(undefined)) // => "Missing key" + * ``` + * + * @see {@link makeFormatterStandardSchemaV1} — produces Standard Schema V1 format instead + * @see {@link Formatter} + * + * @category formatting + * @since 4.0.0 + */ +export function makeFormatterDefault(): Formatter { + return (issue) => formatIssue(issue, "") +} + +/** @internal */ +export const defaultFormatter = makeFormatterDefault() + +function formatIssue(issue: Issue, path: string): string { + let message: string + switch (issue._tag) { + case "Filter": { + const annotated = defaultCheckHook(issue) + if (annotated !== undefined) { + message = annotated + } else { + if (issue.issue._tag !== "InvalidValue") { + return formatIssue(issue.issue, path) + } + const expected = findExpected(issue.issue) + message = expected === undefined + ? getExpectedMessage(formatCheck(issue.filter), issue) + : getExpectedMessage(expected, issue.issue) + } + break + } + case "Encoding": + return formatIssue(issue.issue, path) + case "Pointer": + return formatIssue(issue.issue, path + formatPath(issue.path)) + case "Composite": + case "AnyOf": { + if (issue._tag === "Composite" || issue.issues.length > 0) { + return issue.issues.map((issue) => formatIssue(issue, path)).join("\n") + } + message = findMessage(issue) ?? getExpectedMessage(InternalAnnotations.getExpected(issue.ast), issue) + break + } + default: + message = defaultLeafHook(issue) + break + } + return path ? `${message}\n at ${path}` : message +} + +function findMessage(issue: Issue): string | undefined { + if (issue._tag === "Pointer") return + if (issue._tag === "Encoding") return findMessage(issue.issue) + const annotations = issue._tag === "Filter" + ? issue.filter.annotations + : "annotations" in issue + ? issue.annotations + : issue.ast.annotations + const message = annotations?.[ + issue._tag === "MissingKey" + ? "messageMissingKey" + : issue._tag === "UnexpectedKey" + ? "messageUnexpectedKey" + : "message" + ] + if (typeof message === "string") return message +} diff --git a/.repos/effect/packages/effect/src/SchemaParser.ts b/.repos/effect/packages/effect/src/SchemaParser.ts new file mode 100644 index 000000000..7cc35ebff --- /dev/null +++ b/.repos/effect/packages/effect/src/SchemaParser.ts @@ -0,0 +1,1219 @@ +/** + * Runs schemas against real values. + * + * Schema parsers construct values from schema input, check whether a value + * matches a schema, decode encoded input, and encode decoded values back to + * their external form. This module exposes those operations through several + * result styles, including `Effect`, `Promise`, `Exit`, `Option`, `Result`, and + * synchronous functions that throw. It also contains the lower-level runner that + * walks a schema AST and reports schema failures as `SchemaIssue.Issue` values. + * + * @since 4.0.0 + */ +import * as Cause from "./Cause.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import { memoize } from "./Function.ts" +import { effectIsExit } from "./internal/effect.ts" +import * as InternalSchemaCause from "./internal/schema/cause.ts" +import * as InternalParser from "./internal/schema/parser.ts" +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 * as SchemaIssue from "./SchemaIssue.ts" + +/** + * Creates an effectful maker for the schema's decoded type side. + * + * **When to use** + * + * Use to construct decoded schema values in `Effect` while preserving + * construction failures as `SchemaIssue.Issue` values in the error channel. + * + * **Details** + * + * The returned function accepts constructor input, applies constructor defaults, + * runs type-side validation unless checks are disabled, and fails with a + * `SchemaIssue.Issue` when construction fails. + * + * @category constructors + * @since 4.0.0 + */ +export function makeEffect(schema: S) { + const parser = runWithCompiler(constructorCompiler, SchemaAST.toType(schema.ast)) + return (input: S["~type.make.in"], options?: Schema.MakeOptions): Effect.Effect => { + return parser( + input, + options?.disableChecks + ? options?.parseOptions ? { ...options.parseOptions, disableChecks: true } : { disableChecks: true } + : options?.parseOptions + ) + } +} + +/** + * Creates a synchronous maker that returns `Option.some` with the constructed + * value on success, or `Option.none` when construction fails with schema issues. + * + * **When to use** + * + * Use when you need to validate schema constructor input and only care whether + * construction succeeds, without exposing `SchemaIssue.Issue` details. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `Option.none`. + * Causes that contain defects, interruptions, or asynchronous work at this + * synchronous boundary throw an `Error` whose cause is the underlying `Cause`. + * + * @category constructors + * @since 4.0.0 + */ +export function makeOption(schema: S) { + const parser = makeEffect(schema) + return (input: S["~type.make.in"], options?: Schema.MakeOptions): Option.Option => { + const exit = Effect.runSyncExit(parser(input, options)) + if (Exit.isSuccess(exit)) { + return Option.some(exit.value) + } + InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Option adapter can only return none for schema issues") + return Option.none() + } +} + +/** + * Creates a synchronous maker for the schema's decoded type side. + * + * **When to use** + * + * Use to construct decoded schema values synchronously when invalid input + * should throw an `Error` whose cause is `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function constructs a value from constructor input and throws an + * `Error` with the `SchemaIssue.Issue` in its `cause` when construction fails. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or asynchronous work at this + * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, + * instead of being converted to a schema validation error. + * + * @category constructors + * @since 4.0.0 + */ +export function make(schema: S) { + const parser = makeEffect(schema) + return (input: S["~type.make.in"], options?: Schema.MakeOptions): S["Type"] => { + const exit = Effect.runSyncExit(parser(input, options)) + if (Exit.isSuccess(exit)) { + return exit.value + } + const issue = InternalSchemaCause.getSchemaIssueOrThrow( + exit.cause, + "Constructor adapter can only throw schema issues" + ) + throw new Error("Schema validation failed", { cause: issue }) + } +} + +/** + * Creates a type guard that checks whether an input satisfies the schema's decoded + * type side. + * + * **When to use** + * + * Use to build a type guard for checking the decoded side of a schema without + * exposing issue details. + * + * **Details** + * + * The guard returns `true` on successful validation and `false` when validation + * fails only with schema issues, without exposing issue details. + * + * **Gotchas** + * + * Only causes made entirely of schema issues are converted to `false`. Causes + * that contain defects, interruptions, or asynchronous work at this synchronous + * boundary throw an `Error` whose cause is the underlying `Cause`. + * + * @category guards + * @since 3.10.0 + */ +export function is(schema: S): (input: I) => input is I & S["Type"] { + return _is(schema.ast) +} + +/** @internal */ +export function _is(ast: SchemaAST.AST) { + const parser = asExit(run(SchemaAST.toType(ast))) + return (input: I): input is I & T => { + const exit = parser(input, SchemaAST.defaultParseOptions) + if (Exit.isSuccess(exit)) { + return true + } + InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Type guard adapter can only return false for schema issues") + return false + } +} + +/** @internal */ +export function _issue(ast: SchemaAST.AST) { + const parser = run(ast) + return (input: unknown, options: SchemaAST.ParseOptions): SchemaIssue.Issue | undefined => { + const exit = Effect.runSyncExit(parser(input, options)) + if (Exit.isSuccess(exit)) { + return undefined + } + return InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Issue adapter can only return schema issues") + } +} + +/** + * Asserts that an input satisfies the schema's decoded type side. + * + * **When to use** + * + * Use to assert that an input satisfies the decoded side of a schema when schema + * validation failures should throw an `Error` whose cause is `SchemaIssue.Issue`. + * + * **Details** + * + * The assertion returns normally when validation succeeds. When the input does + * not satisfy the schema with a schema-only failure, it throws an `Error` with + * the `SchemaIssue.Issue` in its `cause`. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or asynchronous work at this + * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, + * instead of being converted to a schema validation error. + * + * @category guards + * @since 4.0.0 + */ +export function asserts(schema: S, input: I): asserts input is I & S["Type"] { + const parser = asExit(run(SchemaAST.toType(schema.ast))) + const exit = parser(input, SchemaAST.defaultParseOptions) + if (Exit.isFailure(exit)) { + const issue = InternalSchemaCause.getSchemaIssueOrThrow( + exit.cause, + "Assertion adapter can only throw schema issues" + ) + throw new Error("Schema validation failed", { cause: issue }) + } +} + +/** + * Creates an effectful decoder for `unknown` input. + * + * **When to use** + * + * Use when you need to decode untyped boundary input in an `Effect` whose + * failure channel is `SchemaIssue.Issue`, while preserving transformations + * and service requirements. + * + * **Details** + * + * The returned function succeeds with the schema's decoded `Type` or fails with a + * `SchemaIssue.Issue`. Decoding service requirements are preserved in the returned + * `Effect`. Parse options may be provided when creating the decoder and overridden + * when applying it. + * + * @see {@link decodeEffect} for input already typed as the schema's `Encoded` type + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUnknownEffect( + schema: S, + options?: SchemaAST.ParseOptions +): ( + input: unknown, + options?: SchemaAST.ParseOptions +) => Effect.Effect { + const parser = run(schema.ast) + return options === undefined + ? parser + : (input, overrideOptions) => parser(input, mergeParseOptions(options, overrideOptions)) +} + +/** + * Creates an effectful decoder for input already typed as the schema's `Encoded` + * type. + * + * **When to use** + * + * Use when you already have input typed as the schema's `Encoded` type and + * need an `Effect` whose failure channel is `SchemaIssue.Issue`, while + * preserving decoding service requirements. + * + * **Details** + * + * The returned function succeeds with the decoded `Type` or fails with a + * `SchemaIssue.Issue`, preserving any decoding service requirements in the + * returned `Effect`. + * + * @see {@link decodeUnknownEffect} for untyped boundary input + * @see {@link encodeEffect} for the opposite direction + * + * @category decoding + * @since 4.0.0 + */ +export const decodeEffect: ( + schema: S, + options?: SchemaAST.ParseOptions +) => ( + input: S["Encoded"], + options?: SchemaAST.ParseOptions +) => Effect.Effect = decodeUnknownEffect + +/** + * Creates a Promise-based decoder for `unknown` input. + * + * **When to use** + * + * Use when you need to decode untyped input with a service-free schema and + * return a JavaScript `Promise`. + * + * **Details** + * + * The returned function resolves with the decoded `Type` on success and rejects + * with an `Error` whose cause is a `SchemaIssue.Issue` on decoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or other non-schema reasons reject + * with an `Error` whose cause is the underlying `Cause`. + * + * @see {@link decodePromise} for input already typed as the schema's `Encoded` type + * @see {@link decodeUnknownEffect} for schemas that require decoding services or when failures should remain in `Effect` + * + * @category decoding + * @since 3.10.0 + */ +export function decodeUnknownPromise>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Promise { + return asPromise(decodeUnknownEffect(schema, options)) +} + +/** + * Creates a Promise-based decoder for input already typed as the schema's + * `Encoded` type. + * + * **When to use** + * + * Use when you already have input typed as the schema's `Encoded` type and need + * decoding to return a JavaScript `Promise`. + * + * **Details** + * + * The returned function resolves with the decoded `Type` on success and rejects + * with an `Error` whose cause is a `SchemaIssue.Issue` on decoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or other non-schema reasons reject + * with an `Error` whose cause is the underlying `Cause`. + * + * @see {@link decodeUnknownPromise} for untyped input returning a JavaScript `Promise` + * @see {@link decodeEffect} for preserving decoding services and failures in `Effect` + * + * @category decoding + * @since 3.10.0 + */ +export function decodePromise>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Promise { + return asPromise(decodeEffect(schema, options)) +} + +/** + * Creates a synchronous decoder for `unknown` input that reports failure safely + * as an `Exit`. + * + * **When to use** + * + * Use when you need to decode unknown input synchronously into an `Exit` whose + * failure contains `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function produces `Exit.Success` with the decoded `Type`. + * Schema issues are represented by an `Exit.Failure` cause containing a + * `SchemaIssue.Issue`. + * + * **Gotchas** + * + * Because this adapter runs synchronously, async decoding work can produce an + * `Exit.Failure` with a defect cause. When the cause contains both schema + * issues and non-schema reasons, all reasons remain in the returned `Cause`. + * + * @see {@link decodeExit} for input already typed as the schema's `Encoded` type + * @see {@link decodeUnknownEffect} for preserving decoding services and failures in `Effect` + * @see {@link decodeUnknownResult} for returning schema issues as data + * @see {@link decodeUnknownSync} for throwing on decoding failure + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUnknownExit>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Exit.Exit { + return asExit(decodeUnknownEffect(schema, options)) +} + +/** + * Creates a synchronous decoder for input already typed as the schema's `Encoded` + * type, reporting failure safely as an `Exit`. + * + * **When to use** + * + * Use when you need synchronous decoding of already typed `Encoded` input into + * an `Exit` whose failure contains `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function produces `Exit.Success` with the decoded `Type` or + * `Exit.Failure` with a `SchemaIssue.Issue`. + * + * **Gotchas** + * + * Because this adapter runs synchronously, async decoding work can produce an + * `Exit.Failure` with a defect cause. When the cause contains both schema + * issues and non-schema reasons, all reasons remain in the returned `Cause`. + * + * @see {@link decodeUnknownExit} for untyped input with the same `Exit` result shape + * @see {@link decodeEffect} for preserving decoding services and failures in `Effect` + * + * @category decoding + * @since 4.0.0 + */ +export const decodeExit: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Exit.Exit = + decodeUnknownExit + +/** @internal */ +export function decodeUnknownOption>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Option.Option { + return asOption(decodeUnknownEffect(schema, options)) +} + +/** @internal */ +export const decodeOption: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Option.Option = decodeUnknownOption + +/** + * Creates a decoder for `unknown` input that reports failure safely as a + * `Result`. + * + * **When to use** + * + * Use when decoding untyped boundary input and you want `SchemaIssue.Issue` + * failures returned as data in a `Result`. + * + * **Details** + * + * The returned function produces `Result.succeed` with the decoded `Type` on + * success or `Result.fail` with a `SchemaIssue.Issue` on decoding failure. + * + * **Gotchas** + * + * This adapter runs synchronously. Causes made entirely of schema issues become + * `Result.fail`, but causes that contain defects, interruptions, or asynchronous + * work at this synchronous boundary throw instead. + * + * @see {@link decodeResult} for input already typed as the schema's `Encoded` type + * @see {@link decodeUnknownEffect} for effectful or service-requiring decoding + * + * @category decoding + * @since 4.0.0 + */ +export function decodeUnknownResult>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Result.Result { + return asResult(decodeUnknownEffect(schema, options)) +} + +/** + * Creates a decoder for input already typed as the schema's `Encoded` type, + * reporting failure safely as a `Result`. + * + * **When to use** + * + * Use when you already have input typed as the schema's `Encoded` type and want + * schema decoding failures represented as `Result.fail` with `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function produces `Result.succeed` with the decoded `Type` on + * success or `Result.fail` with a `SchemaIssue.Issue` on decoding failure. + * + * **Gotchas** + * + * This synchronous adapter returns `Result.fail` for causes made entirely of + * schema issues, but causes that contain defects, interruptions, or other + * non-schema reasons throw instead. + * + * @see {@link decodeUnknownResult} for untyped input with the same `Result` shape + * @see {@link decodeEffect} for effectful or service-requiring decoding + * + * @category decoding + * @since 4.0.0 + */ +export const decodeResult: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Result.Result = + decodeUnknownResult + +/** + * Creates a synchronous decoder for `unknown` input. + * + * **When to use** + * + * Use to decode untrusted or dynamically typed input at a synchronous boundary + * where invalid data should throw an `Error` whose cause is `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function returns the decoded `Type` on success and throws an + * `Error` with the `SchemaIssue.Issue` in its `cause` on decoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or asynchronous work at this + * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, + * instead of being converted to a schema validation error. + * + * @see {@link decodeSync} for input already typed as the schema's `Encoded` type + * @see {@link decodeUnknownEffect} for preserving decoding failures in `Effect` + * @see {@link decodeUnknownResult} for returning schema issues as data + * + * @category decoding + * @since 3.10.0 + */ +export function decodeUnknownSync>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => S["Type"] { + return asSync(decodeUnknownEffect(schema, options)) +} + +/** + * Creates a synchronous decoder for input already typed as the schema's `Encoded` + * type. + * + * **When to use** + * + * Use to decode values already typed as the schema's `Encoded` input when + * decoding failure should throw an `Error` whose cause is `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function returns the decoded `Type` on success and throws an + * `Error` with the `SchemaIssue.Issue` in its `cause` on decoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or asynchronous work at this + * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, + * instead of being converted to a schema validation error. + * + * @see {@link decodeUnknownSync} for untrusted or dynamically typed input + * @see {@link decodeResult} for returning schema issues as data + * @see {@link decodeEffect} for preserving decoding failures in `Effect` + * + * @category decoding + * @since 3.10.0 + */ +export const decodeSync: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => S["Type"] = decodeUnknownSync + +/** + * Creates an effectful encoder for `unknown` input. + * + * **When to use** + * + * Use when you need to encode untyped boundary input in an `Effect` whose + * failure channel is `SchemaIssue.Issue`, while preserving service + * requirements. + * + * **Details** + * + * The returned function succeeds with the schema's `Encoded` value or fails with a + * `SchemaIssue.Issue`. Encoding service requirements are preserved in the returned + * `Effect`. Parse options may be provided when creating the encoder and overridden + * when applying it. + * + * @see {@link encodeEffect} for the typed-input variant when the value is already typed as the schema's decoded `Type` + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUnknownEffect( + schema: S, + options?: SchemaAST.ParseOptions +): ( + input: unknown, + options?: SchemaAST.ParseOptions +) => Effect.Effect { + const parser = run(SchemaAST.flip(schema.ast)) + return options === undefined + ? parser + : (input, overrideOptions) => parser(input, mergeParseOptions(options, overrideOptions)) +} + +/** + * Creates an effectful encoder for input already typed as the schema's decoded + * `Type`. + * + * **When to use** + * + * Use when you need to encode values already typed as the schema's decoded + * `Type` in an `Effect` whose failure channel is `SchemaIssue.Issue`, while + * preserving service requirements. + * + * **Details** + * + * The returned function succeeds with the schema's `Encoded` value or fails with a + * `SchemaIssue.Issue`, preserving any encoding service requirements in the + * returned `Effect`. + * + * @see {@link encodeUnknownEffect} for encoding unknown input before the value is statically typed as the schema's `Type` + * + * @category encoding + * @since 4.0.0 + */ +export const encodeEffect: ( + schema: S, + options?: SchemaAST.ParseOptions +) => ( + input: S["Type"], + options?: SchemaAST.ParseOptions +) => Effect.Effect = encodeUnknownEffect + +/** + * Creates a Promise-based encoder for `unknown` input. + * + * **When to use** + * + * Use when you need to encode untrusted or dynamically typed values with a + * service-free schema and return a JavaScript `Promise`. + * + * **Details** + * + * The returned function resolves with the schema's `Encoded` value on success and + * rejects with an `Error` whose cause is a `SchemaIssue.Issue` on encoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or other non-schema reasons reject + * with an `Error` whose cause is the underlying `Cause`. + * + * @see {@link encodePromise} for input already typed as the schema's decoded `Type` + * @see {@link encodeUnknownEffect} for schemas that require encoding services or when failures should remain in `Effect` + * + * @category encoding + * @since 3.10.0 + */ +export const encodeUnknownPromise = >( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Promise => + asPromise(encodeUnknownEffect(schema, options)) + +/** + * Creates a Promise-based encoder for input already typed as the schema's decoded + * `Type`. + * + * **When to use** + * + * Use when you already have values typed as the schema's decoded `Type` and + * need encoding to return a JavaScript `Promise`. + * + * **Details** + * + * The returned function resolves with the schema's `Encoded` value on success and + * rejects with an `Error` whose cause is a `SchemaIssue.Issue` on encoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or other non-schema reasons reject + * with an `Error` whose cause is the underlying `Cause`. + * + * @see {@link encodeUnknownPromise} for encoding untyped input + * @see {@link encodeEffect} for effectful encoding or schemas with encoding service requirements + * + * @category encoding + * @since 3.10.0 + */ +export const encodePromise: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Promise = encodeUnknownPromise + +/** + * Creates a synchronous encoder for `unknown` input that reports failure safely + * as an `Exit`. + * + * **When to use** + * + * Use when you need synchronous encoding of unknown input into an `Exit` whose + * failure contains `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function produces `Exit.Success` with the schema's `Encoded` value + * or `Exit.Failure` with a `SchemaIssue.Issue`. + * + * **Gotchas** + * + * Because this adapter runs synchronously, async encoding work can produce an + * `Exit.Failure` with a defect cause. When the cause contains both schema + * issues and non-schema reasons, all reasons remain in the returned `Cause`. + * + * @see {@link encodeExit} for input already typed as the schema's decoded `Type` + * @see {@link encodeUnknownEffect} for effectful encoding that preserves service requirements + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUnknownExit>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Exit.Exit { + return asExit(encodeUnknownEffect(schema, options)) +} + +/** + * Creates a synchronous encoder for input already typed as the schema's decoded + * `Type`, reporting failure safely as an `Exit`. + * + * **When to use** + * + * Use when you need synchronous encoding of already typed schema values into + * an `Exit` whose failure contains `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function produces `Exit.Success` with the schema's `Encoded` value + * or `Exit.Failure` with a `SchemaIssue.Issue`. + * + * **Gotchas** + * + * Because this adapter runs synchronously, async encoding work can produce an + * `Exit.Failure` with a defect cause. When the cause contains both schema + * issues and non-schema reasons, all reasons remain in the returned `Cause`. + * + * @see {@link encodeUnknownExit} for unknown input with the same `Exit` result shape + * @see {@link encodeEffect} for effectful encoding that preserves service requirements + * + * @category encoding + * @since 4.0.0 + */ +export const encodeExit: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Exit.Exit = + encodeUnknownExit + +/** @internal */ +export function encodeUnknownOption>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Option.Option { + return asOption(encodeUnknownEffect(schema, options)) +} + +/** @internal */ +export const encodeOption: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Option.Option = encodeUnknownOption + +/** + * Creates an encoder for `unknown` input that reports failure safely as a + * `Result`. + * + * **When to use** + * + * Use when encoding values from an unknown or dynamically typed boundary + * synchronously, and you want `SchemaIssue.Issue` failures returned as `Result` + * data. + * + * **Details** + * + * The returned function produces `Result.succeed` with the schema's `Encoded` + * value on success or `Result.fail` with a `SchemaIssue.Issue` on encoding + * failure. + * + * **Gotchas** + * + * This adapter runs synchronously. Causes made entirely of schema issues become + * `Result.fail`, but causes that contain defects, interruptions, or asynchronous + * work at this synchronous boundary throw instead. + * + * @see {@link encodeResult} for input already typed as the schema's decoded `Type` + * @see {@link encodeUnknownEffect} for effectful encoding, including schemas with encoding service requirements + * + * @category encoding + * @since 4.0.0 + */ +export function encodeUnknownResult>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => Result.Result { + return asResult(encodeUnknownEffect(schema, options)) +} + +/** + * Creates an encoder for input already typed as the schema's decoded `Type`, + * reporting failure safely as a `Result`. + * + * **When to use** + * + * Use when you already have input typed as the schema's decoded `Type` and want + * encoding failures returned as `Result.fail` with `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function produces `Result.succeed` with the schema's `Encoded` + * value on success or `Result.fail` with a `SchemaIssue.Issue` on encoding + * failure. + * + * **Gotchas** + * + * This synchronous adapter returns `Result.fail` for causes made entirely of + * schema issues, but causes that contain defects, interruptions, or other + * non-schema reasons throw instead. + * + * @see {@link encodeUnknownResult} for the same `Result` shape when the input is not already typed + * + * @category encoding + * @since 4.0.0 + */ +export const encodeResult: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Result.Result = + encodeUnknownResult + +/** + * Creates a synchronous encoder for `unknown` input. + * + * **When to use** + * + * Use when you need to encode values from untyped input in synchronous code and + * want encoding failures to throw an `Error` whose cause is `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function returns the schema's `Encoded` value on success and throws + * an `Error` with the `SchemaIssue.Issue` in its `cause` on encoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or asynchronous work at this + * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, + * instead of being converted to a schema validation error. + * + * @see {@link encodeSync} for input already typed as the schema's decoded `Type` + * @see {@link encodeUnknownEffect} for effectful encoding that preserves service requirements + * + * @category encoding + * @since 3.10.0 + */ +export function encodeUnknownSync>( + schema: S, + options?: SchemaAST.ParseOptions +): (input: unknown, options?: SchemaAST.ParseOptions) => S["Encoded"] { + return asSync(encodeUnknownEffect(schema, options)) +} + +/** + * Creates a synchronous encoder for input already typed as the schema's decoded + * `Type`. + * + * **When to use** + * + * Use to encode already typed schema values synchronously when encoding failure + * should throw an `Error` whose cause is `SchemaIssue.Issue`. + * + * **Details** + * + * The returned function returns the schema's `Encoded` value on success and throws + * an `Error` with the `SchemaIssue.Issue` in its `cause` on encoding failure. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * **Gotchas** + * + * Causes that contain defects, interruptions, or asynchronous work at this + * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, + * instead of being converted to a schema validation error. + * + * @see {@link encodeUnknownSync} for unknown input with the same throwing boundary + * @see {@link encodeResult} for returning schema issues as data + * @see {@link encodeEffect} for effectful encoding that preserves service requirements + * + * @category encoding + * @since 3.10.0 + */ +export const encodeSync: >( + schema: S, + options?: SchemaAST.ParseOptions +) => (input: S["Type"], options?: SchemaAST.ParseOptions) => S["Encoded"] = encodeUnknownSync + +const mergeParseOptions = ( + options: SchemaAST.ParseOptions, + overrideOptions: SchemaAST.ParseOptions | undefined +): SchemaAST.ParseOptions => overrideOptions ? { ...options, ...overrideOptions } : options + +const getValue = (value: unknown): Effect.Effect => { + if (value === InternalParser.missing) { + return Effect.fail(new SchemaIssue.InvalidValue()) + } + return Effect.succeed(value) +} + +/** @internal */ +export function run(ast: SchemaAST.AST) { + return runWithCompiler(normalCompiler, ast) +} + +function runWithCompiler(compiler: Compiler, ast: SchemaAST.AST) { + let parser: Parser + return (input: unknown, options?: SchemaAST.ParseOptions): Effect.Effect => { + const result = (parser ??= compiler(ast))( + input, + options ?? SchemaAST.defaultParseOptions + ) + if (result === InternalParser.sameExit) { + return Effect.succeed(input) as Effect.Effect + } + if (!effectIsExit(result)) { + return Effect.flatMapEager(result, getValue) + } + return (result as InternalParser.Success)[InternalParser.args] === + InternalParser.missing + ? getValue(InternalParser.missing) + : result as Effect.Effect + } +} + +function asPromise( + parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect +): (input: E, options?: SchemaAST.ParseOptions) => Promise { + return (input: E, options?: SchemaAST.ParseOptions) => + Effect.runPromiseExit(parser(input, options)).then((exit) => { + if (Exit.isSuccess(exit)) { + return exit.value + } + const issue = InternalSchemaCause.getSchemaIssueOrThrow( + exit.cause, + "Promise adapter can only reject schema issues" + ) + throw new Error("Schema validation failed", { cause: issue }) + }) +} + +function asExit( + parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect +): (input: E, options?: SchemaAST.ParseOptions) => Exit.Exit { + return (input: E, options?: SchemaAST.ParseOptions) => Effect.runSyncExit(parser(input, options) as any) +} + +/** @internal */ +export function asOption( + parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect +): (input: E, options?: SchemaAST.ParseOptions) => Option.Option { + const parserExit = asExit(parser) + return (input: E, options?: SchemaAST.ParseOptions) => { + const exit = parserExit(input, options) + if (Exit.isSuccess(exit)) { + return Option.some(exit.value) + } + InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Option adapter can only return none for schema issues") + return Option.none() + } +} + +function asResult( + parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect +): (input: E, options?: SchemaAST.ParseOptions) => Result.Result { + const parserExit = asExit(parser) + return (input: E, options?: SchemaAST.ParseOptions) => { + const exit = parserExit(input, options) + if (Exit.isSuccess(exit)) { + return Result.succeed(exit.value) + } + return Result.fail( + InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Result adapter can only return schema issues") + ) + } +} + +function asSync( + parser: (input: E, options?: SchemaAST.ParseOptions) => Effect.Effect +): (input: E, options?: SchemaAST.ParseOptions) => T { + const parserExit = asExit(parser) + return (input: E, options?: SchemaAST.ParseOptions) => { + const exit = parserExit(input, options) + if (Exit.isSuccess(exit)) { + return exit.value + } + const issue = InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Sync adapter can only throw schema issues") + throw new Error("Schema validation failed", { cause: issue }) + } +} + +/** @internal */ +export interface Parser { + ( + input: unknown, + options: SchemaAST.ParseOptions + ): Effect.Effect +} + +/** @internal */ +export interface Compiler { + (ast: SchemaAST.AST): Parser +} + +const normalCompiler: Compiler = memoize((ast) => makeParser(ast, normalCompiler)) +const constructorCompiler: Compiler = memoize((ast) => makeParser(ast, constructorCompiler, compileConstructorDefault)) +const compileDefaulted = memoize((ast: SchemaAST.AST) => + makeParser(ast, constructorCompiler, compileConstructorDefault, ast.context?.constructorDefault) +) + +function compileConstructorDefault(ast: SchemaAST.AST): Parser { + return ast.context?.constructorDefault ? compileDefaulted(ast) : constructorCompiler(ast) +} + +function applyTransformation( + result: Effect.Effect, + current: unknown, + transformation: SchemaAST.Link["transformation"], + options: SchemaAST.ParseOptions +): Effect.Effect { + let transformed: Effect.Effect, SchemaIssue.Issue, unknown> + if (effectIsExit(result) && result._tag === "Success") { + const optional = InternalParser.toOption( + result === InternalParser.sameExit + ? current + : (result as InternalParser.Success)[InternalParser.args] + ) + transformed = transformation._tag === "Transformation" + ? transformation.decode.run(optional, options) + : transformation.decode(InternalParser.succeed(optional), options) + } else if (transformation._tag === "Transformation") { + transformed = Effect.flatMapEager( + result, + (value) => transformation.decode.run(InternalParser.toOption(value), options) + ) + } else { + transformed = transformation.decode( + Effect.mapEager(result, InternalParser.toOption), + options + ) + } + return effectIsExit(transformed) && transformed._tag === "Success" + ? InternalParser.fromOptionExit( + (transformed as InternalParser.Success, SchemaIssue.Issue>)[InternalParser.args] + ) + : Effect.flatMapEager(transformed, InternalParser.fromOptionExit) +} + +function makeConstructorParser(descriptor: SchemaAST.ConstructorDescriptor, compile: Compiler): Parser { + let sourceParser: Parser + return (input, options) => { + if (input === InternalParser.missing) return InternalParser.missingExit + if (descriptor.isConstructed(input)) return InternalParser.sameExit + const result = (sourceParser ??= compile(descriptor.link.to))(input, options) + return applyTransformation(result, input, descriptor.link.transformation, options) + } +} + +function makeParser( + ast: SchemaAST.AST, + compile: Compiler, + compileConstructorDefault?: Compiler, + constructorDefault?: SchemaAST.Link +): Parser { + const descriptor = compileConstructorDefault ? SchemaAST.getConstructorDescriptor(ast) : undefined + const parser = descriptor + ? makeConstructorParser(descriptor, compile) + : ast.getParser(compile, compileConstructorDefault) + const checks = ast.checks + const links = constructorDefault + ? ast.encoding ? [...ast.encoding, constructorDefault] : [constructorDefault] + : ast.encoding + const encodingChecks = (ast as any).encodingChecks + const astOptions = (checks ? checks[checks.length - 1].annotations : ast.annotations) + ?.["parseOptions"] + if (!links && !checks && !encodingChecks) { + if (!astOptions) { + return parser + } + return (input, options) => parser(input, mergeParseOptions(options, astOptions)) + } + let encodingParsers: ReadonlyArray | undefined + const parseLocal = ( + input: unknown, + options: SchemaAST.ParseOptions + ) => { + let result = parser(input, options) + if (encodingChecks && !options.disableChecks) { + if (effectIsExit(result)) { + if (result._tag === "Success") { + const output = result === InternalParser.sameExit + ? input + : (result as InternalParser.Success)[InternalParser.args] + if (input !== InternalParser.missing && output !== InternalParser.missing) { + const issues = SchemaAST.collectIssues(encodingChecks, input, undefined, ast, options) + if (issues) { + result = Effect.fail(new SchemaIssue.Composite(ast, issues, input, options)) + } + } + } + } else { + result = Effect.flatMap(result, (value) => { + if (input !== InternalParser.missing && value !== InternalParser.missing) { + const issues = SchemaAST.collectIssues(encodingChecks, input, undefined, ast, options) + if (issues) { + return Effect.fail(new SchemaIssue.Composite(ast, issues, input, options)) + } + } + return Effect.succeed(value) + }) + } + } + + if (checks && !options.disableChecks) { + if (effectIsExit(result)) { + if (result._tag === "Success") { + const value = result === InternalParser.sameExit + ? input + : (result as InternalParser.Success)[InternalParser.args] + if (value === InternalParser.missing) return result + const issues = SchemaAST.collectIssues(checks, value, undefined, ast, options) + if (issues) { + result = Effect.fail(new SchemaIssue.Composite(ast, issues, value, options)) + } + } + } else { + result = Effect.flatMap(result, (value) => { + if (value !== InternalParser.missing) { + const issues = SchemaAST.collectIssues(checks, value, undefined, ast, options) + if (issues) { + return Effect.fail(new SchemaIssue.Composite(ast, issues, value, options)) + } + } + return Effect.succeed(value) + }) + } + } + + return result + } + if (!links) { + return astOptions + ? (input, options) => parseLocal(input, mergeParseOptions(options, astOptions)) + : parseLocal + } + return ( + input: unknown, + options: SchemaAST.ParseOptions + ) => { + if (astOptions) { + options = mergeParseOptions(options, astOptions) + } + const parsers = encodingParsers ??= links.map((link) => compile(link.to)) + let current = input + let result = parsers[parsers.length - 1](input, options) + for (let i = links.length - 1; i >= 0; i--) { + result = applyTransformation(result, current, links[i].transformation, options) + if (i !== 0) { + const next = parsers[i - 1] + if ((result as Exit.Exit)._tag === "Success") { + current = (result as InternalParser.Success)[InternalParser.args] + result = next(current, options) + } else { + result = Effect.flatMapEager(result, (value) => { + const nextResult = next(value, options) + return nextResult === InternalParser.sameExit ? InternalParser.succeed(value) : nextResult + }) + } + } + } + if ((result as Exit.Exit)._tag === "Success") { + const value = (result as InternalParser.Success)[InternalParser.args] + const local = parseLocal(value, options) + return local === InternalParser.sameExit ? result : local + } + result = Effect.catchCause( + result, + (cause) => + Effect.failCauseSync(() => + Cause.map( + cause, + (issue) => + new SchemaIssue.Encoding( + ast, + issue, + input, + options + ) + ) + ) + ) + return Effect.flatMapEager(result, (value) => { + const local = parseLocal(value, options) + return local === InternalParser.sameExit ? InternalParser.succeed(value) : local + }) + } +} diff --git a/.repos/effect/packages/effect/src/SchemaRepresentation.ts b/.repos/effect/packages/effect/src/SchemaRepresentation.ts new file mode 100644 index 000000000..c554eed14 --- /dev/null +++ b/.repos/effect/packages/effect/src/SchemaRepresentation.ts @@ -0,0 +1,1348 @@ +/** + * Open, compiler-extensible representation of Effect schemas. + * + * @since 4.0.0 + */ +import * as InternalRecord from "./internal/record.ts" +import * as InternalFromJsonSchemaDocument from "./internal/schema/fromJsonSchemaDocument.ts" +import * as InternalFromRepresentation from "./internal/schema/fromRepresentation.ts" +import * as InternalSchema from "./internal/schema/schema.ts" +import * as InternalToCodeDocument from "./internal/schema/toCodeDocument.ts" +import * as InternalToJsonSchemaDocument from "./internal/schema/toJsonSchemaDocument.ts" +import * as InternalToRepresentation from "./internal/schema/toRepresentation.ts" +import type * as JsonSchema from "./JsonSchema.ts" +import * as Option from "./Option.ts" +import * as Schema from "./Schema.ts" +import * as SchemaAST from "./SchemaAST.ts" +import * as SchemaGetter from "./SchemaGetter.ts" + +/** + * Open persistence identity carried by declarations and opaque checks. + * + * @category annotations + * @since 4.0.0 + */ +export interface RepresentationAnnotation { + readonly id: string + readonly payload: Schema.Json +} + +/** + * Open persistence identity and schema dependencies carried by opaque checks. + * + * @category annotations + * @since 4.0.0 + */ +export interface CheckRepresentationAnnotation extends RepresentationAnnotation { + readonly schemas?: ReadonlyArray | undefined +} + +/** + * Input passed to JSON Schema compiler annotations. + * + * @since 4.0.0 + */ +export declare namespace ToJsonSchema { + /** + * Input for a check compiler. + * + * @category models + * @since 4.0.0 + */ + export interface CheckInput { + readonly type: JsonSchema.Type | undefined + readonly schemas: ReadonlyArray + } + + /** + * Compiles a check to a JSON Schema fragment. + * + * **Gotchas** + * + * Treat the input schemas as immutable. The returned value must be a valid JSON Schema object graph and must not be + * mutated after this function returns. Return a new object graph to produce different output during a later + * compilation. + * + * @category models + * @since 4.0.0 + */ + export type Check = (input: CheckInput) => JsonSchema.JsonSchema +} + +/** + * Input and output contracts for code generation annotations. + * + * @since 4.0.0 + */ +export declare namespace Generation { + /** + * Input for declaration code generation. + * + * @category models + * @since 4.0.0 + */ + export interface DeclarationInput { + readonly typeParameters: ReadonlyArray + } + + /** + * Output of declaration code generation. + * + * @category models + * @since 4.0.0 + */ + export interface DeclarationOutput { + readonly runtime: string + readonly Type: string + readonly importDeclarations?: ReadonlyArray | undefined + } + + /** + * Declaration code generator. + * + * @category models + * @since 4.0.0 + */ + export type Declaration = (input: DeclarationInput) => DeclarationOutput + + /** + * Input for check code generation. + * + * @category models + * @since 4.0.0 + */ + export interface CheckInput { + readonly schemas: ReadonlyArray + } + + /** + * Output of check code generation. + * + * @category models + * @since 4.0.0 + */ + export interface CheckOutput { + readonly runtime: string + readonly importDeclarations?: ReadonlyArray | undefined + } + + /** + * Check code generator. + * + * @category models + * @since 4.0.0 + */ + export type Check = (input: CheckInput) => CheckOutput +} + +/** + * A custom opaque declaration. + * + * @category models + * @since 4.0.0 + */ +export interface Declaration { + readonly _tag: "Declaration" + readonly representation?: RepresentationAnnotation | undefined + readonly annotations?: Schema.Annotations.Annotations | undefined + readonly typeParameters: ReadonlyArray + readonly checks: ReadonlyArray +} + +/** + * A lazily resolved representation. + * + * @category models + * @since 4.0.0 + */ +export interface Suspend { + readonly _tag: "Suspend" + readonly annotations?: Schema.Annotations.Annotations | undefined + readonly checks: readonly [] + readonly thunk: Representation +} + +/** + * A named reference. + * + * @category models + * @since 4.0.0 + */ +export interface Reference { + readonly _tag: "Reference" + readonly $ref: string +} + +interface Keyword { + readonly _tag: Tag + readonly annotations?: Schema.Annotations.Annotations | undefined + readonly checks: ReadonlyArray +} + +/** + * The null keyword representation. + * + * @category models + * @since 4.0.0 + */ +export interface Null extends Keyword<"Null"> {} +/** + * The undefined keyword representation. + * + * @category models + * @since 4.0.0 + */ +export interface Undefined extends Keyword<"Undefined"> {} +/** + * The void keyword representation. + * + * @category models + * @since 4.0.0 + */ +export interface Void extends Keyword<"Void"> {} +/** + * The never keyword representation. + * + * @category models + * @since 4.0.0 + */ +export interface Never extends Keyword<"Never"> {} +/** + * The unknown keyword representation. + * + * @category models + * @since 4.0.0 + */ +export interface Unknown extends Keyword<"Unknown"> {} +/** + * The any keyword representation. + * + * @category models + * @since 4.0.0 + */ +export interface Any extends Keyword<"Any"> {} + +/** + * A string representation. + * + * @category models + * @since 4.0.0 + */ +export interface String extends Keyword<"String"> {} + +/** + * A number representation. + * + * @category models + * @since 4.0.0 + */ +export interface Number extends Keyword<"Number"> {} +/** + * A boolean representation. + * + * @category models + * @since 4.0.0 + */ +export interface Boolean extends Keyword<"Boolean"> {} +/** + * A bigint representation. + * + * @category models + * @since 4.0.0 + */ +export interface BigInt extends Keyword<"BigInt"> {} +/** + * A symbol representation. + * + * @category models + * @since 4.0.0 + */ +export interface Symbol extends Keyword<"Symbol"> {} + +/** + * A literal representation. + * + * **Details** + * + * The live representation stores the native literal value. Persistent codecs + * add an explicit type discriminator when encoding it. + * + * @category models + * @since 4.0.0 + */ +export interface Literal extends Keyword<"Literal"> { + readonly literal: SchemaAST.LiteralValue +} + +/** + * A unique global symbol representation. + * + * @category models + * @since 4.0.0 + */ +export interface UniqueSymbol extends Keyword<"UniqueSymbol"> { + readonly symbol: symbol +} + +/** + * The object keyword representation. + * + * @category models + * @since 4.0.0 + */ +export interface ObjectKeyword extends Keyword<"ObjectKeyword"> {} + +/** + * An enum representation. + * + * **Details** + * + * Enum members are stored as native string or number values. Persistent + * codecs add an explicit type discriminator when encoding them. + * + * @category models + * @since 4.0.0 + */ +export interface Enum extends Keyword<"Enum"> { + readonly enums: ReadonlyArray +} + +/** + * A template literal representation. + * + * @category models + * @since 4.0.0 + */ +export interface TemplateLiteral extends Keyword<"TemplateLiteral"> { + readonly parts: ReadonlyArray +} + +/** + * A tuple element. + * + * @category models + * @since 4.0.0 + */ +export interface Element { + readonly isOptional: boolean + readonly type: Representation + readonly annotations?: Schema.Annotations.Annotations | undefined +} + +/** + * An array or tuple representation. + * + * @category models + * @since 4.0.0 + */ +export interface Arrays extends Keyword<"Arrays"> { + readonly elements: ReadonlyArray + readonly rest: ReadonlyArray +} + +/** + * A property signature. + * + * **Details** + * + * The live representation stores the native property key. Persistent codecs + * add an explicit type discriminator when encoding it. + * + * **Gotchas** + * + * Local symbols can be represented while the schema is live, but persistent + * codecs reject them because they cannot be reconstructed by identity. + * + * @category models + * @since 4.0.0 + */ +export interface PropertySignature { + readonly name: PropertyKey + readonly type: Representation + readonly isOptional: boolean + readonly isMutable: boolean + readonly annotations?: Schema.Annotations.Annotations | undefined +} + +/** + * An index signature. + * + * @category models + * @since 4.0.0 + */ +export interface IndexSignature { + readonly parameter: Representation + readonly type: Representation +} + +/** + * An object representation. + * + * @category models + * @since 4.0.0 + */ +export interface Objects extends Keyword<"Objects"> { + readonly propertySignatures: ReadonlyArray + readonly indexSignatures: ReadonlyArray +} + +/** + * A union representation. + * + * @category models + * @since 4.0.0 + */ +export interface Union extends Keyword<"Union"> { + readonly types: ReadonlyArray + readonly mode: "anyOf" | "oneOf" +} + +/** + * The structural schema representation. + * + * @category models + * @since 4.0.0 + */ +export type Representation = + | Declaration + | Reference + | Suspend + | Null + | Undefined + | Void + | Never + | Unknown + | Any + | String + | Number + | Boolean + | BigInt + | Symbol + | Literal + | UniqueSymbol + | ObjectKeyword + | Enum + | TemplateLiteral + | Arrays + | Objects + | Union + +/** + * A structural check. + * + * @category models + * @since 4.0.0 + */ +export type Check = Filter | FilterGroup + +/** + * An opaque leaf check. + * + * @category models + * @since 4.0.0 + */ +export interface Filter { + readonly _tag: "Filter" + readonly representation?: CheckRepresentationAnnotation | undefined + readonly annotations?: Schema.Annotations.Annotations | undefined + readonly aborted: boolean +} + +/** + * A non-empty group of checks. + * + * @category models + * @since 4.0.0 + */ +export interface FilterGroup { + readonly _tag: "FilterGroup" + readonly representation?: CheckRepresentationAnnotation | undefined + readonly annotations?: Schema.Annotations.Annotations | undefined + readonly checks: readonly [Check, ...Array] +} + +/** + * Named representation definitions. + * + * @category models + * @since 4.0.0 + */ +export interface References { + readonly [$ref: string]: Representation +} + +/** + * A single representation and its definitions. + * + * @category models + * @since 4.0.0 + */ +export interface Document { + readonly representation: Representation + readonly references: References +} + +/** + * Multiple representations sharing definitions. + * + * @category models + * @since 4.0.0 + */ +export interface MultiDocument { + readonly representations: readonly [Representation, ...Array] + readonly references: References +} + +/** + * Reviver for a declaration. + * + * @category models + * @since 4.0.0 + */ +export interface DeclarationReviver

{ + readonly id: string + readonly payloadSchema: Schema.Decoder

+ readonly revive: (input: { + readonly payload: P + readonly typeParameters: ReadonlyArray + readonly annotations: Schema.Annotations.Annotations | undefined + }) => Schema.Top +} + +/** + * Reviver for a leaf check. + * + * @category models + * @since 4.0.0 + */ +export interface FilterReviver

{ + readonly id: string + readonly payloadSchema: Schema.Decoder

+ readonly revive: (input: { + readonly payload: P + readonly schemas: ReadonlyArray + readonly annotations: Schema.Annotations.Filter | undefined + }) => SchemaAST.Filter +} + +/** + * Reviver for a check group. + * + * @category models + * @since 4.0.0 + */ +export interface FilterGroupReviver

{ + readonly id: string + readonly payloadSchema: Schema.Decoder

+ readonly revive: (input: { + readonly payload: P + readonly schemas: ReadonlyArray + readonly annotations: Schema.Annotations.Filter | undefined + }) => SchemaAST.FilterGroup +} + +/** + * A check reviver. + * + * @category models + * @since 4.0.0 + */ +export type CheckReviver

= FilterReviver

| FilterGroupReviver

+ +/** + * A typed reviver. + * + * @category models + * @since 4.0.0 + */ +export type Reviver

= DeclarationReviver

| CheckReviver

+ +/** + * A reviver erased only at collection boundaries. + * + * @category models + * @since 4.0.0 + */ +export type AnyReviver = Reviver + +/** + * Creates a declaration reviver while inferring its payload type from `payloadSchema`. + * + * @category constructors + * @since 4.0.0 + */ +export const makeDeclarationReviver:

( + id: string, + payloadSchema: Schema.Decoder

, + revive: DeclarationReviver

["revive"] +) => DeclarationReviver

= InternalSchema.makeDeclarationReviver + +/** + * Creates a filter reviver while inferring its payload type from `payloadSchema`. + * + * @category constructors + * @since 4.0.0 + */ +export const makeFilterReviver:

( + id: string, + payloadSchema: Schema.Decoder

, + revive: FilterReviver

["revive"] +) => FilterReviver

= InternalSchema.makeFilterReviver + +/** + * Creates a filter group reviver while inferring its payload type from `payloadSchema`. + * + * @category constructors + * @since 4.0.0 + */ +export const makeFilterGroupReviver:

( + id: string, + payloadSchema: Schema.Decoder

, + revive: FilterGroupReviver

["revive"] +) => FilterGroupReviver

= InternalSchema.makeFilterGroupReviver + +/** + * Options for importing JSON Schema Draft 2020-12 documents. + * + * **When to use** + * + * Use when you need to configure pattern handling or transform each JSON Schema node before translation. + * + * **Details** + * + * `patterns` controls pattern constraints reached during best-effort translation, including `pattern`, the keys of + * `patternProperties`, and patterns nested in `propertyNames`: + * + * - `"error"` rejects the document and is the default. + * - `"ignore"` skips the constraint. + * - `"apply"` compiles and enforces the constraint with the runtime's native regular expression engine. + * + * **Gotchas** + * + * Use `patterns: "apply"` only for trusted documents because regular expression evaluation may block for an unbounded + * amount of time. `patterns: "ignore"` weakens validation by accepting values that the source document may reject. + * Ignoring `patternProperties` also skips its value constraints and `additionalProperties`, because matching keys cannot + * be determined without evaluating the patterns. + * `onEnter` must return a JSON Schema object. Its result is used directly, and exceptions raised by the callback pass + * through unchanged. + * + * @category models + * @since 4.0.0 + */ +export interface FromJsonSchemaOptions { + readonly onEnter?: ((schema: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined + /** + * Controls how reached JSON Schema regular expression patterns are imported. + * + * @default "error" + */ + readonly patterns?: "error" | "ignore" | "apply" | undefined +} + +/** + * Runtime and TypeScript source generated for one schema. + * + * @category models + * @since 4.0.0 + */ +export interface Code { + readonly runtime: string + readonly Type: string +} + +/** + * Creates generated runtime and TypeScript source strings for a schema. + * + * @category constructors + * @since 4.0.0 + */ +export const makeCode: (runtime: string, Type: string) => Code = InternalToCodeDocument.makeCode + +/** + * Auxiliary source artifact emitted while generating schema code. + * + * @category models + * @since 4.0.0 + */ +export type Artifact = + | { + readonly _tag: "Symbol" + readonly identifier: string + readonly code: Code + } + | { + readonly _tag: "Enum" + readonly identifier: string + readonly code: Code + } + | { + readonly _tag: "Import" + readonly importDeclaration: string + } + +/** + * Generated schema code together with named references and auxiliary artifacts. + * + * @category models + * @since 4.0.0 + */ +export interface CodeDocument { + readonly codes: ReadonlyArray + readonly references: { + readonly nonRecursives: ReadonlyArray<{ + readonly $ref: string + readonly code: Code + }> + readonly recursives: Readonly> + } + readonly artifacts: ReadonlyArray +} + +/** + * Information supplied to a reference policy for one representation candidate. + * + * @category models + * @since 4.0.0 + */ +export interface ReferencePolicyInput { + /** The encoded-side AST owner for the candidate. Contextual copies can share the same owner. */ + readonly ast: SchemaAST.AST + /** The number of times this candidate was encountered. Structurally equal ASTs remain distinct candidates. */ + readonly occurrences: number + /** The resolved encoded-side identifier, including an inherited `Encoded` suffix when applicable. */ + readonly identifier: string | undefined +} + +/** + * Function that chooses whether a representation candidate is emitted as a named reference. + * + * **When to use** + * + * Use when you need reference allocation based on schema identity, occurrence counts, identifiers, or another + * application-specific rule. + * + * **Details** + * + * Return a reference name to extract the candidate, or `undefined` to keep it inline. The policy is called once per + * candidate after all occurrences have been counted. The `identifier` is the resolved identifier for the encoded AST, + * including an `Encoded` suffix when an identifier is inherited from the source side of an encoding. If different + * candidates request the same name, later names receive numeric suffixes in encounter order. + * + * **Gotchas** + * + * Recursive candidates always require a reference. When the policy returns `undefined` for one, the generator assigns + * a synthetic name. Treat the input AST as immutable and keep the policy deterministic. + * + * @see {@link ToRepresentationOptions} for configuring representation generation + * + * @category models + * @since 4.0.0 + */ +export type ReferencePolicy = (input: ReferencePolicyInput) => string | undefined + +/** + * Options for generating schema representations. + * + * @category configuration + * @since 4.0.0 + */ +export interface ToRepresentationOptions { + /** + * Chooses which representation candidates are extracted as named references. + * + * **Details** + * + * The default policy returns the resolved `identifier`, so anonymous non-recursive candidates remain inline even when + * they occur more than once. + * + * **Gotchas** + * + * Recursive candidates always require a reference and receive a synthetic name when the policy returns `undefined`. + * + * @default ({ identifier }) => identifier + */ + readonly referencePolicy?: ReferencePolicy | undefined +} + +/** + * Lowers the encoded side of an AST to a live representation document. + * + * **When to use** + * + * Use when you have one `SchemaAST.AST` and need a live `Document` for inspection, persistence, or compilation. + * + * **Details** + * + * Apply `SchemaAST.toType` to the AST first to lower its type side instead. The optional reference policy controls which + * candidates are moved into the document's shared reference table. + * + * @see {@link toRepresentations} for multiple roots sharing one reference table + * + * @category constructors + * @since 4.0.0 + */ +export function toRepresentation(ast: SchemaAST.AST, options?: ToRepresentationOptions): Document { + return InternalToRepresentation.toRepresentation(ast, options) +} + +/** + * Lowers one or more AST encoded sides in a shared reference environment. + * + * **When to use** + * + * Use when several AST roots must share identifiers, occurrence counts, recursion, and allocated reference names. + * + * **Details** + * + * Apply `SchemaAST.toType` to an AST first to lower its type side instead. The reference policy observes candidates from + * every root before any representation is emitted. + * + * @see {@link toRepresentation} for a single AST root + * + * @category constructors + * @since 4.0.0 + */ +export function toRepresentations( + asts: readonly [SchemaAST.AST, ...Array], + options?: ToRepresentationOptions +): MultiDocument { + return InternalToRepresentation.toRepresentations(asts, options) +} + +/** + * Wraps a single representation document as a multi-document with one root. + * + * **When to use** + * + * Use when an API such as `toCodeDocument` requires a `MultiDocument`. + * + * @category transforming + * @since 4.0.0 + */ +export function toMultiDocument(document: Document): MultiDocument { + return { + representations: [document.representation], + references: document.references + } +} + +/** + * Compiles a live representation document to JSON Schema Draft 2020-12. + * + * **When to use** + * + * Use when you need JSON Schema output from a representation whose checks carry compiler annotations. + * + * **Details** + * + * For representation documents whose validation semantics can be expressed exactly in JSON Schema, importing the + * emitted document with {@link fromJsonSchemaDocument} reconstructs a schema that accepts the same JSON values. This + * is a semantic round-trip guarantee; the emitted document and reconstructed representation may have different shapes. + * + * **Gotchas** + * + * - Reference allocation is already fixed in the input `Document`. The inherited `referencePolicy` option has no effect + * here; pass it to {@link toRepresentation} when creating the document. + * - Opaque declarations are represented by an unconstrained JSON Schema and are outside the exact round-trip subset. + * - Check callback results are used directly, and exceptions raised by a callback pass through unchanged. Callbacks + * must treat their input schemas as immutable. Each returned value must be a valid JSON Schema object graph and must + * not be mutated after the callback returns. + * - Local definition references returned by callbacks are resolved together with compiler-generated references. + * - Effect decoding may discard excess object properties by default. Use `onExcessProperty: "error"` when comparing + * validation semantics with the emitted JSON Schema. + * + * @see {@link toJsonSchemaMultiDocument} for multiple roots sharing definitions + * + * @category transforming + * @since 4.0.0 + */ +export function toJsonSchemaDocument( + document: Document, + options?: Schema.ToJsonSchemaOptions +): JsonSchema.Document<"draft-2020-12"> { + return InternalToJsonSchemaDocument.toJsonSchemaDocument(document, options) +} + +/** + * Compiles multiple live representations to a shared JSON Schema Draft 2020-12 document. + * + * **When to use** + * + * Use when several representation roots must share the same JSON Schema definitions. + * + * **Gotchas** + * + * - Reference allocation is already fixed in the input `MultiDocument`. The inherited `referencePolicy` option has no + * effect here; pass it to {@link toRepresentations} when creating the document. + * - Every definition is compiled, including definitions that are not reachable from a root. Check callbacks must treat + * their input schemas as immutable. Each returned value must be a valid JSON Schema object graph and must not be + * mutated after the callback returns. Local definition references returned by callbacks are resolved together with + * compiler-generated references. + * + * @see {@link toJsonSchemaDocument} for a single root + * + * @category transforming + * @since 4.0.0 + */ +export function toJsonSchemaMultiDocument( + document: MultiDocument, + options?: Schema.ToJsonSchemaOptions +): JsonSchema.MultiDocument<"draft-2020-12"> { + return InternalToJsonSchemaDocument.toJsonSchemaMultiDocument(document, options) +} + +/** + * Generates TypeScript source for live schema representations and their definitions. + * + * **When to use** + * + * Use when custom declarations and checks provide `toCode` callbacks and must be emitted without a central handler registry. + * + * **Gotchas** + * + * Opaque declarations and leaf checks require `toCode` callbacks. Callback results are used directly, and exceptions raised by a callback pass through unchanged. + * + * @category transforming + * @since 4.0.0 + */ +export function toCodeDocument(document: MultiDocument): CodeDocument { + return InternalToCodeDocument.toCodeDocument(document) +} + +const RepresentationSchema = Schema.suspend( + (): Schema.Codec => RepresentationUnion +) +const RepresentationsSchema = Schema.Array(RepresentationSchema) + +const RepresentationAnnotationSchema = Schema.Struct({ + id: Schema.NonEmptyString, + payload: Schema.Json +}) + +const CheckRepresentationAnnotationSchema = Schema.Struct({ + ...RepresentationAnnotationSchema.fields, + schemas: Schema.optional(RepresentationsSchema) +}) + +function pruneAnnotations( + annotations: Readonly> +): Option.Option>> { + const out: Record = {} + for (const [key, value] of Object.entries(annotations)) { + if (SchemaAST.isJson(value)) { + InternalRecord.assignProperty(out, key, value) + } + } + return Object.keys(out).length === 0 ? Option.none() : Option.some(out) +} + +const AnnotationsSchema = Schema.optional(Schema.Record(Schema.String, Schema.Unknown)).pipe( + Schema.encodeTo(Schema.optionalKey(Schema.JsonObject), { + decode: SchemaGetter.passthroughSubtype(), + encode: SchemaGetter.transformOptional((annotations) => + Option.isNone(annotations) || annotations.value === undefined + ? Option.none() + : pruneAnnotations(annotations.value) + ) + }) +) + +const CheckSchema = Schema.suspend((): Schema.Codec => CheckUnion) +const ChecksSchema = Schema.Array(CheckSchema) +const KeywordFields = { + annotations: AnnotationsSchema, + checks: ChecksSchema +} +const FilterSchema = Schema.Struct({ + _tag: Schema.tag("Filter"), + representation: CheckRepresentationAnnotationSchema, + annotations: AnnotationsSchema, + aborted: Schema.Boolean +}) +const FilterGroupSchema = Schema.Struct({ + _tag: Schema.tag("FilterGroup"), + representation: Schema.optional(CheckRepresentationAnnotationSchema), + annotations: AnnotationsSchema, + checks: Schema.NonEmptyArray(CheckSchema) +}) +const CheckUnion = Schema.Union([FilterSchema, FilterGroupSchema]) + +function makeKeywordSchema>(tag: Tag) { + return Schema.Struct({ + _tag: Schema.tag(tag), + ...KeywordFields + }) +} + +const DeclarationSchema = Schema.Struct({ + _tag: Schema.tag("Declaration"), + representation: RepresentationAnnotationSchema, + annotations: AnnotationsSchema, + typeParameters: RepresentationsSchema, + checks: ChecksSchema +}) +const SuspendSchema = Schema.Struct({ + _tag: Schema.tag("Suspend"), + annotations: AnnotationsSchema, + checks: Schema.Tuple([]), + thunk: RepresentationSchema +}) +function makeValueSchema(type: Type, value: Schema.Codec) { + return value.pipe( + Schema.encodeTo(Schema.Struct({ type: Schema.tag(type), value }), { + decode: SchemaGetter.transform((encoded: { readonly type: Type; readonly value: Value }) => encoded.value), + encode: SchemaGetter.transform((value: Value) => ({ type, value })) + }) + ) +} +const StringValueCodec = makeValueSchema("string", Schema.String) +const NumberValueCodec = makeValueSchema("number", Schema.Number) +const LiteralSchema = Schema.Struct({ + _tag: Schema.tag("Literal"), + ...KeywordFields, + literal: Schema.Union([ + StringValueCodec, + makeValueSchema("number", Schema.Finite), + makeValueSchema("bigint", Schema.BigInt), + makeValueSchema("boolean", Schema.Boolean) + ]) +}) +const UniqueSymbolSchema = Schema.Struct({ + _tag: Schema.tag("UniqueSymbol"), + ...KeywordFields, + symbol: Schema.Symbol +}) +const EnumSchema = Schema.Struct({ + _tag: Schema.tag("Enum"), + ...KeywordFields, + enums: Schema.Array(Schema.Tuple([ + Schema.String, + Schema.Union([StringValueCodec, NumberValueCodec]) + ])) +}) +const TemplateLiteralSchema = Schema.Struct({ + _tag: Schema.tag("TemplateLiteral"), + ...KeywordFields, + parts: RepresentationsSchema +}) +const ElementSchema = Schema.Struct({ + isOptional: Schema.Boolean, + type: RepresentationSchema, + annotations: AnnotationsSchema +}) +const ArraysSchema = Schema.Struct({ + _tag: Schema.tag("Arrays"), + ...KeywordFields, + elements: Schema.Array(ElementSchema), + rest: RepresentationsSchema +}) +const PropertySignatureSchema = Schema.Struct({ + name: Schema.Union([ + StringValueCodec, + NumberValueCodec, + makeValueSchema("symbol", Schema.Symbol) + ]), + type: RepresentationSchema, + isOptional: Schema.Boolean, + isMutable: Schema.Boolean, + annotations: AnnotationsSchema +}) +const IndexSignatureSchema = Schema.Struct({ + parameter: RepresentationSchema, + type: RepresentationSchema +}) +const ObjectsSchema = Schema.Struct({ + _tag: Schema.tag("Objects"), + ...KeywordFields, + propertySignatures: Schema.Array(PropertySignatureSchema), + indexSignatures: Schema.Array(IndexSignatureSchema) +}) +const UnionSchema = Schema.Struct({ + _tag: Schema.tag("Union"), + ...KeywordFields, + types: RepresentationsSchema, + mode: Schema.Literals(["anyOf", "oneOf"]) +}) +const ReferenceSchema = Schema.Struct({ + _tag: Schema.tag("Reference"), + $ref: Schema.NonEmptyString +}) + +const RepresentationUnion = Schema.Union([ + DeclarationSchema, + ReferenceSchema, + SuspendSchema, + makeKeywordSchema("Null"), + makeKeywordSchema("Undefined"), + makeKeywordSchema("Void"), + makeKeywordSchema("Never"), + makeKeywordSchema("Unknown"), + makeKeywordSchema("Any"), + makeKeywordSchema("String"), + makeKeywordSchema("Number"), + makeKeywordSchema("Boolean"), + makeKeywordSchema("BigInt"), + makeKeywordSchema("Symbol"), + makeKeywordSchema("ObjectKeyword"), + LiteralSchema, + UniqueSymbolSchema, + EnumSchema, + TemplateLiteralSchema, + ArraysSchema, + ObjectsSchema, + UnionSchema +]) + +const ReferencesSchema = Schema.Record(Schema.String, RepresentationSchema) + +const DocumentFromJson: Schema.Codec = Schema.toCodecJson( + Schema.Struct({ + representation: RepresentationSchema, + references: ReferencesSchema + }) +) + +const MultiDocumentFromJson: Schema.Codec = Schema.toCodecJson( + Schema.Struct({ + representations: Schema.NonEmptyArray(RepresentationSchema), + references: ReferencesSchema + }) +) + +const encodeDocument = Schema.encodeSync(DocumentFromJson) +const encodeMultiDocument = Schema.encodeSync(MultiDocumentFromJson) +const decodeDocument = Schema.decodeSync(DocumentFromJson) +const decodeMultiDocument = Schema.decodeSync(MultiDocumentFromJson) + +/** + * Projects a live single-root representation document and encodes it as JSON. + * + * **When to use** + * + * Use when you need a stable JSON value for storage or transport after calling `toRepresentation`. + * + * **Gotchas** + * + * Generic annotations that are not JSON are omitted. Invalid persistence identities and unsupported structural values throw an `Error` containing their representation path. + * + * @see {@link toRepresentation} for constructing the live document + * @see {@link toJsonMultiDocument} for documents with multiple roots + * + * @category encoding + * @since 4.0.0 + */ +export function toJson(document: Document): Schema.Json { + return encodeDocument(document) +} + +/** + * Projects a live multi-root representation document and encodes it as JSON. + * + * **When to use** + * + * Use when you need one JSON value for multiple live roots that share a reference environment. + * + * **Gotchas** + * + * The root order and shared reference keys are preserved, while non-JSON generic annotations are omitted. + * + * @see {@link toRepresentations} for constructing the live multi-document + * @see {@link toJson} for a single-root document + * + * @category encoding + * @since 4.0.0 + */ +export function toJsonMultiDocument(document: MultiDocument): Schema.Json { + return encodeMultiDocument(document) +} + +/** + * Decodes a persisted single-root representation document from JSON. + * + * **When to use** + * + * Use when reading a representation document from storage or transport before inspecting it or passing it to `fromRepresentation`. + * + * **Gotchas** + * + * Invalid documents throw a schema decoding error. Decoding does not reconstruct runtime callbacks. + * + * @see {@link toJson} for encoding a document + * @see {@link fromRepresentation} for reconstructing a runtime schema + * @see {@link fromJsonMultiDocument} for multiple roots sharing references + * + * @category decoding + * @since 4.0.0 + */ +export function fromJson(input: Schema.Json): Document { + return decodeDocument(input) +} + +/** + * Decodes a persisted multi-root representation document from JSON. + * + * **When to use** + * + * Use when reading multiple representation roots that share references before inspecting them or passing them to `fromRepresentations`. + * + * **Gotchas** + * + * Invalid documents throw a schema decoding error. Decoding does not reconstruct runtime callbacks. + * + * @see {@link toJsonMultiDocument} for encoding a multi-document + * @see {@link fromRepresentations} for reconstructing runtime schemas + * @see {@link fromJson} for a single root + * + * @category decoding + * @since 4.0.0 + */ +export function fromJsonMultiDocument(input: Schema.Json): MultiDocument { + return decodeMultiDocument(input) +} + +/** + * Reconstructs a runtime schema from a representation document. + * + * **When to use** + * + * Use when you have decoded or constructed a document whose declaration and check annotations may require revivers. + * + * **Gotchas** + * + * Revivers are resolved locally by `id`; none are installed implicitly. Reviver results are used directly, and exceptions raised by a reviver pass through unchanged. + * + * **Example** (Restoring a persisted schema) + * + * ```ts import.meta.vitest + * import { Schema, SchemaRepresentation } from "effect" + * + * const document = SchemaRepresentation.toRepresentation(Schema.Struct({ name: Schema.String }).ast) + * const persisted = SchemaRepresentation.toJson(document) + * const restored = SchemaRepresentation.fromJson(persisted) + * const schema = SchemaRepresentation.fromRepresentation(restored, { revivers: [] }) + * const Person = Schema.make>(schema.ast) + * + * Schema.decodeUnknownSync(Person)({ name: "Ada" }) // => { name: "Ada" } + * ``` + * + * @see {@link fromJson} for decoding a persisted document + * @see {@link fromRepresentations} for multiple roots sharing references + * + * @category transforming + * @since 4.0.0 + */ +export function fromRepresentation( + document: Document, + options: { readonly revivers: ReadonlyArray } +): Schema.Top { + return InternalFromRepresentation.fromRepresentation(document, options.revivers) +} + +/** + * Reconstructs multiple runtime schemas from a representation multi-document. + * + * **When to use** + * + * Use when multiple roots must be rebuilt in one shared reference environment. + * + * **Gotchas** + * + * Only references reachable from a root are revived. Revivers are resolved locally by `id`; none are installed implicitly. + * + * @see {@link fromJsonMultiDocument} for decoding a persisted multi-document + * @see {@link fromRepresentation} for a single root + * + * @category transforming + * @since 4.0.0 + */ +export function fromRepresentations( + document: MultiDocument, + options: { readonly revivers: ReadonlyArray } +): readonly [Schema.Top, ...Array] { + return InternalFromRepresentation.fromRepresentations(document, options.revivers) +} + +/** + * Imports a JSON Schema Draft 2020-12 document as a runtime schema. + * + * **When to use** + * + * Use when you need to validate or transform values described by an external JSON Schema document. + * + * **Details** + * + * For the Draft 2020-12 subset translated exactly by this importer, compiling the imported schema through + * {@link toRepresentation} and {@link toJsonSchemaDocument} produces a document that accepts the same JSON values as + * the input. This is a semantic round-trip guarantee; keyword layout, definitions, and annotations may be normalized. + * + * **Gotchas** + * + * - `$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. + * - Objects and arrays used as `const` values or `enum` members throw an `Unsupported structured JSON Schema value` + * error. + * - 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. + * - Unknown extension keywords are ignored and their semantics are not enforced. + * - 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. + * - Built-in declarations and checks are reconstructed with importer-owned revivers. + * - Pattern constraints reached during translation cause an error by default. Use `patterns: "apply"` only for trusted + * documents, or `patterns: "ignore"` to weaken validation explicitly; ignored patterns are outside the round-trip + * guarantee. + * - `onEnter` results replace the corresponding input nodes, so the round-trip guarantee applies to the rewritten + * document. + * - Callback results are used directly, and exceptions raised by a callback pass through unchanged. + * + * @see {@link fromJsonSchemaMultiDocument} for multiple roots sharing definitions + * @see {@link toRepresentation} for converting the result to a representation document + * + * @category constructors + * @since 4.0.0 + */ +export function fromJsonSchemaDocument( + document: JsonSchema.Document<"draft-2020-12">, + options?: FromJsonSchemaOptions +): Schema.Top { + return InternalFromJsonSchemaDocument.fromJsonSchemaDocument(document, options) +} + +/** + * Imports multiple JSON Schema Draft 2020-12 roots as runtime schemas with shared definitions. + * + * **When to use** + * + * Use when multiple imported roots share reachable definitions, aliases, or recursion. + * + * **Gotchas** + * + * - Only definitions reachable from a root are translated. + * - Unsupported standard validation and applicator keywords throw an `Unsupported JSON Schema keyword` error. 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. + * - 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. + * - 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. + * - Pattern constraints reached during translation cause an error by default. Use `patterns: "apply"` only for trusted + * documents, or `patterns: "ignore"` to weaken validation explicitly. + * - Callback results are used directly, and exceptions raised by a callback pass through unchanged. + * + * @see {@link fromJsonSchemaDocument} for a single root + * @see {@link toRepresentations} for converting the returned schema ASTs to a representation document + * + * @category constructors + * @since 4.0.0 + */ +export function fromJsonSchemaMultiDocument( + document: JsonSchema.MultiDocument<"draft-2020-12">, + options?: FromJsonSchemaOptions +): readonly [Schema.Top, ...Array] { + return InternalFromJsonSchemaDocument.fromJsonSchemaMultiDocument(document, options) +} diff --git a/.repos/effect/packages/effect/src/SchemaTransformation.ts b/.repos/effect/packages/effect/src/SchemaTransformation.ts new file mode 100644 index 000000000..e90c1a653 --- /dev/null +++ b/.repos/effect/packages/effect/src/SchemaTransformation.ts @@ -0,0 +1,1946 @@ +/** + * Builds two-way conversions used by schemas. + * + * A `Transformation` describes how to decode an encoded value into a + * decoded value and how to encode it back again. Schema APIs use + * transformations to connect two representations, such as a string and a + * number, a JSON value and a richer TypeScript value, or a form field and an + * application value. This module includes transformation and middleware types, + * constructors for pure or effectful conversions, and common conversions used + * by the Schema module. + * + * @since 4.0.0 + */ + +import * as BigDecimal from "./BigDecimal.ts" +import * as DateTime from "./DateTime.ts" +import * as Duration from "./Duration.ts" +import * as Effect from "./Effect.ts" +import { format, formatDate, formatJson } from "./Formatter.ts" +import * as Option from "./Option.ts" +import * as Predicate from "./Predicate.ts" +import type { ErrorOptions, Json } from "./Schema.ts" +import type * as SchemaAST from "./SchemaAST.ts" +import * as SchemaGetter from "./SchemaGetter.ts" +import * as SchemaIssue from "./SchemaIssue.ts" + +/** + * Middleware that wraps the entire parsing `Effect` pipeline for both + * decode and encode directions. + * + * **When to use** + * + * Use when you need a schema middleware to catch or recover from parsing + * errors (e.g. `Schema.catchDecoding`), run side effects around the parsing + * pipeline, or access the full `Effect` rather than a single decoded value. + * + * **Details** + * + * Unlike `Transformation`, which operates on individual values via `Getter`, + * `Middleware` receives the full `Effect` produced by the inner schema and can + * intercept, modify, retry, or replace it. + * + * - `decode` receives an `Effect, Issue, RDE>` and returns + * `Effect, Issue, RDT>`. + * - `encode` receives an `Effect, Issue, RET>` and returns + * `Effect, Issue, REE>`. + * - `flip()` swaps the decode and encode functions, producing a + * `Middleware`. + * + * Typically constructed indirectly via `Schema.middlewareDecoding` or + * `Schema.middlewareEncoding` rather than instantiating this class directly. + * + * **Example** (Creating a middleware that falls back on decode failure) + * + * ```ts import.meta.vitest + * import { Effect, Option, SchemaIssue, SchemaTransformation } from "effect" + * + * const fallback = new SchemaTransformation.Middleware( + * (effect) => Effect.catch(effect, () => Effect.succeed(Option.some("fallback"))), + * (effect) => effect + * ) + * const issue = new SchemaIssue.InvalidValue({ message: "Missing value" }) + * await Effect.runPromise(fallback.decode(Effect.fail(issue), {})) // => Option.some("fallback") + * ``` + * + * @see {@link Transformation} — value-level bidirectional transformation + * + * @category models + * @since 4.0.0 + */ +export class Middleware { + readonly _tag = "Middleware" + readonly decode: ( + effect: Effect.Effect, SchemaIssue.Issue, RDE>, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, RDT> + readonly encode: ( + effect: Effect.Effect, SchemaIssue.Issue, RET>, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, REE> + + constructor( + decode: ( + effect: Effect.Effect, SchemaIssue.Issue, RDE>, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, RDT>, + encode: ( + effect: Effect.Effect, SchemaIssue.Issue, RET>, + options: SchemaAST.ParseOptions + ) => Effect.Effect, SchemaIssue.Issue, REE> + ) { + this.decode = decode + this.encode = encode + } + flip(): Middleware { + return new Middleware(this.encode, this.decode) + } +} + +const TypeId = "~effect/SchemaTransformation/Transformation" + +/** + * Represents a bidirectional transformation between a decoded type `T` and an encoded + * type `E`, built from a pair of `Getter`s. + * + * **When to use** + * + * Use when you need a schema transformation that defines how a schema converts + * between two representations. + * - You want to compose multiple transformations into a pipeline. + * - You want to flip a transformation to swap decode/encode. + * + * **Details** + * + * This is the primary building block for `Schema.decodeTo`, `Schema.encodeTo`, + * `Schema.decode`, `Schema.encode`, and `Schema.link`. Each direction is a + * `SchemaGetter.Getter` that handles optionality, failure, and Effect services. + * + * - Immutable — `flip()` and `compose()` return new instances. + * - `flip()` swaps the decode and encode getters. + * - `compose(other)` chains: `this.decode` then `other.decode` for decoding, + * `other.encode` then `this.encode` for encoding. + * + * **Example** (Composing two transformations) + * + * ```ts import.meta.vitest + * import { SchemaTransformation } from "effect" + * + * const trimAndLower = SchemaTransformation.trim().compose( + * SchemaTransformation.toLowerCase() + * ) + * trimAndLower._tag // => "Transformation" + * ``` + * + * @see {@link make} — construct from `{ decode, encode }` getters + * @see {@link transform} — construct from pure functions + * @see {@link transformOrFail} — construct from effectful functions + * @see {@link Middleware} — effect-pipeline-level alternative + * + * @category models + * @since 4.0.0 + */ +export class Transformation { + readonly [TypeId] = TypeId + readonly _tag = "Transformation" + readonly decode: SchemaGetter.Getter + readonly encode: SchemaGetter.Getter + + constructor( + decode: SchemaGetter.Getter, + encode: SchemaGetter.Getter + ) { + this.decode = decode + this.encode = encode + } + flip(): Transformation { + return new Transformation(this.encode, this.decode) + } + compose(other: Transformation): Transformation { + return new Transformation( + this.decode.compose(other.decode), + other.encode.compose(this.encode) + ) + } +} + +/** + * Returns `true` if `u` is a `Transformation` instance. + * + * **When to use** + * + * Use to check whether a value is already a schema transformation before + * wrapping it. + * + * **Details** + * + * - Pure predicate, no side effects. + * - Acts as a TypeScript type guard. + * + * **Example** (Checking a value) + * + * ```ts import.meta.vitest + * import { SchemaTransformation } from "effect" + * + * SchemaTransformation.isTransformation(SchemaTransformation.trim()) // => true + * SchemaTransformation.isTransformation({ decode: null, encode: null }) // => false + * ``` + * + * @see {@link Transformation} + * @see {@link make} + * + * @category guards + * @since 4.0.0 + */ +export function isTransformation(u: unknown): u is Transformation { + return Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId +} + +/** + * Constructs a `Transformation` from an object with `decode` and `encode` + * `Getter`s. If the input is already a `Transformation`, returns it as-is. + * + * **When to use** + * + * Use when you already have schema getter instances and want to pair them into + * a schema transformation. + * - You want idempotent wrapping (won't double-wrap). + * + * **Details** + * + * - Returns the input unchanged if it is already a `Transformation`. + * + * **Example** (Wrapping existing getters) + * + * ```ts import.meta.vitest + * import { SchemaGetter, SchemaTransformation } from "effect" + * + * const t = SchemaTransformation.make({ + * decode: SchemaGetter.transform((s) => Number(s)), + * encode: SchemaGetter.transform((n) => String(n)) + * }) + * t._tag // => "Transformation" + * ``` + * + * @see {@link transform} — simpler constructor from pure functions + * @see {@link transformOrFail} — constructor from effectful functions + * @see {@link Transformation} + * + * @category constructors + * @since 3.10.0 + */ +export const make = (options: { + readonly decode: SchemaGetter.Getter + readonly encode: SchemaGetter.Getter +}): Transformation => { + if (isTransformation(options)) { + return options as any + } + return new Transformation(options.decode, options.encode) +} + +/** + * Creates a `Transformation` from effectful decode and encode functions that + * can fail with `Issue`. + * + * **When to use** + * + * Use when you need a schema transformation that may fail or require Effect + * services. + * + * **Details** + * + * - Each function receives the input value and `ParseOptions`. + * - Must return an `Effect` that succeeds with the output or fails with `Issue`. + * - Skips `None` inputs (missing keys) — functions are only called on present values. + * + * **Example** (Parsing a date string that can fail) + * + * ```ts import.meta.vitest + * import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect" + * + * const DateFromString = Schema.String.pipe( + * Schema.decodeTo( + * Schema.Date, + * SchemaTransformation.transformOrFail({ + * decode: (s, options) => { + * const d = new Date(s) + * return isNaN(d.getTime()) + * ? Effect.fail(new SchemaIssue.InvalidValue({ message: "Invalid date" }, s, options)) + * : Effect.succeed(d) + * }, + * encode: (d) => Effect.succeed(d.toISOString()) + * }) + * ) + * ) + * Schema.decodeSync(DateFromString)("2024-01-01").toISOString() // => "2024-01-01T00:00:00.000Z" + * ``` + * + * @see {@link transform} — for infallible, pure transformations + * @see {@link transformOptional} — for transformations that handle missing keys + * @see {@link make} — for transformations from existing Getters + * + * @category transforming + * @since 3.10.0 + */ +export function transformOrFail(options: { + readonly decode: (e: E, options: SchemaAST.ParseOptions) => Effect.Effect + readonly encode: (t: T, options: SchemaAST.ParseOptions) => Effect.Effect +}): Transformation { + return new Transformation( + SchemaGetter.transformOrFail(options.decode), + SchemaGetter.transformOrFail(options.encode) + ) +} + +/** + * Creates a `Transformation` from pure (sync, infallible) decode and encode + * functions. + * + * **When to use** + * + * Use when you need an infallible schema transformation that does not require + * Effect services. + * + * **Details** + * + * - Each function receives the input and returns the output directly. + * - Skips `None` inputs (missing keys) — functions are only called on present values. + * - Does not allocate Effects internally; uses optimized sync path. + * + * **Example** (Converting between cents and dollars) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const CentsFromDollars = Schema.Number.pipe( + * Schema.decodeTo( + * Schema.Number, + * SchemaTransformation.transform({ + * decode: (dollars) => dollars * 100, + * encode: (cents) => cents / 100 + * }) + * ) + * ) + * Schema.decodeSync(CentsFromDollars)(2.5) // => 250 + * ``` + * + * @see {@link transformOrFail} — for fallible or effectful transformations + * @see {@link transformOptional} — for transformations that handle missing keys + * @see {@link passthrough} — when no conversion is needed + * + * @category transforming + * @since 3.10.0 + */ +export function transform(options: { + readonly decode: (input: E) => T + readonly encode: (input: T) => E +}): Transformation { + return new Transformation( + SchemaGetter.transform(options.decode), + SchemaGetter.transform(options.encode) + ) +} + +/** + * Creates a `Transformation` where decode and encode operate on `Option` + * values, giving full control over missing-key handling. + * + * **When to use** + * + * Use when you need a schema transformation to produce or consume `Option.None` + * for absent keys. + * - You are working with optional struct fields. + * + * **Details** + * + * - Each function receives `Option` and returns `Option`. + * - `Option.None` input means the key is absent; returning `Option.None` + * omits the key from the output. + * - Pure and synchronous. + * + * **Example** (Converting an optional key to Option) + * + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.Struct({ + * a: Schema.optionalKey(Schema.Number).pipe( + * Schema.decodeTo( + * Schema.Option(Schema.Number), + * SchemaTransformation.transformOptional({ + * decode: Option.some, + * encode: Option.flatten + * }) + * ) + * ) + * }) + * Schema.decodeSync(schema)({}).a // => Option.none() + * ``` + * + * @see {@link transform} — when you don't need Option-level control + * @see {@link optionFromOptionalKey} — built-in for the common optional-key-to-Option pattern + * @see {@link optionFromOptional} — built-in for optional (undefined) to Option + * + * @category transforming + * @since 4.0.0 + */ +export function transformOptional(options: { + readonly decode: (input: Option.Option) => Option.Option + readonly encode: (input: Option.Option) => Option.Option +}): Transformation { + return new Transformation( + SchemaGetter.transformOptional(options.decode), + SchemaGetter.transformOptional(options.encode) + ) +} + +/** + * Transforms strings by trimming whitespace on decode. + * Encode is passthrough (no change). + * + * **When to use** + * + * Use when you need a schema transformation to normalize user input by + * stripping leading/trailing whitespace. + * + * **Details** + * + * Decoding applies `String.prototype.trim()`. Encoding is passthrough and + * returns the string unchanged. This is not round-trippable if the original had + * whitespace. + * + * **Example** (Trimming on decode) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const Trimmed = Schema.String.pipe( + * Schema.decode(SchemaTransformation.trim()) + * ) + * Schema.decodeSync(Trimmed)(" hello ") // => "hello" + * ``` + * + * @see {@link toLowerCase} + * @see {@link toUpperCase} + * @see {@link snakeToCamel} + * + * @category transforming + * @since 4.0.0 + */ +export function trim(): Transformation { + return new Transformation( + SchemaGetter.trim(), + SchemaGetter.passthrough() + ) +} + +/** + * Transforms strings by converting snake_case to camelCase + * on decode and camelCase to snake_case on encode. + * + * **When to use** + * + * Use when you need a schema transformation to convert API field names between + * snake_case and camelCase conventions. + * + * **Details** + * + * Decoding converts values such as `"my_field_name"` to `"myFieldName"`. + * Encoding converts values such as `"myFieldName"` back to `"my_field_name"`. + * The transformation is round-trippable for standard snake_case and camelCase. + * + * **Example** (Converting snake case to camel case) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const SnakeToCamel = Schema.String.pipe( + * Schema.decode(SchemaTransformation.snakeToCamel()) + * ) + * Schema.decodeSync(SnakeToCamel)("user_name") // => "userName" + * ``` + * + * @see {@link trim} + * @see {@link toLowerCase} + * + * @category transforming + * @since 4.0.0 + */ +export function snakeToCamel(): Transformation { + return new Transformation( + SchemaGetter.snakeToCamel(), + SchemaGetter.camelToSnake() + ) +} + +/** + * Transforms strings by lowercasing on decode. + * Encode is passthrough. + * + * **When to use** + * + * Use when you need a schema transformation to normalize strings to lowercase + * (e.g. email addresses). + * + * **Details** + * + * Decoding applies `String.prototype.toLowerCase()`. Encoding is passthrough. + * This is not round-trippable if the original had uppercase characters. + * + * **Example** (Lowercasing on decode) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const Lowered = Schema.String.pipe( + * Schema.decode(SchemaTransformation.toLowerCase()) + * ) + * Schema.decodeSync(Lowered)("HELLO") // => "hello" + * ``` + * + * @see {@link toUpperCase} + * @see {@link trim} + * + * @category transforming + * @since 4.0.0 + */ +export function toLowerCase(): Transformation { + return new Transformation( + SchemaGetter.toLowerCase(), + SchemaGetter.passthrough() + ) +} + +/** + * Transforms strings by uppercasing on decode. + * Encode is passthrough. + * + * **When to use** + * + * Use when you need a schema transformation to normalize strings to uppercase + * (e.g. country codes). + * + * **Details** + * + * Decoding applies `String.prototype.toUpperCase()`. Encoding is passthrough. + * This is not round-trippable if the original had lowercase characters. + * + * **Example** (Uppercasing on decode) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const Uppered = Schema.String.pipe( + * Schema.decode(SchemaTransformation.toUpperCase()) + * ) + * Schema.decodeSync(Uppered)("hello") // => "HELLO" + * ``` + * + * @see {@link toLowerCase} + * @see {@link trim} + * + * @category transforming + * @since 4.0.0 + */ +export function toUpperCase(): Transformation { + return new Transformation( + SchemaGetter.toUpperCase(), + SchemaGetter.passthrough() + ) +} + +/** + * Transforms strings by capitalizing the first character on + * decode. Encode is passthrough. + * + * **When to use** + * + * Use when you need a schema transformation to normalize display names or + * titles. + * + * **Details** + * + * Decoding uppercases the first character and leaves the rest unchanged. + * Encoding is passthrough. + * + * **Example** (Capitalizing on decode) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const Capitalized = Schema.String.pipe( + * Schema.decode(SchemaTransformation.capitalize()) + * ) + * Schema.decodeSync(Capitalized)("hello") // => "Hello" + * ``` + * + * @see {@link uncapitalize} + * @see {@link toUpperCase} + * + * @category transforming + * @since 4.0.0 + */ +export function capitalize(): Transformation { + return new Transformation( + SchemaGetter.capitalize(), + SchemaGetter.passthrough() + ) +} + +/** + * Transforms strings by lowercasing the first character on + * decode. Encode is passthrough. + * + * **When to use** + * + * Use when you need a schema transformation to normalize identifiers or field + * names. + * + * **Details** + * + * Decoding lowercases the first character and leaves the rest unchanged. + * Encoding is passthrough. + * + * **Example** (Uncapitalizing on decode) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const Uncapitalized = Schema.String.pipe( + * Schema.decode(SchemaTransformation.uncapitalize()) + * ) + * Schema.decodeSync(Uncapitalized)("Hello") // => "hello" + * ``` + * + * @see {@link capitalize} + * @see {@link toLowerCase} + * + * @category transforming + * @since 4.0.0 + */ +export function uncapitalize(): Transformation { + return new Transformation( + SchemaGetter.uncapitalize(), + SchemaGetter.passthrough() + ) +} + +/** + * Transforms a string into a record of key-value pairs and + * encodes a record of key-value pairs into a string. + * + * **When to use** + * + * Use when you need a schema transformation to parse query-string-like or + * config-file-like strings into records. + * + * **Details** + * + * Decoding splits the string by `separator` (default `","`) into pairs, then + * splits each pair by `keyValueSeparator` (default `"="`). Encoding joins the + * record back into a string using the same separators. The transformation is + * round-trippable when keys and values do not contain the separators. + * + * **Example** (Parsing key-value pairs) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const Config = Schema.String.pipe( + * Schema.decodeTo( + * Schema.Record(Schema.String, Schema.String), + * SchemaTransformation.splitKeyValue({ separator: ";", keyValueSeparator: ":" }) + * ) + * ) + * Schema.decodeSync(Config)("host:localhost;port:3000") // => { host: "localhost", port: "3000" } + * ``` + * + * @see {@link trim} + * @see {@link snakeToCamel} + * + * @category transforming + * @since 4.0.0 + */ +export function splitKeyValue(options?: { + readonly separator?: string | undefined + readonly keyValueSeparator?: string | undefined +}): Transformation, string> { + return new Transformation( + SchemaGetter.splitKeyValue(options), + SchemaGetter.joinKeyValue(options) + ) +} + +const passthrough_ = new Transformation( + SchemaGetter.passthrough(), + SchemaGetter.passthrough() +) + +/** + * Transforms values by returning the input unchanged in both + * directions. + * + * **When to use** + * + * Use when you need a schema transformation to connect two schemas that share + * the same type with no actual conversion. + * + * **Details** + * + * - Both decode and encode are no-ops. + * - Returns a shared singleton instance (no allocation per call). + * - By default, `T` and `E` must be the same type. Pass `{ strict: false }` + * to bypass the type constraint. + * + * **Example** (Chaining schemas with no conversion) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.Trim.pipe( + * Schema.decodeTo(Schema.FiniteFromString, SchemaTransformation.passthrough()) + * ) + * Schema.decodeSync(schema)("1") // => 1 + * ``` + * + * @see {@link passthroughSupertype} + * @see {@link passthroughSubtype} + * @see {@link transform} + * + * @category constructors + * @since 4.0.0 + */ +export function passthrough(options: { readonly strict: false }): Transformation +export function passthrough(): Transformation +export function passthrough(): Transformation { + return passthrough_ +} + +/** + * Transforms values without changing them, typed so that `T extends E`, where the decoded + * type `T` is a subtype of the encoded type `E`. + * + * **When to use** + * + * Use when you need a no-op schema transformation whose decoded side is + * narrower than the encoded side. + * + * **Details** + * + * Both decode and encode are no-ops and return a shared singleton + * transformation. + * + * **Example** (Passing through supertypes) + * + * ```ts import.meta.vitest + * import { SchemaTransformation } from "effect" + * + * const t: SchemaTransformation.Transformation<"a" | "b", string> = + * SchemaTransformation.passthroughSupertype<"a" | "b", string>() + * ``` + * + * @see {@link passthrough} + * @see {@link passthroughSubtype} + * + * @category constructors + * @since 4.0.0 + */ +export function passthroughSupertype(): Transformation +export function passthroughSupertype(): Transformation { + return passthrough_ +} + +/** + * Transforms values without changing them, typed so that `E extends T` — the encoded + * type is a subtype of the decoded type. + * + * **When to use** + * + * Use when you need a no-op schema transformation whose encoded side is more + * specific than its decoded side. + * + * **Details** + * + * - Both decode and encode are no-ops (same as {@link passthrough}). + * - Returns a shared singleton instance. + * + * **Example** (Passing through subtypes) + * + * ```ts import.meta.vitest + * import { SchemaTransformation } from "effect" + * + * const t: SchemaTransformation.Transformation = + * SchemaTransformation.passthroughSubtype() + * ``` + * + * @see {@link passthrough} + * @see {@link passthroughSupertype} + * + * @category constructors + * @since 4.0.0 + */ +export function passthroughSubtype(): Transformation +export function passthroughSubtype(): Transformation { + return passthrough_ +} + +/** + * Decodes a `string` into a `number` and encodes a `number` back to a + * `string`. + * + * **When to use** + * + * Use when you need a schema transformation to parse numeric strings from APIs, + * form data, or URL parameters. + * + * **Details** + * + * Decoding coerces the string to a number like `Number(s)`. Encoding coerces + * the number to a string like `String(n)`. This does not validate that the + * result is finite; combine with `Schema.Finite` or `Schema.Int` for stricter + * checks. + * + * **Example** (Converting a string to a number) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.Number, SchemaTransformation.numberFromString) + * ) + * Schema.decodeSync(schema)("42") // => 42 + * ``` + * + * @see {@link bigintFromString} + * @see {@link transform} + * + * @category converting + * @since 4.0.0 + */ +export const numberFromString = new Transformation( + SchemaGetter.Number(), + SchemaGetter.String() +) + +/** + * Decodes a `string` into a `bigint` and encodes a `bigint` back to a + * `string`. + * + * **When to use** + * + * Use when you need a schema transformation to parse large integer strings + * (e.g. database IDs, blockchain values). + * + * **Details** + * + * Decoding coerces the string to a bigint like `BigInt(s)`. Encoding coerces + * the bigint to a string like `String(n)`. Decoding fails if the string is not + * a valid bigint representation. + * + * **Example** (Converting a string to a BigInt) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.BigInt, SchemaTransformation.bigintFromString) + * ) + * Schema.decodeSync(schema)("42") // => 42n + * ``` + * + * @see {@link numberFromString} + * @see {@link transform} + * + * @category converting + * @since 4.0.0 + */ +export const bigintFromString = new Transformation( + SchemaGetter.BigInt(), + SchemaGetter.String() +) + +/** + * Decodes a `string` into a `Date` and encodes a `Date` back to a `string`. + * + * **When to use** + * + * Use when you need a schema transformation to parse date strings from APIs or + * user input. + * + * **Details** + * + * Decoding creates a `Date` from the string like `new Date(s)`. Encoding + * converts the `Date` to an ISO string like `date.toISOString()`, returning + * `"Invalid Date"` for invalid dates. + * + * **Example** (Converting a string to a Date) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.Date, SchemaTransformation.dateFromString) + * ) + * Schema.decodeSync(schema)("2024-01-01").toISOString() // => "2024-01-01T00:00:00.000Z" + * ``` + * + * @see {@link dateFromMillis} + * @see {@link dateTimeUtcFromString} + * + * @category converting + * @since 4.0.0 + */ +export const dateFromString: Transformation = new Transformation( + SchemaGetter.Date(), + SchemaGetter.transform(formatDate) +) + +/** + * Decodes epoch milliseconds into a `Date` and encodes a `Date` back to epoch + * milliseconds. + * + * **When to use** + * + * Use when you need a schema transformation for numeric timestamps represented + * as milliseconds since the Unix epoch. + * + * **Details** + * + * Decoding creates a `Date` from the number like `new Date(ms)`. Encoding + * returns the `Date` timestamp like `date.getTime()`. + * + * **Gotchas** + * + * This transformation does not validate date validity. `NaN`, `Infinity`, and + * `-Infinity` decode to invalid `Date` instances. + * + * **Example** (Converting milliseconds to a Date) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.Number.pipe( + * Schema.decodeTo(Schema.Date, SchemaTransformation.dateFromMillis) + * ) + * Schema.decodeSync(schema)(0).toISOString() // => "1970-01-01T00:00:00.000Z" + * ``` + * + * @see {@link dateFromString} + * @see {@link SchemaGetter.dateTimeUtcFromInput} + * + * @category converting + * @since 4.0.0 + */ +export const dateFromMillis: Transformation = new Transformation( + SchemaGetter.Date(), + SchemaGetter.transform((date) => date.getTime()) +) + +/** + * Decodes a `string` into a `Duration` and encodes a `Duration` back to a + * parseable `string`. + * + * **When to use** + * + * Use when you need a schema transformation to parse human-readable duration + * strings from APIs, config, or user input. + * + * **Details** + * + * Decoding accepts any string that `Duration.fromInput` can parse, including + * `"Infinity"` and `"-Infinity"`. Encoding returns `String(duration)`, + * producing strings such as `"2000 millis"` or `"10 nanos"` that round-trip + * through the parser. + * + * **Example** (Converting a string to a Duration) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.Duration, SchemaTransformation.durationFromString) + * ) + * String(Schema.decodeSync(schema)("5 seconds")) // => "5000 millis" + * ``` + * + * @see {@link durationFromNanos} + * @see {@link durationFromMillis} + * + * @category transforming + * @since 4.0.0 + */ +export const durationFromString: Transformation = transformOrFail< + Duration.Duration, + string +>({ + decode: (s, options) => + Option.match(Duration.fromInput(s as Duration.Input), { + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Duration string" }, + s, + options + ) + ), + onSome: Effect.succeed + }), + encode: (duration) => Effect.succeed(globalThis.String(duration)) +}) + +/** + * Decodes a `bigint` (nanoseconds) into a `Duration` and encodes a + * `Duration` back to `bigint` nanoseconds. + * + * **When to use** + * + * Use when you need a schema transformation for nanosecond-precision timestamps + * or intervals. + * + * **Details** + * + * Decoding always succeeds and creates a `Duration` from nanoseconds. Encoding + * fails with `InvalidValue` if the `Duration` cannot be represented as a + * `bigint`, such as `Duration.infinity`. + * + * **Example** (Converting nanoseconds to a Duration) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.BigInt.pipe( + * Schema.decodeTo(Schema.Duration, SchemaTransformation.durationFromNanos) + * ) + * String(Schema.decodeSync(schema)(5n)) // => "5 nanos" + * ``` + * + * @see {@link durationFromMillis} + * + * @category transforming + * @since 4.0.0 + */ +export const durationFromNanos: Transformation = transformOrFail({ + decode: (i) => Effect.succeed(Duration.nanos(i)), + encode: (a, options) => + Option.match(Duration.toNanos(a), { + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a Duration representable as a bigint" }, + a, + options + ) + ), + onSome: (nanos) => Effect.succeed(nanos) + }) +}) + +/** + * Decodes a `number` of milliseconds into a `Duration` and encodes a `Duration` + * back to milliseconds. + * + * **When to use** + * + * Use when you need a schema transformation to decode timeouts, delays, elapsed + * intervals, or other duration values stored as millisecond counts. + * + * **Details** + * + * Decode creates a duration from the number, and encode returns the duration + * length in milliseconds. + * + * **Example** (Converting milliseconds to a Duration) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.Number.pipe( + * Schema.decodeTo(Schema.Duration, SchemaTransformation.durationFromMillis) + * ) + * String(Schema.decodeSync(schema)(5000)) // => "5000 millis" + * ``` + * + * @see {@link durationFromNanos} + * + * @category transforming + * @since 4.0.0 + */ +export const durationFromMillis: Transformation = transform({ + decode: (i) => Duration.millis(i), + encode: (a) => Duration.toMillis(a) +}) + +type JsonError = { + message: string + name?: string + stack?: string + cause?: Json +} + +const isJsonError = (input: unknown): input is JsonError => + Predicate.isObject(input) && typeof input["message"] === "string" + +const decodeJsonError = (input: JsonError): Error => { + const hasCause = Object.hasOwn(input, "cause") + const err = hasCause + ? new Error(input.message, { cause: decodeDefect(input.cause as Json) }) + : new Error(input.message) + if (typeof input.name === "string" && input.name !== "Error") err.name = input.name + if (typeof input.stack === "string") err.stack = input.stack + return err +} + +const encodeUnknownAsJson = (input: unknown): Json => { + try { + const json = formatJson(input) + return json === undefined ? format(input) : JSON.parse(json) + } catch { + return format(input) + } +} + +const encodeJsonError = ( + input: Error, + options: ErrorOptions | undefined, + encodeDefect: (input: unknown) => Json +): JsonError => { + const encoded: JsonError = { + name: input.name, + message: typeof input.message === "string" ? input.message : "" + } + if (options?.includeStack && typeof input.stack === "string") { + encoded.stack = input.stack + } + if (!options?.excludeCause && input.cause !== undefined) { + encoded.cause = encodeDefect(input.cause) + } + return encoded +} + +const makeEncodeDefect = (options?: ErrorOptions): (input: unknown) => Json => { + const seen = new WeakSet() + const encode = (input: unknown): Json => { + if (Predicate.isError(input)) { + if (seen.has(input)) { + return "[Circular]" + } + seen.add(input) + const encoded = encodeJsonError(input, options, encode) + seen.delete(input) + return encoded + } + return encodeUnknownAsJson(input) + } + return encode +} + +const decodeDefect = (input: Json): unknown => isJsonError(input) ? decodeJsonError(input) : input + +/** @internal */ +export const errorFromJsonError = (options?: ErrorOptions): Transformation => + transform({ + decode: decodeJsonError, + encode: (input) => makeEncodeDefect(options)(input) as JsonError + }) + +/** @internal */ +export const defectFromJson = (options?: ErrorOptions) => + transform({ + decode: decodeDefect, + encode: makeEncodeDefect(options) + }) + +/** + * Decodes `T | null` into `Option` and encodes `Option` back to + * `T | null`. + * + * **When to use** + * + * Use when you need a schema transformation to convert nullable API fields to + * `Option`. + * + * **Details** + * + * Decoding maps `null` to `Option.none()` and non-null values to + * `Option.some(value)`. Encoding maps `Option.none()` to `null` and + * `Option.some(value)` to `value`. The transformation is pure and synchronous. + * + * **Example** (Converting nullable values to an Option) + * + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.NullOr(Schema.String).pipe( + * Schema.decodeTo( + * Schema.Option(Schema.String), + * SchemaTransformation.optionFromNullOr() + * ) + * ) + * Schema.decodeSync(schema)(null) // => Option.none() + * ``` + * + * @see {@link optionFromNullishOr} + * + * @category transforming + * @since 4.0.0 + */ +export function optionFromNullOr(): Transformation, T | null> { + return transform({ + decode: Option.fromNullOr, + encode: Option.getOrNull + }) +} + +/** + * Decodes `T | undefined` into `Option` and encodes `Option.none()` back to + * `undefined`. + * + * **When to use** + * + * Use when you need a schema transformation to convert API fields that use + * `undefined` for absence to `Option`. + * + * **Details** + * + * Decoding maps `undefined` to `Option.none()` and non-undefined values to + * `Option.some(value)`. Encoding maps `Option.none()` to `undefined` and + * `Option.some(value)` to `value`. The transformation is pure and synchronous. + * + * **Example** (Converting undefined-or values to an Option) + * + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.UndefinedOr(Schema.String).pipe( + * Schema.decodeTo( + * Schema.Option(Schema.String), + * SchemaTransformation.optionFromUndefinedOr() + * ) + * ) + * Schema.decodeSync(schema)(undefined) // => Option.none() + * ``` + * + * @see {@link optionFromOptionalKey} + * @see {@link optionFromOptional} + * + * @category transforming + * @since 4.0.0 + */ +export function optionFromUndefinedOr(): Transformation, T | undefined> { + return transform({ + decode: Option.fromUndefinedOr, + encode: Option.getOrUndefined + }) +} + +/** + * Decodes `T | null | undefined` into `Option` and encodes `Option` + * back to `T | null` or `T | undefined` depending on the provided + * `options.onNoneEncoding` (defaults to `undefined`). + * + * **When to use** + * + * Use when you need a schema transformation to convert nullish API fields to + * `Option` when both `null` and `undefined` represent absence. + * + * **Details** + * + * Decoding maps `null` and `undefined` to `Option.none()` and all other values + * to `Option.some(value)`. Encoding maps `Option.none()` to `null` or + * `undefined` according to `options.onNoneEncoding`, and maps + * `Option.some(value)` to `value`. The transformation is pure and synchronous. + * + * **Example** (Converting nullish values to an Option and encoding None as null) + * + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.NullishOr(Schema.String).pipe( + * Schema.decodeTo( + * Schema.Option(Schema.String), + * SchemaTransformation.optionFromNullishOr({ onNoneEncoding: null }) + * ) + * ) + * Schema.encodeSync(schema)(Option.none()) // => null + * ``` + * + * @see {@link optionFromNullOr} + * @see {@link optionFromUndefinedOr} + * + * @category transforming + * @since 4.0.0 + */ +export function optionFromNullishOr( + options?: { + onNoneEncoding: null | undefined + } +): Transformation, T | null | undefined> { + return transform({ + decode: Option.fromNullishOr, + encode: options?.onNoneEncoding === null ? Option.getOrNull : Option.getOrUndefined + }) +} + +/** + * Decodes an optional struct key into `Option` and encodes `Option` + * back to an optional key. + * + * **When to use** + * + * Use when you need a schema transformation to convert optional struct keys + * (declared with `Schema.optionalKey`) to `Option` values. + * + * **Details** + * + * Decoding maps an absent key (`None`) to `Some(None)` and a present key + * (`Some(v)`) to `Some(Some(v))`. Encoding maps `Some(None)` to `None` to omit + * the key, and maps `Some(Some(v))` to `Some(v)`. This uses + * `transformOptional` under the hood. + * + * **Example** (Converting an optional key to an Option) + * + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.Struct({ + * name: Schema.optionalKey(Schema.String).pipe( + * Schema.decodeTo( + * Schema.Option(Schema.String), + * SchemaTransformation.optionFromOptionalKey() + * ) + * ) + * }) + * Schema.decodeSync(schema)({}).name // => Option.none() + * ``` + * + * @see {@link optionFromOptional} + * @see {@link optionFromUndefinedOr} + * @see {@link transformOptional} + * + * @category transforming + * @since 4.0.0 + */ +export function optionFromOptionalKey(): Transformation, T> { + return transformOptional({ + decode: Option.some, + encode: Option.flatten + }) +} + +/** + * Decodes optional values into `Option` and encodes `Option.none()` back to + * an omitted optional value. + * + * **When to use** + * + * Use when you need a schema transformation to convert optional (possibly + * `undefined`) values to `Option`. + * + * **Details** + * + * Decoding maps an absent or `undefined` value to `Some(None)` and a present + * value to `Some(Some(v))`. Encoding maps `Some(None)` to `None` to omit the + * value, and maps `Some(Some(v))` to `Some(v)`. This uses + * `transformOptional` under the hood and filters out `undefined` on decode. + * + * **Example** (Converting an optional value to an Option) + * + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.Struct({ + * age: Schema.optional(Schema.Number).pipe( + * Schema.decodeTo( + * Schema.Option(Schema.Number), + * SchemaTransformation.optionFromOptional() + * ) + * ) + * }) + * Schema.decodeSync(schema)({ age: undefined }).age // => Option.none() + * ``` + * + * @see {@link optionFromOptionalKey} + * @see {@link optionFromUndefinedOr} + * @see {@link transformOptional} + * + * @category transforming + * @since 4.0.0 + */ +export function optionFromOptional(): Transformation, T | undefined> { + return transformOptional, T | undefined>({ + decode: (ot) => ot.pipe(Option.filter(Predicate.isNotUndefined), Option.some), + encode: Option.flatten + }) +} + +/** + * Decodes a `string` into a `URL` and encodes a `URL` back to its `href` + * string. + * + * **When to use** + * + * Use when you need a schema transformation to parse URL strings from user + * input or API responses. + * + * **Details** + * + * Decoding checks `URL.canParse(s)` and fails with `InvalidValue` if the string + * is not a valid URL. Encoding returns `url.href`. + * + * **Example** (Converting a string to a URL) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.URL, SchemaTransformation.urlFromString) + * ) + * Schema.decodeSync(schema)("https://example.com/path").href // => "https://example.com/path" + * ``` + * + * @see {@link numberFromString} + * @see {@link transformOrFail} + * + * @category transforming + * @since 4.0.0 + */ +export const urlFromString: Transformation = transformOrFail({ + decode: (s, options) => + URL.canParse(s) + ? Effect.succeed(new URL(s)) + : Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid URL string" }, + s, + options + ) + ), + encode: (url) => Effect.succeed(url.href) +}) + +/** + * Decodes a `string` into a `BigDecimal` and encodes a `BigDecimal` back to + * its string representation. + * + * **When to use** + * + * Use when you need a schema transformation to parse decimal number strings + * from APIs or user input. + * + * **Details** + * + * Decoding calls `BigDecimal.fromString(s)` and fails with `InvalidValue` if + * the string is not a valid `BigDecimal` representation. Encoding returns + * `BigDecimal.format(bd)`. + * + * @category transforming + * @since 4.0.0 + */ +export const bigDecimalFromString: Transformation = transformOrFail< + BigDecimal.BigDecimal, + string +>({ + decode: (s, options) => { + const result = BigDecimal.fromString(s) + return Option.isNone(result) + ? Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid BigDecimal string" }, + s, + options + ) + ) + : Effect.succeed(result.value) + }, + encode: (bd) => Effect.succeed(BigDecimal.format(bd)) +}) + +/** + * Decodes a Base64-encoded `string` into a `Uint8Array` and encodes a + * `Uint8Array` back to a Base64 string. + * + * **When to use** + * + * Use when you need a schema transformation for binary data transmitted as + * Base64 strings (e.g. file uploads, API payloads). + * + * **Details** + * + * Decoding parses the Base64 string into bytes. Encoding writes the byte array + * as a Base64 string. + * + * **Example** (Converting Base64 to a Uint8Array) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.Uint8Array, SchemaTransformation.uint8ArrayFromBase64String) + * ) + * Array.from(Schema.decodeSync(schema)("AQID")) // => [1, 2, 3] + * ``` + * + * @see {@link fromJsonString} + * @see `Schema.Uint8ArrayFromBase64` - a ready-made schema wrapping this transformation. + * + * @category encoding + * @since 4.0.0 + */ +export const uint8ArrayFromBase64String: Transformation, string> = new Transformation( + SchemaGetter.decodeBase64(), + SchemaGetter.encodeBase64() +) + +/** + * Decodes a Base64-encoded `string` into a UTF-8 `string` and encodes a + * UTF-8 `string` back to a Base64 string. + * + * **When to use** + * + * Use when you need a schema transformation for text data transmitted as Base64 + * strings. + * + * **Details** + * + * Decoding parses the Base64 string into a UTF-8 string. Encoding writes the + * string as a Base64 string. + * + * **Example** (Converting Base64 to a string) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.String, SchemaTransformation.stringFromBase64String) + * ) + * Schema.decodeSync(schema)("aGVsbG8=") // => "hello" + * ``` + * + * @see {@link uint8ArrayFromBase64String} + * @see `Schema.StringFromBase64` - a ready-made schema wrapping this transformation. + * + * @category encoding + * @since 4.0.0 + */ +export const stringFromBase64String: Transformation = new Transformation( + SchemaGetter.decodeBase64String(), + SchemaGetter.encodeBase64() +) + +/** + * Decodes a base64 (URL) encoded `string` into a UTF-8 `string` and encodes it back. + * + * **When to use** + * + * Use when you need a schema transformation for text data transmitted as Base64 + * URL-safe strings. + * + * **Details** + * + * Decoding parses the Base64 URL string into a UTF-8 string. Encoding writes + * the string as a Base64 URL string. + * + * **Example** (Converting Base64Url to a string) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.String, SchemaTransformation.stringFromBase64UrlString) + * ) + * Schema.decodeSync(schema)("aGVsbG8") // => "hello" + * ``` + * + * @see {@link stringFromBase64String} + * @see `Schema.StringFromBase64Url` - a ready-made schema wrapping this transformation. + * + * @category encoding + * @since 4.0.0 + */ +export const stringFromBase64UrlString: Transformation = new Transformation( + SchemaGetter.decodeBase64UrlString(), + SchemaGetter.encodeBase64Url() +) + +/** + * Decodes a hex encoded `string` into a UTF-8 `string` and encodes it back. + * + * **When to use** + * + * Use when you need a schema transformation for text data transmitted as + * hexadecimal strings. + * + * **Details** + * + * Decoding parses the hex string into a UTF-8 string. Encoding writes the + * string as a hex string. + * + * **Example** (Converting hex to a string) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.String, SchemaTransformation.stringFromHexString) + * ) + * Schema.decodeSync(schema)("68656c6c6f") // => "hello" + * ``` + * + * @see {@link stringFromBase64String} + * @see `Schema.StringFromHex` - a ready-made schema wrapping this transformation. + * + * @category encoding + * @since 4.0.0 + */ +export const stringFromHexString: Transformation = new Transformation( + SchemaGetter.decodeHexString(), + SchemaGetter.encodeHex() +) + +/** + * Decodes a URI component encoded string into a UTF-8 string and encodes a + * UTF-8 string into a URI component encoded string. + * + * **When to use** + * + * Use when you need a schema transformation to store structured data in URL + * query parameters or fragments, such as composing with `Schema.parseJson` to + * round-trip JSON through a URL. + * + * **Details** + * + * Decoding calls `decodeURIComponent` and fails if the input contains malformed + * percent-encoding sequences. Encoding calls `encodeURIComponent`. + * + * **Example** (Defining a URI component schema) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.String, SchemaTransformation.stringFromUriComponent) + * ) + * Schema.decodeSync(schema)("hello%20world") // => "hello world" + * ``` + * + * @see {@link stringFromBase64String} + * @see `Schema.StringFromUriComponent` - a ready-made schema wrapping this transformation. + * + * @category encoding + * @since 4.0.0 + */ +export const stringFromUriComponent: Transformation = new Transformation( + SchemaGetter.decodeUriComponent(), + SchemaGetter.encodeUriComponent() +) + +/** + * Decodes a JSON string with `JSON.parse` and encodes a value with + * `JSON.stringify`. + * + * **When to use** + * + * Use when you need a schema transformation to decode JSON stored or + * transmitted as a string, usually before composing with another schema that + * validates the parsed structure. + * + * **Details** + * + * The `reviver` option is passed to `JSON.parse` during decoding. The + * `replacer` and `space` options are passed to `JSON.stringify` during + * encoding. Decode fails with `InvalidValue` for invalid JSON, and encode can + * fail with `InvalidValue` when `JSON.stringify` cannot serialize the value. + * + * **Example** (Parsing JSON) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.String.pipe( + * Schema.decodeTo(Schema.Unknown, SchemaTransformation.fromJsonString()) + * ) + * Schema.decodeSync(schema)("{\"ok\":true}") // => { ok: true } + * ``` + * + * @see {@link uint8ArrayFromBase64String} + * @see {@link fromFormData} + * + * @category decoding + * @since 4.0.0 + */ +export function fromJsonString(options?: { + readonly reviver?: Parameters[1] | undefined + readonly replacer?: SchemaGetter.JsonReplacer | undefined + readonly space?: Parameters[2] | undefined +}): Transformation { + return new Transformation( + SchemaGetter.parseJson(options ?? {}), + SchemaGetter.stringifyJson(options) + ) +} + +/** + * Decodes a `FormData` instance into a nested record using bracket-path keys and + * encodes object-like values back into `FormData`. + * + * **When to use** + * + * Use when you need a schema transformation for form or multipart payloads + * whose keys, such as `user[name]` or `items[0]`, should become nested data. + * + * **Details** + * + * Decode preserves string and `Blob` leaves. Encode flattens nested objects and + * arrays into bracket-path entries and returns an empty `FormData` for + * non-object inputs. + * + * **Example** (Decoding FormData) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.instanceOf(FormData).pipe( + * Schema.decodeTo(Schema.Unknown, SchemaTransformation.fromFormData) + * ) + * const formData = new FormData() + * formData.append("user[name]", "Alice") + * Schema.decodeSync(schema)(formData) // => { user: { name: "Alice" } } + * ``` + * + * @see {@link fromURLSearchParams} + * @see {@link fromJsonString} + * + * @category decoding + * @since 4.0.0 + */ +export const fromFormData = new Transformation( + SchemaGetter.decodeFormData(), + SchemaGetter.encodeFormData() +) + +/** + * Decodes `URLSearchParams` into a nested record using bracket-path keys and + * encodes object-like values back into `URLSearchParams`. + * + * **When to use** + * + * Use when you need a schema transformation for query strings whose keys, such + * as `filter[name]` or `items[0]`, should become nested data. + * + * **Details** + * + * Decode produces string leaves. Encode flattens nested objects and arrays into + * bracket-path entries and returns empty `URLSearchParams` for non-object + * inputs. + * + * **Example** (Decoding URLSearchParams) + * + * ```ts import.meta.vitest + * import { Schema, SchemaTransformation } from "effect" + * + * const schema = Schema.instanceOf(URLSearchParams).pipe( + * Schema.decodeTo(Schema.Unknown, SchemaTransformation.fromURLSearchParams) + * ) + * Schema.decodeSync(schema)(new URLSearchParams("user[name]=Alice")) // => { user: { name: "Alice" } } + * ``` + * + * @see {@link fromFormData} + * @see {@link fromJsonString} + * + * @category decoding + * @since 4.0.0 + */ +export const fromURLSearchParams = new Transformation( + SchemaGetter.decodeURLSearchParams(), + SchemaGetter.encodeURLSearchParams() +) + +/** + * Decodes a numeric time-zone offset in milliseconds into a + * `DateTime.TimeZone.Offset` and encodes it back to the offset number. + * + * **When to use** + * + * Use when you need a schema transformation to represent fixed-offset time + * zones with numeric millisecond offsets. + * + * **Details** + * + * Decode uses `DateTime.zoneMakeOffset`; encode returns the offset's `offset` + * field. + * + * @see {@link timeZoneFromString} for IANA or offset string encodings + * @see {@link timeZoneNamedFromString} for IANA named-zone strings + * + * @category transforming + * @since 4.0.0 + */ +export const timeZoneOffsetFromNumber: Transformation = transform< + DateTime.TimeZone.Offset, + number +>({ + decode: (n) => DateTime.zoneMakeOffset(n), + encode: (tz) => tz.offset +}) + +/** + * Decodes an IANA time-zone identifier string into a + * `DateTime.TimeZone.Named` and encodes a named time zone back to its `id`. + * + * **When to use** + * + * Use when you need a schema transformation to accept only IANA time-zone + * identifier strings and produce `DateTime.TimeZone.Named` values. + * + * **Details** + * + * Decode fails with `InvalidValue` when the string is not a valid IANA time-zone + * identifier. + * + * @see {@link timeZoneFromString} for time-zone strings that may be either IANA identifiers or offset strings + * + * @category transforming + * @since 4.0.0 + */ +export const timeZoneNamedFromString: Transformation = transformOrFail< + DateTime.TimeZone.Named, + string +>({ + decode: (s, options) => { + return Option.match(DateTime.zoneMakeNamed(s), { + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid IANA time zone" }, + s, + options + ) + ), + onSome: Effect.succeed + }) + }, + encode: (tz) => Effect.succeed(tz.id) +}) + +/** + * Decodes a string into a `DateTime.TimeZone` and encodes a time zone back to + * its string representation. + * + * **When to use** + * + * Use when you need a schema transformation to accept either an IANA time-zone + * identifier or an offset string and produce a general `DateTime.TimeZone`. + * + * **Details** + * + * Accepted decode inputs include valid IANA identifiers and offset strings such + * as `"+03:00"`. Decode fails with `InvalidValue` when the string cannot be + * parsed as a time zone. + * + * @see {@link timeZoneNamedFromString} for IANA named-zone strings only + * @see {@link timeZoneOffsetFromNumber} for fixed-offset zones encoded as numbers + * + * @category transforming + * @since 4.0.0 + */ +export const timeZoneFromString: Transformation = transformOrFail< + DateTime.TimeZone, + string +>({ + decode: (s, options) => { + return Option.match(DateTime.zoneFromString(s), { + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid time zone" }, + s, + options + ) + ), + onSome: Effect.succeed + }) + }, + encode: (tz) => Effect.succeed(DateTime.zoneToString(tz)) +}) + +/** + * Decodes a date-time string into a `DateTime.Utc` and encodes it back to an ISO + * string. + * + * **When to use** + * + * Use when you need a schema transformation to decode date-time strings to a + * normalized `DateTime.Utc` and encode back as a UTC ISO string. + * + * **Details** + * + * Decode accepts strings supported by `DateTime.make`, converts the result to + * UTC, and fails with `InvalidValue` when parsing fails. Encode uses + * `DateTime.formatIso`. + * + * @see {@link dateFromString} for decoding into JavaScript `Date` + * @see {@link dateTimeZonedFromString} for ISO strings that should preserve zoned date-time information + * + * @category transforming + * @since 4.0.0 + */ +export const dateTimeUtcFromString: Transformation = transformOrFail< + DateTime.Utc, + string +>({ + decode: (s, options) => { + return Option.match(DateTime.make(s), { + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid UTC DateTime string" }, + s, + options + ) + ), + onSome: (result) => Effect.succeed(DateTime.toUtc(result)) + }) + }, + encode: (utc) => Effect.succeed(DateTime.formatIso(utc)) +}) + +/** + * Decodes a zoned date-time string into a `DateTime.Zoned` and encodes it back + * to an ISO zoned string. + * + * **When to use** + * + * Use when you need a schema transformation for ISO zoned date-time strings + * that decode to `DateTime.Zoned` and encode with `DateTime.formatIsoZoned`. + * + * **Details** + * + * Decode uses `DateTime.makeZonedFromString` and fails with `InvalidValue` when + * the input is not a valid zoned date-time. Encode uses + * `DateTime.formatIsoZoned`. + * + * @see {@link dateTimeUtcFromString} for date-time strings that should decode to `DateTime.Utc` and encode as UTC ISO strings + * + * @category transforming + * @since 4.0.0 + */ +export const dateTimeZonedFromString: Transformation = transformOrFail< + DateTime.Zoned, + string +>({ + decode: (s, options) => { + return Option.match(DateTime.makeZonedFromString(s), { + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Zoned DateTime string" }, + s, + options + ) + ), + onSome: Effect.succeed + }) + }, + encode: (zoned) => Effect.succeed(DateTime.formatIsoZoned(zoned)) +}) diff --git a/.repos/effect/packages/effect/src/Scope.ts b/.repos/effect/packages/effect/src/Scope.ts new file mode 100644 index 000000000..12e1c6b1b --- /dev/null +++ b/.repos/effect/packages/effect/src/Scope.ts @@ -0,0 +1,545 @@ +/** + * Controls how long resources stay open. + * + * A scope is a lifetime boundary. Code can register cleanup effects on it, and + * closing the scope runs those cleanups with the `Exit` value that ended the + * work. Most application code uses higher-level APIs such as `Effect.scoped` + * and `Layer`, while this module is useful when code needs to create, provide, + * fork, close, or inspect scopes directly. + * + * @since 2.0.0 + */ + +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" + +const TypeId = effect.ScopeTypeId +const CloseableTypeId = effect.ScopeCloseableTypeId + +/** + * A `Scope` represents a context where resources can be acquired and + * automatically cleaned up when the scope is closed. Scopes can use + * either sequential or parallel finalization strategies. + * + * **Example** (Managing scoped resources) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const program = Effect.gen(function*() { + * const scope = yield* Scope.make("sequential") + * + * const initial = [scope.strategy, scope.state._tag] + * yield* Scope.close(scope, Exit.void) + * return [initial, scope.state._tag] + * }) + * + * Effect.runSync(program) // => [["sequential", "Empty"], "Closed"] + * ``` + * + * @category services + * @since 2.0.0 + */ +export interface Scope { + readonly [TypeId]: typeof TypeId + readonly strategy: "sequential" | "parallel" + state: State.Open | State.Closed | State.Empty +} +/** + * A `Closeable` scope extends the base `Scope` interface with the ability + * to be closed, executing all registered finalizers. + * + * **Example** (Closing a scope) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const cleanups: Array = [] + * const program = Effect.gen(function*() { + * const scope = yield* Scope.make() + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup!"))) + * yield* Scope.close(scope, Exit.void) + * }) + * + * Effect.runSync(program) + * cleanups // => ["Cleanup!"] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Closeable extends Scope { + readonly [CloseableTypeId]: typeof CloseableTypeId +} + +/** + * The `State` namespace contains the concrete states of a scope: `Empty` + * before any finalizers are registered, `Open` with registered finalizers, and + * `Closed` with the exit value used to close the scope. + * + * **Example** (Checking scope states) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const program = Effect.gen(function*() { + * const scope = yield* Scope.make() + * const before = scope.state._tag + * yield* Scope.close(scope, Exit.void) + * return [before, scope.state._tag] + * }) + * + * Effect.runSync(program) // => ["Empty", "Closed"] + * ``` + * + * @since 4.0.0 + */ +export declare namespace State { + /** + * Represents an open scope with no registered finalizers yet. + * + * **Details** + * + * Adding the first finalizer transitions the scope to `Open`; closing an + * empty scope transitions directly to `Closed` without producing a finalizer + * effect. + * + * **Example** (Inspecting an empty scope state) + * + * ```ts import.meta.vitest + * import { Scope } from "effect" + * + * const scope = Scope.makeUnsafe() + * + * scope.state._tag // => "Empty" + * ``` + * + * @category models + * @since 4.0.0 + */ + export type Empty = { + readonly _tag: "Empty" + } + /** + * Represents an open scope state where finalizers can be added and + * the scope is still accepting new resources. + * + * **Details** + * + * Stores one finalizer inline and allocates the `finalizers` map when a + * second is added. + * + * **Example** (Inspecting an open scope state) + * + * ```ts import.meta.vitest + * import { Effect, Scope } from "effect" + * + * const scope = Scope.makeUnsafe() + * + * Effect.runSync(Scope.addFinalizer(scope, Effect.void)) + * const state = scope.state + * if (state._tag !== "Open") throw new Error("unexpected state") + * + * state._tag // => "Open" + * state.finalizer !== undefined // => true + * ``` + * + * @category models + * @since 4.0.0 + */ + export type Open = { + readonly _tag: "Open" + finalizerKey: {} | undefined + finalizer: ((exit: Exit) => Effect) | undefined + finalizers: Map<{}, (exit: Exit) => Effect> | undefined + } + /** + * Represents a closed scope state where finalizers have been executed + * and the scope is no longer accepting new resources. + * + * **Example** (Inspecting a closed scope state) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const program = Effect.gen(function*() { + * const scope = yield* Scope.make() + * + * yield* Scope.close(scope, Exit.succeed("Done")) + * if (scope.state._tag === "Closed") { + * return scope.state.exit + * } + * return Exit.die("unexpected state") + * }) + * + * Effect.runSync(program) // => Exit.succeed("Done") + * ``` + * + * @category models + * @since 4.0.0 + */ + export type Closed = { + readonly _tag: "Closed" + readonly exit: Exit + } +} + +/** + * Service tag for the active resource lifetime. + * + * **When to use** + * + * Use to access the active lifetime when registering finalizers or sharing + * resources with the surrounding scope. + * + * **Example** (Accessing the scope service) + * + * ```ts import.meta.vitest + * import { Effect, Scope } from "effect" + * + * const cleanups: Array = [] + * const program = Effect.gen(function*() { + * const scope = yield* Scope.Scope + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup"))) + * }) + * + * Effect.runSync(Effect.scoped(program)) + * cleanups // => ["Cleanup"] + * ``` + * + * @category services + * @since 2.0.0 + */ +export const Scope: Context.Service = effect.scopeTag + +/** + * Creates a new `Scope` with the specified finalizer strategy. + * + * **Example** (Creating a scope) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const cleanups: Array = [] + * const program = Effect.gen(function*() { + * const scope = yield* Scope.make("sequential") + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup 1"))) + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup 2"))) + * yield* Scope.close(scope, Exit.void) + * }) + * + * Effect.runSync(program) + * cleanups // => ["Cleanup 2", "Cleanup 1"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make: (finalizerStrategy?: "sequential" | "parallel") => Effect = effect.scopeMake + +/** + * Creates a new `Scope` synchronously without wrapping it in an `Effect`. + * This is useful when you need a scope immediately but should be used with caution + * as it doesn't provide the same safety guarantees as the `Effect`-wrapped version. + * + * **When to use** + * + * Use when a scope must be allocated synchronously and the caller will close it + * manually. + * + * **Example** (Creating a scope synchronously) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const scope = Scope.makeUnsafe("sequential") + * const cleanups: Array = [] + * const program = Effect.gen(function*() { + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup"))) + * yield* Scope.close(scope, Exit.void) + * }) + * + * Effect.runSync(program) + * cleanups // => ["Cleanup"] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeUnsafe: (finalizerStrategy?: "sequential" | "parallel") => Closeable = effect.scopeMakeUnsafe + +/** + * Provides a concrete `Scope` to an effect. + * + * **When to use** + * + * Use to run an effect that requires `Scope` with a scope managed by the + * caller. + * + * **Details** + * + * Providing the scope removes the `Scope` requirement from the effect context. + * + * **Example** (Providing a scope) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const events: Array = [] + * const program = Effect.gen(function*() { + * const scope = yield* Scope.Scope + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup"))) + * events.push("working") + * }) + * + * const withScope = Effect.gen(function*() { + * const scope = yield* Scope.make() + * yield* Scope.provide(scope)(program) + * yield* Scope.close(scope, Exit.void) + * }) + * + * Effect.runSync(withScope) + * events // => ["working", "cleanup"] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const provide: { + (value: Scope): (self: Effect) => Effect> + (self: Effect, value: Scope): Effect> +} = effect.provideScope + +/** + * Registers an exit-aware finalizer on a scope. + * + * **When to use** + * + * Use when cleanup needs to know whether the scope closed with success, + * failure, or interruption. + * + * **Details** + * + * If the scope is open, the finalizer runs when the scope closes and receives + * the scope's exit value. If the scope is already closed, the finalizer runs + * immediately with the stored exit value. + * + * **Example** (Adding an exit-aware finalizer) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const exits: Array> = [] + * const withResource = Effect.gen(function*() { + * const scope = yield* Scope.make() + * yield* Scope.addFinalizerExit(scope, (exit) => Effect.sync(() => exits.push(exit))) + * yield* Scope.close(scope, Exit.void) + * }) + * + * Effect.runSync(withResource) + * exits // => [Exit.void] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const addFinalizerExit: (scope: Scope, finalizer: (exit: Exit) => Effect) => Effect = + effect.scopeAddFinalizerExit + +/** + * Registers a finalizer effect on a scope. + * + * **Details** + * + * If the scope is open, the finalizer runs when the scope closes, regardless of + * whether the scope closes successfully or with an error. If the scope is + * already closed, the finalizer runs immediately. + * + * **Example** (Adding finalizers) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const events: Array = [] + * const program = Effect.gen(function*() { + * const scope = yield* Scope.make() + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 1"))) + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 2"))) + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 3"))) + * events.push("work") + * yield* Scope.close(scope, Exit.void) + * }) + * + * Effect.runSync(program) + * events // => ["work", "cleanup 3", "cleanup 2", "cleanup 1"] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const addFinalizer: (scope: Scope, finalizer: Effect) => Effect = effect.scopeAddFinalizer + +/** + * Creates a closeable child scope registered with a parent scope. + * + * **Details** + * + * Closing the parent closes the child with the same exit value, and closing the + * child detaches it from the parent. The optional finalizer strategy configures + * the child scope and defaults to `"sequential"` when omitted. + * + * **Example** (Creating a child scope) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const cleanups: Array = [] + * const nestedScopes = Effect.gen(function*() { + * const parentScope = yield* Scope.make("sequential") + * yield* Scope.addFinalizer(parentScope, Effect.sync(() => cleanups.push("parent"))) + * const childScope = yield* Scope.fork(parentScope, "parallel") + * yield* Scope.addFinalizer(childScope, Effect.sync(() => cleanups.push("child"))) + * yield* Scope.close(childScope, Exit.void) + * yield* Scope.close(parentScope, Exit.void) + * }) + * + * Effect.runSync(nestedScopes) + * cleanups // => ["child", "parent"] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const fork: ( + scope: Scope, + finalizerStrategy?: "sequential" | "parallel" +) => Effect = effect.scopeFork + +/** + * Creates a closeable child scope synchronously and registers it with a parent scope. + * + * **When to use** + * + * Use when a child scope must be created synchronously and the caller controls + * both parent and child scope lifetimes. + * + * **Details** + * + * Closing the parent closes the child with the same exit value, and closing the + * child detaches it from the parent. The optional finalizer strategy configures + * the child scope and defaults to `"sequential"` when omitted. + * + * **Example** (Creating a child scope synchronously) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const cleanups: Array = [] + * const program = Effect.gen(function*() { + * const parentScope = Scope.makeUnsafe("sequential") + * const childScope = Scope.forkUnsafe(parentScope, "parallel") + * yield* Scope.addFinalizer(parentScope, Effect.sync(() => cleanups.push("parent"))) + * yield* Scope.addFinalizer(childScope, Effect.sync(() => cleanups.push("child"))) + * yield* Scope.close(childScope, Exit.void) + * yield* Scope.close(parentScope, Exit.void) + * }) + * + * Effect.runSync(program) + * cleanups // => ["child", "parent"] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const forkUnsafe: (scope: Scope, finalizerStrategy?: "sequential" | "parallel") => Closeable = + effect.scopeForkUnsafe + +/** + * Closes a scope and runs its registered finalizers. + * + * **When to use** + * + * Use to close a scope manually with a specific exit value. + * + * **Details** + * + * Finalizers run in the scope's configured order and receive the supplied + * `Exit`. + * + * **Example** (Running scope finalizers) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" + * + * const events: Array = [] + * const resourceManagement = Effect.gen(function*() { + * const scope = yield* Scope.make("sequential") + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("database"))) + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("file"))) + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("memory"))) + * events.push("work") + * yield* Scope.close(scope, Exit.succeed("Success!")) + * }) + * + * Effect.runSync(resourceManagement) + * events // => ["work", "memory", "file", "database"] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const close: (self: Scope, exit: Exit) => Effect = effect.scopeClose + +/** + * Closes a scope unsafely with the provided exit value. + * + * **When to use** + * + * Use when implementing lower-level scope machinery that must transition a + * scope to `Closed` immediately and can run the returned finalizer effect when + * one is produced. + * + * **Details** + * + * Returns an effect that runs registered finalizers, or `undefined` when the + * scope was already closed or no finalizers need to run. + * + * **Gotchas** + * + * Ignoring the returned effect skips registered finalizers. + * + * @see {@link close} for the usual effectful close operation that always returns an `Effect` + * + * @category unsafe + * @since 4.0.0 + */ +export const closeUnsafe: (self: Scope, exit_: Exit) => Effect | undefined = + effect.scopeCloseUnsafe + +/** + * Runs an effect with the provided closeable scope in its context and closes + * that scope when the effect exits. + * + * **When to use** + * + * Use when you already have a `Closeable` scope and want to run an effect that + * requires `Scope` while automatically closing that scope when the effect exits. + * + * **Details** + * + * The scope is closed with the same exit value as the effect, so registered + * finalizers can observe whether the effect succeeded, failed, or was + * interrupted. + * + * @see `provide` for providing a scope without closing it automatically + * @see `Effect.scoped` for creating and closing a fresh scope around a workflow + * + * @category combinators + * @since 2.0.0 + */ +export const use: { + (scope: Closeable): (self: Effect) => Effect> + (self: Effect, scope: Closeable): Effect> +} = effect.scopeUse diff --git a/.repos/effect/packages/effect/src/ScopedCache.ts b/.repos/effect/packages/effect/src/ScopedCache.ts new file mode 100644 index 000000000..664be8138 --- /dev/null +++ b/.repos/effect/packages/effect/src/ScopedCache.ts @@ -0,0 +1,820 @@ +/** + * Caches values that need scoped resource management. + * + * Each cached entry owns its own `Scope`, so resources opened while creating a + * value stay alive while that entry is cached and are released when the entry is + * removed. A `ScopedCache` also belongs to an outer scope, which closes all + * remaining entries when the cache is closed. Lookups for the same missing key + * share one in-progress effect, and entries can expire, be refreshed, be + * invalidated, or be evicted by capacity limits. + * + * @since 4.0.0 + */ +import * as Arr from "./Array.ts" +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" +import * as Fiber from "./Fiber.ts" +import { dual, identity } from "./Function.ts" +import * as core from "./internal/core.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as effect from "./internal/effect.ts" +import * as MutableHashMap from "./MutableHashMap.ts" +import * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import * as Predicate from "./Predicate.ts" +import * as Scope from "./Scope.ts" + +const TypeId = "~effect/ScopedCache" + +/** + * A scoped cache whose values are acquired by a lookup effect and stored in + * per-entry scopes. + * + * **When to use** + * + * Use to cache values that acquire scoped resources and must release those + * resources when entries expire, are evicted, or are invalidated. + * + * **Details** + * + * Concurrent requests for the same key share the same in-flight lookup. + * Entries can expire based on the lookup exit, are evicted when capacity is + * exceeded, and release their entry scopes when invalidated, evicted, expired, + * or when the cache's owning scope closes. + * + * @see {@link make} for creating a scoped cache with a fixed time-to-live + * @see {@link makeWith} for creating a scoped cache with dynamic time-to-live + * + * @category models + * @since 2.0.0 + */ +export interface ScopedCache extends Pipeable { + readonly [TypeId]: typeof TypeId + state: State + readonly capacity: number + readonly lookup: (key: Key) => Effect.Effect + readonly timeToLive: (exit: Exit.Exit, key: Key) => Duration.Duration +} + +/** + * Represents whether a `ScopedCache` is open or closed. + * + * **When to use** + * + * Use when inspecting the low-level lifecycle state of a scoped cache. + * + * **Details** + * + * `Open` stores cached entries in access order for reuse and eviction. + * `Closed` means the owning scope has closed and the cache can no longer + * perform lookup operations. + * + * @category models + * @since 4.0.0 + */ +export type State = { + readonly _tag: "Open" + readonly map: MutableHashMap.MutableHashMap> +} | { + readonly _tag: "Closed" +} + +/** + * A single scoped cache entry. + * + * **When to use** + * + * Use when inspecting the open state of a `ScopedCache` and you need the stored + * deferred result, entry scope, or expiration timestamp for a key. + * + * **Details** + * + * The entry contains the deferred lookup result shared by readers, the scope + * that owns resources acquired while computing the value, and an optional + * expiration time in milliseconds. Removing the entry closes its scope. + * + * @see {@link State} for the open/closed cache state that stores entries by key + * + * @category models + * @since 4.0.0 + */ +export interface Entry { + expiresAt: number | undefined + readonly deferred: Deferred.Deferred + readonly scope: Scope.Closeable +} + +/** + * Creates a `ScopedCache` from a lookup function, maximum capacity, and a + * time-to-live function computed from each lookup exit and key. + * + * **When to use** + * + * Use when you need a scoped cache whose entry lifetime depends on each lookup + * result or key. + * + * **Details** + * + * The cache must be constructed in a `Scope`. Each lookup runs in its own entry + * scope, and that scope is closed when the entry expires, is invalidated, is + * evicted by capacity, or when the cache's owning scope closes. + * `requireServicesAt` controls whether lookup services are captured at + * construction time or required when lookup operations run. + * + * @see {@link make} for creating a scoped cache with one fixed time-to-live + * + * @category constructors + * @since 2.0.0 + */ +export const makeWith = < + Key, + A, + E = never, + R = never, + ServiceMode extends "lookup" | "construction" = never +>(options: { + readonly lookup: (key: Key) => Effect.Effect + readonly capacity: number + readonly timeToLive?: ((exit: Exit.Exit, key: Key) => Duration.Input) | undefined + readonly requireServicesAt?: ServiceMode | undefined +}): Effect.Effect< + ScopedCache : never>, + never, + ("lookup" extends ServiceMode ? never : R) | Scope.Scope +> => + effect.contextWith((context: Context.Context) => { + const scope = Context.get(context, Scope.Scope) + const self = Object.create(Proto) + self.lookup = (key: Key): Effect.Effect => + effect.updateContext( + options.lookup(key), + (input) => Context.merge(context, input) + ) + const map = MutableHashMap.empty>() + self.state = { _tag: "Open", map } + self.capacity = options.capacity + self.timeToLive = options.timeToLive + ? (exit: Exit.Exit, key: Key) => Duration.fromInputUnsafe(options.timeToLive!(exit, key)) + : defaultTimeToLive + return effect.as( + Scope.addFinalizer( + scope, + core.withFiber((fiber) => { + self.state = { _tag: "Closed" } + return invalidateAllImpl(fiber, map) + }) + ), + self + ) + }) + +/** + * Creates a `ScopedCache` with a fixed time-to-live for every lookup result. + * + * **When to use** + * + * Use to create a scoped cache when every cached lookup result should share the + * same lifetime. + * + * **Details** + * + * This is the constant-TTL variant of `makeWith`: values are acquired by the + * lookup effect in per-entry scopes, capacity can evict older entries, and + * entry scopes are closed when entries expire, are invalidated, are evicted, or + * when the cache's owning scope closes. + * + * @see {@link makeWith} for computing time-to-live from each lookup result and key + * + * @category constructors + * @since 2.0.0 + */ +export const make = < + Key, + A, + E = never, + R = never, + ServiceMode extends "lookup" | "construction" = never +>( + options: { + readonly lookup: (key: Key) => Effect.Effect + readonly capacity: number + readonly timeToLive?: Duration.Input | undefined + readonly requireServicesAt?: ServiceMode | undefined + } +): Effect.Effect< + ScopedCache : never>, + never, + ("lookup" extends ServiceMode ? never : R) | Scope.Scope +> => + makeWith({ + ...options, + timeToLive: options.timeToLive !== undefined ? () => options.timeToLive! : defaultTimeToLive + }) + +const Proto = { + ...PipeInspectableProto, + [TypeId]: TypeId, + toJSON(this: ScopedCache) { + return { + _id: "ScopedCache", + capacity: this.capacity, + state: this.state + } + } +} + +const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Duration => Duration.infinity + +/** + * Gets the value for a key, running the cache lookup when no unexpired entry is + * present. + * + * **When to use** + * + * Use to retrieve a scoped cached value by key when a missing or expired entry + * should run the cache lookup and share the in-flight lookup with concurrent + * callers. + * + * **Details** + * + * Concurrent `get` calls for the same key share the same in-flight lookup. + * Successful and failed lookup exits are cached according to the configured + * TTL. If the cache is closed, the effect is interrupted. + * + * @see {@link getOption} for reading only when an unexpired entry is already cached + * @see {@link getSuccess} for inspecting an already-completed successful entry + * @see {@link refresh} for forcing a new lookup + * + * @category combinators + * @since 4.0.0 + */ +export const get: { + (key: Key): (self: ScopedCache) => Effect.Effect + (self: ScopedCache, key: Key): Effect.Effect +} = dual( + 2, + (self: ScopedCache, key: Key): Effect.Effect => + effect.uninterruptibleMask((restore) => + core.withFiber((fiber) => { + const state = self.state + if (state._tag === "Closed") { + return effect.interrupt + } + const oentry = MutableHashMap.get(state.map, key) + if (Option.isSome(oentry) && !hasExpired(oentry.value, fiber)) { + // Move the entry to the end of the map to keep it fresh + MutableHashMap.remove(state.map, key) + MutableHashMap.set(state.map, key, oentry.value) + return restore(Deferred.await(oentry.value.deferred)) + } + const scope = Scope.makeUnsafe() + const deferred = Deferred.makeUnsafe() + const entry: Entry = { + expiresAt: undefined, + deferred, + scope + } + MutableHashMap.set(state.map, key, entry) + return checkCapacity(fiber, state.map, self.capacity).pipe( + Option.isSome(oentry) ? effect.flatMap(() => Scope.close(oentry.value.scope, effect.exitVoid)) : identity, + effect.flatMap(() => Scope.provide(restore(self.lookup(key)), scope)), + effect.onExit((exit) => { + Deferred.doneUnsafe(deferred, exit) + const ttl = self.timeToLive(exit, key) + if (Duration.isFinite(ttl)) { + entry.expiresAt = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) + } + return effect.void + }) + ) + }) + ) +) + +const hasExpired = (entry: Entry, fiber: Fiber.Fiber): boolean => { + if (entry.expiresAt === undefined) { + return false + } + return fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() >= entry.expiresAt +} + +const checkCapacity = ( + parent: Fiber.Fiber, + map: MutableHashMap.MutableHashMap>, + capacity: number +): Effect.Effect => { + if (!Number.isFinite(capacity)) return effect.void + let diff = MutableHashMap.size(map) - capacity + if (diff <= 0) return effect.void + // MutableHashMap has insertion order, so we can remove the oldest entries + const fibers = Arr.empty>() + for (const [key, entry] of map) { + MutableHashMap.remove(map, key) + fibers.push(effect.forkUnsafe(parent as any, Scope.close(entry.scope, effect.exitVoid), true)) + diff-- + if (diff === 0) break + } + return effect.fiberAwaitAll(fibers) +} + +/** + * Reads an existing unexpired cache entry without running the lookup function. + * + * **When to use** + * + * Use to read a scoped value only when it is already cached, without starting + * the lookup for missing or expired keys. + * + * **Details** + * + * Returns `Option.none` when the key is absent or expired. If an entry exists, + * the effect waits for its cached result and returns `Option.some(value)` on + * success, or fails with the cached lookup error. + * + * @see {@link get} for running the lookup on missing or expired keys + * @see {@link getSuccess} for inspecting only already-completed successful entries + * + * @category combinators + * @since 4.0.0 + */ +export const getOption: { + (key: Key): (self: ScopedCache) => Effect.Effect, E> + (self: ScopedCache, key: Key): Effect.Effect, E> +} = dual( + 2, + (self: ScopedCache, key: Key): Effect.Effect, E> => + effect.uninterruptibleMask((restore) => + core.withFiber((fiber) => + effect.flatMap( + getImpl(self, key, fiber), + (entry) => entry ? effect.asSome(restore(Deferred.await(entry.deferred))) : effect.succeedNone + ) + ) + ) +) + +const getImpl = ( + self: ScopedCache, + key: Key, + fiber: Fiber.Fiber, + isRead = true +): Effect.Effect | undefined> => { + if (self.state._tag === "Closed") { + return effect.interrupt + } + const state = self.state + const oentry = MutableHashMap.get(state.map, key) + if (Option.isNone(oentry)) { + return effect.undefined + } else if (hasExpired(oentry.value, fiber)) { + MutableHashMap.remove(state.map, key) + return effect.as( + Scope.close(oentry.value.scope, effect.exitVoid), + undefined + ) + } else if (isRead) { + MutableHashMap.remove(state.map, key) + MutableHashMap.set(state.map, key, oentry.value) + } + return effect.succeed(oentry.value) +} + +/** + * Retrieves the value associated with the specified key from the cache, only if + * it contains a resolved successful value. + * + * **When to use** + * + * Use to inspect an already-completed successful scoped cache entry without + * running or awaiting the lookup effect. + * + * **Details** + * + * Returns `Option.some` for a resolved successful entry. Returns `Option.none` + * for missing, expired, failed, or still-pending entries. + * + * @see {@link get} for awaiting or starting the lookup effect + * @see {@link getOption} for awaiting an already-cached entry without starting a lookup + * + * @category combinators + * @since 4.0.0 + */ +export const getSuccess: { + (key: Key): (self: ScopedCache) => Effect.Effect> + (self: ScopedCache, key: Key): Effect.Effect> +} = dual( + 2, + (self: ScopedCache, key: Key): Effect.Effect> => + effect.uninterruptible( + core.withFiber((fiber) => + effect.map( + getImpl(self, key, fiber), + (entry) => { + const exit = entry?.deferred.effect as Exit.Exit | undefined + if (exit && effect.exitIsSuccess(exit)) { + return Option.some(exit.value) + } + return Option.none() + } + ) + ) + ) +) + +/** + * Sets a successful value for a key without running the lookup function. + * + * **When to use** + * + * Use to seed or overwrite a scoped cache entry with an already available + * successful value. + * + * **Details** + * + * This replaces and closes any existing entry scope for the key, applies the + * cache's TTL using a successful exit for the value, and may evict older + * entries if the cache capacity is exceeded. + * + * @see {@link get} for reading or computing a cached value + * @see {@link refresh} for replacing an entry by running the lookup function + * + * @category combinators + * @since 4.0.0 + */ +export const set: { + (key: Key, value: A): (self: ScopedCache) => Effect.Effect + (self: ScopedCache, key: Key, value: A): Effect.Effect +} = dual( + 3, + (self: ScopedCache, key: Key, value: A): Effect.Effect => + effect.uninterruptible( + core.withFiber((fiber) => { + if (self.state._tag === "Closed") { + return effect.interrupt + } + const oentry = MutableHashMap.get(self.state.map, key) + const state = self.state + const exit = core.exitSucceed(value) + const deferred = Deferred.makeUnsafe() + Deferred.doneUnsafe(deferred, exit) + const ttl = self.timeToLive(exit, key) + MutableHashMap.set(state.map, key, { + scope: Scope.makeUnsafe(), + deferred, + expiresAt: Duration.isFinite(ttl) + ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) + : undefined + }) + const check = checkCapacity(fiber, state.map, self.capacity) + return Option.isSome(oentry) + ? effect.flatMap(Scope.close(oentry.value.scope, effect.exitVoid), () => check) + : check + }) + ) +) + +/** + * Checks whether the cache contains an entry for the specified key. + * + * **When to use** + * + * Use to test whether an unexpired entry exists for a key without running the + * cache lookup. + * + * **Details** + * + * This does not start lookups and does not refresh access order. Expired + * entries are treated as absent and their scopes are closed while checking. If + * the cache is closed, the effect is interrupted. + * + * @see {@link getOption} for reading an existing cached entry + * @see {@link get} for running the lookup on missing or expired keys + * + * @category combinators + * @since 4.0.0 + */ +export const has: { + (key: Key): (self: ScopedCache) => Effect.Effect + (self: ScopedCache, key: Key): Effect.Effect +} = dual( + 2, + (self: ScopedCache, key: Key): Effect.Effect => + effect.uninterruptible( + core.withFiber((fiber) => effect.map(getImpl(self, key, fiber, false), Predicate.isNotUndefined)) + ) +) + +/** + * Removes the entry associated with a key and closes its entry scope. + * + * **When to use** + * + * Use to remove a single key from a scoped cache and release any resources owned + * by that entry before a later lookup computes it again. + * + * **Details** + * + * If the key is absent, this is a no-op. + * + * **Gotchas** + * + * If the cache is closed, the effect is interrupted. + * + * @see {@link refresh} for replacing a key by running a new lookup immediately + * @see {@link invalidateWhen} for invalidating only when a cached value matches a predicate + * @see {@link invalidateAll} for removing every cached entry + * + * @category combinators + * @since 4.0.0 + */ +export const invalidate: { + (key: Key): (self: ScopedCache) => Effect.Effect + (self: ScopedCache, key: Key): Effect.Effect +} = dual(2, (self: ScopedCache, key: Key): Effect.Effect => + effect.uninterruptible( + effect.suspend(() => { + if (self.state._tag === "Closed") { + return effect.interrupt + } + const oentry = MutableHashMap.get(self.state.map, key) + if (Option.isNone(oentry)) { + return effect.void + } + MutableHashMap.remove(self.state.map, key) + return Scope.close(oentry.value.scope, effect.exitVoid) + }) + )) + +/** + * Invalidates the entry associated with the specified key in the cache when the + * predicate returns true for the cached value. + * + * **When to use** + * + * Use to remove an already-cached scoped value only when the successful cached + * value satisfies a predicate. + * + * **Details** + * + * Returns `true` only when a successful cached value matches and is removed. It + * returns `false` for absent, expired, failed, or non-matching entries. + * + * **Gotchas** + * + * A matching invalidation closes the entry scope and releases its resources. + * + * @see {@link invalidate} for unconditional removal by key + * + * @category combinators + * @since 4.0.0 + */ +export const invalidateWhen: { + (key: Key, f: Predicate.Predicate): (self: ScopedCache) => Effect.Effect + (self: ScopedCache, key: Key, f: Predicate.Predicate): Effect.Effect +} = dual( + 3, + (self: ScopedCache, key: Key, f: Predicate.Predicate): Effect.Effect => + effect.uninterruptibleMask((restore) => + core.withFiber((fiber) => + effect.flatMap(getImpl(self, key, fiber, false), (entry) => { + if (entry === undefined) { + return effect.succeed(false) + } + return restore(Deferred.await(entry.deferred)).pipe( + effect.flatMap((value) => { + if (self.state._tag === "Closed") { + return effect.succeed(false) + } else if (f(value)) { + MutableHashMap.remove(self.state.map, key) + return effect.as(Scope.close(entry.scope, effect.exitVoid), true) + } + return effect.succeed(false) + }), + effect.catch_(() => effect.succeed(false)) + ) + }) + ) + ) +) + +/** + * Forces a refresh of the value associated with the specified key in the cache. + * + * **When to use** + * + * Use to recompute a scoped cache entry immediately, even when an unexpired + * value is already cached. + * + * **Details** + * + * It will always invoke the lookup function to construct a new value, + * overwriting any existing value for that key. + * + * @see {@link get} for reusing an unexpired entry before running the lookup + * @see {@link invalidate} for removing an entry without recomputing it + * + * @category combinators + * @since 4.0.0 + */ +export const refresh: { + (key: Key): (self: ScopedCache) => Effect.Effect + (self: ScopedCache, key: Key): Effect.Effect +} = dual( + 2, + (self: ScopedCache, key: Key): Effect.Effect => + effect.uninterruptibleMask(effect.fnUntraced(function*(restore) { + if (self.state._tag === "Closed") return yield* effect.interrupt + const fiber = Fiber.getCurrent()! + const scope = Scope.makeUnsafe() + const deferred = Deferred.makeUnsafe() + const entry: Entry = { + scope, + expiresAt: undefined, + deferred + } + const newEntry = !MutableHashMap.has(self.state.map, key) + if (newEntry) { + MutableHashMap.set(self.state.map, key, entry) + yield* checkCapacity(fiber, self.state.map, self.capacity) + } + const exit = yield* effect.exit(restore(Scope.provide(self.lookup(key), scope))) + Deferred.doneUnsafe(deferred, exit) + // @ts-ignore async gap + if (self.state._tag === "Closed") { + if (!newEntry) { + yield* Scope.close(scope, effect.exitVoid) + } + return yield* effect.interrupt + } + const ttl = self.timeToLive(exit, key) + entry.expiresAt = Duration.isFinite(ttl) + ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) + : undefined + if (!newEntry) { + const oentry = MutableHashMap.get(self.state.map, key) + MutableHashMap.set(self.state.map, key, entry) + if (Option.isSome(oentry)) { + yield* Scope.close(oentry.value.scope, effect.exitVoid) + } + } + return yield* exit + })) +) + +/** + * Removes every entry from the cache and closes each entry scope. + * + * **When to use** + * + * Use to clear a scoped cache and release resources owned by all cached entries. + * + * **Details** + * + * If the cache is closed, the effect is interrupted. + * + * @see {@link invalidate} for removing one cached entry + * + * @category combinators + * @since 4.0.0 + */ +export const invalidateAll = (self: ScopedCache): Effect.Effect => + core.withFiber((parent) => { + if (self.state._tag === "Closed") { + return effect.interrupt + } + return invalidateAllImpl(parent, self.state.map) + }) + +const invalidateAllImpl = ( + parent: Fiber.Fiber, + map: MutableHashMap.MutableHashMap> +): Effect.Effect => { + const fibers = Arr.empty>() + for (const [, entry] of map) { + fibers.push(effect.forkUnsafe(parent as any, Scope.close(entry.scope, effect.exitVoid), true, true)) + } + MutableHashMap.clear(map) + return effect.fiberAwaitAll(fibers) +} + +/** + * Retrieves the approximate number of entries in the cache. + * + * **When to use** + * + * Use to inspect how many entries are currently stored in the scoped cache. + * + * **Gotchas** + * + * Note that expired entries are counted until they are accessed and removed. + * The size reflects the current number of entries stored, not the number + * of valid entries. + * + * @category combinators + * @since 4.0.0 + */ +export const size = (self: ScopedCache): Effect.Effect => + effect.sync(() => self.state._tag === "Closed" ? 0 : MutableHashMap.size(self.state.map)) + +/** + * Retrieves all active keys from the cache, automatically filtering out expired entries. + * + * **When to use** + * + * Use to inspect currently cached unexpired keys without running cache lookups. + * + * **Gotchas** + * + * Expired entries are removed and their scopes are closed while filtering. + * + * @see {@link entries} for retrieving successful cached key-value pairs + * @see {@link values} for retrieving only successfully cached values + * + * @category combinators + * @since 4.0.0 + */ +export const keys = (self: ScopedCache): Effect.Effect> => + core.withFiber((fiber) => { + if (self.state._tag === "Closed") return effect.succeed([]) + const state = self.state + const now = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + const fibers = Arr.empty>() + const keys: Array = [] + for (const [key, entry] of state.map) { + if (entry.expiresAt === undefined || entry.expiresAt > now) { + keys.push(key) + } else { + MutableHashMap.remove(state.map, key) + fibers.push(effect.forkUnsafe(fiber, Scope.close(entry.scope, effect.exitVoid), true, true)) + } + } + return fibers.length === 0 ? effect.succeed(keys) : effect.as(effect.fiberAwaitAll(fibers), keys) + }) + +/** + * Retrieves all successfully cached values from the cache, excluding failed + * lookups and expired entries. + * + * **When to use** + * + * Use to inspect currently successful cached values without running cache + * lookups. + * + * **Gotchas** + * + * Expired entries are removed and their scopes are closed while filtering. + * + * @see {@link entries} for retrieving successful cached key-value pairs + * @see {@link keys} for retrieving only cached keys + * + * @category combinators + * @since 4.0.0 + */ +export const values = (self: ScopedCache): Effect.Effect> => + effect.map(entries(self), Arr.map(([, value]) => value)) + +/** + * Retrieves all key-value pairs from the cache as an array. This function + * only returns entries with successfully resolved values, filtering out any + * failed lookups or expired entries. + * + * **When to use** + * + * Use to inspect the currently successful cached key-value pairs without + * running cache lookups. + * + * **Gotchas** + * + * Expired entries are removed and their scopes are closed while filtering. + * + * @see {@link keys} for retrieving only cached keys + * @see {@link values} for retrieving only cached values + * + * @category combinators + * @since 4.0.0 + */ +export const entries = (self: ScopedCache): Effect.Effect> => + core.withFiber((fiber) => { + if (self.state._tag === "Closed") return effect.succeed([]) + const state = self.state + const now = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + const fibers = Arr.empty>() + const arr: Array<[Key, A]> = [] + for (const [key, entry] of state.map) { + if (entry.expiresAt === undefined || entry.expiresAt > now) { + const exit = entry.deferred.effect + if (core.isExit(exit) && !effect.exitIsFailure(exit)) { + arr.push([key, exit.value as A]) + } + } else { + MutableHashMap.remove(state.map, key) + fibers.push(effect.forkUnsafe(fiber, Scope.close(entry.scope, effect.exitVoid), true, true)) + } + } + return fibers.length === 0 + ? effect.succeed(arr) + : effect.as(effect.fiberAwaitAll(fibers), arr) + }) diff --git a/.repos/effect/packages/effect/src/ScopedRef.ts b/.repos/effect/packages/effect/src/ScopedRef.ts new file mode 100644 index 000000000..8cbb2609e --- /dev/null +++ b/.repos/effect/packages/effect/src/ScopedRef.ts @@ -0,0 +1,194 @@ +/** + * Stores a current value together with the scope that owns it. + * + * A `ScopedRef` is useful for resource-backed values such as clients, + * connections, subscriptions, or handles. Replacing the value acquires the + * replacement in a new scope and releases the resources owned by the previous + * value. Reads can be effectful or synchronous, and updates are synchronized so + * only one replacement happens at a time. + * + * @since 2.0.0 + */ +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import { dual, type LazyArg } from "./Function.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import type { Pipeable } from "./Pipeable.ts" +import * as Scope from "./Scope.ts" +import * as Synchronized from "./SynchronizedRef.ts" + +const TypeId = "~effect/ScopedRef" + +/** + * A `ScopedRef` is a reference whose value is associated with resources, + * which must be released properly. You can both get the current value of any + * `ScopedRef`, as well as set it to a new value (which may require new + * resources). The reference itself takes care of properly releasing resources + * for the old value whenever a new value is obtained. + * + * **When to use** + * + * Use when an application needs to keep a current resource-backed value and + * later replace it with another acquired value while ensuring the previous + * value is released. + * + * @category models + * @since 2.0.0 + */ +export interface ScopedRef extends Pipeable { + readonly [TypeId]: typeof TypeId + readonly backing: Synchronized.SynchronizedRef +} + +const Proto = { + ...PipeInspectableProto, + [TypeId]: TypeId, + toJSON(this: ScopedRef) { + return { + _id: "ScopedRef", + value: this.backing.backing.ref.current[1] + } + } +} + +const makeUnsafe = ( + scope: Scope.Closeable, + value: A +): ScopedRef => { + const self = Object.create(Proto) + self.backing = Synchronized.makeUnsafe([scope, value] as const) + return self +} + +/** + * Creates a new `ScopedRef` from an effect that acquires the initial value. + * + * **When to use** + * + * Use when creating a `ScopedRef` whose initial value requires acquiring + * resources that must be released. + * + * @see {@link make} for creating a `ScopedRef` from a value that does not require resource acquisition + * @category constructors + * @since 2.0.0 + */ +export const fromAcquire: ( + acquire: Effect.Effect +) => Effect.Effect, E, Scope.Scope | R> = Effect.fnUntraced(function*( + acquire: Effect.Effect +) { + const scope = Scope.makeUnsafe() + const value = yield* acquire.pipe( + Scope.provide(scope), + Effect.tapCause((cause) => Scope.close(scope, Exit.failCause(cause))) + ) + const self = makeUnsafe(scope, value) + yield* Effect.addFinalizer((exit) => Scope.close(self.backing.backing.ref.current[0], exit)) + return self +}, Effect.uninterruptible) + +/** + * Retrieves the current value of the scoped reference synchronously. + * + * **When to use** + * + * Use when you need immediate synchronous access to the current `ScopedRef` + * value and can guarantee that reading outside the `Effect` API is safe. + * + * @see {@link get} for Effect-wrapped access in Effect programs + * + * @category getters + * @since 4.0.0 + */ +export const getUnsafe = (self: ScopedRef): A => self.backing.backing.ref.current[1] + +/** + * Retrieves the current value of the scoped reference effectfully. + * + * **When to use** + * + * Use to read the value currently stored in a `ScopedRef` inside an `Effect` + * workflow. + * + * @see {@link getUnsafe} for reading the current value synchronously when an unsafe read is acceptable + * + * @category getters + * @since 2.0.0 + */ +export const get = (self: ScopedRef): Effect.Effect => Effect.sync(() => getUnsafe(self)) + +/** + * Creates a new `ScopedRef` from the specified value. + * + * **When to use** + * + * Use to create a `ScopedRef` when the initial value is already available or + * can be produced without acquiring resources. + * + * **Details** + * + * The `evaluate` function runs when the returned effect runs. The returned + * effect requires a `Scope`, and the reference closes the currently stored + * value's scope when that outer scope closes. + * + * **Gotchas** + * + * Do not use `make` for an initial value whose creation acquires resources; use + * `fromAcquire` so acquisition and finalization are tracked. + * + * @see {@link fromAcquire} for creating a `ScopedRef` from an effect that acquires the initial value + * @see {@link set} for replacing the current value with a newly acquired value + * + * @category constructors + * @since 2.0.0 + */ +export const make = (evaluate: LazyArg): Effect.Effect, never, Scope.Scope> => + Effect.suspend(() => { + const scope = Scope.makeUnsafe() + const value = evaluate() + const self = makeUnsafe(scope, value) + return Effect.as(Effect.addFinalizer((exit) => Scope.close(self.backing.backing.ref.current[0], exit)), self) + }) + +/** + * Sets the value of this reference to a newly acquired scoped value, releasing + * any resources associated with the old value. + * + * **When to use** + * + * Use to replace the current value of an existing `ScopedRef` with a newly + * acquired scoped value while releasing resources for the previous value. + * + * **Details** + * + * This method will not return until either the reference is successfully + * changed to the new value, with old resources released, or until the attempt + * to acquire a new value fails. + * + * @category mutations + * @since 2.0.0 + */ +export const set: { + (acquire: Effect.Effect): (self: ScopedRef) => Effect.Effect> + (self: ScopedRef, acquire: Effect.Effect): Effect.Effect> +} = dual( + 2, + Effect.fnUntraced( + function*( + self: ScopedRef, + acquire: Effect.Effect + ) { + const scope = Scope.makeUnsafe() + const value = yield* acquire.pipe( + Scope.provide(scope), + Effect.tapCause((cause) => Scope.close(scope, Exit.failCause(cause))) + ) + yield* Scope.close(self.backing.backing.ref.current[0], Exit.void).pipe( + Effect.tapCause((cause) => Scope.close(scope, Exit.failCause(cause))) + ) + self.backing.backing.ref.current = [scope, value] + }, + Effect.uninterruptible, + (effect, self) => self.backing.semaphore.withPermit(effect) + ) +) diff --git a/.repos/effect/packages/effect/src/Semaphore.ts b/.repos/effect/packages/effect/src/Semaphore.ts new file mode 100644 index 000000000..217e80920 --- /dev/null +++ b/.repos/effect/packages/effect/src/Semaphore.ts @@ -0,0 +1,574 @@ +/** + * Limits how many effects can use a shared resource at the same time. + * + * A `Semaphore` owns a number of permits. Work can run only after acquiring the + * permits it needs, and those permits are returned when the work finishes. This + * module includes constructors, automatic wrappers that acquire and release + * permits around an effect, manual permit operations, a non-waiting variant for + * work that should only run immediately, and resizing support for an existing + * semaphore. + * + * @since 4.0.0 + */ +import type * as Effect from "./Effect.ts" +import type { Fiber } from "./Fiber.ts" +import { dual } from "./Function.ts" +import * as core from "./internal/core.ts" +import * as internal from "./internal/effect.ts" +import type * as Option from "./Option.ts" + +/** + * A counting semaphore that coordinates concurrent access with permits. + * + * **When to use** + * + * Use to coordinate concurrent effects that need bounded access to a shared + * resource. + * + * **Details** + * + * Effects can acquire permits, wait until enough permits are available, + * release permits, or run with permits that are automatically released when + * the effect exits. + * + * **Example** (Controlling concurrent access) + * + * ```ts import.meta.vitest + * import { Effect, Semaphore } from "effect" + * + * // Create and use a semaphore for controlling concurrent access + * const program = Effect.gen(function*() { + * const semaphore = yield* Semaphore.make(2) + * + * return yield* semaphore.withPermits(1)( + * Effect.succeed("Resource accessed") + * ) + * }) + * + * await Effect.runPromise(program) // => "Resource accessed" + * ``` + * + * @see {@link make} for creating a semaphore inside Effect code + * @see {@link makeUnsafe} for creating a semaphore synchronously + * + * @category models + * @since 4.0.0 + */ +export interface Semaphore { + /** + * Adjusts the number of permits available in the semaphore. + * + * **When to use** + * + * Use to change the total permit count of an existing semaphore. + */ + resize(this: Semaphore, permits: number): Effect.Effect + + /** + * Runs an effect with the given number of permits and releases the permits + * when the effect completes. + * + * **When to use** + * + * Use to run an effect while holding a specified number of semaphore permits. + * + * **Details** + * + * This function acquires the specified number of permits before executing + * the provided effect. Once the effect finishes, the permits are released. + * If insufficient permits are available, the function will wait until they + * are released by other tasks. + */ + withPermits(this: Semaphore, permits: number): (self: Effect.Effect) => Effect.Effect + + /** + * Runs an effect with the given number of permits and releases the permits + * when the effect completes. + * + * **When to use** + * + * Use to run an effect while holding exactly one semaphore permit. + * + * **Details** + * + * This function acquires the specified number of permits before executing + * the provided effect. Once the effect finishes, the permits are released. + * If insufficient permits are available, the function will wait until they + * are released by other tasks. + */ + withPermit(self: Effect.Effect): Effect.Effect + + /** + * Runs an effect only if the specified number of permits are immediately + * available. + * + * **When to use** + * + * Use when guarded work should run only if the requested permits are + * immediately available. + * + * **Details** + * + * This function attempts to acquire the specified number of permits. If they + * are available, it runs the effect and releases the permits after the effect + * completes. If permits are not available, the effect does not execute, and + * the result is `Option.none`. + */ + withPermitsIfAvailable( + this: Semaphore, + permits: number + ): (self: Effect.Effect) => Effect.Effect, E, R> + + /** + * Acquires the specified number of permits and returns the acquired permit + * count, suspending the task if they are not yet available. Pending `take` + * calls are scanned in registration order, but a request is served only when + * enough permits are available, so a smaller later request may overtake a + * larger earlier request. + * + * **When to use** + * + * Use to manually acquire permits for lower-level coordination protocols. + */ + take(this: Semaphore, permits: number): Effect.Effect + + /** + * Acquires the specified number of permits only if they are immediately + * available. + * + * **When to use** + * + * Use to manually acquire permits without waiting, paired with `release`. + */ + takeIfAvailable(this: Semaphore, permits: number): Effect.Effect + + /** + * Releases the specified number of permits and returns the resulting + * available permits. + * + * **When to use** + * + * Use to manually return permits acquired by a lower-level coordination + * protocol. + */ + release(this: Semaphore, permits: number): Effect.Effect + + /** + * Releases all permits held by this semaphore and returns the resulting available permits. + * + * **When to use** + * + * Use to return every currently taken permit to the semaphore at once. + */ + readonly releaseAll: Effect.Effect +} + +/** + * Creates a `Semaphore` synchronously with the specified total + * number of permits. + * + * **When to use** + * + * Use to construct a semaphore synchronously when an immediate value is + * required outside an Effect workflow. + * + * **Example** (Creating an unsafe semaphore) + * + * ```ts import.meta.vitest + * import { Effect, Semaphore } from "effect" + * + * const semaphore = Semaphore.makeUnsafe(3) + * + * const task = (id: number) => + * semaphore.withPermits(1)( + * Effect.gen(function*() { + * yield* Effect.yieldNow + * return id + * }) + * ) + * + * // Only 3 tasks can run concurrently + * const program = Effect.all([ + * task(1), + * task(2), + * task(3), + * task(4), + * task(5) + * ], { concurrency: "unbounded" }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeUnsafe = (permits: number): Semaphore => new SemaphoreImpl(permits) + +const waitForPermits = ( + self: SemaphoreImpl, + n: number, + effect: Effect.Effect +): Effect.Effect => + internal.callback((resume) => { + if (self.free >= n) return resume(effect) + const observer = () => { + if (self.free < n) return + self.waiters.delete(observer) + resume(effect) + } + self.waiters.add(observer) + return internal.sync(() => { + self.waiters.delete(observer) + }) + }) + +class SemaphoreImpl implements Semaphore { + public waiters = new Set<() => void>() + public taken = 0 + public permits: number + + constructor(permits: number) { + this.permits = permits + } + + get free() { + return this.permits - this.taken + } + + take(n: number): Effect.Effect { + const take: Effect.Effect = internal.suspend(() => { + if (this.free < n) { + return waitForPermits(this, n, take) + } + this.taken += n + return internal.succeed(n) + }) + return take + } + + takeIfAvailable(n: number): Effect.Effect { + return internal.suspend(() => { + if (this.free < n) return internal.succeed(false) + this.taken += n + return internal.succeed(true) + }) + } + + releaseUnsafe(fiber: Fiber, n: number): number { + this.taken -= n + if (this.waiters.size > 0) { + fiber.currentDispatcher.scheduleTask(() => { + for (const observer of this.waiters) { + if (this.free <= 0) break + observer() + } + }, 0) + } + return this.free + } + + resize(permits: number) { + return core.withFiber((fiber) => { + this.permits = permits + if (this.free < 0) return internal.void + this.releaseUnsafe(fiber, 0) + return internal.void + }) + } + + release(n: number): Effect.Effect { + return core.withFiber((fiber) => internal.succeed(this.releaseUnsafe(fiber, n))) + } + + get releaseAll(): Effect.Effect { + return core.withFiber((fiber) => internal.succeed(this.releaseUnsafe(fiber, this.taken))) + } + + withPermits(n: number) { + return (self: Effect.Effect) => + internal.uninterruptibleMask((restore) => { + const acquire: Effect.Effect = internal.suspend(() => { + if (this.free < n) { + const wait = waitForPermits(this, n, internal.void) + return internal.flatMap(restore(wait), () => acquire) + } + this.taken += n + return internal.onExitPrimitive( + restore(self), + () => { + this.releaseUnsafe(internal.getCurrentFiber()!, n) + return undefined + }, + true + ) + }) + return acquire + }) + } + + readonly withPermit = this.withPermits(1) + + withPermitsIfAvailable(n: number) { + return (self: Effect.Effect) => + internal.uninterruptibleMask((restore) => { + if (this.free < n) return internal.succeedNone + this.taken += n + return internal.onExitPrimitive(restore(internal.asSome(self)), () => { + this.releaseUnsafe(internal.getCurrentFiber()!, n) + return undefined + }, true) + }) + } +} + +/** + * Creates a `Semaphore` initialized with the specified total number of permits. + * + * **When to use** + * + * Use to create a semaphore inside Effect code for bounding concurrency with + * automatic or manual permit management. + * + * **Example** (Creating a semaphore) + * + * ```ts import.meta.vitest + * import { Effect, Semaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* Semaphore.make(2) + * + * const task = (id: number) => + * semaphore.withPermits(1)( + * Effect.gen(function*() { + * yield* Effect.yieldNow + * return id + * }) + * ) + * + * // Run 4 tasks, but only 2 can run concurrently + * return yield* Effect.all([task(1), task(2), task(3), task(4)]) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const make = (permits: number): Effect.Effect => internal.sync(() => new SemaphoreImpl(permits)) + +/** + * Sets the total number of permits managed by the semaphore. + * + * **When to use** + * + * Use to change the concurrency limit of an existing semaphore while keeping + * current acquisitions in place. + * + * **Details** + * + * Existing acquisitions remain taken after resizing. If the new total is less + * than the currently taken permit count, new acquisitions wait until enough + * permits are released. + * + * @see {@link make} for creating a semaphore with an initial permit count + * @see {@link release} for returning permits without changing semaphore capacity + * + * @category combinators + * @since 4.0.0 + */ +export const resize: { + (permits: number): (self: Semaphore) => Effect.Effect + (self: Semaphore, permits: number): Effect.Effect +} = dual(2, (self: Semaphore, permits: number) => self.resize(permits)) + +/** + * Runs an effect with the given number of permits and releases the permits when + * the effect completes. + * + * **When to use** + * + * Use to run an effect while holding a specified number of semaphore permits + * for the duration of that effect. + * + * **Details** + * + * The effect waits until enough permits are available. Acquired permits are + * released when the wrapped effect exits. + * + * @see {@link withPermit} for acquiring exactly one permit + * @see {@link withPermitsIfAvailable} for running only when permits are immediately available + * @see {@link take} for manually acquiring permits + * @see {@link release} for manually returning permits + * + * @category combinators + * @since 4.0.0 + */ +export const withPermits: { + (self: Semaphore, permits: number): (effect: Effect.Effect) => Effect.Effect + (self: Semaphore, permits: number, effect: Effect.Effect): Effect.Effect +} = ((self: Semaphore, permits: number, effect?: Effect.Effect) => { + const withPermits = self.withPermits(permits) + return effect ? withPermits(effect) : withPermits +}) as any + +/** + * Runs an effect with a single permit and releases the permit when the effect + * completes. + * + * **When to use** + * + * Use to guard an effect with exactly one semaphore permit while automatically + * releasing that permit when the effect exits. + * + * @see {@link withPermits} for acquiring more than one permit + * @see {@link withPermitsIfAvailable} for running only when permits are immediately available + * @see {@link take} for manually acquiring permits + * @see {@link release} for manually returning permits + * + * @category combinators + * @since 4.0.0 + */ +export const withPermit: { + (self: Semaphore): (effect: Effect.Effect) => Effect.Effect + (self: Semaphore, effect: Effect.Effect): Effect.Effect +} = ((self: Semaphore, effect?: Effect.Effect) => { + if (!effect) return self.withPermit + return self.withPermit(effect) +}) as any + +/** + * Runs an effect only if the specified number of permits are immediately + * available. + * + * **When to use** + * + * Use when guarded work should run only if the requested permits are + * immediately available. + * + * **Details** + * + * When the permits are unavailable, the effect is not run and the result is + * `Option.none`. When permits are available, the effect is run, its result is + * wrapped in `Option.some`, and the acquired permits are released when the + * effect exits. + * + * @see {@link withPermits} for the variant that waits until permits are available + * + * @category combinators + * @since 4.0.0 + */ +export const withPermitsIfAvailable: { + (self: Semaphore, permits: number): (effect: Effect.Effect) => Effect.Effect, E, R> + ( + self: Semaphore, + permits: number, + effect: Effect.Effect + ): Effect.Effect, E, R> +} = ((self: Semaphore, permits: number, effect?: Effect.Effect) => { + const withPermits = self.withPermitsIfAvailable(permits) + return effect ? withPermits(effect) : withPermits +}) as any + +/** + * Acquires the specified number of permits and returns the acquired permit + * count. + * + * **When to use** + * + * Use when you need manual permit acquisition for a lower-level protocol with + * explicit acquisition and release control. + * + * **Details** + * + * The effect waits until enough permits are available. + * + * @see {@link withPermit} for automatically acquiring and releasing one permit around an effect + * @see {@link withPermits} for automatically acquiring and releasing multiple permits around an effect + * @see {@link takeIfAvailable} for manually acquiring permits without waiting + * @see {@link release} for returning manually acquired permits + * + * @category combinators + * @since 4.0.0 + */ +export const take: { + (permits: number): (self: Semaphore) => Effect.Effect + (self: Semaphore, permits: number): Effect.Effect +} = dual(2, (self: Semaphore, permits: number) => self.take(permits)) + +/** + * Acquires the specified number of permits only if they are immediately + * available. + * + * **When to use** + * + * Use when you need fail-fast manual permit acquisition for a lower-level + * protocol with explicit acquisition and release control. + * + * **Details** + * + * If enough permits are available, they are acquired and the effect returns + * `true`. Otherwise, the effect returns `false` immediately without acquiring + * any permits. + * + * @see {@link take} for the variant that waits until permits are available + * @see {@link release} for returning manually acquired permits + * @see {@link withPermitsIfAvailable} for automatic acquisition and release around an effect + * + * @category combinators + * @since 4.0.0 + */ +export const takeIfAvailable: { + (permits: number): (self: Semaphore) => Effect.Effect + (self: Semaphore, permits: number): Effect.Effect +} = dual(2, (self: Semaphore, permits: number) => self.takeIfAvailable(permits)) + +/** + * Releases the specified number of permits and returns the resulting available + * permits. + * + * **When to use** + * + * Use when you need to return permits acquired with `take` in a lower-level + * permit protocol with explicit release control. + * + * **Details** + * + * Running the effect releases the requested permits, wakes waiting acquirers + * when permits become available, and returns the current available permit + * count. + * + * **Gotchas** + * + * Manual `take` / `release` usage must keep permit counts balanced. Prefer + * `withPermit` or `withPermits` when the acquisition can be scoped to one + * effect. + * + * @see {@link take} for manually acquiring permits + * @see {@link releaseAll} for returning every currently taken permit + * @see {@link withPermits} for automatic acquire and release around an effect + * + * @category combinators + * @since 4.0.0 + */ +export const release: { + (permits: number): (self: Semaphore) => Effect.Effect + (self: Semaphore, permits: number): Effect.Effect +} = dual(2, (self: Semaphore, permits: number) => self.release(permits)) + +/** + * Releases all permits held by this semaphore and returns the resulting + * available permits. + * + * **When to use** + * + * Use to return every currently taken permit to a semaphore at once, typically + * during cleanup of manual `take` / `release` protocols. + * + * @see {@link release} for releasing a known permit count + * @see {@link withPermits} for automatic acquire and release around an effect + * + * @category combinators + * @since 4.0.0 + */ +export const releaseAll = (self: Semaphore): Effect.Effect => self.releaseAll diff --git a/.repos/effect/packages/effect/src/Sink.ts b/.repos/effect/packages/effect/src/Sink.ts new file mode 100644 index 000000000..2d5b7ddd8 --- /dev/null +++ b/.repos/effect/packages/effect/src/Sink.ts @@ -0,0 +1,2189 @@ +/** + * Consumes values from a `Stream` and produces one final result. + * + * A `Sink` may read no input, a fixed amount of input, or keep reading until a + * condition is met. If it reads more than it needs, it can return leftovers so + * the stream can continue from those values. Sinks are used to collect, fold, + * search, count, or otherwise reduce streamed input, and they can be composed + * when a stream needs more than one consuming step. + * + * @since 2.0.0 + */ +import type { NonEmptyReadonlyArray } from "./Array.ts" +import * as Arr from "./Array.ts" +import * as Cause from "./Cause.ts" +import * as Channel from "./Channel.ts" +import * as Clock from "./Clock.ts" +import type * as Context from "./Context.ts" +import * as Duration from "./Duration.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import type * as Filter from "./Filter.ts" +import type { LazyArg } from "./Function.ts" +import { constant, constFalse, constTrue, constVoid, dual, identity, pipe } from "./Function.ts" +import * as internalStream from "./internal/stream.ts" +import * as Option from "./Option.ts" +import { type Pipeable, pipeArguments } from "./Pipeable.ts" +import type { Predicate, Refinement } from "./Predicate.ts" +import { hasProperty } from "./Predicate.ts" +import * as PubSub from "./PubSub.ts" +import * as Pull from "./Pull.ts" +import * as Queue from "./Queue.ts" +import * as Result from "./Result.ts" +import * as Scope from "./Scope.ts" +import type { Stream } from "./Stream.ts" +import type * as Types from "./Types.ts" +import type * as Unify from "./Unify.ts" + +const TypeId = "~effect/Sink" + +/** + * A `Sink` is used to consume elements produced by a `Stream`. + * You can think of a sink as a function that will consume a variable amount of + * `In` elements (could be 0, 1, or many), might fail with an error of type `E`, + * and will eventually yield a value of type `A` together with a remainder of + * type `L` (i.e. any leftovers). + * + * **Example** (Running a sink with a stream) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * // Create a simple sink that always succeeds with a value + * const sink: Sink.Sink = Sink.succeed(42) + * + * // Use the sink to consume a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromise(Stream.run(stream, sink)) // => 42 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Sink + extends Sink.Variance, Pipeable +{ + readonly transform: ( + upstream: Pull.Pull, never, void>, + scope: Scope.Scope + ) => Effect.Effect, E, R> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: SinkUnify + [Unify.ignoreSymbol]?: SinkUnifyIgnore +} + +/** + * Tuple returned when a `Sink` finishes. + * + * **Details** + * + * The first element is the sink result. The optional second element contains a + * non-empty array of leftover input that was pulled but not consumed. + * + * @category models + * @since 4.0.0 + */ +export type End = readonly [value: A, leftover?: NonEmptyReadonlyArray | undefined] + +const endVoid = Effect.succeed([void 0] as End) + +/** + * Type-level unification support for `Sink` values. + * + * **Details** + * + * This preserves the result, input, leftover, error, and service type + * parameters when Effect's `Unify` machinery normalizes generic values that + * include sinks. Users normally do not need to reference this interface + * directly. + * + * @category models + * @since 2.0.0 + */ +export interface SinkUnify extends Effect.EffectUnify { + Sink?: () => A[Unify.typeSymbol] extends + | Sink< + infer A, + infer In, + infer L, + infer E, + infer R + > + | infer _ ? Sink + : never +} + +/** + * Marker used by Effect's `Unify` machinery for `Sink` values. + * + * **Details** + * + * It prevents the inherited `Effect` unifier from being selected when + * sink-specific unification should preserve the `Sink` type parameters. Users + * normally do not need to reference this interface directly. + * + * @category models + * @since 2.0.0 + */ +export interface SinkUnifyIgnore { + Effect?: true +} + +/** + * Namespace containing types and interfaces for Sink variance and type relationships. + * + * @since 2.0.0 + */ +export declare namespace Sink { + /** + * Type-level variance marker for `Sink`. + * + * **Details** + * + * The result `A`, leftovers `L`, errors `E`, and services `R` are + * covariant. The input type `In` is contravariant because values flow into + * the sink. + * + * @category models + * @since 2.0.0 + */ + export interface Variance { + readonly [TypeId]: VarianceStruct + } + /** + * Structural encoding used by `Sink.Variance` to record each `Sink` type + * parameter's variance. + * + * **Details** + * + * `_A`, `_L`, `_E`, and `_R` are covariant markers. `_In` is a + * contravariant marker. + * + * @category models + * @since 2.0.0 + */ + export interface VarianceStruct { + _A: Types.Covariant + _In: Types.Contravariant + _L: Types.Covariant + _E: Types.Covariant + _R: Types.Covariant + } +} + +const sinkVariance = { + _A: identity, + _In: identity, + _L: identity, + _E: identity, + _R: identity +} + +const SinkProto = { + [TypeId]: sinkVariance, + pipe() { + return pipeArguments(this, arguments) + } +} + +/** + * Checks whether a value is a Sink. + * + * **Example** (Checking for a sink) + * + * ```ts import.meta.vitest + * import { Sink } from "effect" + * + * const sink = Sink.never + * const notStream = { data: [1, 2, 3] } + * + * Sink.isSink(sink) // => true + * Sink.isSink(notStream) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isSink = (u: unknown): u is Sink => hasProperty(u, TypeId) + +/** + * Creates a sink from a `Channel`. + * + * **When to use** + * + * Use to create a `Sink` from a `Channel` that processes non-empty arrays of + * input values. + * + * **Example** (Using channel completion as the sink result) + * + * ```ts import.meta.vitest + * import { Channel, Effect, Sink, Stream } from "effect" + * + * const channel = Channel.identity], never, void>().pipe( + * Channel.drain, + * Channel.mapDone(() => ["consumed"] as const) + * ) + * const sink = Sink.fromChannel(channel) + * + * await Effect.runPromise(Stream.run(Stream.make(1, 2, 3), sink)) // => "consumed" + * ``` + * + * @see {@link toChannel} for converting a `Sink` back to a `Channel` + * @category constructors + * @since 2.0.0 + */ +export const fromChannel = ( + channel: Channel.Channel< + never, + E, + End, + NonEmptyReadonlyArray, + never, + void, + R + > +): Sink => + fromTransform((upstream, scope) => + Channel.toTransform(channel)(upstream, scope).pipe( + Effect.flatMap(Effect.forever({ disableYield: true })), + Pull.catchDone(Effect.succeed) + ) as Effect.Effect, E, R> + ) + +/** + * Creates a sink that writes its input to a Web `WritableStream`. + * + * **Example** (Collecting values in a Web stream) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * const written: Array = [] + * const sink = Sink.fromWritableStream({ + * evaluate: () => new WritableStream({ + * write(value) { + * written.push(value) + * } + * }), + * onError: (cause) => new Error(String(cause)) + * }) + * + * await Effect.runPromise(Stream.run(Stream.make(1, 2, 3), sink)) + * written // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromWritableStream = (options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly closeOnDone?: boolean | undefined +}): Sink => + fromChannel( + Channel.mapDone( + Channel.fromWritableStream(options), + (_) => [_] + ) + ) + +/** + * Creates a `Sink` from a low-level transform function. + * + * **Details** + * + * The transform receives the upstream pull of non-empty input arrays and the + * active scope, and returns an effect that completes with the sink's `End` + * value. + * + * @category constructors + * @since 4.0.0 + */ +export const fromTransform = ( + transform: ( + upstream: Pull.Pull, never, void>, + scope: Scope.Scope + ) => Effect.Effect, E, R> +): Sink => { + const self = Object.create(SinkProto) + self.transform = transform + return self +} + +/** + * Creates a `Channel` from a Sink. + * + * **Example** (Running a sink as a channel) + * + * ```ts import.meta.vitest + * import { Channel, Effect, Sink, Stream } from "effect" + * + * const channel = Stream.toChannel(Stream.make(1, 2, 3)).pipe( + * Channel.pipeTo(Sink.toChannel(Sink.sum)) + * ) + * + * await Effect.runPromise(Channel.runDone(channel)) // => [6] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const toChannel = ( + self: Sink +): Channel.Channel, NonEmptyReadonlyArray, never, void, R> => + Channel.fromTransform((upstream, scope) => + Effect.succeed(Effect.flatMap( + self.transform(upstream, scope), + Cause.done + )) + ) + +/** + * Creates a pipe-style constructor for sinks over input type `In`. + * + * **Details** + * + * The returned function exposes the sink input as a `Stream`, applies the + * provided pipeline, and uses the final effect's success value as the sink + * result. + * + * @category constructors + * @since 4.0.0 + */ +export const make = (): make.Constructor => (...fns: []) => + fromTransform((upstream, scope) => + pipe( + internalStream.fromChannel(Channel.fromPull(Effect.succeed(upstream))), + ...fns as any as [() => Effect.Effect], + Effect.flatMap((a) => Cause.done>([a])), + Scope.provide(scope) + ) + ) + +/** + * Companion namespace containing overload types for the pipe-style sink + * constructor returned by `Sink.make`. + * + * @since 4.0.0 + */ +export declare namespace make { + /** + * Overloaded function type returned by `Sink.make`. + * + * **Details** + * + * The first pipeline function receives the sink input as a `Stream`. The + * final pipeline step must return an `Effect`, whose success value becomes + * the sink result. + * + * @category models + * @since 4.0.0 + */ + export interface Constructor { + (ab: (_: Stream) => Effect.Effect): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => Effect.Effect + ): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => Effect.Effect + ): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => D, + df: (_: D) => Effect.Effect + ): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => D, + df: (_: D) => F, + fg: (_: F) => Effect.Effect + ): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => D, + df: (_: D) => F, + fg: (_: F) => G, + gh: (_: G) => Effect.Effect + ): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => D, + df: (_: D) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => Effect.Effect + ): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => D, + df: (_: D) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => Effect.Effect + ): Sink> + ( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => D, + df: (_: D) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => Effect.Effect + ): Sink> + < + E, + R, + B = never, + C = never, + D = never, + F = never, + G = never, + H = never, + I = never, + J = never, + K = never, + L = never + >( + ab: (_: Stream) => B, + bc: (_: B) => C, + cd: (_: C) => D, + df: (_: D) => F, + fg: (_: F) => G, + gh: (_: G) => H, + hi: (_: H) => I, + ij: (_: I) => J, + jk: (_: J) => K, + kl: (_: K) => Effect.Effect + ): Sink> + } +} + +/** + * Creates a sink that ignores upstream input and completes from an effect that + * already returns an `End`. + * + * **When to use** + * + * Use when you need to create a sink from an effect that returns both the sink + * result value and optional leftovers. + * + * @category constructors + * @since 4.0.0 + */ +export const fromEffectEnd = ( + effect: Effect.Effect, E, R> +): Sink => fromTransform(() => effect) + +/** + * Creates a sink that ignores upstream input and completes with the success + * value of the provided effect. + * + * **Details** + * + * If the effect fails, the sink fails with the same error. + * + * @category constructors + * @since 2.0.0 + */ +export const fromEffect = ( + effect: Effect.Effect +): Sink => fromEffectEnd(Effect.map(effect, (a) => [a])) + +/** + * Creates a sink that offers every consumed input element to a queue. + * + * **Details** + * + * When the upstream stream ends, the sink ends the queue and completes with + * `void`. + * + * @category constructors + * @since 2.0.0 + */ +export const fromQueue = ( + queue: Queue.Queue +): Sink => + fromTransform((upstream) => + upstream.pipe( + Effect.flatMap((arr) => Queue.offerAll(queue, arr)), + Effect.forever({ disableYield: true }), + Pull.catchDone((_) => { + Queue.endUnsafe(queue) + return endVoid + }) + ) + ) + +/** + * Creates a sink that publishes every consumed input element to a `PubSub`. + * + * **Details** + * + * The sink completes with `void` when the upstream stream ends. + * + * @category constructors + * @since 2.0.0 + */ +export const fromPubSub = ( + pubsub: PubSub.PubSub +): Sink => forEachArray((arr) => PubSub.publishAll(pubsub, arr)) + +/** + * A sink that immediately ends with the specified value. + * + * **Example** (Succeeding with a value) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * // Create a sink that always yields the same value + * const sink = Sink.succeed(42) + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromise(Stream.run(stream, sink)) // => 42 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const succeed = (a: A, leftovers?: NonEmptyReadonlyArray | undefined): Sink => + fromEffectEnd(Effect.succeed([a, leftovers])) + +/** + * A sink that immediately ends with the specified lazily evaluated value. + * + * @category constructors + * @since 2.0.0 + */ +export const sync = (a: LazyArg): Sink => fromEffect(Effect.sync(a)) + +/** + * A sink that is created from a lazily evaluated sink. + * + * @category constructors + * @since 2.0.0 + */ +export const suspend = (evaluate: LazyArg>): Sink => + fromTransform((upstream, scope) => evaluate().transform(upstream, scope)) + +/** + * A sink that always fails with the specified error. + * + * **Example** (Failing with an error) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Sink, Stream } from "effect" + * + * // Create a sink that always fails + * const sink = Sink.fail("Sink failed") + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.fail("Sink failed") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fail = (e: E): Sink => fromEffectEnd(Effect.fail(e)) + +/** + * A sink that always fails with the specified lazily evaluated error. + * + * **Example** (Failing with a lazy error) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Sink, Stream } from "effect" + * + * // Create a sink that fails with a lazy error + * const sink = Sink.failSync(() => "Lazy error") + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.fail("Lazy error") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failSync = (evaluate: LazyArg): Sink => + fromEffectEnd(Effect.failSync(evaluate)) + +/** + * Creates a sink halting with a specified `Cause`. + * + * **Example** (Failing with a cause) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Sink, Stream } from "effect" + * + * // Create a sink that fails with a specific cause + * const sink = Sink.failCause(Cause.fail("Custom cause")) + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.fail("Custom cause") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCause = (cause: Cause.Cause): Sink => + fromEffectEnd(Effect.failCause(cause)) + +/** + * Creates a sink halting with a specified lazily evaluated `Cause`. + * + * **Example** (Failing with a lazy cause) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Sink, Stream } from "effect" + * + * // Create a sink that fails with a lazy cause + * const sink = Sink.failCauseSync(() => Cause.fail("Lazy cause")) + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.fail("Lazy cause") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCauseSync = (evaluate: LazyArg>): Sink => + fromEffectEnd(Effect.failCauseSync(evaluate)) + +/** + * Creates a sink halting with a specified defect. + * + * **Example** (Dying with a defect) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Sink, Stream } from "effect" + * + * // Create a sink that dies with a defect + * const sink = Sink.die("Defect error") + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.die("Defect error") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const die = (defect: unknown): Sink => fromEffectEnd(Effect.die(defect)) + +/** + * A sink that never completes. + * + * @category constructors + * @since 2.0.0 + */ +export const never: Sink = fromEffectEnd(Effect.never) + +/** + * Drops leftovers produced by a sink. + * + * **Details** + * + * The sink result is preserved, but any leftover elements are discarded + * instead of being returned to downstream sink composition. This does not + * continue pulling additional elements from the upstream stream. + * + * @category filtering + * @since 2.0.0 + */ +export const ignoreLeftover = (self: Sink): Sink => + mapEnd(self, ([a]) => [a]) + +/** + * Consumes and ignores all stream inputs. + * + * **When to use** + * + * Use to consume all upstream input and complete with void when the input + * values and any aggregate result are not needed. + * + * @see {@link count} for consuming all input while returning the number of elements + * @see {@link forEach} for consuming all input while running an effect for each element + * + * @category constructors + * @since 2.0.0 + */ +export const drain: Sink = fromTransform((upstream) => + Pull.catchDone( + Effect.forever(upstream, { disableYield: true }), + () => endVoid + ) +) + +/** + * A sink that folds its inputs with the provided function, termination + * predicate and initial state. + * + * **When to use** + * + * Use to accumulate stream input element by element with an effectful step and + * stop based on the accumulated state. + * + * **Details** + * + * The initial state is evaluated lazily. Each input element is folded with the + * effectful function, and the sink continues while `contFn` returns `true`. If + * the sink stops in the middle of a pulled array, the remaining elements from + * that array are returned as leftovers. + * + * @see {@link foldArray} for folding each pulled non-empty input array at once + * @see {@link foldUntil} for folding until a fixed maximum number of elements is consumed + * + * @category folding + * @since 2.0.0 + */ +export const fold = ( + s: LazyArg, + contFn: Predicate, + f: (s: S, input: In) => Effect.Effect +): Sink => + fromTransform((upstream) => { + let state = s() + return Effect.gen(function*() { + while (true) { + const arr = yield* upstream + for (let i = 0; i < arr.length; i++) { + state = yield* f(state, arr[i]) + if (contFn(state)) continue + return [ + state, + (i + 1) < arr.length ? (arr.slice(i + 1) as any) : undefined + ] as const + } + } + }).pipe( + Pull.catchDone(() => Effect.succeed>([state])) + ) + }) + +/** + * Folds non-empty input arrays into state with an effectful function. + * + * **When to use** + * + * Use to update state with an effectful function once per pulled non-empty + * input array when batch-level processing is the natural unit. + * + * **Details** + * + * The initial state is evaluated lazily. After each pulled array is folded, + * the sink continues while `contFn` returns `true`; otherwise it completes + * with the current state. + * + * @see {@link fold} for folding element by element and returning leftovers when stopping mid-array + * @see {@link reduceWhileArrayEffect} for array-level effectful reducing that checks the predicate before consuming input + * + * @category folding + * @since 4.0.0 + */ +export const foldArray = ( + s: LazyArg, + contFn: Predicate, + f: (s: S, input: Arr.NonEmptyReadonlyArray) => Effect.Effect +): Sink => + fromTransform((upstream) => { + let state = s() + return Effect.gen(function*() { + while (true) { + const arr = yield* upstream + state = yield* f(state, arr) + if (contFn(state)) continue + return [state] as const + } + }).pipe( + Pull.catchDone(() => Effect.succeed>([state])) + ) + }) + +/** + * Folds input elements into state until the specified maximum number of + * elements has been consumed or the upstream stream ends. + * + * **Details** + * + * If the sink stops in the middle of a pulled array, the remaining elements + * from that array are returned as leftovers. + * + * @category folding + * @since 2.0.0 + */ +export const foldUntil = ( + s: LazyArg, + max: number, + f: (s: S, input: In) => Effect.Effect +): Sink => + fold( + () => [s(), 0], + (tuple) => tuple[1] < max, + ([output, count], input) => Effect.map(f(output, input), (s) => [s, count + 1] as const) + ).pipe( + map((tuple) => tuple[0]) + ) + +/** + * A sink that returns whether all elements satisfy the specified predicate. + * + * **When to use** + * + * Use to reduce a stream to a boolean that is true only when every input + * satisfies a pure predicate. + * + * @see {@link some} for the dual any-match check + * + * @category constructors + * @since 2.0.0 + */ +export const every = (predicate: Predicate): Sink => + fold( + constTrue, + identity, + (_, a) => Effect.succeed(predicate(a)) + ) + +/** + * A sink that returns whether an element satisfies the specified predicate. + * + * **When to use** + * + * Use to reduce a stream to a boolean that is true when any input satisfies a + * pure predicate. + * + * @see {@link every} for the all-match check + * + * @category constructors + * @since 2.0.0 + */ +export const some = (predicate: Predicate): Sink => + fold( + constFalse, + (b) => !b, + (_, a) => Effect.succeed(predicate(a)) + ) + +/** + * Transforms this sink's result. + * + * **When to use** + * + * Use to compute a new result from the original sink result while preserving + * the sink's input consumption behavior. + * + * **Details** + * + * The transformed sink preserves the original sink's input type, leftovers, + * errors, and requirements. + * + * @see {@link mapEffect} for effectful result transformations + * @see {@link as} for replacing the result with a constant value + * @see {@link mapEnd} for transforming both the result and leftovers + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (a: A) => A2): (self: Sink) => Sink + (self: Sink, f: (a: A) => A2): Sink +} = dual( + 2, + (self: Sink, f: (a: A) => A2): Sink => + mapEnd(self, ([a, l]) => [f(a), l]) +) + +/** + * Sets the sink's result to a constant value. + * + * **When to use** + * + * Use to keep a sink's input consumption, errors, requirements, and leftovers + * while replacing only its result with a known value. + * + * @see {@link map} for computing the replacement from the original result + * + * @category mapping + * @since 2.0.0 + */ +export const as: { + (a2: A2): (self: Sink) => Sink + (self: Sink, a2: A2): Sink +} = dual( + 2, + (self: Sink, a2: A2): Sink => map(self, () => a2) +) + +/** + * Transforms this sink's input elements. + * + * @category mapping + * @since 2.0.0 + */ +export const mapInput: { + (f: (input: In0) => In): (self: Sink) => Sink + (self: Sink, f: (input: In0) => In): Sink +} = dual( + 2, + (self: Sink, f: (input: In0) => In): Sink => + mapInputArray(self, Arr.map(f)) +) + +/** + * Transforms this sink's input elements effectfully. + * + * @category mapping + * @since 2.0.0 + */ +export const mapInputEffect: { + ( + f: (input: In0) => Effect.Effect + ): (self: Sink) => Sink + ( + self: Sink, + f: (input: In0) => Effect.Effect + ): Sink +} = dual( + 2, + ( + self: Sink, + f: (input: In0) => Effect.Effect + ): Sink => mapInputArrayEffect(self, Effect.forEach(f)) +) + +/** + * Transforms each non-empty array of upstream input before it is fed to this + * sink. + * + * @category mapping + * @since 4.0.0 + */ +export const mapInputArray: { + ( + f: (input: Arr.NonEmptyReadonlyArray) => Arr.NonEmptyReadonlyArray + ): (self: Sink) => Sink + ( + self: Sink, + f: (input: Arr.NonEmptyReadonlyArray) => Arr.NonEmptyReadonlyArray + ): Sink +} = dual( + 2, + ( + self: Sink, + f: (input: Arr.NonEmptyReadonlyArray) => Arr.NonEmptyReadonlyArray + ): Sink => fromTransform((upstream, scope) => self.transform(Effect.map(upstream, f), scope)) +) + +/** + * Transforms each non-empty array of upstream input effectfully before it is + * fed to this sink. + * + * @category mapping + * @since 4.0.0 + */ +export const mapInputArrayEffect: { + ( + f: (input: Arr.NonEmptyReadonlyArray) => Effect.Effect, E2, R2> + ): (self: Sink) => Sink + ( + self: Sink, + f: (input: Arr.NonEmptyReadonlyArray) => Effect.Effect, E2, R2> + ): Sink +} = dual( + 2, + ( + self: Sink, + f: (input: Arr.NonEmptyReadonlyArray) => Effect.Effect, E2, R2> + ): Sink => + fromTransform((upstream, scope) => + self.transform( + Effect.flatMap(upstream, f) as any, + scope + ) + ) +) + +/** + * Transforms the full `End` produced by this sink. + * + * **Details** + * + * This can change both the result value and the optional leftovers. + * + * @category mapping + * @since 4.0.0 + */ +export const mapEnd: { + ( + f: (a: End) => End + ): (self: Sink) => Sink + (self: Sink, f: (a: End) => End): Sink +} = dual( + 2, + ( + self: Sink, + f: (a: End) => End + ): Sink => + fromTransform((upstream, scope) => + Effect.map( + self.transform(upstream, scope), + f + ) + ) +) + +const transformEffect = ( + self: Sink, + f: (effect: Effect.Effect, E, R>) => Effect.Effect, E2, R2> +): Sink => fromTransform((upstream, scope) => f(self.transform(upstream, scope))) + +/** + * Transforms the full `End` produced by this sink effectfully. + * + * **Details** + * + * This can change both the result value and the optional leftovers, and the + * transformation can fail or require services. + * + * @category mapping + * @since 4.0.0 + */ +export const mapEffectEnd: { + ( + f: (end: End) => Effect.Effect, E2, R2> + ): (self: Sink) => Sink + ( + self: Sink, + f: (end: End) => Effect.Effect, E2, R2> + ): Sink +} = dual(2, ( + self: Sink, + f: (end: End) => Effect.Effect, E2, R2> +): Sink => transformEffect(self, Effect.flatMap(f))) + +/** + * Transforms this sink's result effectfully. + * + * **When to use** + * + * Use when you need a sink result transformation that is effectful, can fail, + * or requires services. + * + * **Details** + * + * The transformed sink preserves the original sink's input consumption and + * leftovers while adding the errors and requirements of the transformation. + * + * @see {@link map} for pure result transformations + * @see {@link mapEffectEnd} for effectfully transforming both the result and leftovers + * @see {@link flatMap} for continuing with another sink based on the result + * + * @category mapping + * @since 2.0.0 + */ +export const mapEffect: { + ( + f: (a: A) => Effect.Effect + ): (self: Sink) => Sink + ( + self: Sink, + f: (a: A) => Effect.Effect + ): Sink +} = dual(2, ( + self: Sink, + f: (a: A) => Effect.Effect +): Sink => mapEffectEnd(self, ([a, l]) => Effect.map(f(a), (a2) => [a2, l] as End))) + +/** + * Transforms the errors emitted by this sink using `f`. + * + * @category mapping + * @since 2.0.0 + */ +export const mapError: { + (f: (error: E) => E2): (self: Sink) => Sink + (self: Sink, f: (error: E) => E2): Sink +} = dual(2, ( + self: Sink, + f: (error: E) => E2 +): Sink => transformEffect(self, Effect.mapError(f))) + +/** + * Transforms the leftovers emitted by this sink using `f`. + * + * @category mapping + * @since 2.0.0 + */ +export const mapLeftover: { + (f: (leftover: L) => L2): (self: Sink) => Sink + (self: Sink, f: (leftover: L) => L2): Sink +} = dual(2, ( + self: Sink, + f: (leftover: L) => L2 +): Sink => mapEnd(self, ([a, l]) => [a, l && Arr.map(l, f)])) + +/** + * Collects up to `n` input elements into an array. + * + * **Details** + * + * If `n` is less than or equal to zero, the sink completes with an empty array. + * If more elements are pulled than needed, the remaining elements from the same + * array are returned as leftovers. + * + * @category constructors + * @since 2.0.0 + */ +export const take = (n: number): Sink, In, In> => + fromTransform((upstream) => { + const taken: Array = [] + if (n <= 0) { + return Effect.succeed([taken] as const) + } + let leftover: NonEmptyReadonlyArray | undefined = undefined + return upstream.pipe( + Effect.flatMap((arr) => { + if (taken.length + arr.length <= n) { + taken.push(...arr) + if (taken.length === n) { + return Cause.done() + } + return Effect.void + } + for (let i = 0; i < arr.length; i++) { + taken.push(arr[i]) + if (taken.length === n) { + if ((i + 1) < arr.length) { + leftover = arr.slice(i + 1) as any + } + return Cause.done() + } + } + return Effect.void + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([taken, leftover] as const)) + ) + }) + +/** + * Runs this sink until it yields a result, then uses that result to create + * another sink from the provided function which will continue to run until it + * yields a result. + * + * **When to use** + * + * Use to compose sinks when the next sink depends on the result produced by the + * previous sink. + * + * **Details** + * + * Leftovers from the first sink are fed to the sink returned by `f` before more + * upstream input is pulled. + * + * @see {@link map} for transforming the result without switching sinks + * @see {@link mapEffect} for effectfully transforming the result without switching sinks + * + * @category sequencing + * @since 2.0.0 + */ +export const flatMap: { + ( + f: (a: A) => Sink + ): (self: Sink) => Sink + ( + self: Sink, + f: (a: A) => Sink + ): Sink +} = dual(2, ( + self: Sink, + f: (a: A) => Sink +): Sink => + fromTransform((upstream, scope) => { + let upstreamDone = false + const pull = Effect.catchCause(upstream, (cause) => { + upstreamDone = true + return Effect.failCause(cause) + }) + return Effect.flatMap( + self.transform(pull, scope), + ([a, leftover]) => + f(a).transform( + Effect.suspend(() => { + if (leftover) { + const arr = leftover as Arr.NonEmptyReadonlyArray + leftover = undefined + return Effect.succeed(arr) + } else if (upstreamDone) { + return Cause.done() + } + return upstream + }), + scope + ) + ) + })) + +/** + * A sink that reduces input elements from the provided `initial` state with + * `f` while the specified `predicate` returns `true`. + * + * @category folding + * @since 4.0.0 + */ +export const reduceWhile = ( + initial: LazyArg, + predicate: Predicate, + f: (s: S, input: In) => S +): Sink => + fromTransform((upstream) => { + let state = initial() + let leftover: NonEmptyReadonlyArray | undefined = undefined + if (!predicate(state)) { + return Effect.succeed([state] as const) + } + return upstream.pipe( + Effect.flatMap((arr) => { + for (let i = 0; i < arr.length; i++) { + state = f(state, arr[i]) + if (!predicate(state)) { + if ((i + 1) < arr.length) { + leftover = arr.slice(i + 1) as any + } + return Cause.done() + } + } + return Effect.void + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([state, leftover] as const)) + ) + }) + +/** + * A sink that effectfully reduces input elements from the provided `initial` + * state with `f` while the specified `predicate` returns `true`. + * + * @category folding + * @since 4.0.0 + */ +export const reduceWhileEffect = ( + initial: LazyArg, + predicate: Predicate, + f: (s: S, input: In) => Effect.Effect +): Sink => + fromTransform((upstream) => { + let state = initial() + let leftover: NonEmptyReadonlyArray | undefined = undefined + if (!predicate(state)) { + return Effect.succeed([state] as const) + } + return upstream.pipe( + Effect.flatMap((arr) => { + let i = 0 + return Effect.whileLoop({ + while: () => i < arr.length, + body: constant(Effect.flatMap(Effect.suspend(() => f(state, arr[i++])), (s) => { + state = s + if (!predicate(state)) { + if (i < arr.length) { + leftover = arr.slice(i) as any + } + return Cause.done() + } + return Effect.void + })), + step: constVoid + }) + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([state, leftover] as const)) + ) + }) + +/** + * A sink that reduces non-empty input arrays from the provided `initial` state + * with `f` while the specified `predicate` returns `true`. + * + * @category folding + * @since 4.0.0 + */ +export const reduceWhileArray = ( + initial: LazyArg, + contFn: Predicate, + f: (s: S, input: NonEmptyReadonlyArray) => S +): Sink => + fromTransform((upstream) => { + let state = initial() + if (!contFn(state)) { + return Effect.succeed([state] as const) + } + return upstream.pipe( + Effect.flatMap((arr) => { + state = f(state, arr) + if (!contFn(state)) { + return Cause.done() + } + return Effect.void + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([state] as const)) + ) + }) + +/** + * A sink that effectfully reduces non-empty input arrays from the provided + * `initial` state with `f` while the specified `predicate` returns `true`. + * + * @category folding + * @since 4.0.0 + */ +export const reduceWhileArrayEffect = ( + initial: LazyArg, + predicate: Predicate, + f: (s: S, input: NonEmptyReadonlyArray) => Effect.Effect +): Sink => + fromTransform((upstream) => { + let state = initial() + if (!predicate(state)) { + return Effect.succeed([state] as const) + } + return upstream.pipe( + Effect.flatMap((arr) => f(state, arr)), + Effect.flatMap((s) => { + state = s + if (!predicate(state)) { + return Cause.done() + } + return Effect.void + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([state] as const)) + ) + }) + +/** + * A sink that reduces its inputs using the provided function `f` starting from + * the provided `initial` state. + * + * @category folding + * @since 4.0.0 + */ +export const reduce = (initial: LazyArg, f: (s: S, input: In) => S): Sink => + reduceArray(initial, (s, arr) => { + for (let i = 0; i < arr.length; i++) { + s = f(s, arr[i]) + } + return s + }) + +/** + * A sink that reduces its inputs using the provided function `f` starting from + * the specified `initial` state. + * + * @category folding + * @since 4.0.0 + */ +export const reduceArray = ( + initial: LazyArg, + f: (s: S, input: NonEmptyReadonlyArray) => S +): Sink => + fromTransform((upstream) => { + let state = initial() + return upstream.pipe( + Effect.flatMap((arr) => { + state = f(state, arr) + return Effect.void + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([state] as const)) + ) + }) + +/** + * A sink that reduces its inputs using the provided effectful function `f` + * starting from the specified `initial` state. + * + * @category folding + * @since 4.0.0 + */ +export const reduceEffect = ( + initial: LazyArg, + f: (s: S, input: In) => Effect.Effect +): Sink => reduceWhileEffect(initial, constTrue, f) as any + +const head_ = reduceWhile(Option.none, Option.isNone, (_, in_) => Option.some(in_)) + +/** + * Creates a sink containing the first value. + * + * **Details** + * + * Returns `Option.some(first)` for non-empty input, or `Option.none` when the + * upstream ends without input. The first element is consumed; later elements + * from the same pulled array are emitted as leftovers. + * + * @category constructors + * @since 2.0.0 + */ +export const head = (): Sink, In, In> => head_ as any + +const last_ = reduceArray(Option.none, (_, arr) => Arr.last(arr)) + +/** + * Creates a sink containing the last value. + * + * **When to use** + * + * Use when you need to consume all upstream input and keep only the final + * element. + * + * **Details** + * + * Returns `Option.some(last)` with the final input value, or `Option.none` when + * the upstream ends without input. + * + * **Gotchas** + * + * This sink produces a result only when the upstream ends, so it does not + * complete for a stream that does not end. + * + * @see {@link head} for taking the first input value instead + * + * @category constructors + * @since 2.0.0 + */ +export const last = (): Sink, In> => last_ as any + +/** + * Creates a sink containing the first value matched by a synchronous predicate. + * + * **When to use** + * + * Use to scan stream input until the first matching element is found and return + * that element as an `Option`. + * + * **Details** + * + * Returns `Option.none` if the upstream stream ends before a match is found. + * Refinement predicates narrow the returned value type. The matching input is + * consumed; any later elements from the same pulled array are returned as + * leftovers. + * + * @see {@link findEffect} for an effectful predicate that can fail or require services + * + * @category constructors + * @since 4.0.0 + */ +export const find: { + (refinement: Refinement): Sink, In, In> + (predicate: Predicate): Sink, In, In> +} = (predicate: Predicate): Sink, In, In> => + reduceWhile( + Option.none, + Option.isNone, + (acc, in_) => predicate(in_) ? Option.some(in_) : acc + ) + +/** + * Creates a sink containing the first value matched by an effectful predicate. + * + * **When to use** + * + * Use when you need to run effects, fail, or use services while searching for + * the first matching input. + * + * **Details** + * + * Returns `Option.some` with the first input whose predicate result is `true`, + * or `Option.none` if the upstream stream ends first. If the predicate effect + * fails, the sink fails with the same error. + * + * @see {@link find} for the synchronous predicate variant + * + * @category constructors + * @since 2.0.0 + */ +export const findEffect = ( + predicate: (input: In) => Effect.Effect +): Sink, In, In, E, R> => + reduceWhileEffect( + Option.none, + Option.isNone, + (acc, in_) => Effect.map(predicate(in_), (b) => b ? Option.some(in_) : acc) + ) + +/** + * Creates a sink which sums up its inputs. + * + * @category constructors + * @since 2.0.0 + */ +export const sum: Sink = reduceArray(() => 0, (s, arr) => { + for (let i = 0; i < arr.length; i++) { + s += arr[i] + } + return s +}) + +/** + * A sink that counts the number of elements fed to it. + * + * **When to use** + * + * Use to consume input and return only the number of elements received. + * + * @category constructors + * @since 2.0.0 + */ +export const count: Sink = reduceArray(() => 0, (s, arr) => s + arr.length) + +/** + * Accumulates incoming elements into an array. + * + * **When to use** + * + * Use when you need a sink result containing all upstream input elements. + * + * @see {@link take} for collecting only a fixed number of input elements + * + * @category constructors + * @since 4.0.0 + */ +export const collect = (): Sink, In> => + reduceArray(Arr.empty, (s, arr) => { + s.push(...arr) + return s + }) + +/** + * Collects the longest input prefix whose elements satisfy the predicate or + * refinement. + * + * **Details** + * + * The first failing input is consumed and excluded from the result. Any later + * elements from the same pulled array are returned as leftovers. + * + * @category constructors + * @since 4.0.0 + */ +export const takeWhile: { + (refinement: Refinement): Sink, In, In> + (predicate: Predicate): Sink, In, In> +} = (predicate: Predicate): Sink, In, In> => + fromTransform((upstream) => { + const out = Arr.empty() + return upstream.pipe( + Effect.flatMap((arr) => { + for (let i = 0; i < arr.length; i++) { + if (!predicate(arr[i])) { + const leftover: Arr.NonEmptyReadonlyArray | undefined = (i + 1) < arr.length + ? arr.slice(i + 1) as any + : undefined + return Cause.done([out, leftover] as const) + } + out.push(arr[i]) + } + return Effect.void + }), + Effect.forever({ disableYield: true }), + Pull.catchDone((end) => Effect.succeed, In>>(end ?? [out])) + ) + }) + +/** + * Applies a `Filter` to input elements while it succeeds, collecting each + * successful output. + * + * **Details** + * + * The first input for which the filter fails is consumed and excluded from the + * result. Any later elements from the same pulled array are returned as + * leftovers. + * + * @category constructors + * @since 4.0.0 + */ +export const takeWhileFilter = ( + filter: Filter.Filter +): Sink, In, In> => + fromTransform((upstream) => { + const out = Arr.empty() + return upstream.pipe( + Effect.flatMap((arr) => { + for (let i = 0; i < arr.length; i++) { + const result = filter(arr[i]) + if (Result.isFailure(result)) { + const leftover: Arr.NonEmptyReadonlyArray | undefined = (i + 1) < arr.length + ? arr.slice(i + 1) as any + : undefined + return Cause.done([out, leftover] as const) + } + out.push(result.success) + } + return Effect.void + }), + Effect.forever({ disableYield: true }), + Pull.catchDone((end) => Effect.succeed, In>>(end ?? [out])) + ) + }) + +/** + * Collects input elements effectfully while the predicate succeeds. + * + * **Details** + * + * The first input for which the predicate returns `false` is consumed and + * excluded from the result. Any later elements from the same pulled array are + * returned as leftovers. + * + * @category constructors + * @since 4.0.0 + */ +export const takeWhileEffect: { + (predicate: (input: In) => Effect.Effect): Sink, In, In, E, R> +} = ( + predicate: (input: In) => Effect.Effect +): Sink, In, In, E, R> => + fromTransform((upstream) => { + const out = Arr.empty() + let leftover: Arr.NonEmptyReadonlyArray | undefined = undefined + return upstream.pipe( + Effect.flatMap((arr) => { + let i = 0 + return Effect.whileLoop({ + while: () => i < arr.length, + body: constant(Effect.flatMap( + Effect.suspend(() => { + const input = arr[i++] + return Effect.map(predicate(input), (passes) => [input, passes] as const) + }), + ([input, passes]) => { + if (!passes) { + if (i < arr.length) { + leftover = arr.slice(i) as any + } + return Cause.done() + } + out.push(input) + return Effect.void + } + )), + step: constVoid + }) + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([out, leftover] as const)) + ) + }) + +/** + * Applies a `FilterEffect` to input elements effectfully while it succeeds, + * collecting each successful output. + * + * **Details** + * + * The first input for which the filter fails is consumed and excluded from the + * result. Any later elements from the same pulled array are returned as + * leftovers. + * + * @category constructors + * @since 4.0.0 + */ +export const takeWhileFilterEffect = ( + filter: Filter.FilterEffect +): Sink, In, In, E, R> => + fromTransform((upstream) => { + const out = Arr.empty() + let leftover: Arr.NonEmptyReadonlyArray | undefined = undefined + return upstream.pipe( + Effect.flatMap((arr) => { + let i = 0 + return Effect.whileLoop({ + while: () => i < arr.length, + body: constant(Effect.flatMap(Effect.suspend(() => filter(arr[i++])), (result) => { + if (Result.isFailure(result)) { + if (i < arr.length) { + leftover = arr.slice(i) as any + } + return Cause.done() + } + out.push(result.success) + return Effect.void + })), + step: constVoid + }) + }), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => Effect.succeed([out, leftover] as const)) + ) + }) + +/** + * Collects input elements until the predicate returns `true`, including the + * matching element in the result. + * + * @category constructors + * @since 4.0.0 + */ +export const takeUntil = (predicate: Predicate): Sink, In, In> => + suspend(() => { + let done = false + return takeWhile((i) => { + if (done) return false + done = predicate(i) + return true + }) + }) + +/** + * Collects input elements effectfully until the predicate returns `true`, + * including the matching element in the result. + * + * **Details** + * + * If the predicate effect fails, the sink fails with the same error. + * + * @category constructors + * @since 4.0.0 + */ +export const takeUntilEffect = ( + predicate: (input: In) => Effect.Effect +): Sink, In, In, E, R> => + suspend(() => { + let done = false + return takeWhileEffect((input) => { + if (done) { + return Effect.succeed(false) + } + return Effect.map(predicate(input), (b) => { + done = b + return true + }) + }) + }) + +/** + * A sink that executes the provided effectful function for every item fed + * to it. + * + * **Example** (Running effects for each item) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * const processed: Array = [] + * const sink = Sink.forEach((item: number) => Effect.sync(() => processed.push(item))) + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromise(Stream.run(stream, sink)) + * processed // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const forEach = ( + f: (input: In) => Effect.Effect +): Sink => forEachArray(Effect.forEach((_) => f(_), { discard: true })) + +/** + * A sink that executes the provided effectful function for every Chunk fed + * to it. + * + * **Example** (Running effects for each chunk) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * const processed: Array> = [] + * const sink = Sink.forEachArray((chunk: ReadonlyArray) => Effect.sync(() => processed.push([...chunk]))) + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3, 4, 5) + * await Effect.runPromise(Stream.run(stream, sink)) + * processed // => [[1, 2, 3, 4, 5]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const forEachArray = ( + f: (input: NonEmptyReadonlyArray) => Effect.Effect +): Sink => + fromTransform((upstream) => + upstream.pipe( + Effect.flatMap(f), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => endVoid) + ) + ) + +/** + * Runs an effectful function for each input element while it returns `true`. + * + * **Details** + * + * The sink stops consuming input when the function returns `false` or when the + * upstream stream ends, and completes with `void`. + * + * @category constructors + * @since 2.0.0 + */ +export const forEachWhile = ( + f: (input: In) => Effect.Effect +): Sink => + forEachWhileArray(Effect.fnUntraced(function*(input) { + for (let i = 0; i < input.length; i++) { + const cont = yield* f(input[i]) + if (!cont) return false + } + return true + })) + +/** + * Runs an effectful function for each non-empty input array while it returns + * `true`. + * + * **Details** + * + * The sink stops consuming input when the function returns `false` or when the + * upstream stream ends, and completes with `void`. + * + * @category constructors + * @since 4.0.0 + */ +export const forEachWhileArray = ( + f: (input: NonEmptyReadonlyArray) => Effect.Effect +): Sink => + fromTransform((upstream) => + upstream.pipe( + Effect.flatMap(f), + Effect.flatMap((cont) => cont ? Effect.void : Cause.done()), + Effect.forever({ disableYield: true }), + Pull.catchDone(() => endVoid) + ) + ) + +/** + * Creates a sink produced from a scoped effect. + * + * **Example** (Unwrapping a sink effect) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * // Create a sink from an effect that produces a sink + * const processed: Array = [] + * const sinkEffect = Effect.succeed( + * Sink.forEach((item: number) => Effect.sync(() => processed.push(item))) + * ) + * const sink = Sink.unwrap(sinkEffect) + * + * // Use it with a stream + * const stream = Stream.make(1, 2, 3) + * await Effect.runPromise(Stream.run(stream, sink)) + * processed // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unwrap = ( + effect: Effect.Effect, E, R> +): Sink | R2> => fromChannel(Channel.unwrap(Effect.map(effect, toChannel))) + +/** + * Runs a summary effect when the sink starts and again when it completes. + * + * @category mapping + * @since 2.0.0 + */ +export const summarized: { + ( + summary: Effect.Effect, + f: (start: A2, end: A2) => A3 + ): (self: Sink) => Sink<[A, A3], In, L, E2 | E, R2 | R> + ( + self: Sink, + summary: Effect.Effect, + f: (start: A2, end: A2) => A3 + ): Sink<[A, A3], In, L, E | E2, R | R2> +} = dual(3, ( + self: Sink, + summary: Effect.Effect, + f: (start: A2, end: A2) => A3 +): Sink<[A, A3], In, L, E | E2, R | R2> => + fromTransform(Effect.fnUntraced(function*(upstream, scope) { + const start = yield* summary + const [done, leftover] = yield* self.transform(upstream, scope) + const end = yield* summary + return [[done, f(start, end)], leftover] as const + }))) + +/** + * Returns the sink that executes this one and times its execution. + * + * @category mapping + * @since 2.0.0 + */ +export const withDuration = ( + self: Sink +): Sink<[A, Duration.Duration], In, L, E, R> => + summarized(self, Clock.monotonicTimeNanos, (start, end) => Duration.nanos(end - start)) + +/** + * A sink that drains all input and returns the elapsed duration. + * + * @category constructors + * @since 2.0.0 + */ +export const timed: Sink = map(withDuration(drain), ([, duration]) => duration) + +/** + * Provides a `Context` to this sink. + * + * **Details** + * + * Services contained in the provided context are removed from the sink's + * service requirements. + * + * @category providing services + * @since 2.0.0 + */ +export const provideContext: { + ( + context: Context.Context + ): (self: Sink) => Sink> + ( + self: Sink, + context: Context.Context + ): Sink> +} = dual(2, ( + self: Sink, + context: Context.Context +): Sink> => + fromTransform((upstream, scope) => + self.transform(upstream, scope).pipe( + Effect.provideContext(context) + ) + )) + +/** + * Provides a single service implementation to this sink. + * + * **Details** + * + * The service identified by `key` is removed from the sink's service + * requirements. + * + * @category providing services + * @since 4.0.0 + */ +export const provideService: { + ( + key: Context.Key, + value: Types.NoInfer + ): (self: Sink) => Sink> + ( + self: Sink, + key: Context.Key, + value: Types.NoInfer + ): Sink> +} = dual(3, ( + self: Sink, + key: Context.Key, + value: Types.NoInfer +): Sink> => + fromTransform((upstream, scope) => + self.transform(upstream, scope).pipe( + Effect.provideService(key, value) + ) + )) + +/** + * Runs a fallback sink if this sink fails with a typed error. + * + * **Details** + * + * The fallback is built from the error and continues consuming from the same + * upstream stream. If the upstream stream had already ended, the fallback sees + * the upstream end instead. + * + * @category error handling + * @since 2.0.0 + */ +export const orElse: { + ( + f: (error: Types.NoInfer) => Sink + ): (self: Sink) => Sink + ( + self: Sink, + f: (error: E) => Sink + ): Sink +} = dual(2, ( + self: Sink, + f: (error: E) => Sink +): Sink => + fromTransform((upstream, scope) => { + let upstreamDone = false + const pull = Effect.catchCause(upstream, (cause) => { + upstreamDone = true + return Effect.failCause(cause) + }) + return Effect.catch( + self.transform(pull, scope) as Effect.Effect, E, R>, + (error) => + f(error).transform( + Effect.suspend(() => { + if (upstreamDone) { + return Cause.done() + } + return upstream + }), + scope + ) + ) + })) + +/** + * Handles failures from this sink by inspecting the full `Cause`. + * + * **When to use** + * + * Use to recover from a sink failure based on the full `Cause` instead of only + * the typed error value. + * + * **Details** + * + * When this sink fails, the handler effect is run and its success value + * becomes the sink result. If the handler fails, the returned sink fails with + * that error. + * + * @see {@link catch_ catch} for recovering from typed errors only + * @see {@link orElse} for recovering by switching to another sink + * + * @category error handling + * @since 4.0.0 + */ +export const catchCause: { + ( + f: (error: Cause.Cause>) => Effect.Effect + ): (self: Sink) => Sink + ( + self: Sink, + f: (error: Cause.Cause) => Effect.Effect + ): Sink +} = dual(2, ( + self: Sink, + f: (error: Cause.Cause) => Effect.Effect +): Sink => + transformEffect( + self, + Effect.catchCause((cause) => Effect.map(f(cause), (a2) => [a2 as A | A2] as const)) + )) + +const catch_: { + ( + f: (error: Types.NoInfer) => Effect.Effect + ): (self: Sink) => Sink + ( + self: Sink, + f: (error: E) => Effect.Effect + ): Sink +} = dual(2, ( + self: Sink, + f: (error: E) => Effect.Effect +): Sink => + transformEffect( + self, + Effect.catch((error) => Effect.map(f(error), (a2) => [a2 as A | A2] as const)) + )) + +export { + /** + * Handles typed errors from this sink with an effectful fallback value. + * + * **When to use** + * + * Use to recover from a typed sink failure by producing the replacement + * result with an `Effect`. + * + * @see {@link catchCause} for recovering from the full failure cause + * @see {@link orElse} for recovering by switching to another sink + * + * @category error handling + * @since 4.0.0 + */ + catch_ as catch +} + +/** + * Runs an effect after this sink completes, fails, or is interrupted. + * + * **Details** + * + * The effect receives the sink's `Exit` for the result value. The original + * sink result and leftovers are preserved unless the finalizer itself fails. + * + * @category resource management + * @since 4.0.0 + */ +export const onExit: { + ( + f: (exit: Exit.Exit) => Effect.Effect + ): (self: Sink) => Sink + ( + self: Sink, + f: (exit: Exit.Exit) => Effect.Effect + ): Sink +} = dual(2, ( + self: Sink, + f: (exit: Exit.Exit) => Effect.Effect +): Sink => + transformEffect( + self, + Effect.onExit((exit) => f(Exit.map(exit, ([a]) => a))) + )) + +/** + * Runs a finalizer effect after this sink completes, fails, or is interrupted. + * + * **Details** + * + * The original sink result and leftovers are preserved unless the finalizer + * itself fails. + * + * @category resource management + * @since 2.0.0 + */ +export const ensuring: { + ( + effect: Effect.Effect + ): (self: Sink) => Sink + ( + self: Sink, + effect: Effect.Effect + ): Sink +} = dual(2, ( + self: Sink, + effect: Effect.Effect +): Sink => onExit(self, () => effect)) diff --git a/.repos/effect/packages/effect/src/StandardSchema.ts b/.repos/effect/packages/effect/src/StandardSchema.ts new file mode 100644 index 000000000..885765170 --- /dev/null +++ b/.repos/effect/packages/effect/src/StandardSchema.ts @@ -0,0 +1,207 @@ +/** + * Type definitions from the Standard Schema specification. + * + * The declarations below are vendored verbatim from `@standard-schema/spec` 1.1.0. + * + * @since 4.0.0 + */ + +/* +MIT License + +Copyright (c) 2024 Colin McDonnell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +// ######################### +// ### Standard Typed ### +// ######################### + +/** The Standard Typed interface. This is a base type extended by other specs. */ +export interface StandardTypedV1 { + /** The Standard properties. */ + readonly "~standard": StandardTypedV1.Props; +} + +export declare namespace StandardTypedV1 { + /** The Standard Typed properties interface. */ + export interface Props { + /** The version number of the standard. */ + readonly version: 1; + /** The vendor name of the schema library. */ + readonly vendor: string; + /** Inferred types associated with the schema. */ + readonly types?: Types | undefined; + } + + /** The Standard Typed types interface. */ + export interface Types { + /** The input type of the schema. */ + readonly input: Input; + /** The output type of the schema. */ + readonly output: Output; + } + + /** Infers the input type of a Standard Typed. */ + export type InferInput = NonNullable< + Schema["~standard"]["types"] + >["input"]; + + /** Infers the output type of a Standard Typed. */ + export type InferOutput = NonNullable< + Schema["~standard"]["types"] + >["output"]; +} + +// ########################## +// ### Standard Schema ### +// ########################## + +/** The Standard Schema interface. */ +export interface StandardSchemaV1 { + /** The Standard Schema properties. */ + readonly "~standard": StandardSchemaV1.Props; +} + +export declare namespace StandardSchemaV1 { + /** The Standard Schema properties interface. */ + export interface Props + extends StandardTypedV1.Props { + /** Validates unknown input values. */ + readonly validate: ( + value: unknown, + options?: StandardSchemaV1.Options | undefined + ) => Result | Promise>; + } + + /** The result interface of the validate function. */ + export type Result = SuccessResult | FailureResult; + + /** The result interface if validation succeeds. */ + export interface SuccessResult { + /** The typed output value. */ + readonly value: Output; + /** A falsy value for `issues` indicates success. */ + readonly issues?: undefined; + } + + export interface Options { + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } + + /** The result interface if validation fails. */ + export interface FailureResult { + /** The issues of failed validation. */ + readonly issues: ReadonlyArray; + } + + /** The issue interface of the failure output. */ + export interface Issue { + /** The error message of the issue. */ + readonly message: string; + /** The path of the issue, if any. */ + readonly path?: ReadonlyArray | undefined; + } + + /** The path segment interface of the issue. */ + export interface PathSegment { + /** The key representing a path segment. */ + readonly key: PropertyKey; + } + + /** The Standard types interface. */ + export interface Types + extends StandardTypedV1.Types {} + + /** Infers the input type of a Standard. */ + export type InferInput = + StandardTypedV1.InferInput; + + /** Infers the output type of a Standard. */ + export type InferOutput = + StandardTypedV1.InferOutput; +} + +// ############################### +// ### Standard JSON Schema ### +// ############################### + +/** The Standard JSON Schema interface. */ +export interface StandardJSONSchemaV1 { + /** The Standard JSON Schema properties. */ + readonly "~standard": StandardJSONSchemaV1.Props; +} + +export declare namespace StandardJSONSchemaV1 { + /** The Standard JSON Schema properties interface. */ + export interface Props + extends StandardTypedV1.Props { + /** Methods for generating the input/output JSON Schema. */ + readonly jsonSchema: StandardJSONSchemaV1.Converter; + } + + /** The Standard JSON Schema converter interface. */ + export interface Converter { + /** Converts the input type to JSON Schema. May throw if conversion is not supported. */ + readonly input: ( + options: StandardJSONSchemaV1.Options + ) => Record; + /** Converts the output type to JSON Schema. May throw if conversion is not supported. */ + readonly output: ( + options: StandardJSONSchemaV1.Options + ) => Record; + } + + /** + * The target version of the generated JSON Schema. + * + * It is *strongly recommended* that implementers support `"draft-2020-12"` and `"draft-07"`, as they are both in wide use. All other targets can be implemented on a best-effort basis. Libraries should throw if they don't support a specified target. + * + * The `"openapi-3.0"` target is intended as a standardized specifier for OpenAPI 3.0 which is a superset of JSON Schema `"draft-04"`. + */ + export type Target = + | "draft-2020-12" + | "draft-07" + | "openapi-3.0" + // Accepts any string for future targets while preserving autocomplete + | ({} & string); + + /** The options for the input/output methods. */ + export interface Options { + /** Specifies the target version of the generated JSON Schema. Support for all versions is on a best-effort basis. If a given version is not supported, the library should throw. */ + readonly target: Target; + + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } + + /** The Standard types interface. */ + export interface Types + extends StandardTypedV1.Types {} + + /** Infers the input type of a Standard. */ + export type InferInput = + StandardTypedV1.InferInput; + + /** Infers the output type of a Standard. */ + export type InferOutput = + StandardTypedV1.InferOutput; +} diff --git a/.repos/effect/packages/effect/src/Stdio.ts b/.repos/effect/packages/effect/src/Stdio.ts new file mode 100644 index 000000000..d51caa541 --- /dev/null +++ b/.repos/effect/packages/effect/src/Stdio.ts @@ -0,0 +1,162 @@ +/** + * Service contract for command-line arguments and standard input, output, and + * error output. It lets programs depend on standard I/O through the Effect + * environment instead of reading from or writing to global process handles + * directly. + * + * The service exposes arguments as an `Effect`, stdout and stderr as `Sink`s + * that accept strings or bytes, and stdin as a byte `Stream`. This module also + * provides a constructor for service values and a small test layer with + * overridable defaults. + * + * @since 4.0.0 + */ +import * as Context from "./Context.ts" +import * as Effect from "./Effect.ts" +import * as Layer from "./Layer.ts" +import type { PlatformError } from "./PlatformError.ts" +import * as Sink from "./Sink.ts" +import * as Stream from "./Stream.ts" + +/** + * String literal type used as the unique brand for the `Stdio` service. + * + * **When to use** + * + * Use to type the runtime identifier stored on `Stdio` service implementations. + * + * @category type IDs + * @since 4.0.0 + */ +export type TypeId = "~effect/Stdio" + +/** + * Runtime identifier stored on `Stdio` service implementations. + * + * **Details** + * + * This marker is part of the runtime representation of `Stdio` service + * implementations. + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId: TypeId = "~effect/Stdio" + +/** + * Defines the service interface for process standard I/O. + * + * **When to use** + * + * Use to depend on command-line arguments and standard I/O through the Effect + * environment. + * + * **Details** + * + * The service provides command-line arguments, sinks for standard output and + * standard error, and a stream of standard input bytes. I/O operations can fail + * with `PlatformError`. + * + * @category services + * @since 4.0.0 + */ +export interface Stdio { + readonly [TypeId]: TypeId + readonly args: Effect.Effect> + /** + * Whether standard input is attached to a terminal. + * + * @since 4.0.0 + */ + readonly stdinIsTerminal: Effect.Effect + /** + * Whether standard output is attached to a terminal. + * + * @since 4.0.0 + */ + readonly stdoutIsTerminal: Effect.Effect + stdout(options?: { + readonly endOnDone?: boolean | undefined + }): Sink.Sink + stderr(options?: { + readonly endOnDone?: boolean | undefined + }): Sink.Sink + readonly stdin: Stream.Stream +} +/** + * Service tag for process standard I/O. + * + * **When to use** + * + * Use when you need command-line arguments or standard I/O streams supplied by + * an effect's environment. + * + * @see {@link make} for constructing a `Stdio` service directly + * @see {@link layerTest} for a test layer with defaults and overrides + * + * @category services + * @since 4.0.0 + */ +export const Stdio: Context.Service = Context.Service(TypeId) + +/** + * Creates a `Stdio` service implementation from the provided fields and + * attaches the `Stdio` type identifier. + * + * **When to use** + * + * Use when you need to assemble a concrete `Stdio` service from command-line + * arguments and standard I/O implementations. + * + * **Details** + * + * The returned service reuses the supplied fields unchanged and adds the + * `Stdio` type identifier. Omitted terminal-detection fields default to + * effects that succeed with `false`. + * + * @see {@link layerTest} for a test layer with default fields that can be overridden + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + options: + & Omit + & Partial> +): Stdio => ({ + [TypeId]: TypeId, + stdinIsTerminal: Effect.succeed(false), + stdoutIsTerminal: Effect.succeed(false), + ...options +}) + +/** + * Creates a test layer for `Stdio`. + * + * **When to use** + * + * Use to provide deterministic standard I/O in tests while overriding only the + * command-line arguments, input stream, or output sinks relevant to the case. + * + * **Details** + * + * Any provided fields override defaults. By default, arguments are empty, + * standard output and error are draining sinks, and standard input is an empty + * stream, and terminal-detection effects succeed with `false`. + * + * @see {@link make} for constructing a `Stdio` service directly without a `Layer` or defaults + * + * @category layers + * @since 4.0.0 + */ +export const layerTest = (impl: Partial): Layer.Layer => + Layer.succeed( + Stdio, + make({ + args: Effect.succeed([]), + stdout: () => Sink.drain, + stderr: () => Sink.drain, + stdin: Stream.empty, + ...impl + }) + ) diff --git a/.repos/effect/packages/effect/src/Stream.ts b/.repos/effect/packages/effect/src/Stream.ts new file mode 100644 index 000000000..4c2f749ac --- /dev/null +++ b/.repos/effect/packages/effect/src/Stream.ts @@ -0,0 +1,11513 @@ +/** + * Describes effectful sources that emit values over time. + * + * A `Stream` can emit many `A` values, fail with `E`, and require + * services `R` while it is being consumed. Streams are useful for data that is + * pulled in steps, such as values from collections, queues, pubsubs, schedules, + * callbacks, async iterables, or platform streams. The APIs here cover the full + * stream lifecycle: create a stream, transform or combine it, control buffering + * and timing, handle failures, and finally consume it. + * + * @since 2.0.0 + */ +// @effect-diagnostics returnEffectInGen:off +import * as Arr from "./Array.ts" +import * as Cause from "./Cause.ts" +import * as Channel from "./Channel.ts" +import { Clock } from "./Clock.ts" +import * as Context from "./Context.ts" +import * as Duration from "./Duration.ts" +import * as Effect from "./Effect.ts" +import * as Equal from "./Equal.ts" +import * as ExecutionPlan from "./ExecutionPlan.ts" +import * as Exit from "./Exit.ts" +import * as Fiber from "./Fiber.ts" +import type { SizeInput } from "./FileSystem.ts" +import type * as Filter from "./Filter.ts" +import type { LazyArg } from "./Function.ts" +import { constant, constTrue, constVoid, dual, identity } from "./Function.ts" +import type { TypeLambda } from "./HKT.ts" +import * as internalExecutionPlan from "./internal/executionPlan.ts" +import * as internal from "./internal/stream.ts" +import { addSpanStackTrace } from "./internal/tracer.ts" +import * as Iterable from "./Iterable.ts" +import * as Latch from "./Latch.ts" +import type * as Layer from "./Layer.ts" +import type { Severity } from "./LogLevel.ts" +import * as MutableHashMap from "./MutableHashMap.ts" +import * as MutableList from "./MutableList.ts" +import * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { Predicate, Refinement } from "./Predicate.ts" +import { hasProperty, isNotUndefined, isTagged } from "./Predicate.ts" +import * as PubSub from "./PubSub.ts" +import * as Pull from "./Pull.ts" +import * as Queue from "./Queue.ts" +import * as RcMap from "./RcMap.ts" +import * as RcRef from "./RcRef.ts" +import * as Result from "./Result.ts" +import * as Schedule from "./Schedule.ts" +import * as Scope from "./Scope.ts" +import * as Sink from "./Sink.ts" +import { isString } from "./String.ts" +import type * as Take from "./Take.ts" +import type { ParentSpan, SpanOptions } from "./Tracer.ts" +import type { + Covariant, + ExcludeReason, + ExcludeTag, + ExtractReason, + ExtractTag, + NarrowReason, + NoInfer, + OmitReason, + ReasonTags, + Tags, + TupleOf, + unassigned +} from "./Types.ts" +import type * as Unify from "./Unify.ts" + +/** + * String literal type used as the unique brand for `Stream` values. + * + * @category type IDs + * @since 4.0.0 + */ +export type TypeId = "~effect/Stream" + +/** + * Runtime identifier stored on `Stream` values and used by `isStream` to + * recognize them. + * + * **Details** + * + * This marker is part of the runtime representation of `Stream` values. Prefer + * `isStream` when narrowing unknown values. + * + * @see {@link isStream} for the public guard that checks this identifier + * + * @category type IDs + * @since 4.0.0 + */ +export const TypeId: TypeId = "~effect/Stream" + +/** + * A `Stream` describes a program that can emit many `A` values, fail + * with `E`, and require `R`. + * + * **Details** + * + * Streams are pull-based with backpressure and emit chunks to amortize effect + * evaluation. They support monadic composition and error handling similar to + * `Effect`, adapted for multiple values. + * + * **Example** (Creating and consuming streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const values = await Effect.runPromise( + * Stream.make(1, 2, 3).pipe( + * Stream.map((n) => n * 2), + * Stream.runCollect + * ) + * ) + * values // => [2, 4, 6] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Stream extends Variance, Pipeable { + readonly channel: Channel.Channel, E, void, unknown, unknown, unknown, R> + [Unify.typeSymbol]?: unknown + [Unify.unifySymbol]?: StreamUnify + [Unify.ignoreSymbol]?: StreamUnifyIgnore +} + +/** + * Type-level unification hook for Stream within the Effect type system. + * + * @category models + * @since 2.0.0 + */ +export interface StreamUnify extends Effect.EffectUnify { + Stream?: () => A[Unify.typeSymbol] extends Stream | infer _ ? Stream : never +} + +/** + * Type-level marker that excludes Stream from unification. + * + * @category models + * @since 2.0.0 + */ +export interface StreamUnifyIgnore { + Effect?: true +} + +/** + * Type lambda for Stream used in higher-kinded type operations. + * + * **Example** (Using the stream type lambda) + * + * ```ts import.meta.vitest + * import { Effect, HKT, Stream } from "effect" + * + * // Create a Stream type using the type lambda + * type NumberStream = HKT.Kind + * // Equivalent to: Stream + * const stream: NumberStream = Stream.make(1, 2, 3) + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export interface StreamTypeLambda extends TypeLambda { + readonly type: Stream +} + +/** + * Type-level variance marker for `Stream`. + * + * **Details** + * + * The emitted value `A`, error `E`, and service requirement `R` type + * parameters are covariant. + * + * @category models + * @since 2.0.0 + */ +export interface Variance { + readonly [TypeId]: VarianceStruct +} + +/** + * Structural encoding used by `Variance` to record each `Stream` type + * parameter's variance. + * + * **Details** + * + * `_A`, `_E`, and `_R` are covariant markers. + * + * @category models + * @since 3.4.0 + */ +export interface VarianceStruct { + readonly _A: Covariant + readonly _E: Covariant + readonly _R: Covariant +} + +/** + * Extract the success type from a Stream type. + * + * **Example** (Extracting the success type from a Stream type) + * + * ```ts import.meta.vitest + * import { Stream } from "effect" + * + * type NumberStream = Stream.Stream + * type SuccessType = Stream.Success + * const value: SuccessType = 42 + * ``` + * + * @category utility types + * @since 3.4.0 + */ +export type Success> = [T] extends [Stream] ? _A : never + +/** + * Extract the error type from a Stream type. + * + * **Example** (Extracting the error type from a Stream type) + * + * ```ts import.meta.vitest + * import { Stream } from "effect" + * + * type NumberStream = Stream.Stream + * type ErrorType = Stream.Error + * const error: ErrorType = "boom" + * ``` + * + * @category utility types + * @since 3.4.0 + */ +export type Error> = [T] extends [Stream] ? _E : never + +/** + * Extract the services type from a Stream type. + * + * **Example** (Extracting the services type from a Stream type) + * + * ```ts import.meta.vitest + * import { Stream } from "effect" + * + * interface Database { + * query: (sql: string) => unknown + * } + * type NumberStream = Stream.Stream + * type RequiredServices = Stream.Services + * const services: RequiredServices = { db: { query: (sql) => sql } } + * services.db.query("SELECT 1") // => "SELECT 1" + * ``` + * + * @category utility types + * @since 4.0.0 + */ +export type Services> = [T] extends [Stream] ? _R + : never + +/** + * Checks whether a value is a Stream. + * + * **Example** (Checking whether a value is a Stream) + * + * ```ts import.meta.vitest + * import { Stream } from "effect" + * + * Stream.isStream(Stream.make(1, 2, 3)) // => true + * Stream.isStream({ data: [1, 2, 3] }) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isStream = (u: unknown): u is Stream => hasProperty(u, TypeId) + +/** + * The default chunk size used by Stream constructors and combinators. + * + * **Example** (Reading the default chunk size) + * + * ```ts import.meta.vitest + * import { Stream } from "effect" + * + * Stream.DefaultChunkSize // => 4096 + * ``` + * + * @category constants + * @since 2.0.0 + */ +export const DefaultChunkSize: number = Channel.DefaultChunkSize + +/** + * Describes how merged streams decide when to halt. + * + * @category models + * @since 4.0.0 + */ +export type HaltStrategy = Channel.HaltStrategy + +/** + * Creates a stream from a array-emitting `Channel`. + * + * **Example** (Creating a stream from an array-emitting channel) + * + * ```ts import.meta.vitest + * import { Channel, Effect, Stream } from "effect" + * + * const channel = Channel.succeed([1, 2, 3] as const) + * const stream = Stream.fromChannel(channel) + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromChannel: , E, R>( + channel: Channel.Channel +) => Stream ? A : never, E, R> = internal.fromChannel + +/** + * Creates a stream from an effect. + * + * **Example** (Creating a stream from an effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.fromEffect(Effect.succeed(42)) + * await Effect.runPromise(Stream.runCollect(stream)) // => [42] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromEffect = (effect: Effect.Effect): Stream => + fromChannel(Channel.fromEffect(Effect.map(effect, Arr.of))) + +/** + * Accesses a service from the context and emits it as a single element. + * + * **Example** (Accessing a service as a stream) + * + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" + * + * class Greeter extends Context.Service string + * }>()("Greeter") {} + * + * const stream = Stream.service(Greeter).pipe( + * Stream.map((greeter) => greeter.greet("World")) + * ) + * + * await Effect.runPromise( + * stream.pipe( + * Stream.provideService(Greeter, { + * greet: (name) => `Hello, ${name}!` + * }), + * Stream.runCollect + * ) + * ) // => ["Hello, World!"] + * ``` + * + * @category accessors + * @since 4.0.0 + */ +export const service = (service: Context.Key): Stream => fromEffect(Effect.service(service)) + +/** + * Optionally accesses a service from the context and emits the result as a + * single element. + * + * **When to use** + * + * Use when you need a stream that emits an optional service from the context + * without requiring that service to be present. + * + * **Example** (Accessing an optional service as a stream) + * + * ```ts import.meta.vitest + * import { Context, Effect, Option, Stream } from "effect" + * + * class Greeter extends Context.Service string + * }>()("Greeter") {} + * + * const stream = Stream.serviceOption(Greeter).pipe( + * Stream.map((maybeGreeter) => + * Option.match(maybeGreeter, { + * onNone: () => "No greeter", + * onSome: (greeter) => greeter.greet("World") + * }) + * ) + * ) + * + * await Effect.runPromise( + * stream.pipe( + * Stream.provideService(Greeter, { + * greet: (name) => `Hello, ${name}!` + * }), + * Stream.runCollect + * ) + * ) // => ["Hello, World!"] + * ``` + * + * @category accessors + * @since 4.0.0 + */ +export const serviceOption = (service: Context.Key): Stream> => + fromEffect(Effect.serviceOption(service)) + +/** + * Creates a stream that runs the effect and emits no elements. + * + * **Example** (Draining an effect into a stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * let drained = false + * await Effect.runPromise( + * Stream.fromEffectDrain(Effect.sync(() => { + * drained = true + * })).pipe(Stream.runDrain) + * ) + * drained // => true + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromEffectDrain = (effect: Effect.Effect): Stream => + fromPull(Effect.succeed(Effect.flatMap(effect, () => Cause.done()))) + +/** + * Creates a stream from an effect producing a value of type `A` which repeats forever. + * + * **Example** (Repeating an effect forever) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * let n = 0 + * const stream = Stream.fromEffectRepeat(Effect.sync(() => ++n)).pipe(Stream.take(5)) + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3, 4, 5] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromEffectRepeat = (effect: Effect.Effect): Stream, R> => + fromPull(Effect.succeed(Effect.map(effect, Arr.of))) + +/** + * Creates a stream from an effect producing a value of type `A`, which is + * repeated using the specified schedule. + * + * **Example** (Repeating an effect with a schedule) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" + * + * const stream = Stream.fromEffectSchedule(Effect.succeed("ping"), Schedule.recurs(2)) + * await Effect.runPromise(Stream.runCollect(stream)) // => ["ping", "ping", "ping"] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromEffectSchedule = ( + effect: Effect.Effect, + schedule: Schedule.Schedule +): Stream => + fromPull(Effect.gen(function*() { + const step = yield* Schedule.toStepWithMetadata(schedule) + let s = yield* Effect.provideService(effect, Schedule.CurrentMetadata, Schedule.CurrentMetadata.defaultValue()) + let initial = true + const pull = Effect.suspend(() => step(s as AS)).pipe( + Effect.flatMap((meta) => Effect.provideService(effect, Schedule.CurrentMetadata, meta)), + Effect.map((next) => { + s = next + return Arr.of(next) + }) + ) as Pull.Pull, E | ES, void, R | RS> + return Effect.suspend(() => { + if (initial) { + initial = false + return Effect.succeed(Arr.of(s)) + } + return pull + }) + })) + +/** + * Creates a stream that emits `void` immediately once, then emits another + * `void` after each specified interval. + * + * **Example** (Emitting ticks on an interval) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Stream.tick(0).pipe(Stream.take(3), Stream.runCollect)) // => [undefined, undefined, undefined] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const tick = (interval: Duration.Input): Stream => + fromPull(Effect.sync(() => { + let first = true + const effect = Effect.succeed(Arr.of(undefined)) + const delayed = Effect.delay(effect, interval) + return Effect.suspend(() => { + if (first) { + first = false + return effect + } + return delayed + }) + })) + +/** + * Creates a stream from a pull effect, such as one produced by `Stream.toPull`. + * + * **Details** + * + * A pull effect yields chunks on demand and completes when the upstream stream ends. + * See `Stream.toPull` for a matching producer. + * + * **Example** (Creating a stream from a pull effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const source = Stream.make(1, 2, 3) + * const pull = yield* Stream.toPull(source) + * const stream = Stream.fromPull(Effect.succeed(pull)) + * return yield* Stream.runCollect(stream) + * }) + * ) + * + * await Effect.runPromise(program) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromPull = ( + pull: Effect.Effect, E, void, R>, EX, RX> +): Stream | EX, R | RX> => fromChannel(Channel.fromPull(pull)) + +/** + * Derives a stream by transforming its pull effect. + * + * **Example** (Transforming a pull effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * + * const transformed = Stream.transformPull(stream, (pull) => Effect.succeed(pull)) + * + * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const transformPull = ( + self: Stream, + f: (pull: Pull.Pull, E, void>, scope: Scope.Scope) => Effect.Effect< + Pull.Pull, E2, void, R2>, + EX, + RX + > +): Stream, R | R2 | RX> => + fromChannel( + Channel.fromTransform((_, scope) => + Effect.flatMap(Channel.toPullScoped(self.channel, scope), (pull) => f(pull as any, scope)) + ) + ) + +/** + * Transforms a stream by effectfully transforming its pull effect. + * + * **Details** + * + * A forked scope is also provided to the transformation function, which is + * closed once the resulting stream has finished processing. + * + * **Example** (Transforming a stream by effectfully transforming its pull effect) + * + * ```ts import.meta.vitest + * import { Effect, Scope, Stream } from "effect" + * + * const finalized: Array = [] + * const stream = Stream.make(1, 2, 3) + * + * const transformed = Stream.transformPullBracket( + * stream, + * (pull, _scope, forkedScope) => + * Effect.gen(function*() { + * yield* Scope.addFinalizer(forkedScope, Effect.sync(() => finalized.push(true))) + * return pull + * }) + * ) + * + * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] + * finalized // => [true] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const transformPullBracket = ( + self: Stream, + f: ( + pull: Pull.Pull, E, void, R>, + scope: Scope.Scope, + forkedScope: Scope.Scope + ) => Effect.Effect< + Pull.Pull, E2, void, R2>, + EX, + RX + > +): Stream, R | R2 | RX> => + fromChannel( + Channel.fromTransformBracket((_, scope, forkedScope) => + Effect.flatMap(Channel.toPullScoped(self.channel, scope), (pull) => f(pull, scope, forkedScope)) + ) + ) + +/** + * Creates a channel from a stream. + * + * **Example** (Converting a stream to a channel) + * + * ```ts import.meta.vitest + * import { Channel, Effect, Stream } from "effect" + * + * const channel = Stream.toChannel(Stream.make(1, 2, 3)) + * const values = await Effect.runPromise(Channel.runCollect(channel)) + * values.flat() // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const toChannel = ( + stream: Stream +): Channel.Channel, E, void, unknown, unknown, unknown, R> => stream.channel + +/** + * Creates a stream from a callback that can emit values into a queue. + * + * **When to use** + * + * Use when you need callback-based code to emit stream values by offering to a + * `Queue`, or signal stream completion through the `Queue` module APIs. + * + * By default it uses an "unbounded" buffer size. + * You can customize the buffer size and strategy by passing an object as the + * second argument with the `bufferSize` and `strategy` fields. + * + * **Example** (Creating a stream from a callback that can emit values into a queue) + * + * ```ts import.meta.vitest + * import { Effect, Queue, Stream } from "effect" + * + * const stream = Stream.callback((queue) => + * Effect.sync(() => { + * // Emit values to the stream + * Queue.offerUnsafe(queue, 1) + * Queue.offerUnsafe(queue, 2) + * Queue.offerUnsafe(queue, 3) + * // Signal completion + * Queue.endUnsafe(queue) + * }) + * ) + * + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const callback = ( + f: (queue: Queue.Queue) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + } +): Stream> => fromChannel(Channel.callbackArray(f, options)) + +/** + * Creates an empty stream. + * + * **Example** (Creating an empty stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Stream.runCollect(Stream.empty)) // => [] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty: Stream = fromChannel(Channel.empty) + +/** + * Creates a single-valued pure stream. + * + * **Example** (Creating a single-valued pure stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Stream.runCollect(Stream.succeed(3))) // => [3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const succeed = (value: A): Stream => fromChannel(Channel.succeed(Arr.of(value))) + +/** + * Creates a stream from a sequence of values. + * + * **Example** (Creating a stream from a sequence of values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = >(...values: As): Stream => fromArray(values) + +/** + * Creates a stream that synchronously evaluates a function and emits the result as a single value. + * + * **Details** + * + * The function is evaluated each time the stream is run. + * + * **Example** (Evaluating a value synchronously) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Stream.sync(() => 2 + 1).pipe(Stream.runCollect)) // => [3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const sync = (evaluate: LazyArg): Stream => fromChannel(Channel.sync(() => Arr.of(evaluate()))) + +/** + * Creates a lazily constructed stream. + * + * **Details** + * + * The stream factory is evaluated each time the stream is run. + * + * **Example** (Creating a lazily constructed stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Stream.suspend(() => Stream.make(1, 2, 3)).pipe(Stream.runCollect)) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const suspend = (stream: LazyArg>): Stream => + fromChannel(Channel.suspend(() => stream().channel)) + +/** + * Terminates with the specified error. + * + * **Example** (Failing a stream) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Stream } from "effect" + * + * await Effect.runPromise(Effect.exit(Stream.runCollect(Stream.fail("Uh oh!")))) // => Exit.fail("Uh oh!") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fail = (error: E): Stream => fromChannel(Channel.fail(error)) + +/** + * Terminates with the specified lazily evaluated error. + * + * **Example** (Failing a stream lazily) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Stream } from "effect" + * + * const stream = Stream.failSync(() => "Uh oh!") + * + * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Uh oh!") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failSync = (evaluate: LazyArg): Stream => fromChannel(Channel.failSync(evaluate)) + +/** + * Creates a stream that fails with the specified `Cause`. + * + * **Example** (Failing with a cause) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Stream } from "effect" + * + * const stream = Stream.failCause(Cause.fail("Database connection failed")).pipe( + * Stream.catchCause(() => Stream.succeed("recovered")) + * ) + * + * await Effect.runPromise(Stream.runCollect(stream)) // => ["recovered"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCause = (cause: Cause.Cause): Stream => fromChannel(Channel.failCause(cause)) + +/** + * The stream that dies with the specified defect. + * + * **Example** (Dying with a defect) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Stream } from "effect" + * + * const defect = new Error("Boom") + * const stream = Stream.die(defect) + * + * await Effect.runPromise(Effect.exit(Stream.runCollect(stream))) // => Exit.failCause(Cause.die(defect)) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const die = (defect: unknown): Stream => fromChannel(Channel.die(defect)) + +/** + * The stream that always fails with the specified lazily evaluated `Cause`. + * + * **Example** (Failing with a lazy cause) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Stream } from "effect" + * + * const stream = Stream.failCauseSync(() => + * Cause.fail("Connection timeout after retries") + * ) + * + * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Connection timeout after retries") + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const failCauseSync = (evaluate: LazyArg>): Stream => + fromChannel(Channel.failCauseSync(evaluate)) + +/** + * Creates a stream that consumes values from an iterator. + * + * **Details** + * + * The `maxChunkSize` parameter controls how many values are pulled per chunk. + * + * **Example** (Consuming values from an iterator) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * function* numbers() { + * yield 1 + * yield 2 + * yield 3 + * } + * + * const stream = Stream.fromIteratorSucceed(numbers()) + * + * const program = Effect.gen(function* () { + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIteratorSucceed = (iterator: IterableIterator, maxChunkSize?: number): Stream => + fromChannel(Channel.fromIteratorArray(() => iterator, maxChunkSize)) + +/** + * Creates a new `Stream` from an iterable collection of values. + * + * **Details** + * + * - `chunkSize`: Maximum number of values emitted per chunk. + * + * **Example** (Creating a stream from an iterable) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const numbers = [1, 2, 3] + * + * const program = Effect.gen(function*() { + * const stream = Stream.fromIterable(numbers) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = ( + iterable: Iterable, + options?: { + readonly chunkSize?: number | undefined + } +): Stream => + Array.isArray(iterable) && options?.chunkSize === undefined + ? fromArray(iterable) + : fromChannel(Channel.fromIterableArray(iterable, options?.chunkSize)) + +/** + * Creates a stream from an effect producing an iterable of values. + * + * **When to use** + * + * Use when the iterable must be acquired from an Effect before the stream emits, + * and acquisition services or failures should be part of the stream. + * + * **Example** (Creating a stream from an iterable effect) + * + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" + * + * class UserRepo extends Context.Service> + * }>()("UserRepo") {} + * + * const listUsers = Effect.service(UserRepo).pipe( + * Effect.andThen((repo) => repo.list) + * ) + * + * const stream = Stream.fromIterableEffect(listUsers) + * + * const program = Effect.gen(function*() { + * const users = yield* stream.pipe( + * Stream.provideService(UserRepo, { + * list: Effect.succeed(["user1", "user2"]) + * }), + * Stream.runCollect + * ) + * users // => ["user1", "user2"] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterableEffect = (iterable: Effect.Effect, E, R>): Stream => + unwrap(Effect.map(iterable, fromIterable)) + +/** + * Creates a stream by repeatedly running an effect that yields an iterable of values. + * + * **Example** (Repeating an iterable effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.fromIterableEffectRepeat(Effect.succeed([1, 2])).pipe( + * Stream.take(5) + * ) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 1, 2, 1] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromIterableEffectRepeat = ( + iterable: Effect.Effect, E, R> +): Stream, R> => flatMap(fromEffectRepeat(iterable), fromIterable) + +/** + * Creates a stream from an array of values. + * + * **Example** (Creating a stream from an array of values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.fromArray([1, 2, 3]) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromArray = (array: ReadonlyArray): Stream => + Arr.isReadonlyArrayNonEmpty(array) ? fromChannel(Channel.succeed(array)) : empty + +/** + * Creates a stream from an effect that produces an array of values. + * + * **When to use** + * + * Use when the array must be acquired from an Effect before the stream emits, + * and acquisition services or failures should be part of the stream. + * + * **Example** (Creating a stream from an effect that produces an array of values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.fromArrayEffect(Effect.succeed(["Ada", "Grace"])) + * const values = yield* Stream.runCollect(stream) + * values // => ["Ada", "Grace"] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromArrayEffect = ( + effect: Effect.Effect, E, R> +): Stream, R> => unwrap(Effect.map(effect, fromArray)) as any + +/** + * Creates a stream from an arbitrary number of arrays. + * + * **Example** (Creating a stream from an arbitrary number of arrays) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.fromArrays([1, 2], [3, 4]) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3, 4] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromArrays = >>( + ...arrays: Arr +): Stream => fromChannel(Channel.fromArray(Arr.filter(arrays, Arr.isReadonlyArrayNonEmpty))) + +/** + * Creates a stream that pulls values from a `Queue.Dequeue`. + * + * **Details** + * + * The stream emits non-empty batches of queued values and ends when the queue + * fails with `Cause.Done`; other queue failures are propagated. + * + * **Example** (Creating a stream from a queue of values) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.unbounded() + * yield* Queue.offer(queue, 1) + * yield* Queue.offer(queue, 2) + * yield* Queue.offer(queue, 3) + * yield* Queue.end(queue) + * + * const stream = Stream.fromQueue(queue) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromQueue = (queue: Queue.Dequeue): Stream> => + fromChannel(Channel.fromQueueArray(queue)) + +/** + * Creates a stream from a subscription to a `PubSub`. + * + * **Example** (Creating a stream from a subscription to a PubSub) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, PubSub, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.unbounded({ replay: 3 }) + * + * const fiber = yield* Stream.fromPubSub(pubsub).pipe( + * Stream.take(3), + * Stream.runCollect, + * Effect.forkChild + * ) + * + * yield* PubSub.publish(pubsub, 1) + * yield* PubSub.publish(pubsub, 2) + * yield* PubSub.publish(pubsub, 3) + * + * const values = yield* Fiber.join(fiber) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromPubSub = (pubsub: PubSub.PubSub): Stream => fromChannel(Channel.fromPubSubArray(pubsub)) + +/** + * Creates a stream from a PubSub of `Take` values. + * + * **Details** + * + * `Take` values include end and failure signals. + * + * **Example** (Creating a stream from PubSub takes) + * + * ```ts import.meta.vitest + * import { Effect, Exit, PubSub, Stream, Take } from "effect" + * + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.unbounded>({ + * replay: 3 + * }) + * + * yield* PubSub.publish(pubsub, [1]) + * yield* PubSub.publish(pubsub, [2]) + * yield* PubSub.publish(pubsub, Exit.succeed(undefined)) + * + * const values = yield* Stream.fromPubSubTake(pubsub).pipe(Stream.runCollect) + * values // => [1, 2] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromPubSubTake = (pubsub: PubSub.PubSub>): Stream => + fromChannel(Channel.fromPubSubTake(pubsub)) + +/** + * Creates a stream from a lazily supplied Web `ReadableStream`. + * + * **Details** + * + * The stream reads from a `ReadableStreamDefaultReader`, maps read failures + * with `onError`, and closes the reader when the stream finalizes. By default + * the reader is canceled; set `releaseLockOnEnd` to release the lock instead. + * + * **Example** (Creating a stream from a ReadableStream) + * + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" + * + * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} + * + * const readableStream = new ReadableStream({ + * start(controller) { + * controller.enqueue(1) + * controller.enqueue(2) + * controller.enqueue(3) + * controller.close() + * } + * }) + * + * const program = Effect.gen(function*() { + * const stream = Stream.fromReadableStream({ + * evaluate: () => readableStream, + * onError: (cause) => new StreamError({ cause }) + * }) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromReadableStream = ( + options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly releaseLockOnEnd?: boolean | undefined + } +): Stream => fromChannel(Channel.fromReadableStream(options)) + +/** + * Creates a stream from an AsyncIterable. + * + * **Example** (Creating a stream from an AsyncIterable) + * + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" + * + * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} + * + * const iterable = (async function*() { + * yield 1 + * yield 2 + * yield 3 + * })() + * + * await Effect.runPromise(Effect.gen(function*() { + * const stream = Stream.fromAsyncIterable(iterable, (cause) => new StreamError({ cause })) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * })) + * + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromAsyncIterable = ( + iterable: AsyncIterable, + onError: (error: unknown) => E +): Stream => fromChannel(Channel.fromAsyncIterableArray(iterable, onError)) + +/** + * Creates a stream that emits each output of a schedule that does not require input, + * for as long as the schedule continues. + * + * **Example** (Creating a stream from a schedule) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const schedule = Schedule.recurs(3) + * const stream = Stream.fromSchedule(schedule) + * const values = yield* Stream.runCollect(stream) + * values // => [0, 1, 2] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromSchedule = (schedule: Schedule.Schedule): Stream => + fromPull( + Effect.map( + Schedule.toStepWithSleep(schedule), + (step) => Pull.catchDone(Effect.map(step(void 0), Arr.of), () => Cause.done()) + ) + ) + +/** + * Creates a stream from a PubSub subscription. + * + * **When to use** + * + * Use when you already have a `PubSub.Subscription` and want to expose its + * messages as a `Stream`, with `Stream.take` or cancellation controlling how + * many values are consumed. + * + * **Example** (Creating a stream from a PubSub subscription) + * + * ```ts import.meta.vitest + * import { Effect, PubSub, Stream } from "effect" + * + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.unbounded() + * const subscription = yield* PubSub.subscribe(pubsub) + * + * yield* PubSub.publish(pubsub, 1) + * yield* PubSub.publish(pubsub, 2) + * + * const stream = Stream.fromSubscription(subscription) + * const values = yield* stream.pipe(Stream.take(2), Stream.runCollect) + * values // => [1, 2] + * })) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromSubscription = (pubsub: PubSub.Subscription): Stream => + fromChannel(Channel.fromSubscriptionArray(pubsub)) + +/** + * Interface representing an event listener target. + * + * @category models + * @since 3.4.0 + */ +export interface EventListener { + addEventListener( + event: string, + f: (event: A) => void, + options?: { + readonly capture?: boolean + readonly passive?: boolean + readonly once?: boolean + readonly signal?: AbortSignal + } | boolean + ): void + removeEventListener( + event: string, + f: (event: A) => void, + options?: { + readonly capture?: boolean + } | boolean + ): void +} + +/** + * Creates a stream from an event listener. + * + * **Example** (Creating a stream from an event listener) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * class NumberTarget implements Stream.EventListener { + * addEventListener(event: string, f: (event: number) => void) { + * if (event === "data") { + * f(1) + * f(2) + * f(3) + * } + * } + * removeEventListener(_event: string, _f: (event: number) => void) {} + * } + * + * await Effect.runPromise(Effect.gen(function*() { + * const stream = Stream.fromEventListener(new NumberTarget(), "data").pipe( + * Stream.take(3) + * ) + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * })) + * + * ``` + * + * @category constructors + * @since 3.1.0 + */ +export const fromEventListener = ( + target: EventListener, + type: string, + options?: boolean | { + readonly capture?: boolean + readonly passive?: boolean + readonly once?: boolean + readonly bufferSize?: number | undefined + } | undefined +): Stream => + callback((queue) => { + function emit(event: A) { + Queue.offerUnsafe(queue, event) + } + return Effect.acquireRelease( + Effect.sync(() => target.addEventListener(type, emit, options)), + () => Effect.sync(() => target.removeEventListener(type, emit, options)) + ) + }, { bufferSize: typeof options === "object" ? options.bufferSize : undefined }) + +/** + * Creates a stream by repeatedly applying an effectful step function to a + * state. + * + * **Details** + * + * Each `readonly [value, nextState]` result emits `value` and continues with + * `nextState`; returning `undefined` ends the stream. + * + * **Example** (Unfolding stream state) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.unfold(1, (n) => Effect.succeed([n, n + 1] as const)) + * const values = yield* Stream.runCollect(stream.pipe(Stream.take(5))) + * values // => [ 1, 2, 3, 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unfold = ( + s: S, + f: (s: S) => Effect.Effect +): Stream => + fromPull(Effect.sync(() => { + let state = s + return Effect.flatMap(Effect.suspend(() => f(state)), (next) => { + if (next === undefined) return Cause.done() + state = next[1] + return Effect.succeed(Arr.of(next[0])) + }) + })) + +/** + * Creates a stream by repeatedly evaluating an effectful page function. + * + * **When to use** + * + * Use to consume paginated APIs where each step returns a batch of values + * together with an optional next state. + * + * **Details** + * + * This is similar to {@link unfold}, but each step can emit zero or more values + * and independently decide whether another state should be requested. + * + * **Example** (Paginating stream state) + * + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" + * + * const stream = Stream.paginate(0, (n: number) => + * Effect.succeed( + * [ + * [n], + * n < 3 ? Option.some(n + 1) : Option.none() + * ] as const + * )) + * + * await Effect.runPromise(Stream.runCollect(stream)) // => [0, 1, 2, 3] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const paginate = ( + s: S, + f: ( + s: S + ) => Effect.Effect, Option.Option], E, R> +): Stream => + fromPull(Effect.sync(() => { + let state = s + let done = false + return Effect.suspend(function loop(): Pull.Pull, E, void, R> { + if (done) return Cause.done() + return Effect.flatMap(f(state), ([a, s]) => { + if (Option.isNone(s)) { + done = true + } else { + state = s.value + } + if (!Arr.isReadonlyArrayNonEmpty(a)) return loop() + return Effect.succeed(a) + }) + }) + })) + +/** + * Creates an infinite stream by repeatedly applying a function to a seed value. + * + * **Example** (Iterating from a seed value) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.iterate(1, (n) => n + 1).pipe(Stream.take(3)) + * + * const program = Effect.gen(function* () { + * const values = yield* Stream.runCollect(stream) + * values // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const iterate = (value: A, next: (value: A) => A): Stream => + unfold(value, (a) => Effect.succeed([a, next(a)])) + +/** + * Constructs a stream from a range of integers, including both endpoints. + * + * **Details** + * + * If the provided `min` is greater than `max`, the stream will not emit any + * values. + * + * **Example** (Creating a numeric range) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.range(1, 5).pipe(Stream.runCollect) + * values // => [ 1, 2, 3, 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const range = ( + min: number, + max: number, + chunkSize = Channel.DefaultChunkSize +): Stream => + min > max ? empty : fromPull(Effect.sync(() => { + const size = Math.max(1, chunkSize) + let start = min + let done = false + return Effect.suspend(() => { + if (done) return Cause.done() + const remaining = max - start + 1 + if (remaining > size) { + const chunk = Arr.range(start, start + size - 1) + start += size + return Effect.succeed(chunk) + } + const chunk = Arr.range(start, start + remaining - 1) + done = true + return Effect.succeed(chunk) + }) + })) + +/** + * The stream that never produces any value or fails with any error. + * + * **Example** (Creating a never-ending stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Stream.never.pipe( + * Stream.take(0), + * Stream.runCollect + * ) + * + * await Effect.runPromise(program) // => [] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const never: Stream = fromChannel(Channel.never) + +/** + * Creates a stream produced from an `Effect`. + * + * **Example** (Unwrapping a stream effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const effect = Effect.succeed(Stream.make(1, 2, 3)) + * + * const stream = Stream.unwrap(effect) + * + * const program = Effect.gen(function*() { + * const chunk = yield* Stream.runCollect(stream) + * chunk // => [ 1, 2, 3 ] + * }) + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unwrap = ( + effect: Effect.Effect, E, R> +): Stream> => fromChannel(Channel.unwrap(Effect.map(effect, toChannel))) + +/** + * Runs a stream that requires `Scope` in a managed scope, ensuring its + * finalizers are run when the stream completes. + * + * **Example** (Scoping a stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const stream = Stream.scoped( + * Stream.fromEffect( + * Effect.acquireRelease( + * Effect.sync(() => { + * events.push("acquire") + * return "resource" + * }), + * () => Effect.sync(() => events.push("release")) + * ) + * ) + * ) + * + * await Effect.runPromise(Stream.runCollect(stream)) // => ["resource"] + * events // => ["acquire", "release"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const scoped = ( + self: Stream +): Stream> => fromChannel(Channel.scoped(self.channel)) + +/** + * Transforms the elements of this stream using the supplied function. + * + * **Example** (Mapping stream values) + * + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" + * + * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i)) + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5] + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const map: { + (f: (a: A, i: number) => B): (self: Stream) => Stream + (self: Stream, f: (a: A, i: number) => B): Stream +} = dual(2, (self: Stream, f: (a: A, i: number) => B): Stream => + suspend(() => { + let i = 0 + return fromChannel(Channel.map( + self.channel, + Arr.map((o) => f(o, i++)) + )) + })) + +/** + * Maps both the failure and success channels of a stream. + * + * **Example** (Mapping both the failure and success channels of a stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const mapper = { + * onFailure: (error: string) => `error: ${error}`, + * onSuccess: (value: number) => value * 2 + * } + * + * const program = Effect.gen(function*() { + * const success = yield* Stream.make(1, 2).pipe( + * Stream.mapBoth(mapper), + * Stream.runCollect + * ) + * success // => [ 2, 4 ] + * + * const failure = yield* Stream.fail("boom").pipe( + * Stream.mapBoth(mapper), + * Stream.catch((error: string) => Stream.succeed(error)), + * Stream.runCollect + * ) + * failure // => [ 'error: boom' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const mapBoth: { + ( + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): (self: Stream) => Stream + ( + self: Stream, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Stream +} = dual(2, ( + self: Stream, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } +): Stream => + self.pipe( + map(options.onSuccess), + mapError(options.onFailure) + )) + +/** + * Transforms each emitted chunk using the provided function, which receives the chunk and its index. + * + * **Example** (Mapping stream chunks) + * + * ```ts import.meta.vitest + * import { Array, Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.make(1, 2, 3, 4).pipe( + * Stream.rechunk(2), + * Stream.mapArray((chunk, index) => Array.map(chunk, (n) => n + index)), + * Stream.runCollect + * ) + * result // => [ 1, 2, 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 4.0.0 + */ +export const mapArray: { + ( + f: (a: Arr.NonEmptyReadonlyArray, i: number) => Arr.NonEmptyReadonlyArray + ): (self: Stream) => Stream + ( + self: Stream, + f: (a: Arr.NonEmptyReadonlyArray, i: number) => Arr.NonEmptyReadonlyArray + ): Stream +} = dual(2, ( + self: Stream, + f: (a: Arr.NonEmptyReadonlyArray, i: number) => Arr.NonEmptyReadonlyArray +): Stream => fromChannel(Channel.map(self.channel, f))) + +/** + * Maps over elements of the stream with the specified effectful function. + * + * **When to use** + * + * Use when each stream element transformation needs an Effect, service + * dependency, failure channel, or configured concurrency. + * + * **Example** (Effectfully mapping stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const stream = Stream.make(1, 2, 3) + * + * const mappedStream = stream.pipe( + * Stream.mapEffect((n) => + * Effect.sync(() => { + * events.push(`Processing: ${n}`) + * return n * 2 + * }) + * ) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(mappedStream) + * result // => [2, 4, 6] + * }) + * + * await Effect.runPromise(program) + * events // => ["Processing: 1", "Processing: 2", "Processing: 3"] + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const mapEffect: { + ( + f: (a: A, i: number) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } | undefined + ): (self: Stream) => Stream + ( + self: Stream, + f: (a: A, i: number) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } | undefined + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + f: (a: A, i: number) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } | undefined +): Stream => + self.channel.pipe( + Channel.flattenArray, + Channel.mapEffect(f, options), + Channel.map(Arr.of), + fromChannel + )) + +/** + * Flattens a stream of `Effect` values into a stream of their results. + * + * **When to use** + * + * Use when stream elements already are effects and their successes should become + * stream elements while their failures enter the stream error channel. + * + * **Example** (Flattening a stream of Effect values into a stream of their results) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(Effect.succeed(1), Effect.succeed(2), Effect.succeed(3)) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream.pipe(Stream.flattenEffect())) + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const flattenEffect: < + Arg extends Stream, any, any> | { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } | undefined = { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } +>( + selfOrOptions?: Arg, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } | undefined +) => [Arg] extends [Stream, infer _E, infer _R>] ? + Stream<_A, _EX | _E, _RX | _R> + : (self: Stream, E, R>) => Stream = dual( + (args) => isStream(args[0]), + ( + self: Stream, E, R>, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly unordered?: boolean | undefined + } | undefined + ): Stream => mapEffect(self, identity, options) + ) + +/** + * Maps over non-empty array chunks emitted by the stream effectfully. + * + * **When to use** + * + * Use when transformation needs to see and replace each non-empty emitted chunk + * effectfully instead of mapping individual stream elements. + * + * **Example** (Effectfully mapping stream chunks) + * + * ```ts import.meta.vitest + * import { Array, Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.fromArray([1, 2, 3, 4]).pipe( + * Stream.rechunk(2), + * Stream.mapArrayEffect((chunk, index) => + * Effect.succeed(Array.map(chunk, (n) => n + index * 10)) + * ), + * Stream.runCollect + * ) + * result // => [ 1, 2, 13, 14 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 4.0.0 + */ +export const mapArrayEffect: { + ( + f: (a: Arr.NonEmptyReadonlyArray, i: number) => Effect.Effect, E2, R2> + ): (self: Stream) => Stream + ( + self: Stream, + f: (a: Arr.NonEmptyReadonlyArray, i: number) => Effect.Effect, E2, R2> + ): Stream +} = dual(2, ( + self: Stream, + f: (a: Arr.NonEmptyReadonlyArray, i: number) => Effect.Effect, E2, R2> +): Stream => fromChannel(Channel.mapEffect(self.channel, f))) + +/** + * Lifts failures and successes into a `Result`, yielding a stream that cannot fail. + * + * **Details** + * + * The stream ends after the first failure, emitting a `Result.fail` value. + * + * **Example** (Converting failures to results) + * + * ```ts import.meta.vitest + * import { Effect, Result, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const results = yield* Stream.make(1, 2).pipe( + * Stream.concat(Stream.fail("boom")), + * Stream.result, + * Stream.map(Result.match({ + * onFailure: (error) => `failure: ${error}`, + * onSuccess: (value) => `success: ${value}` + * })), + * Stream.runCollect + * ) + * results // => [ 'success: 1', 'success: 2', 'failure: boom' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const result = (self: Stream): Stream, never, R> => + self.pipe( + map(Result.succeed), + catch_((e) => succeed(Result.fail(e))) + ) + +/** + * Runs the provided effect for each element while preserving the elements. + * + * **Example** (Tapping stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const program = Effect.gen(function*() { + * const result = yield* Stream.fromArray([1, 2, 3]).pipe( + * Stream.tap((n) => Effect.sync(() => events.push(`before mapping: ${n}`))), + * Stream.map((n) => n * 2), + * Stream.tap((n) => Effect.sync(() => events.push(`after mapping: ${n}`))), + * Stream.runCollect + * ) + * + * result // => [2, 4, 6] + * }) + * + * await Effect.runPromise(program) + * events // => ["before mapping: 1", "after mapping: 2", "before mapping: 2", "after mapping: 4", "before mapping: 3", "after mapping: 6"] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const tap: { + ( + f: (a: NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + } | undefined + ): (self: Stream) => Stream + ( + self: Stream, + f: (a: NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + } | undefined + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + f: (a: NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + } | undefined +): Stream => + mapEffect( + self, + (a) => Effect.as(f(a), a), + options + )) + +/** + * Returns a stream that effectfully "peeks" at elements and failures. + * + * **Example** (Tapping values and errors) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const program = Effect.gen(function*() { + * const stream = Stream.make(1, 2).pipe( + * Stream.concat(Stream.fail("boom")), + * Stream.tapBoth({ + * onElement: (value) => Effect.sync(() => events.push(`seen: ${value}`)), + * onError: (error) => Effect.sync(() => events.push(`error: ${error}`)) + * }), + * Stream.catch(() => Stream.make(3)) + * ) + * const result = yield* Stream.runCollect(stream) + * result // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * events // => ["seen: 1", "seen: 2", "error: boom"] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const tapBoth: { + ( + options: { + readonly onElement: (a: NoInfer) => Effect.Effect + readonly onError: (a: NoInfer) => Effect.Effect + readonly concurrency?: number | "unbounded" | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + options: { + readonly onElement: (a: NoInfer) => Effect.Effect + readonly onError: (a: NoInfer) => Effect.Effect + readonly concurrency?: number | "unbounded" | undefined + } + ): Stream +} = dual(2, ( + self: Stream, + options: { + readonly onElement: (a: NoInfer) => Effect.Effect + readonly onError: (a: NoInfer) => Effect.Effect + readonly concurrency?: number | "unbounded" | undefined + } +): Stream => + self.pipe( + tapError(options.onError), + tap(options.onElement, { concurrency: options.concurrency }) + )) + +/** + * Runs a sink for all stream elements while still emitting them downstream. + * + * **Example** (Tapping values with a sink) + * + * ```ts import.meta.vitest + * import { Effect, Ref, Sink, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const seen = yield* Ref.make>([]) + * const sink = Sink.forEach((value: number) => + * Ref.update(seen, (items) => [...items, value]) + * ) + * const result = yield* Stream.make(1, 2, 3).pipe( + * Stream.tapSink(sink), + * Stream.runCollect + * ) + * const tapped = yield* Ref.get(seen) + * tapped // => [ 1, 2, 3 ] + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const tapSink: { + (sink: Sink.Sink): (self: Stream) => Stream + (self: Stream, sink: Sink.Sink): Stream +} = dual( + 2, + ( + self: Stream, + sink: Sink.Sink + ): Stream => + transformPullBracket( + self, + Effect.fnUntraced(function*(pull, _, scope) { + const upstreamLatch = Latch.makeUnsafe() + const sinkLatch = Latch.makeUnsafe() + let chunk: Arr.NonEmptyReadonlyArray | undefined = undefined + let causeSink: Cause.Cause | undefined = undefined + let sinkDone = false + let streamDone = false + + const sinkUpstream = upstreamLatch.whenOpen(Effect.suspend(() => { + if (chunk) { + const arr = chunk! + chunk = undefined + if (!streamDone) upstreamLatch.closeUnsafe() + return Effect.as(sinkLatch.open, arr) + } + return Cause.done() + })) + + yield* Effect.suspend(() => sink.transform(sinkUpstream, scope)).pipe( + (eff) => + Effect.onExitPrimitive(eff, (exit) => { + sinkDone = true + if (Exit.isFailure(exit)) { + causeSink = exit.cause + } + return sinkLatch.open + }, true), + Effect.forkIn(scope) + ) + + const pullAndOffer = pull.pipe( + Effect.flatMap((chunk_) => { + chunk = chunk_ + sinkLatch.closeUnsafe() + upstreamLatch.openUnsafe() + return Effect.as(sinkLatch.await, chunk_) + }), + Pull.catchDone(() => { + streamDone = true + sinkLatch.closeUnsafe() + upstreamLatch.openUnsafe() + return Effect.flatMap(sinkLatch.await, () => Cause.done()) + }) + ) + + return Effect.suspend((): Pull.Pull, E | E2, void, R> => { + if (causeSink) { + return Effect.failCause(causeSink) + } else if (sinkDone) { + return pull + } + return pullAndOffer + }) + }) + ) +) + +/** + * Maps each element to a stream and flattens the resulting streams. + * + * **Details** + * + * With the default sequential concurrency, inner streams are concatenated in + * input order. When `concurrency` is greater than `1` or `"unbounded"`, + * multiple inner streams may run at the same time and their outputs are merged + * as they arrive. + * + * **Example** (Flat mapping stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.flatMap((n) => Stream.make(n, n * 2)), + * Stream.runCollect + * ) + * values // => [ 1, 2, 2, 4, 3, 6 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const flatMap: { + ( + f: (a: A) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined + ): (self: Stream) => Stream + ( + self: Stream, + f: (a: A) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + f: (a: A) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined +): Stream => + self.channel.pipe( + Channel.flattenArray, + Channel.flatMap((a) => f(a).channel, options), + fromChannel + )) + +/** + * Switches to the latest stream produced by the mapping function, interrupting + * the previous stream when a new element arrives. + * + * **Example** (Switching to the latest stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Stream.make(1, 2, 3).pipe( + * Stream.switchMap((n) => (n === 3 ? Stream.make(n) : Stream.never)), + * Stream.runCollect + * ) + * + * await Effect.runPromise(Effect.gen(function*() { + * const result = yield* program + * result // => [ 3 ] + * })) + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const switchMap: { + ( + f: (a: A) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined + ): (self: Stream) => Stream + ( + self: Stream, + f: (a: A) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + f: (a: A) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined +): Stream => + self.channel.pipe( + Channel.flattenArray, + Channel.switchMap((a) => f(a).channel, options), + fromChannel + )) + +/** + * Flattens a stream of streams into a single stream. + * + * **Details** + * + * With the default sequential concurrency, inner streams are concatenated in + * strict order. When `concurrency` is greater than `1` or `"unbounded"`, + * multiple inner streams may run at the same time and their outputs are merged + * as they arrive. + * + * **Example** (Flattening nested streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const streamOfStreams = Stream.make( + * Stream.make(1, 2), + * Stream.make(3, 4), + * Stream.make(5, 6) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(Stream.flatten(streamOfStreams)) + * values // => [ 1, 2, 3, 4, 5, 6 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const flatten: < + Arg extends Stream, any, any> | { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined = { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } +>( + selfOrOptions?: Arg, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined +) => [Arg] extends [Stream, infer _E2, infer _R2>] ? Stream<_A, _E | _E2, _R | _R2> + : (self: Stream, E2, R2>) => Stream = dual( + (args) => isStream(args[0]), + ( + self: Stream, E2, R2>, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined + ): Stream => flatMap(self, identity, options) + ) + +/** + * Flattens a stream of non-empty arrays into a stream of elements. + * + * **Example** (Flattening a stream of non-empty arrays into a stream of elements) + * + * ```ts import.meta.vitest + * import { Array, Effect, Stream } from "effect" + * + * const stream = Stream.make(Array.make(1, 2), Array.make(3)) + * + * const program = Effect.gen(function* () { + * const result = yield* Stream.runCollect(Stream.flattenArray(stream)) + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const flattenArray = (self: Stream, E, R>): Stream => + fromChannel(Channel.flattenArray(self.channel)) + +/** + * Converts this stream to one that runs its effects but emits no elements. + * + * **Example** (Draining stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.range(1, 6).pipe(Stream.drain, Stream.runCollect) + * result // => [] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const drain = (self: Stream): Stream => fromChannel(Channel.drain(self.channel)) + +/** + * Runs the provided stream in the background while this stream runs, interrupting it + * when this stream completes and failing if the background stream fails or defects. + * + * **Example** (Draining a stream in the background) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const foreground = Stream.make(1, 2) + * const background = Stream.fromEffect(Effect.sync(() => events.push("background task"))) + * + * const program = Effect.gen(function*() { + * const values = yield* foreground.pipe( + * Stream.drainFork(background), + * Stream.runCollect + * ) + * values // => [1, 2] + * }) + * + * await Effect.runPromise(program) + * events // => ["background task"] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const drainFork: { + (that: Stream): (self: Stream) => Stream + (self: Stream, that: Stream): Stream +} = dual( + 2, + (self: Stream, that: Stream): Stream => + mergeEffect(self, runDrain(that)) +) + +/** + * Repeats the entire stream according to the provided schedule. + * + * **Example** (Repeating a stream on a schedule) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" + * + * const program = Effect.gen(function* () { + * const result = yield* Stream.make(1).pipe( + * Stream.repeat(Schedule.recurs(4)), + * Stream.runCollect + * ) + * + * result // => [ 1, 1, 1, 1, 1 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const repeat: { + ( + schedule: + | Schedule.Schedule + | (( + $: (_: Schedule.Schedule) => Schedule.Schedule + ) => Schedule.Schedule) + ): (self: Stream) => Stream + ( + self: Stream, + schedule: + | Schedule.Schedule + | (( + $: (_: Schedule.Schedule) => Schedule.Schedule + ) => Schedule.Schedule) + ): Stream +} = dual(2, ( + self: Stream, + schedule: + | Schedule.Schedule + | (( + $: (_: Schedule.Schedule) => Schedule.Schedule + ) => Schedule.Schedule) +): Stream => fromChannel(Channel.repeat(self.channel, schedule))) + +/** + * Schedules the stream's elements according to the provided schedule. + * + * **Example** (Scheduling stream elements) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.make(1, 2, 3).pipe( + * Stream.schedule(Schedule.recurs(3)), + * Stream.runCollect + * ) + * + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category rate limiting + * @since 2.0.0 + */ +export const schedule: { + ( + schedule: Schedule.Schedule, E2, R2> + ): (self: Stream) => Stream + ( + self: Stream, + schedule: Schedule.Schedule, E2, R2> + ): Stream +} = dual(2, ( + self: Stream, + schedule: Schedule.Schedule, E2, R2> +): Stream => + self.channel.pipe( + Channel.flattenArray, + Channel.schedule(schedule), + Channel.map(Arr.of), + fromChannel + )) + +/** + * Ends the stream if it does not produce a value within the specified duration. + * + * **Example** (Timing out a stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1).pipe( + * Stream.concat(Stream.never), + * Stream.timeout("1 second"), + * Stream.runCollect + * ) + * values // => [ 1 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category delays & timeouts + * @since 2.0.0 + */ +export const timeout: { + (duration: Duration.Input): (self: Stream) => Stream + (self: Stream, duration: Duration.Input): Stream +} = dual( + 2, + (self: Stream, duration: Duration.Input): Stream => + timeoutOrElse(self, { + duration, + orElse: () => empty + }) +) + +/** + * Switches to a fallback stream if this stream does not emit a value within + * the specified duration. + * + * **When to use** + * + * Use when a stream should continue with another stream if an upstream pull + * waits longer than the allowed duration. + * + * **Details** + * + * The timeout is checked for each pull. A zero duration uses `orElse` + * immediately, while an infinite duration leaves the original stream + * unchanged. + * + * **Gotchas** + * + * The fallback stream is not timed after the switch. + * + * @see {@link timeout} for ending the stream instead of switching to a fallback stream + * + * @category delays & timeouts + * @since 4.0.0 + */ +export const timeoutOrElse: { + (options: { + readonly duration: Duration.Input + readonly orElse: () => Stream + }): (self: Stream) => Stream + ( + self: Stream, + options: { + readonly duration: Duration.Input + readonly orElse: () => Stream + } + ): Stream +} = dual( + 2, + ( + self: Stream, + options: { + readonly duration: Duration.Input + readonly orElse: () => Stream + } + ): Stream => { + const duration = Duration.fromInputUnsafe(options.duration) + if (!Duration.isFinite(duration)) return self + if (Duration.isZero(duration)) return suspend(options.orElse) + const timeoutSymbol = Symbol() + return catchCause( + suspend(() => { + const parent = Fiber.getCurrent()! + const clock = parent.getRef(Clock) + const durationMs = Duration.toMillis(duration) + let deadline: number | undefined = undefined + const latch = Latch.makeUnsafe(false) + return merge( + transformPull(self, (pull, _scope) => + Effect.suspend(() => { + deadline = clock.currentTimeMillisUnsafe() + durationMs + latch.openUnsafe() + return pull + }).pipe( + Effect.map((arr) => { + latch.closeUnsafe() + deadline = undefined + return arr + }), + Effect.succeed + )), + fromEffectDrain(Effect.gen(function*() { + while (true) { + yield* latch.await + if (deadline === undefined) continue + yield* Effect.sleep(deadline - clock.currentTimeMillisUnsafe()) + if (deadline === undefined) continue + const remaining = deadline - clock.currentTimeMillisUnsafe() + if (remaining > 0) continue + return yield* Effect.die(timeoutSymbol) + } + })), + { haltStrategy: "left" } + ) + }), + (cause): Stream => { + const isTimeout = cause.reasons.find((r) => r._tag === "Die" && r.defect === timeoutSymbol) + if (isTimeout) return options.orElse() + return failCause(cause as Cause.Cause) + } + ) + } +) + +/** + * Repeats each element of the stream according to the provided schedule, + * including the original emission. + * + * **Example** (Repeating stream elements) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make("A", "B", "C").pipe( + * Stream.repeatElements(Schedule.recurs(1)), + * Stream.runCollect + * ) + * values // => [ 'A', 'A', 'B', 'B', 'C', 'C' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const repeatElements: { + ( + schedule: Schedule.Schedule + ): (self: Stream) => Stream + ( + self: Stream, + schedule: Schedule.Schedule + ): Stream +} = dual( + 2, + ( + self: Stream, + schedule: Schedule.Schedule + ): Stream => + fromChannel(Channel.fromTransform((upstream, scope) => + Effect.map( + Channel.toTransform(Channel.flattenArray(self.channel))(upstream, scope), + (pullElement) => { + let pullRepeat: Pull.Pull, E | E2, void, R | R2> | undefined = undefined + + const pull: Pull.Pull< + Arr.NonEmptyReadonlyArray, + E, + void, + R | R2 + > = Effect.gen(function*() { + const element = yield* pullElement + const chunk = Arr.of(element) + const step = yield* Schedule.toStepWithSleep(schedule) + pullRepeat = step(element).pipe( + Effect.as(chunk), + Pull.catchDone((_) => { + pullRepeat = undefined + return pull + }) + ) + return chunk + }) + + return Effect.suspend(() => pullRepeat ?? pull) + } + ) + )) +) + +/** + * Repeats this stream forever. + * + * **Example** (Repeating a stream forever) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make("A", "B").pipe( + * Stream.forever, + * Stream.take(5) + * ) + * + * const program = Effect.gen(function*() { + * const output = yield* Stream.runCollect(stream) + * output // => [ 'A', 'B', 'A', 'B', 'A' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const forever = (self: Stream): Stream => fromChannel(Channel.forever(self.channel)) + +/** + * Flattens the iterables emitted by this stream into the stream's structure. + * + * **Example** (Flattening iterable values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.make([1, 2], [3, 4]).pipe(Stream.flattenIterable) + * const values = yield* Stream.runCollect(stream) + * values // => [ 1, 2, 3, 4 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 4.0.0 + */ +export const flattenIterable = (self: Stream, E, R>): Stream => + flatMap(self, fromIterable) + +/** + * Unwraps `Take` values, emitting elements from non-empty arrays and ending or + * failing when the `Exit` signals completion. + * + * **Example** (Flattening Take values) + * + * ```ts import.meta.vitest + * import { Array, Effect, Exit, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const takes = Stream.make( + * Array.make(1, 2), + * Array.make(3), + * Exit.succeed(undefined) + * ) + * + * const values = yield* Stream.flattenTake(takes).pipe(Stream.runCollect) + * values // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const flattenTake = (self: Stream, E2, R>): Stream => + self.channel.pipe( + Channel.flattenArray, + Channel.flattenTake, + fromChannel + ) + +/** + * Concatenates two streams, emitting all elements from the first stream + * followed by all elements from the second stream. + * + * **Example** (Concatenating streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.concat(Stream.make(1, 2, 3), Stream.make(4, 5, 6)) + * + * await Effect.runPromise(Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 1, 2, 3, 4, 5, 6 ] + * })) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const concat: { + (that: Stream): (self: Stream) => Stream + (self: Stream, that: Stream): Stream +} = dual( + 2, + (self: Stream, that: Stream): Stream => + flatten(fromArray>([self, that])) +) + +/** + * Prepends the values from the provided iterable before the stream's elements. + * + * **Example** (Prepending values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(3, 4).pipe( + * Stream.prepend([1, 2]), + * Stream.runCollect + * ) + * + * values // => [ 1, 2, 3, 4 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const prepend: { + (values: Iterable): (self: Stream) => Stream + (self: Stream, values: Iterable): Stream +} = dual(2, ( + self: Stream, + values: Iterable +): Stream => concat(fromIterable(values), self)) + +/** + * Merges two streams, emitting elements from both as they arrive. + * + * **Details** + * + * By default, the merged stream ends when both streams end. Use + * `haltStrategy` to change the termination behavior. + * + * **Example** (Merging stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const fast = Stream.make(1, 2, 3) + * const slow = Stream.fromEffect(Effect.delay(Effect.succeed(4), "50 millis")) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(Stream.merge(fast, slow)) + * result // => [ 1, 2, 3, 4 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category merging + * @since 2.0.0 + */ +export const merge: { + ( + that: Stream, + options?: { + readonly haltStrategy?: HaltStrategy | undefined + } | undefined + ): (self: Stream) => Stream + ( + self: Stream, + that: Stream, + options?: { + readonly haltStrategy?: HaltStrategy | undefined + } | undefined + ): Stream +} = dual( + (args) => isStream(args[0]) && isStream(args[1]), + ( + self: Stream, + that: Stream, + options?: { + readonly haltStrategy?: HaltStrategy | undefined + } | undefined + ): Stream => fromChannel(Channel.merge(toChannel(self), toChannel(that), options)) +) + +/** + * Merges this stream with a background effect, keeping the stream's elements. + * + * **When to use** + * + * Use when an effect should run concurrently for the lifetime of a stream while + * only the stream's elements remain in the output. + * + * **Details** + * + * The effect runs concurrently, fails the stream if it fails, and is interrupted + * when the stream completes. + * + * **Example** (Merging with a background effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.mergeEffect(Effect.sync(() => events.push("side task"))), + * Stream.runCollect + * ) + * + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * events // => ["side task"] + * ``` + * + * @category merging + * @since 4.0.0 + */ +export const mergeEffect: { + (effect: Effect.Effect): (self: Stream) => Stream + (self: Stream, effect: Effect.Effect): Stream +} = dual( + 2, + (self: Stream, effect: Effect.Effect): Stream => + self.channel.pipe( + Channel.mergeEffect(effect), + fromChannel + ) +) + +/** + * Merges this stream and the specified stream together, tagging values from the + * left stream as `Result.succeed` and values from the right stream as `Result.fail`. + * + * **When to use** + * + * Use when values from both streams should be emitted and downstream code needs + * left values wrapped as successful `Result` values and right values wrapped as + * failed `Result` values. + * + * **Example** (Merging streams into results) + * + * ```ts import.meta.vitest + * import { Effect, Result, Stream } from "effect" + * + * const left = Stream.fromEffect(Effect.succeed("left")) + * const right = Stream.fromEffect(Effect.delay(Effect.succeed("right"), "10 millis")) + * + * const merged = left.pipe( + * Stream.mergeResult(right), + * Stream.map( + * Result.match({ + * onFailure: (value) => `right:${value}`, + * onSuccess: (value) => `left:${value}` + * }) + * ) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(merged) + * result // => [ 'left:left', 'right:right' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category merging + * @since 4.0.0 + */ +export const mergeResult: { + ( + that: Stream + ): (self: Stream) => Stream, E2 | E, R2 | R> + (self: Stream, that: Stream): Stream, E | E2, R | R2> +} = dual( + 2, + ( + self: Stream, + that: Stream + ): Stream, E | E2, R | R2> => + merge( + map(self, Result.succeed), + map(that, Result.fail) + ) +) + +/** + * Merges two streams while emitting only the values from the left stream. + * + * **When to use** + * + * Use when the right stream is needed for its effects or failures, but downstream + * consumers should only receive values from the left stream. + * + * **Details** + * + * The right stream still runs for its effects, and any failures from the right + * stream are propagated. The merged stream completes when the left stream + * completes, interrupting the right stream. + * + * **Example** (Merging streams while keeping left values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const left = Stream.make(1, 2) + * const right = Stream.make("a", "b") + * const values = yield* left.pipe(Stream.mergeLeft(right), Stream.runCollect) + * values // => [ 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category merging + * @since 2.0.0 + */ +export const mergeLeft: { + (right: Stream): (left: Stream) => Stream + (left: Stream, right: Stream): Stream +} = dual( + 2, + (left: Stream, right: Stream): Stream => + mergeEffect(left, runDrain(right)) +) + +/** + * Merges this stream and the specified stream together, emitting only the + * values from the right stream while the left stream runs for its effects. + * + * **When to use** + * + * Use when the left stream is needed for its effects or failures, but downstream + * consumers should only receive values from the right stream. + * + * **Details** + * + * The merged stream ends when the right stream completes, interrupting the + * left stream. Failures from the left stream still fail the merged stream. + * + * **Example** (Merging streams while keeping right values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const left = Stream.make("left-1", "left-2").pipe( + * Stream.tap(() => Effect.sync(() => undefined)) + * ) + * const right = Stream.make(1, 2) + * + * const merged = Stream.mergeRight(left, right) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(merged) + * result // => [ 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category merging + * @since 2.0.0 + */ +export const mergeRight: { + (right: Stream): (left: Stream) => Stream + (left: Stream, right: Stream): Stream +} = dual( + 2, + (left: Stream, right: Stream): Stream => + mergeEffect(right, runDrain(left)) +) + +/** + * Merges a collection of streams, running up to the specified number concurrently. + * + * **When to use** + * + * Use to merge an iterable of already-created streams while bounding how many + * inner streams may run at the same time. + * + * **Details** + * + * The `concurrency` option is required and may be a number or `"unbounded"`. + * `bufferSize` controls buffering between inner streams, and outputs are + * emitted as they arrive under concurrent merging. + * + * **Example** (Merging streams with bounded concurrency) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const streams = [ + * Stream.fromEffect(Effect.delay(Effect.succeed("A"), "20 millis")), + * Stream.fromEffect(Effect.delay(Effect.succeed("B"), "10 millis")) + * ] + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.mergeAll(streams, { concurrency: 2 }).pipe( + * Stream.runCollect + * ) + * values // => [ 'B', 'A' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @see {@link merge} for merging exactly two streams and choosing a halt strategy + * @see {@link flatten} for flattening a stream that already emits streams + * + * @category merging + * @since 2.0.0 + */ +export const mergeAll: { + ( + options: { + readonly concurrency: number | "unbounded" + readonly bufferSize?: number | undefined + } + ): (streams: Iterable>) => Stream + ( + streams: Iterable>, + options: { + readonly concurrency: number | "unbounded" + readonly bufferSize?: number | undefined + } + ): Stream +} = dual(2, ( + streams: Iterable>, + options: { + readonly concurrency: number | "unbounded" + readonly bufferSize?: number | undefined + } +): Stream => flatten(fromIterable(streams), options)) + +/** + * Creates the cartesian product of two streams, running the `right` stream for + * each element in the `left` stream. + * + * **Details** + * + * See also `Stream.zip` for the more common point-wise variant. + * + * **Example** (Computing cartesian products) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const left = Stream.make(1, 2) + * const right = Stream.make("a", "b") + * const values = yield* Stream.runCollect(Stream.cross(left, right)) + * values // => [ [ 1, 'a' ], [ 1, 'b' ], [ 2, 'a' ], [ 2, 'b' ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const cross: { + (right: Stream): (left: Stream) => Stream<[AL, AR], EL | ER, RL | RR> + (left: Stream, right: Stream): Stream<[AL, AR], EL | ER, RL | RR> +} = dual(2, ( + left: Stream, + right: Stream +): Stream<[AL, AR], EL | ER, RL | RR> => crossWith(left, right, (l, r) => [l, r])) + +/** + * Creates a cartesian product of elements from two streams using a function. + * + * **Details** + * + * The `right` stream is rerun for every element in the `left` stream. + * + * See also `Stream.zipWith` for the more common point-wise variant. + * + * **Example** (Combining cartesian products) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const left = Stream.make(1, 2) + * const right = Stream.make("a", "b") + * const combined = Stream.crossWith(left, right, (n, s) => `${n}-${s}`) + * const result = yield* Stream.runCollect(combined) + * result // => [ '1-a', '1-b', '2-a', '2-b' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const crossWith: { + ( + right: Stream, + f: (left: AL, right: AR) => A + ): (left: Stream) => Stream + ( + left: Stream, + right: Stream, + f: (left: AL, right: AR) => A + ): Stream +} = dual(3, ( + left: Stream, + right: Stream, + f: (left: AL, right: AR) => A +): Stream => flatMap(left, (l) => map(right, (r) => f(l, r)))) + +/** + * Zips two streams point-wise with a combining function, ending when either stream ends. + * + * **Example** (Zipping streams with a function) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream1 = Stream.make(1, 2, 3, 4, 5, 6) + * const stream2 = Stream.make("a", "b", "c") + * + * const zipped = Stream.zipWith(stream1, stream2, (n, s) => `${n}-${s}`) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(zipped) + * result // => [ '1-a', '2-b', '3-c' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWith: { + ( + right: Stream, + f: (left: AL, right: AR) => A + ): (left: Stream) => Stream + ( + left: Stream, + right: Stream, + f: (left: AL, right: AR) => A + ): Stream +} = dual(3, ( + left: Stream, + right: Stream, + f: (left: AL, right: AR) => A +): Stream => zipWithArray(left, right, zipArrays(f))) + +const zipArrays = ( + f: (left: AL, right: AR) => A +) => +( + leftArr: Arr.NonEmptyReadonlyArray, + rightArr: Arr.NonEmptyReadonlyArray +) => { + const minLength = Math.min(leftArr.length, rightArr.length) + const result: Arr.NonEmptyArray = [] as any + + for (let i = 0; i < minLength; i++) { + result.push(f(leftArr[i], rightArr[i])) + } + + return [result, leftArr.slice(minLength), rightArr.slice(minLength)] as const +} + +/** + * Zips two streams by applying a function to non-empty arrays of elements. + * + * **Details** + * + * The function returns output plus leftover arrays that carry into the next pull. + * + * **Example** (Zipping stream chunks) + * + * ```ts import.meta.vitest + * import { Array, Effect, Stream } from "effect" + * + * const left = Stream.fromArrays([1, 2, 3], [4, 5]) + * const right = Stream.fromArrays(["a", "b"], ["c", "d", "e"]) + * + * const zipped = Stream.zipWithArray(left, right, (leftChunk, rightChunk) => { + * const minLength = Math.min(leftChunk.length, rightChunk.length) + * const output = Array.makeBy(minLength, (i) => [leftChunk[i], rightChunk[i]] as const) + * + * return [output, leftChunk.slice(minLength), rightChunk.slice(minLength)] + * }) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(zipped) + * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ], [ 4, 'd' ], [ 5, 'e' ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 4.0.0 + */ +export const zipWithArray: { + ( + right: Stream, + f: ( + left: Arr.NonEmptyReadonlyArray, + right: Arr.NonEmptyReadonlyArray + ) => readonly [ + output: Arr.NonEmptyReadonlyArray, + leftoverLeft: ReadonlyArray, + leftoverRight: ReadonlyArray + ] + ): (left: Stream) => Stream + ( + left: Stream, + right: Stream, + f: ( + left: Arr.NonEmptyReadonlyArray, + right: Arr.NonEmptyReadonlyArray + ) => readonly [ + output: Arr.NonEmptyReadonlyArray, + leftoverLeft: ReadonlyArray, + leftoverRight: ReadonlyArray + ] + ): Stream +} = dual(3, ( + left: Stream, + right: Stream, + f: ( + left: Arr.NonEmptyReadonlyArray, + right: Arr.NonEmptyReadonlyArray + ) => readonly [ + output: Arr.NonEmptyReadonlyArray, + leftoverLeft: ReadonlyArray, + leftoverRight: ReadonlyArray + ] +): Stream => + fromChannel(Channel.fromTransformBracket(Effect.fnUntraced(function*(_, scope) { + const pullLeft = yield* Channel.toPullScoped(left.channel, scope) + const pullRight = yield* Channel.toPullScoped(right.channel, scope) + const pullBoth = Effect.gen(function*() { + const fiberLeft = yield* Effect.forkIn(pullLeft, scope) + const fiberRight = yield* Effect.forkIn(pullRight, scope) + return (yield* Fiber.joinAll([fiberLeft, fiberRight])) as [ + Arr.NonEmptyReadonlyArray, + Arr.NonEmptyReadonlyArray + ] + }) + + type State = + | { _tag: "PullBoth" } + | { _tag: "PullLeft"; rightArray: Arr.NonEmptyReadonlyArray } + | { _tag: "PullRight"; leftArray: Arr.NonEmptyReadonlyArray } + let state: State = { _tag: "PullBoth" } + + const pull: Effect.Effect< + Arr.NonEmptyReadonlyArray, + EL | ER | Cause.Done, + RL | RR + > = Effect.gen(function*() { + const [left, right] = state._tag === "PullBoth" + ? yield* pullBoth + : state._tag === "PullLeft" + ? [yield* pullLeft, state.rightArray] + : [state.leftArray, yield* pullRight] + const result = f(left, right) + if (Arr.isReadonlyArrayNonEmpty(result[1])) { + state = { _tag: "PullRight", leftArray: result[1] } + } else if (Arr.isReadonlyArrayNonEmpty(result[2])) { + state = { _tag: "PullLeft", rightArray: result[2] } + } else { + state = { _tag: "PullBoth" } + } + return result[0] + }) + + return pull + })))) + +/** + * Zips this stream with another point-wise and emits tuples of elements from + * both streams. The new stream ends when either stream ends. + * + * **Example** (Zipping streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream1 = Stream.make(1, 2, 3) + * const stream2 = Stream.make("a", "b", "c") + * + * const zipped = Stream.zip(stream1, stream2) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(zipped) + * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zip: { + (that: Stream): (self: Stream) => Stream<[A, A2], E2 | E, R2 | R> + (self: Stream, that: Stream): Stream<[A, A2], E | E2, R | R2> +} = dual( + 2, + ( + self: Stream, + that: Stream + ): Stream<[A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [a, a2]) +) + +/** + * Zips this stream with another point-wise and keeps only the values from + * the left stream. + * + * **Details** + * + * The resulting stream ends when either side ends. + * + * **Example** (Zipping streams while keeping left values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream1 = Stream.make(1, 2, 3, 4) + * const stream2 = Stream.make("a", "b") + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.zipLeft(stream1, stream2).pipe(Stream.runCollect) + * result // => [ 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipLeft: { + (right: Stream): (left: Stream) => Stream + (left: Stream, right: Stream): Stream +} = dual( + 2, + ( + left: Stream, + right: Stream + ): Stream => + zipWithArray(left, right, (leftArr, rightArr) => { + const minLength = Math.min(leftArr.length, rightArr.length) + const output = leftArr.slice(0, minLength) as Arr.NonEmptyArray + const leftoverLeft = leftArr.slice(minLength) + const leftoverRight = rightArr.slice(minLength) + + return [output, leftoverLeft, leftoverRight] as const + }) +) + +/** + * Zips this stream with another point-wise, keeping only right values and ending when either stream ends. + * + * **Example** (Zipping streams while keeping right values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream1 = Stream.make(1, 2) + * const stream2 = Stream.make("a", "b", "c", "d") + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.zipRight(stream1, stream2).pipe(Stream.runCollect) + * result // => [ 'a', 'b' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipRight: { + (right: Stream): (left: Stream) => Stream + (left: Stream, right: Stream): Stream +} = dual( + 2, + ( + left: Stream, + right: Stream + ): Stream => + zipWithArray(left, right, (leftArr, rightArr) => { + const minLength = Math.min(leftArr.length, rightArr.length) + const output = rightArr.slice(0, minLength) as Arr.NonEmptyArray + const leftoverLeft = leftArr.slice(minLength) + const leftoverRight = rightArr.slice(minLength) + + return [output, leftoverLeft, leftoverRight] as const + }) +) + +/** + * Zips this stream with another point-wise and emits tuples of elements from + * both streams, flattening the left tuple. + * + * **Details** + * + * The new stream will end when one of the sides ends. + * + * **Example** (Zipping and flattening tuples) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream1 = Stream.make( + * [1, "a"] as const, + * [2, "b"] as const, + * [3, "c"] as const + * ) + * const stream2 = Stream.make("x", "y", "z") + * const result = yield* Stream.zipFlatten(stream1, stream2).pipe(Stream.runCollect) + * + * result // => [ [ 1, 'a', 'x' ], [ 2, 'b', 'y' ], [ 3, 'c', 'z' ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipFlatten: { + ( + that: Stream + ): , E, R>(self: Stream) => Stream<[...A, A2], E2 | E, R2 | R> + , E, R, A2, E2, R2>( + self: Stream, + that: Stream + ): Stream<[...A, A2], E | E2, R | R2> +} = dual( + 2, + , E, R, A2, E2, R2>( + self: Stream, + that: Stream + ): Stream<[...A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [...a, a2]) +) + +/** + * Zips this stream together with the index of elements. + * + * **Example** (Zipping elements with indices) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const indexed = yield* Stream.make("a", "b", "c", "d").pipe( + * Stream.zipWithIndex, + * Stream.runCollect + * ) + * indexed // => [ [ 'a', 0 ], [ 'b', 1 ], [ 'c', 2 ], [ 'd', 3 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWithIndex = (self: Stream): Stream<[A, number], E, R> => map(self, (a, i) => [a, i]) + +/** + * Zips each element with the next element, pairing the final element with + * `Option.none()`. + * + * **Example** (Zipping elements with next values) + * + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" + * + * const stream = Stream.zipWithNext(Stream.make(1, 2, 3, 4)) + * + * await Effect.runPromise(Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [[1, Option.some(2)], [2, Option.some(3)], [3, Option.some(4)], [4, Option.none()]] + * })) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWithNext = (self: Stream): Stream<[A, Option.Option], E, R> => + mapAccumArray(self, Option.none, (acc, arr) => { + let i = 0 + if (acc._tag === "None") { + i = 1 + acc = Option.some(arr[0]) as Option.Some + } + const pairs = Arr.empty<[A, Option.Option]>() + for (; i < arr.length; i++) { + const value = acc.value + acc = Option.some(arr[i]) as Option.Some + pairs.push([value, acc]) + } + return [acc, pairs] + }, { + onHalt(state) { + return state._tag === "Some" ? [[state.value, Option.none()]] : [] + } + }) + +/** + * Zips each element with its previous element, starting with `None`. + * + * **Example** (Zipping elements with previous values) + * + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" + * + * const stream = Stream.zipWithPrevious(Stream.make(1, 2, 3, 4)) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => [[Option.none(), 1], [Option.some(1), 2], [Option.some(2), 3], [Option.some(3), 4]] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWithPrevious = (self: Stream): Stream<[Option.Option, A], E, R> => + mapAccumArray(self, Option.none, (acc, arr) => { + const pairs = Arr.empty<[Option.Option, A]>() + for (let i = 0; i < arr.length; i++) { + const value = arr[i] + pairs.push([acc, value]) + acc = Option.some(arr[i]) + } + return [acc, pairs] + }) + +/** + * Zips each element with its previous and next values. + * + * **Example** (Zipping elements with neighbors) + * + * ```ts import.meta.vitest + * import { Console, Effect, Option, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.zipWithPreviousAndNext, + * Stream.runCollect + * ) + * values // => [[Option.none(), 1, Option.some(2)], [Option.some(1), 2, Option.some(3)], [Option.some(2), 3, Option.none()]] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipWithPreviousAndNext = ( + self: Stream +): Stream<[Option.Option, A, Option.Option], E, R> => + mapAccumArray(self, () => ({ + prev: Option.none(), + current: Option.none() + }), (acc, arr) => { + let i = 0 + let current: A + if (acc.current._tag === "None") { + i = 1 + current = arr[0] + acc.current = Option.some(current) + } else { + current = acc.current.value + } + const pairs = Arr.empty<[Option.Option, A, Option.Option]>() + for (; i < arr.length; i++) { + const element = arr[i] + acc.current = Option.some(element) as Option.Some + pairs.push([acc.prev, current, acc.current]) + acc.prev = Option.some(current) + current = element + } + return [acc, pairs] + }, { + onHalt(acc) { + return acc.current._tag === "Some" ? [[acc.prev, acc.current.value, Option.none()]] : [] + } + }) + +/** + * Zips multiple streams so that when a value is emitted by any stream, it is + * combined with the latest values from the other streams to produce a result. + * + * **When to use** + * + * Use when each stream should contribute its latest value after all streams have + * emitted at least once. + * + * **Gotchas** + * + * Note: tracking the latest value is done on a per-array basis. That means + * that emitted elements that are not the last value in arrays will never be + * used for zipping. + * + * **Example** (Zipping latest values from many streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.zipLatestAll( + * Stream.make(1, 2, 3).pipe(Stream.rechunk(1)), + * Stream.make("a", "b", "c").pipe(Stream.rechunk(1)), + * Stream.make(true, false, true).pipe(Stream.rechunk(1)) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => [[1, "a", true], [2, "a", true], [2, "b", true], [2, "b", false], [3, "b", false], [3, "c", false], [3, "c", true]] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 3.3.0 + */ +export const zipLatestAll = >>( + ...streams: T +): Stream< + [T[number]] extends [never] ? never + : { [K in keyof T]: T[K] extends Stream ? A : never }, + [T[number]] extends [never] ? never : T[number] extends Stream ? _E : never, + [T[number]] extends [never] ? never : T[number] extends Stream ? _R : never +> => + fromChannel(Channel.suspend(() => { + const latest: Array = [] + const emitted = new Set() + const readyLatch = Latch.makeUnsafe() + return Channel.mergeAll( + Channel.fromArray( + streams.map((s, i) => + s.channel.pipe( + Channel.flattenArray, + Channel.mapEffect((a) => { + latest[i] = a + if (!emitted.has(i)) { + emitted.add(i) + if (emitted.size < streams.length) { + return readyLatch.await as Effect.Effect + } + return Effect.as(readyLatch.open, Arr.of(latest.slice())) + } + return Effect.succeed(Arr.of(latest.slice())) + }), + Channel.filter(isNotUndefined) + ) + ) + ), + { + concurrency: "unbounded", + bufferSize: 0 + } + ) + })) as any + +/** + * Combines two streams by emitting each new element with the latest value from the other stream. + * + * **When to use** + * + * Use when two streams should start emitting combined pairs after both have + * produced at least one value. + * + * **Gotchas** + * + * Note: tracking the latest value is done on a per-array basis. That means + * that emitted elements that are not the last value in arrays will never be + * used for zipping. + * + * **Example** (Zipping latest values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.zipLatest( + * Stream.make(1), + * Stream.make("a") + * ).pipe(Stream.runCollect) + * + * result // => [ [ 1, 'a' ] ] + * }) + * await Effect.runPromise(program) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipLatest: { + ( + right: Stream + ): (left: Stream) => Stream<[AL, AR], EL | ER, RL | RR> + ( + left: Stream, + right: Stream + ): Stream<[AL, AR], EL | ER, RL | RR> +} = dual( + 2, + ( + left: Stream, + right: Stream + ): Stream<[AL, AR], EL | ER, RL | RR> => zipLatestAll(left, right) +) + +/** + * Combines the latest values from both streams whenever either emits, using + * the provided function. + * + * **When to use** + * + * Use when two streams should start emitting custom combined values after both + * have produced at least one value. + * + * **Gotchas** + * + * Note: tracking the latest value is done on a per-array basis. That means + * that emitted elements that are not the last value in arrays will never be + * used for zipping. + * + * **Example** (Zipping latest values with a function) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Effect.gen(function*() { + * const result = yield* Stream.make(1, 2, 3).pipe( + * Stream.rechunk(1), + * Stream.zipLatestWith( + * Stream.make(10, 20).pipe(Stream.rechunk(1)), + * (n, m) => n + m + * ), + * Stream.runCollect + * ) + * + * result // => [ 11, 12, 22, 23 ] + * })) + * ``` + * + * @category zipping + * @since 2.0.0 + */ +export const zipLatestWith: { + ( + right: Stream, + f: (left: AL, right: AR) => A + ): (left: Stream) => Stream + ( + left: Stream, + right: Stream, + f: (left: AL, right: AR) => A + ): Stream +} = dual( + 3, + ( + left: Stream, + right: Stream, + f: (left: AL, right: AR) => A + ): Stream => map(zipLatestAll(left, right), ([a, a2]) => f(a, a2)) +) + +/** + * Runs all streams concurrently until one stream emits its first value, then + * mirrors that winning stream and interrupts the rest. + * + * **Details** + * + * Failures or completion from losing streams before a winner is chosen are + * ignored unless every stream fails or completes before emitting. After a + * winner is chosen, that stream's later failures are propagated. + * + * **Example** (Racing multiple streams) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.raceAll( + * Stream.empty, + * Stream.make(0, 1, 2) + * ).pipe(Stream.runCollect) + * result // => [ 0, 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category racing + * @since 3.5.0 + */ +export const raceAll = >>( + ...streams: S +): Stream, Error, Services> => + fromChannel(Channel.fromTransform((_, scope) => + Effect.sync(() => { + let winner: + | Pull.Pull>, Error, void, Services> + | undefined + const race = Effect.raceAll(streams.map((stream) => { + const childScope = Scope.forkUnsafe(scope) + return Channel.toPullScoped(stream.channel, childScope).pipe( + Effect.flatMap((pull) => Effect.zip(Effect.succeed(pull), pull)), + Effect.onExit((exit) => { + if (exit._tag === "Success") { + if (winner) { + return Scope.close(childScope, exit) + } + winner = exit.value[0] + return Effect.void + } + return Scope.close(childScope, exit) + }), + Effect.map(([, chunk]) => chunk) + ) + })) + return Effect.suspend(() => winner ?? race) + }) + )) + +/** + * Runs both streams concurrently until one stream emits its first value, then + * mirrors that winning stream and interrupts the other. + * + * **Details** + * + * A failure or completion from one side before the other side emits does not + * win the race unless both sides fail or complete before emitting. After a + * winner is chosen, that stream's later failures are propagated. + * + * **Example** (Racing two streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.race( + * Stream.empty, + * Stream.make(0, 1, 2) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => [ 0, 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category racing + * @since 3.7.0 + */ +export const race: { + ( + right: Stream + ): (left: Stream) => Stream + ( + left: Stream, + right: Stream + ): Stream +} = dual(2, ( + left: Stream, + right: Stream +): Stream => raceAll(left, right)) + +/** + * Filters a stream to the elements that satisfy a predicate. + * + * **Example** (Filtering stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.make(1, 2, 3, 4).pipe( + * Stream.filter((n) => n % 2 === 0) + * ) + * const values = yield* Stream.runCollect(stream) + * values // => [ 2, 4 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (refinement: Refinement, B>): (self: Stream) => Stream + (predicate: Predicate>): (self: Stream) => Stream + (self: Stream, refinement: Refinement): Stream + ( + self: Stream, + predicate: Predicate + ): Stream +} = dual( + 2, + ( + self: Stream, + predicate: Predicate + ): Stream => fromChannel(Channel.filterArray(toChannel(self), predicate)) +) + +/** + * Filters and maps stream elements in one pass using a `Filter`. + * + * **When to use** + * + * Use to keep only stream elements accepted by a `Filter` and emit each filter + * success value. + * + * **Details** + * + * `Result.succeed` values are emitted and `Result.fail` values are skipped. + * + * @see {@link filter} for keeping original elements with a boolean predicate or refinement + * @see {@link filterMapEffect} for an effectful `Filter` + * @see {@link partition} for consuming both filter success and failure values + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + ( + filter: Filter.Filter, B, X> + ): (self: Stream) => Stream + ( + self: Stream, + filter: Filter.Filter + ): Stream +} = dual( + 2, + ( + self: Stream, + filter: Filter.Filter + ): Stream => fromChannel(Channel.filterMapArray(toChannel(self), filter)) +) + +/** + * Filters elements in a single pass effectfully. + * + * **Example** (Effectfully filtering stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.filterEffect((n) => Effect.succeed(n > 2))) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => [ 3, 4 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterEffect: { + ( + predicate: (a: NoInfer, i: number) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + predicate: (a: NoInfer, i: number) => Effect.Effect + ): Stream +} = dual( + 2, + ( + self: Stream, + predicate: (a: NoInfer, i: number) => Effect.Effect + ): Stream => fromChannel(Channel.filterArrayEffect(toChannel(self), predicate)) +) + +/** + * Filters and maps elements in one pass effectfully using a `FilterEffect`. + * + * **When to use** + * + * Use to apply effectful logic that can reject stream elements or emit + * transformed values before they continue downstream. + * + * **Details** + * + * `Result.succeed` values are emitted, `Result.fail` values are skipped, and + * effect failures fail the stream. + * + * @see {@link filterMap} for the synchronous `Filter` variant + * @see {@link filterEffect} for effectfully keeping original elements + * @see {@link mapEffect} for effectfully transforming every element + * + * @category filtering + * @since 2.0.0 + */ +export const filterMapEffect: { + ( + filter: Filter.FilterEffect, B, X, EX, RX> + ): (self: Stream) => Stream + ( + self: Stream, + filter: Filter.FilterEffect + ): Stream +} = dual( + 2, + ( + self: Stream, + filter: Filter.FilterEffect + ): Stream => fromChannel(Channel.filterMapArrayEffect(toChannel(self), filter)) +) + +/** + * Partitions a stream using a `Filter` and exposes passing and failing values + * as scoped queues. + * + * **Details** + * + * The queues are backed by a fiber in the current scope and should be consumed + * while that scope remains open. Each queue fails with the stream error or + * `Cause.Done` when the source ends. + * + * **Example** (Partitioning a stream into queues) + * + * ```ts import.meta.vitest + * import { Effect, Result, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const [passes, fails] = yield* Stream.make(1, 2, 3, 4).pipe( + * Stream.partitionQueue((n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) + * ) + * + * const passValues = yield* Stream.fromQueue(passes).pipe(Stream.runCollect) + * const failValues = yield* Stream.fromQueue(fails).pipe(Stream.runCollect) + * + * passValues // => [ 2, 4 ] + * failValues // => [ 1, 3 ] + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const partitionQueue: { + (filter: Filter.Filter, Pass, Fail>, options?: { + readonly capacity?: number | "unbounded" | undefined + }): (self: Stream) => Effect.Effect< + [ + passes: Queue.Dequeue, + fails: Queue.Dequeue + ], + never, + R | Scope.Scope + > + ( + self: Stream, + filter: Filter.Filter, Pass, Fail>, + options?: { + readonly capacity?: number | "unbounded" | undefined + } + ): Effect.Effect< + [ + passes: Queue.Dequeue, + fails: Queue.Dequeue + ], + never, + R | Scope.Scope + > +} = dual( + (args) => isStream(args[0]), + Effect.fnUntraced( + function*( + self: Stream, + filter: Filter.Filter, Pass, Fail>, + options?: { + readonly capacity?: number | "unbounded" | undefined + } + ): Effect.fn.Return< + [ + passes: Queue.Dequeue, + fails: Queue.Dequeue + ], + never, + R | Scope.Scope + > { + const scope = yield* Effect.scope + const pull = yield* Channel.toPullScoped(self.channel, scope) + const capacity = options?.capacity === "unbounded" ? undefined : options?.capacity ?? DefaultChunkSize + const passes = yield* Queue.make({ capacity }) + const fails = yield* Queue.make({ capacity }) + + yield* Effect.gen(function*() { + while (true) { + const chunk = yield* pull + const excluded: Array = [] + const satisfying: Array = [] + for (let i = 0; i < chunk.length; i++) { + const result = filter(chunk[i] as NoInfer) + if (Result.isFailure(result)) { + excluded.push(result.failure) + } else { + satisfying.push(result.success) + } + } + let passFiber: Fiber.Fiber | undefined = undefined + if (satisfying.length > 0) { + const leftover = Queue.offerAllUnsafe(passes, satisfying) + if (leftover.length > 0) { + passFiber = yield* Effect.forkChild(Queue.offerAll(passes, leftover)) + } + } + if (excluded.length > 0) { + const leftover = Queue.offerAllUnsafe(fails, excluded) + if (leftover.length > 0) { + yield* Queue.offerAll(fails, leftover) + } + } + if (passFiber) yield* Fiber.join(passFiber) + } + }).pipe( + Effect.onError((cause) => { + Queue.failCauseUnsafe(passes, cause) + Queue.failCauseUnsafe(fails, cause) + return Effect.void + }), + Effect.forkIn(scope) + ) + + return [passes, fails] + } + ) +) + +/** + * Splits a stream with an effectful `Filter`, returning scoped streams for + * filter successes and failures. + * + * **When to use** + * + * Use when you need to classify each stream element with an effectful `Filter` + * and consume both passing and failing mapped values as streams. + * + * **Details** + * + * The returned streams are backed by queues in the current scope and should be + * consumed while that scope remains open. The first stream emits success values + * from the filter, and the second emits failure values. + * + * @see {@link partition} for the pure `Filter` variant, which returns the failing stream before the passing stream + * @see {@link partitionQueue} for the lower-level queue result + * @see {@link filterMapEffect} for effectful filtering that discards failed filter results + * + * @category filtering + * @since 4.0.0 + */ +export const partitionEffect: { + (filter: Filter.FilterEffect, Pass, Fail, EX, RX>, options?: { + readonly capacity?: number | "unbounded" | undefined + readonly concurrency?: number | "unbounded" | undefined + }): (self: Stream) => Effect.Effect< + [ + passes: Stream, + fails: Stream + ], + never, + R | RX | Scope.Scope + > + ( + self: Stream, + filter: Filter.FilterEffect, Pass, Fail, EX, RX>, + options?: { + readonly capacity?: number | "unbounded" | undefined + readonly concurrency?: number | "unbounded" | undefined + } + ): Effect.Effect< + [ + passes: Stream, + fails: Stream + ], + never, + R | RX | Scope.Scope + > +} = dual( + (args) => isStream(args[0]), + ( + self: Stream, + filter: Filter.FilterEffect, Pass, Fail, EX, RX>, + options?: { + readonly capacity?: number | "unbounded" | undefined + readonly concurrency?: number | "unbounded" | undefined + } + ): Effect.Effect< + [ + passes: Stream, + fails: Stream + ], + never, + R | RX | Scope.Scope + > => + Effect.map( + partitionQueue, E | EX, R | RX, Pass, Fail>( + mapEffect(self, (a) => filter(a as NoInfer), options), + (result) => result, + options + ), + ([passes, fails]) => [fromQueue(passes), fromQueue(fails)] as const + ) +) + +/** + * Splits a stream into scoped excluded and satisfying substreams using a + * `Filter`. + * + * **Details** + * + * The returned streams are backed by queues in the current scope and should be + * consumed while that scope remains open. The faster stream may advance up to + * `bufferSize` elements ahead of the slower one. + * + * **Example** (Partitioning a stream) + * + * ```ts import.meta.vitest + * import { Effect, Result, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const [excluded, satisfying] = yield* Stream.partition( + * Stream.make(1, 2, 3, 4), + * (n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n) + * ) + * const left = yield* Stream.runCollect(excluded) + * const right = yield* Stream.runCollect(satisfying) + * left // => [ 1, 3 ] + * right // => [ 2, 4 ] + * }) + * await Effect.runPromise(Effect.scoped(program)) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const partition: { + ( + filter: Filter.Filter, Pass, Fail>, + options?: { readonly bufferSize?: number | undefined } + ): ( + self: Stream + ) => Effect.Effect< + [excluded: Stream, satisfying: Stream], + never, + R | Scope.Scope + > + ( + self: Stream, + filter: Filter.Filter, Pass, Fail>, + options?: { readonly bufferSize?: number | undefined } + ): Effect.Effect< + [excluded: Stream, satisfying: Stream], + never, + R | Scope.Scope + > +} = dual( + (args) => isStream(args[0]), + ( + self: Stream, + filter: Filter.Filter, Pass, Fail>, + options?: { readonly bufferSize?: number | undefined } + ): Effect.Effect< + [excluded: Stream, satisfying: Stream], + never, + R | Scope.Scope + > => + Effect.map( + partitionQueue(self, filter, { capacity: options?.bufferSize ?? 16 }), + ([passes, fails]) => [fromQueue(fails), fromQueue(passes)] as const + ) +) + +/** + * Returns the specified stream if the given condition is satisfied, otherwise + * returns an empty stream. + * + * **Example** (Conditionally keeping a stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect( + * Stream.when(Stream.make(1, 2, 3), Effect.succeed(false)) + * ) + * result // => [] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const when: { + ( + test: Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + test: Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + test: Effect.Effect +): Stream => + test.pipe( + Effect.map((pass) => pass ? self : empty), + unwrap + )) + +/** + * Runs a sink to peel off enough elements to produce a value and returns that + * value with the remaining stream in a scope. + * + * **Details** + * + * The returned stream is only valid within the scope. + * + * **Example** (Peeling a stream with a sink) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * const stream = Stream.fromArrays([1, 2, 3], [4, 5, 6]) + * const sink = Sink.take(3) + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const [peeled, rest] = yield* Stream.peel(stream, sink) + * const remaining = yield* Stream.runCollect(rest) + * const result = [peeled, remaining] // => [[1, 2, 3], [4, 5, 6]] + * }) + * ) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const peel: { + ( + sink: Sink.Sink + ): (self: Stream) => Effect.Effect<[A2, Stream], E2 | E, Scope.Scope | R2 | R> + ( + self: Stream, + sink: Sink.Sink + ): Effect.Effect<[A2, Stream], E | E2, Scope.Scope | R | R2> +} = dual( + 2, + Effect.fnUntraced(function*( + self: Stream, + sink: Sink.Sink + ): Effect.fn.Return<[A2, Stream], E | E2, Scope.Scope | R | R2> { + let cause: Cause.Cause> | undefined = undefined + const originalPull = yield* Channel.toPull(self.channel) + const pull: Pull.Pull< + Arr.NonEmptyReadonlyArray, + E + > = Effect.catchCause(originalPull, (cause_) => { + cause = cause_ + return Effect.failCause(cause_) + }) + + let stream = fromPull(Effect.succeed(pull)) as Stream + const leftover = yield* run(stream, sink) + if (cause) return [leftover, empty] + + stream = fromPull(Effect.succeed(originalPull)) + return [leftover, stream] + }) +) + +/** + * Buffers up to `capacity` elements so a faster producer can progress + * independently of a slower consumer. + * + * **Details** + * + * Finite buffers use the configured queue strategy: `"suspend"` applies + * backpressure, while `"dropping"` and `"sliding"` may discard elements when + * the buffer is full. This combinator destroys chunking; use `Stream.rechunk` + * afterward if you need fixed chunk sizes. + * + * **Example** (Buffering stream elements) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.buffer({ capacity: 1 }), + * Stream.runCollect + * ) + * values // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category buffering + * @since 2.0.0 + */ +export const buffer: { + ( + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Stream +} = dual(2, ( + self: Stream, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } +): Stream => fromChannel(Channel.bufferArray(self.channel, options))) + +/** + * Allows a faster producer to progress independently of a slower consumer by + * buffering up to `capacity` chunks in a queue. + * + * **Details** + * + * Finite buffers use the configured queue strategy: `"suspend"` applies + * backpressure, while `"dropping"` and `"sliding"` may discard chunks when the + * buffer is full. This combinator preserves chunking and is best with + * power-of-2 capacities. + * + * **Example** (Buffering stream chunks) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.fromArrays([1, 2], [3, 4]).pipe( + * Stream.bufferArray({ capacity: 2 }), + * Stream.runCollect + * ) + * result // => [ 1, 2, 3, 4 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category buffering + * @since 4.0.0 + */ +export const bufferArray: { + ( + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Stream +} = dual(2, ( + self: Stream, + options: { readonly capacity: "unbounded" } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } +): Stream => fromChannel(Channel.buffer(self.channel, options))) + +/** + * Switches over to the stream produced by the provided function in case this + * one fails. Allows recovery from all causes of failure, including + * interruption if the stream is uninterruptible. + * + * **Example** (Catching stream causes) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2).pipe( + * Stream.concat(Stream.fail("Oops!")), + * Stream.concat(Stream.make(3, 4)) + * ) + * + * const recovered = stream.pipe( + * Stream.catchCause(() => Stream.make(999)) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(recovered) + * values // => [ 1, 2, 999 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const catchCause: { + ( + f: (cause: Cause.Cause) => Stream + ): (self: Stream) => Stream + ( + self: Stream, + f: (cause: Cause.Cause) => Stream + ): Stream +} = dual(2, ( + self: Stream, + f: (cause: Cause.Cause) => Stream +): Stream => + self.channel.pipe( + Channel.catchCause((cause) => f(cause).channel), + fromChannel + )) + +/** + * Runs an effect when the stream fails without changing its values or error, + * unless the tap effect itself fails. + * + * **Example** (Tapping stream causes) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Stream } from "effect" + * + * const observations: Array = [] + * const stream = Stream.make(1, 2).pipe( + * Stream.concat(Stream.fail("boom")), + * Stream.tapCause((cause) => Effect.sync(() => observations.push(Cause.isReason(cause)))), + * Stream.catch(() => Stream.succeed(0)) + * ) + * + * const program = Effect.gen(function* () { + * const result = yield* Stream.runCollect(stream) + * result // => [1, 2, 0] + * }) + * + * await Effect.runPromise(program) + * observations // => [false] + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const tapCause: { + ( + f: (cause: Cause.Cause) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + f: (cause: Cause.Cause) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + f: (cause: Cause.Cause) => Effect.Effect +): Stream => + self.channel.pipe( + Channel.tapCause(f), + fromChannel + )) + +const catch_: { + ( + f: (error: E) => Stream + ): (self: Stream) => Stream + ( + self: Stream, + f: (error: E) => Stream + ): Stream +} = dual(2, ( + self: Stream, + f: (error: E) => Stream +): Stream => fromChannel(Channel.catch(self.channel, (error) => f(error).channel))) + +export { + /** + * Switches over to the stream produced by the provided function if this one fails. + * + * **Example** (Catching stream failures) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2).pipe( + * Stream.concat(Stream.fail("Oops!")), + * Stream.catch(() => Stream.make(999)) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 1, 2, 999 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 4.0.0 + */ + catch_ as catch +} + +/** + * Peeks at errors effectfully without changing the stream unless the tap fails. + * + * **Example** (Effectfully peeking at errors) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const errors: Array = [] + * const stream = Stream.make(1, 2).pipe( + * Stream.concat(Stream.fail("boom")), + * Stream.tapError((error) => Effect.sync(() => errors.push(error))), + * Stream.catch(() => Stream.make(999)) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 999] + * }) + * + * await Effect.runPromise(program) + * errors // => ["boom"] + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const tapError: { + ( + f: (error: E) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + f: (error: E) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + f: (error: E) => Effect.Effect +): Stream => + self.channel.pipe( + Channel.tapError(f), + fromChannel + )) + +/** + * Recovers from errors that match a predicate by switching to a recovery stream. + * + * **Details** + * + * When a failure matches the filter, the stream switches to the recovery + * stream. Non-matching failures propagate downstream, so the error type is + * preserved unless the filter narrows it. + * + * **Example** (Catching matching failures) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2).pipe( + * Stream.concat(Stream.fail(42)), + * Stream.catchIf( + * (error): error is 42 => error === 42, + * () => Stream.make(999) + * ) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 1, 2, 999 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const catchIf: { + ( + refinement: Refinement, EB>, + f: (e: EB) => Stream, + orElse?: ((e: Exclude) => Stream) | undefined + ): ( + self: Stream + ) => Stream, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3> + ( + predicate: Predicate>, + f: (e: NoInfer) => Stream, + orElse?: ((e: NoInfer) => Stream) | undefined + ): ( + self: Stream + ) => Stream, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> + ( + self: Stream, + refinement: Refinement, + f: (e: EB) => Stream, + orElse?: ((e: Exclude) => Stream) | undefined + ): Stream, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3> + ( + self: Stream, + predicate: Predicate, + f: (e: E) => Stream, + orElse?: ((e: E) => Stream) | undefined + ): Stream, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> +} = dual((args) => isStream(args[0]), < + A, + E, + R, + A2, + E2, + R2, + A3 = never, + E3 = E, + R3 = never +>( + self: Stream, + predicate: Predicate, + f: (failure: E) => Stream, + orElse?: ((failure: E) => Stream) | undefined +): Stream => + fromChannel( + Channel.catchIf( + toChannel(self), + predicate, + (e) => f(e).channel, + orElse && ((e) => orElse(e).channel) + ) + )) + +/** + * Recovers from errors that match a `Filter` by switching to a recovery + * stream. + * + * **When to use** + * + * Use to recover from stream errors with a reusable `Filter` when matching can + * also narrow or transform the error before choosing the recovery stream. + * + * **Details** + * + * Successful filter results are passed to `f`. Failed filter results go to + * `orElse` when provided; otherwise the filter failure is re-failed. + * + * @see {@link catchIf} for predicate or refinement based recovery + * @see {@link catchTag} for `_tag` based recovery from one tagged error + * @see {@link catchTags} for `_tag` based recovery from multiple tagged errors + * @see {@link catchCauseFilter} for filtering full causes + * + * @category error handling + * @since 4.0.0 + */ +export const catchFilter: { + ( + filter: Filter.Filter, EB, X>, + f: (failure: EB) => Stream, + orElse?: ((failure: X) => Stream) | undefined + ): ( + self: Stream + ) => Stream, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> + ( + self: Stream, + filter: Filter.Filter, EB, X>, + f: (failure: EB) => Stream, + orElse?: ((failure: X) => Stream) | undefined + ): Stream, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> +} = dual((args) => isStream(args[0]), < + A, + E, + R, + EB, + A2, + E2, + R2, + X, + A3 = never, + E3 = X, + R3 = never +>( + self: Stream, + filter: Filter.Filter, EB, X>, + f: (failure: EB) => Stream, + orElse?: ((failure: X) => Stream) | undefined +): Stream => + fromChannel( + Channel.catchFilter( + toChannel(self), + filter, + (e) => f(e).channel, + orElse && ((e) => orElse(e).channel) + ) + )) + +/** + * Recovers from failures whose `_tag` matches the provided value by switching to + * the stream returned by `f`. + * + * **When to use** + * + * Use when you need to handle a specific error case from a stream whose error + * type is a tagged union with a readonly `_tag` field. + * + * **Example** (Catching tagged failures) + * + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" + * + * class HttpError extends Data.TaggedError("HttpError")<{ message: string }> {} + * + * const stream = Stream.fail(new HttpError({ message: "timeout" })) + * + * const recovered = Stream.catchTag(stream, "HttpError", (error) => + * Stream.make(`Recovered: ${error.message}`) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(recovered) + * values // => [ 'Recovered: timeout' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const catchTag: { + < + const K extends Tags | Arr.NonEmptyReadonlyArray>, + E, + A1, + E1, + R1, + A2 = unassigned, + E2 = never, + R2 = never + >( + k: K, + f: ( + e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Stream, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Stream) + | undefined + ): ( + self: Stream + ) => Stream< + A | A1 | Exclude, + | E1 + | E2 + | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), + R | R1 | R2 + > + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1, + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Stream, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Stream, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Stream) + | undefined + ): Stream< + A | A1 | Exclude, + | E1 + | E2 + | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), + R | R1 | R2 + > +} = dual( + (args) => isStream(args[0]), + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1, + A2 = never, + E2 = ExcludeTag ? K[number] : K>, + R2 = never + >( + self: Stream, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Stream, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Stream) + | undefined + ): Stream => { + const pred = Array.isArray(k) + ? ((e: E): e is any => hasProperty(e, "_tag") && k.includes(e._tag)) + : isTagged(k as string) + return catchIf(self, pred, f, orElse as any) as any + } +) + +/** + * Switches to a recovery stream based on matching `_tag` handlers. + * + * **Example** (Catching tagged failures with handlers) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * class NotFound { + * readonly _tag = "NotFound" + * constructor(readonly resource: string) {} + * } + * + * class Unauthorized { + * readonly _tag = "Unauthorized" + * constructor(readonly user: string) {} + * } + * + * const stream = Stream.fail(new NotFound("profile")) + * + * const program = Effect.gen(function* () { + * const result = yield* stream.pipe( + * Stream.catchTags({ + * NotFound: () => Stream.succeed("fallback"), + * Unauthorized: () => Stream.succeed("login") + * }), + * Stream.runCollect + * ) + * result // => [ 'fallback' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const catchTags: { + < + E, + Cases extends (E extends { _tag: string } ? { + [K in E["_tag"]]+?: (error: Extract) => Stream + } : + {}), + A2 = unassigned, + E2 = never, + R2 = never + >( + cases: Cases, + orElse?: ((e: Exclude) => Stream) | undefined + ): (self: Stream) => Stream< + | A + | Exclude + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? A : never + }[keyof Cases], + | E2 + | (A2 extends unassigned ? Exclude : never) + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? E : never + }[keyof Cases], + | R + | R2 + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? R : never + }[keyof Cases] + > + < + R, + E, + A, + Cases extends (E extends { _tag: string } ? { + [K in E["_tag"]]+?: (error: Extract) => Stream + } : + {}), + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Stream, + cases: Cases, + orElse?: ((e: Exclude) => Stream) | undefined + ): Stream< + | A + | Exclude + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? A : never + }[keyof Cases], + | E2 + | (A2 extends unassigned ? Exclude : never) + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? E : never + }[keyof Cases], + | R + | R2 + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? R : never + }[keyof Cases] + > +} = dual((args) => isStream(args[0]), (self, cases, orElse) => { + let keys: Array + return catchFilter( + self, + (e: any) => { + keys ??= Object.keys(cases) + return hasProperty(e, "_tag") && isString(e["_tag"]) && keys.includes(e["_tag"]) + ? Result.succeed(e) + : Result.fail(e) + }, + (e: any) => cases[e["_tag"] as string](e), + orElse + ) +}) + +/** + * Catches a specific reason within a tagged error. + * + * **When to use** + * + * Use to handle nested error causes without removing the parent error + * from the error channel. + * + * **Details** + * + * The handler receives the unwrapped reason. + * + * **Example** (Catching a tagged error reason) + * + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" + * + * class RateLimitError extends Data.TaggedError("RateLimitError")<{ + * retryAfter: number + * }> {} + * + * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ + * limit: number + * }> {} + * + * class AiError extends Data.TaggedError("AiError")<{ + * reason: RateLimitError | QuotaExceededError + * }> {} + * + * const stream = Stream.fail( + * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* stream.pipe( + * Stream.catchReason("AiError", "RateLimitError", (reason) => + * Stream.succeed(`retry: ${reason.retryAfter}`) + * ), + * Stream.runCollect + * ) + * values // => [ 'retry: 60' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const catchReason: { + < + K extends Tags, + E, + RK extends ReasonTags, K>>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + errorTag: K, + reasonTag: RK, + f: ( + reason: ExtractReason, K>, RK>, + error: NarrowReason, K>, RK> + ) => Stream, + orElse?: + | (( + reason: ExcludeReason, K>, RK>, + error: OmitReason, K>, RK> + ) => Stream) + | undefined + ): ( + self: Stream + ) => Stream< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > + < + A, + E, + R, + K extends Tags, + RK extends ReasonTags>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + self: Stream, + errorTag: K, + reasonTag: RK, + f: (reason: ExtractReason, RK>, error: NarrowReason, RK>) => Stream, + orElse?: + | ((reason: ExcludeReason, RK>, error: OmitReason, RK>) => Stream) + | undefined + ): Stream< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > +} = dual( + (args) => isStream(args[0]), + < + A, + E, + R, + K extends Tags, + RK extends ReasonTags>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + self: Stream, + errorTag: K, + reasonTag: RK, + f: (reason: ExtractReason, RK>, error: NarrowReason, RK>) => Stream, + orElse?: + | ((reason: ExcludeReason, RK>, error: OmitReason, RK>) => Stream) + | undefined + ): Stream< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > => + fromChannel( + Channel.catchReason( + toChannel(self), + errorTag, + reasonTag, + (reason, error) => f(reason, error).channel, + orElse && ((reason, error) => orElse(reason, error).channel) + ) + ) as any +) + +/** + * Catches multiple reasons within a tagged error using an object of handlers. + * + * **Example** (Catching tagged error reasons) + * + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" + * + * class RateLimitError extends Data.TaggedError("RateLimitError")<{ + * retryAfter: number + * }> {} + * + * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ + * limit: number + * }> {} + * + * class AiError extends Data.TaggedError("AiError")<{ + * reason: RateLimitError | QuotaExceededError + * }> {} + * + * const stream = Stream.fail( + * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* stream.pipe( + * Stream.catchReasons("AiError", { + * RateLimitError: (reason) => Stream.succeed(`retry: ${reason.retryAfter}`), + * QuotaExceededError: (reason) => Stream.succeed(`quota: ${reason.limit}`) + * }), + * Stream.runCollect + * ) + * values // => [ 'retry: 60' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const catchReasons: { + < + K extends Tags, + E, + Cases extends { + [RK in ReasonTags, K>>]+?: ( + reason: ExtractReason, K>, RK>, + error: NarrowReason, K>, RK> + ) => Stream + }, + A2 = unassigned, + E2 = never, + R2 = never + >( + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: ExcludeReason, K>, Extract>, + error: OmitReason, K>, Extract> + ) => Stream) + | undefined + ): (self: Stream) => Stream< + | A + | Exclude + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? A : never + }[keyof Cases], + | ExcludeTag + | E2 + | (A2 extends unassigned ? ExtractTag : never) + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? E : never + }[keyof Cases], + | R + | R2 + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? R : never + }[keyof Cases] + > + < + A, + E, + R, + K extends Tags, + Cases extends { + [RK in ReasonTags>]+?: ( + reason: ExtractReason, RK>, + error: NarrowReason, RK> + ) => Stream + }, + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Stream, + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: ExcludeReason, K>, Extract>, + error: OmitReason, K>, Extract> + ) => Stream) + | undefined + ): Stream< + | A + | Exclude + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? A : never + }[keyof Cases], + | ExcludeTag + | E2 + | (A2 extends unassigned ? ExtractTag : never) + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? E : never + }[keyof Cases], + | R + | R2 + | { + [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? R : never + }[keyof Cases] + > +} = dual((args) => isStream(args[0]), (self, errorTag, cases, orElse) => { + const handlers: Record Channel.Channel> = + Object.create(null) + for (const key of Object.keys(cases)) { + const handler = (cases as any)[key] + handlers[key] = (reason, error) => handler(reason, error).channel + } + const orElseHandler = orElse && ((reason: any, error: any) => orElse(reason, error).channel) + return fromChannel( + Channel.catchReasons(self.channel, errorTag as any, handlers as any, orElseHandler as any) as Channel.Channel< + Arr.NonEmptyReadonlyArray, + any, + void, + unknown, + unknown, + unknown, + any + > + ) as any +}) + +/** + * Transforms the errors emitted by this stream using `f`. + * + * **Example** (Mapping stream errors) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.fail("bad").pipe( + * Stream.mapError((error) => `mapped: ${error}`), + * Stream.catch((error) => Stream.make(`recovered from ${error}`)), + * Stream.runCollect + * ) + * result // => [ 'recovered from mapped: bad' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const mapError: { + (f: (error: E) => E2): (self: Stream) => Stream + (self: Stream, f: (error: E) => E2): Stream +} = dual(2, ( + self: Stream, + f: (error: E) => E2 +): Stream => fromChannel(Channel.mapError(self.channel, f))) + +/** + * Recovers from stream failures by filtering the `Cause` and switching to a recovery stream. + * Non-matching causes are re-emitted as failures. + * + * **Example** (Catching matching causes) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const failingStream = Stream.fail("NetworkError") + * const recovered = Stream.catchCauseIf( + * failingStream, + * (cause) => Cause.hasFails(cause), + * (cause) => Stream.make(`Recovered: ${Cause.squash(cause)}`) + * ) + * + * const output = yield* Stream.runCollect(recovered) + * output // => [ 'Recovered: NetworkError' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const catchCauseIf: { + ( + predicate: Predicate>, + f: (cause: Cause.Cause) => Stream + ): ( + self: Stream + ) => Stream + ( + self: Stream, + predicate: Predicate>, + f: (cause: Cause.Cause) => Stream + ): Stream +} = dual(3, ( + self: Stream, + predicate: Predicate>, + f: (cause: Cause.Cause) => Stream +): Stream => + fromChannel( + Channel.catchCauseIf( + self.channel, + predicate, + (cause) => f(cause).channel + ) + )) + +/** + * Recovers from stream failures by filtering the `Cause` and switching to a + * recovery stream. + * + * **When to use** + * + * Use when you need to recover a stream only from causes selected by a + * `Filter`, while giving the recovery both the selected value and the original + * `Cause`. + * + * **Details** + * + * The filter is applied to the full `Cause`. A successful filter result is + * passed to `f` together with the original cause; a failed filter result + * re-fails with the residual cause. + * + * @see {@link catchCauseIf} for predicate-based cause selection + * @see {@link catchFilter} for filtering typed error values instead of full causes + * @see {@link catchCause} for recovering from every cause without filtering + * + * @category error handling + * @since 4.0.0 + */ +export const catchCauseFilter: { + >( + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Stream + ): ( + self: Stream + ) => Stream | E2, R2 | R> + >( + self: Stream, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Stream + ): Stream | E2, R | R2> +} = dual(3, >( + self: Stream, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Stream +): Stream | E2, R | R2> => + fromChannel( + Channel.catchCauseFilter( + self.channel, + filter, + (failure, cause) => f(failure, cause).channel + ) + )) + +/** + * Switches to a fallback stream if this stream is empty. + * + * **Example** (Switching on empty streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.empty.pipe( + * Stream.orElseIfEmpty(() => Stream.make(1, 2)), + * Stream.runCollect + * ) + * values // => [ 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const orElseIfEmpty: { + ( + orElse: LazyArg> + ): (self: Stream) => Stream + ( + self: Stream, + orElse: LazyArg> + ): Stream +} = dual(2, ( + self: Stream, + orElse: LazyArg> +): Stream => + fromChannel(Channel.orElseIfEmpty( + self.channel, + (_) => toChannel(orElse()) + ))) + +/** + * Returns a stream that emits a fallback value when this stream fails. + * + * **Example** (Recovering with a fallback value) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.fail("NetworkError").pipe( + * Stream.orElseSucceed((error) => `Recovered: ${error}`) + * ) + * + * const values = yield* Stream.runCollect(stream) + * values // => [ 'Recovered: NetworkError' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const orElseSucceed: { + ( + f: (error: E) => A2 + ): (self: Stream) => Stream + ( + self: Stream, + f: (error: E) => A2 + ): Stream +} = dual(2, ( + self: Stream, + f: (error: E) => A2 +): Stream => catch_(self, (e) => succeed(f(e)))) + +/** + * Turns typed failures into defects, making the stream infallible. + * + * **Example** (Turning failures into defects) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.orDie, + * Stream.runCollect + * ) + * + * values // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const orDie = (self: Stream): Stream => fromChannel(Channel.orDie(self.channel)) + +/** + * Ignores failures and ends the stream on error. + * + * **When to use** + * + * Use when you want a failing stream to end gracefully rather than propagate + * the error. + * + * **Details** + * + * The `log` option controls whether the failure is logged before the stream + * terminates. + * + * **Example** (Ignoring stream failures) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.concat(Stream.fail("boom")), + * Stream.ignore, + * Stream.runCollect + * ) + * values // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * **Example** (Configuring ignore logging) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Effect.gen(function*() { + * const values = yield* Stream.fail("boom").pipe( + * Stream.ignore({ log: false }), + * Stream.runCollect + * ) + * values // => [] + * })) + * + * ``` + * + * @see {@link ignoreCause} for a variant that also ignores defects, not just typed failures + * + * @category error handling + * @since 4.0.0 + */ +export const ignore: < + Arg extends Stream | { + readonly log?: boolean | Severity | undefined + } | undefined +>( + selfOrOptions: Arg, + options?: { + readonly log?: boolean | Severity | undefined + } | undefined +) => [Arg] extends [Stream] ? Stream + : (self: Stream) => Stream = dual( + (args) => isStream(args[0]), + ( + self: Stream, + options?: { + readonly log?: boolean | Severity | undefined + } | undefined + ): Stream => fromChannel(Channel.ignore(self.channel, options)) + ) + +/** + * Ignores the stream's failure cause, including defects, and ends the stream. + * + * **When to use** + * + * Use when you need to silently suppress a stream's entire failure cause, + * including both typed errors and defects, rather than propagate it downstream. + * + * **Example** (Ignoring stream failure causes) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Effect.gen(function*() { + * const values = yield* Stream.make(1, 2).pipe( + * Stream.concat(Stream.die("boom")), + * Stream.ignoreCause({ log: false }), + * Stream.runCollect + * ) + * values // => [1, 2] + * })) + * + * ``` + * + * @see {@link ignore} to ignore only typed failures without suppressing defects + * + * @category error handling + * @since 4.0.0 + */ +export const ignoreCause: < + Arg extends Stream | { + readonly log?: boolean | Severity | undefined + } | undefined +>( + streamOrOptions: Arg, + options?: { + readonly log?: boolean | Severity | undefined + } | undefined +) => [Arg] extends [Stream] ? Stream + : (self: Stream) => Stream = dual( + (args) => isStream(args[0]), + ( + self: Stream, + options?: { readonly log?: boolean | Severity | undefined } | undefined + ): Stream => fromChannel(Channel.ignoreCause(self.channel, options)) + ) + +/** + * Retries the stream according to the given schedule when it fails. + * + * **Details** + * + * This retries the entire stream, so will re-execute all of the stream's + * acquire operations. + * + * The schedule is reset as soon as the first element passes through the + * stream again. + * + * **Example** (Retrying stream failures) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1).pipe( + * Stream.concat(Stream.fail("boom")), + * Stream.retry(Schedule.recurs(1)), + * Stream.take(2), + * Stream.runCollect + * ) + * + * values // => [ 1, 1 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const retry: { + ( + policy: + | Schedule.Schedule, E2, R2> + | (( + $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule + ) => Schedule.Schedule, E2, R2>) + ): (self: Stream) => Stream + ( + self: Stream, + policy: + | Schedule.Schedule, E2, R2> + | (( + $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule + ) => Schedule.Schedule, E2, R2>) + ): Stream +} = dual( + 2, + ( + self: Stream, + policy: + | Schedule.Schedule, E2, R2> + | (( + $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule + ) => Schedule.Schedule, E2, R2>) + ): Stream => fromChannel(Channel.retry(self.channel, policy)) +) + +const retryWithoutReset = ( + self: Stream, + schedule: Schedule.Schedule +): Stream => + unwrap(Effect.map(Schedule.toStepWithMetadata(schedule), (step) => { + let meta = Schedule.CurrentMetadata.defaultValue() + const loop = (): Stream => + catch_( + provideServiceEffect(self, Schedule.CurrentMetadata, Effect.sync(() => meta)), + (error) => + unwrap(Pull.catchDone( + Effect.map(step(error), (meta_) => { + meta = meta_ + return unwrap(Effect.as(Effect.yieldNow, loop())) + }), + () => Effect.succeed(fail(error)) + )) + ) + return loop() + })) + +/** + * Applies an `ExecutionPlan` to a stream, retrying with step-provided resources + * until it succeeds or the plan is exhausted. + * + * **Details** + * + * By default, a failing step can fallback even after emitting elements; set + * `preventFallbackOnPartialStream` to fail instead of mixing partial output with + * a later fallback. + * + * Attempts can be observed from outside the stream by passing + * `options.onEvent`, which receives an `ExecutionPlan.Event` before each + * attempt and after it settles; see `Effect.withExecutionPlan` for the handler + * semantics. When a downstream consumer stops pulling early (for example + * `Stream.take` outside the plan), the truncated attempt reports + * `AttemptSuccess`: the consumer stopped, not the source. + * + * **Example** (Applying an execution plan) + * + * ```ts import.meta.vitest + * import { Context, Effect, ExecutionPlan, Layer, Stream } from "effect" + * + * class Service extends Context.Service()("Service", { + * make: Effect.succeed({ + * stream: Stream.fail("A") as Stream.Stream + * }) + * }) { + * static Bad = Layer.succeed(Service, Service.of({ stream: Stream.fail("A") })) + * static Good = Layer.succeed(Service, Service.of({ stream: Stream.make(1, 2, 3) })) + * } + * + * const plan = ExecutionPlan.make( + * { provide: Service.Bad }, + * { provide: Service.Good } + * ) + * + * const stream = Stream.unwrap(Effect.map(Service, (_) => _.stream)) + * + * const program = Effect.gen(function*() { + * const items = yield* stream.pipe(Stream.withExecutionPlan(plan), Stream.runCollect) + * items // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category error handling + * @since 3.16.0 + */ +export const withExecutionPlan: { + ( + policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>, + options?: { + readonly preventFallbackOnPartialStream?: boolean | undefined + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect.Effect) | undefined + } + ): (self: Stream) => Stream | RX> + ( + self: Stream, + policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>, + options?: { + readonly preventFallbackOnPartialStream?: boolean | undefined + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect.Effect) | undefined + } + ): Stream | RX> +} = dual((args) => isStream(args[0]), ( + self: Stream, + policy: ExecutionPlan.ExecutionPlan<{ + provides: Provides + input: Input + error: PolicyE + requirements: R2 + }>, + options?: { + readonly preventFallbackOnPartialStream?: boolean | undefined + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect.Effect) | undefined + } +): Stream | RX> => + suspend(() => { + const preventFallbackOnPartialStream = options?.preventFallbackOnPartialStream ?? false + let i = 0 + let meta: ExecutionPlan.Metadata = { + attempt: 0, + stepIndex: 0 + } + const provideMeta = provideServiceEffect( + ExecutionPlan.CurrentMetadata, + Effect.sync(() => { + meta = { + attempt: meta.attempt + 1, + stepIndex: i + } + return meta + }) + ) + const emitter = options?.onEvent === undefined + ? undefined + : internalExecutionPlan.makeEventEmitter(options.onEvent, () => meta) + let attemptState: internalExecutionPlan.AttemptState | undefined + const instrument: (attempt: Stream) => Stream = emitter === undefined + ? identity + : (attempt) => + onExit( + onStart( + attempt, + Effect.map(emitter.begin, (state) => { + attemptState = state + }) + ), + (exit) => + Effect.suspend(() => { + if (attemptState === undefined) return Effect.void + const state = attemptState + attemptState = undefined + return emitter.end(state, exit) + }) + ) + let lastError = Option.none() + const loop: Stream< + A, + E | PolicyE, + R2 | Exclude + > = suspend(() => { + const step = policy.steps[i] + if (!step) { + return fail(Option.getOrThrow(lastError)) + } + + let nextStream: Stream> = provideMeta( + instrument(provide(self, step.provide)) + ) + let receivedElements = false + + if (Option.isSome(lastError)) { + const error = lastError.value + let attempted = false + const wrapped = nextStream + // ensure the schedule is applied at least once + nextStream = suspend(() => { + if (attempted) return wrapped + attempted = true + return fail(error) + }) + nextStream = retryWithoutReset(nextStream, internalExecutionPlan.scheduleFromStep(step, false) as any) + } else { + const schedule = internalExecutionPlan.scheduleFromStep(step, true) + nextStream = schedule ? retryWithoutReset(nextStream, schedule as any) : nextStream + } + + return catch_( + preventFallbackOnPartialStream ? + onFirst(nextStream, (_) => { + receivedElements = true + return Effect.void + }) : + nextStream, + (error) => { + i++ + if (preventFallbackOnPartialStream && receivedElements) { + return fail(error) + } + lastError = Option.some(error) + return loop + } + ) + }) + return loop + })) + +/** + * Takes the first `n` elements from this stream, returning `Stream.empty` when `n < 1`. + * + * **Example** (Taking values from the left) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.take(3), + * Stream.runCollect + * ) + * values // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const take: { + (n: number): (self: Stream) => Stream + (self: Stream, n: number): Stream +} = dual( + 2, + (self: Stream, n: number): Stream => + n < 1 ? empty : takeUntil(self, (_, i) => i === (n - 1)) +) + +/** + * Emits byte chunks until the configured limit would be exceeded, then drops + * the crossing chunk and switches to a fallback stream. + * + * **Example** (Truncating at a byte limit) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Stream.make( + * new Uint8Array([1, 2]), + * new Uint8Array([3, 4, 5]) + * ).pipe( + * Stream.limitBytes(4, () => Stream.empty), + * Stream.runCollect, + * Effect.map((chunks) => chunks.map((chunk) => [...chunk])) + * ) + * + * await Effect.runPromise(program) // => [[1, 2]] + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const limitBytes: { + ( + bytes: SizeInput, + onLimitReached: LazyArg> + ): (self: Stream) => Stream + ( + self: Stream, + bytes: SizeInput, + onLimitReached: LazyArg> + ): Stream +} = dual(3, ( + self: Stream, + bytes: SizeInput, + onLimitReached: LazyArg> +): Stream => + suspend(() => { + const limit = BigInt(bytes) + let size = BigInt(0) + let limitReached = false + return concat( + takeWhile(self, (chunk) => { + const nextSize = size + BigInt(chunk.length) + if (nextSize > limit) { + limitReached = true + return false + } + size = nextSize + return true + }), + suspend(() => limitReached ? onLimitReached() : empty) + ) + })) + +/** + * Keeps the last `n` elements from this stream. + * + * **Example** (Taking elements from the right) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.range(1, 6).pipe( + * Stream.takeRight(3), + * Stream.runCollect + * ) + * values // => [ 4, 5, 6 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const takeRight: { + (n: number): (self: Stream) => Stream + (self: Stream, n: number): Stream +} = dual( + 2, + (self: Stream, n: number): Stream => + mapAccumArray(self, MutableList.make, (list, arr) => { + MutableList.appendAll(list, arr) + if (list.length > n) { + MutableList.takeNVoid(list, list.length - n) + } + return [list, emptyArr] + }, { + onHalt(list) { + return MutableList.takeAll(list) + } + }) +) + +/** + * Takes elements until the predicate matches. + * + * **Details** + * + * When `excludeLast` is `true`, the matching element is dropped. + * + * **Example** (Taking until a predicate matches) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.range(1, 5) + * + * const program = Effect.gen(function*() { + * const inclusive = yield* stream.pipe( + * Stream.takeUntil((n) => n % 3 === 0), + * Stream.runCollect + * ) + * inclusive // => [ 1, 2, 3 ] + * + * const exclusive = yield* stream.pipe( + * Stream.takeUntil((n) => n % 3 === 0, { excludeLast: true }), + * Stream.runCollect + * ) + * exclusive // => [ 1, 2 ] + * }) + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const takeUntil: { + (predicate: (a: NoInfer, n: number) => boolean, options?: { + readonly excludeLast?: boolean | undefined + }): (self: Stream) => Stream + (self: Stream, predicate: (a: A, n: number) => boolean, options?: { + readonly excludeLast?: boolean | undefined + }): Stream +} = dual( + (args) => isStream(args[0]), + (self: Stream, predicate: (a: A, n: number) => boolean, options?: { + readonly excludeLast?: boolean | undefined + }): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let i = 0 + let done = false + const pump: Pull.Pull, E, void, R> = Effect.flatMap( + Effect.suspend(() => done ? Cause.done() : pull), + (chunk) => { + const index = chunk.findIndex((a) => predicate(a, i++)) + if (index >= 0) { + done = true + const arr = chunk.slice(0, options?.excludeLast ? index : index + 1) + return Arr.isReadonlyArrayNonEmpty(arr) ? Effect.succeed(arr) : Cause.done() + } + return Effect.succeed(chunk) + } + ) + return pump + })) +) + +/** + * Takes stream elements until an effectful predicate returns `true`. + * + * **When to use** + * + * Use when the stopping condition needs an Effect or service and predicate + * failure should fail the stream. + * + * **Example** (Taking until an effectful predicate matches) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.range(1, 5).pipe( + * Stream.takeUntilEffect((n) => Effect.succeed(n % 3 === 0)), + * Stream.runCollect + * ) + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const takeUntilEffect: { + ( + predicate: (a: NoInfer, n: number) => Effect.Effect, + options?: { + readonly excludeLast?: boolean | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + predicate: (a: A, n: number) => Effect.Effect, + options?: { + readonly excludeLast?: boolean | undefined + } + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + predicate: (a: A, n: number) => Effect.Effect, + options?: { + readonly excludeLast?: boolean | undefined + } +): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let i = 0 + let done = false + return Effect.gen(function*() { + if (done) return yield* Cause.done() + const chunk = yield* pull + for (let j = 0; j < chunk.length; j++) { + if (yield* predicate(chunk[j], i++)) { + done = true + const arr = chunk.slice(0, options?.excludeLast ? j : j + 1) + return Arr.isReadonlyArrayNonEmpty(arr) ? arr : yield* Cause.done() + } + } + return chunk + }) + }))) + +/** + * Takes the longest initial prefix of elements that satisfy the predicate. + * + * **Example** (Taking while a predicate holds) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.range(1, 5).pipe( + * Stream.takeWhile((n) => n % 3 !== 0) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => [ 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const takeWhile: { + (refinement: (a: NoInfer, n: number) => a is B): (self: Stream) => Stream + (predicate: (a: NoInfer, n: number) => boolean): (self: Stream) => Stream + (self: Stream, refinement: (a: NoInfer, n: number) => a is B): Stream + (self: Stream, predicate: (a: NoInfer, n: number) => boolean): Stream +} = dual( + 2, + ( + self: Stream, + predicate: (a: A, n: number) => boolean + ): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let i = 0 + let done = false + const pump: Pull.Pull, E, void, R> = Effect.flatMap( + Effect.suspend(() => done ? Cause.done() : pull), + (chunk) => { + const out: Array = [] + for (let j = 0; j < chunk.length; j++) { + if (!predicate(chunk[j], i++)) { + done = true + break + } + out.push(chunk[j]) + } + return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : done ? Cause.done() : pump + } + ) + return pump + })) +) + +/** + * Takes the longest initial prefix accepted by a `Filter` and emits the + * filter's success values. + * + * **When to use** + * + * Use to keep the leading stream elements that a `Filter` accepts, emit the + * filter's success values, and stop at the first filter failure. + * + * **Details** + * + * The stream stops at the first `Result.fail` returned by the filter. + * + * @see {@link takeWhile} for keeping original elements with a boolean predicate or refinement + * @see {@link filterMap} for filtering across the whole stream instead of only the leading prefix + * @see {@link dropWhileFilter} for dropping the accepted prefix and keeping the remaining original elements + * + * @category filtering + * @since 4.0.0 + */ +export const takeWhileFilter: { + (f: Filter.Filter, B, X>): (self: Stream) => Stream + (self: Stream, f: Filter.Filter, B, X>): Stream +} = dual( + 2, + ( + self: Stream, + filter: Filter.Filter, B, X> + ): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let done = false + const pump: Pull.Pull, E, void, R> = Effect.flatMap( + Effect.suspend(() => done ? Cause.done() : pull), + (chunk) => { + const out: Array = [] + for (let j = 0; j < chunk.length; j++) { + const result = filter(chunk[j]) + if (Result.isFailure(result)) { + done = true + break + } + out.push(result.success) + } + return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : done ? Cause.done() : pump + } + ) + return pump + })) +) + +/** + * Takes elements from the stream while the effectful predicate is `true`. + * + * **When to use** + * + * Use when the leading-prefix predicate needs an Effect or service and the + * stream should stop before the first false result. + * + * **Example** (Effectfully taking while a predicate holds) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.range(1, 5).pipe( + * Stream.takeWhileEffect((n) => Effect.succeed(n % 3 !== 0)), + * Stream.runCollect + * ) + * result // => [ 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const takeWhileEffect: { + ( + predicate: (a: NoInfer, n: number) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + predicate: (a: NoInfer, n: number) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + predicate: (a: NoInfer, n: number) => Effect.Effect +) => + takeUntilEffect(self, (a, n) => + Effect.map( + predicate(a, n), + (b) => !b + ), { excludeLast: true })) + +/** + * Drops the first `n` elements from this stream. + * + * **Example** (Dropping values from the left) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4, 5) + * const result = Stream.drop(stream, 2) + * + * const program = Effect.gen(function*() { + * const items = yield* Stream.runCollect(result) + * items // => [ 3, 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const drop: { + (n: number): (self: Stream) => Stream + (self: Stream, n: number): Stream +} = dual( + 2, + (self: Stream, n: number): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let dropped = 0 + const pump: Pull.Pull, E, void, R> = pull.pipe( + Effect.flatMap((chunk) => { + if (dropped >= n) return Effect.succeed(chunk) + dropped += chunk.length + if (dropped <= n) return pump + return Effect.succeed(chunk.slice(n - dropped) as Arr.NonEmptyArray) + }) + ) + return pump + })) +) + +/** + * Drops elements until the specified predicate evaluates to `true`, then drops + * that matching element. + * + * **Example** (Dropping until a predicate matches) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4, 5) + * const result = Stream.dropUntil(stream, (n) => n >= 3) + * + * await Effect.runPromise(Effect.gen(function*() { + * const output = yield* Stream.runCollect(result) + * output // => [ 4, 5 ] + * })) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dropUntil: { + (predicate: (a: NoInfer, index: number) => boolean): (self: Stream) => Stream + (self: Stream, predicate: (a: NoInfer, index: number) => boolean): Stream +} = dual(2, ( + self: Stream, + predicate: (a: NoInfer, index: number) => boolean +): Stream => drop(dropWhile(self, (a, i) => !predicate(a, i)), 1)) + +/** + * Drops all elements of the stream until the specified effectful predicate + * evaluates to `true`. + * + * **When to use** + * + * Use when dropping the leading prefix requires an Effect or service and the + * first matching element should also be dropped. + * + * **Example** (Dropping until an effectful predicate matches) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.range(1, 5).pipe( + * Stream.dropUntilEffect((n) => Effect.succeed(n % 3 === 0)), + * Stream.runCollect + * ) + * result // => [ 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dropUntilEffect: { + ( + predicate: (a: NoInfer, index: number) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + predicate: (a: NoInfer, index: number) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + predicate: (a: NoInfer, index: number) => Effect.Effect +): Stream => + drop( + dropWhileEffect( + self, + (a, i) => Effect.map(predicate(a, i), (b) => !b) + ), + 1 + )) + +/** + * Drops elements from the stream while the specified predicate evaluates to `true`. + * + * **Example** (Dropping while a predicate holds) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.dropWhile((n) => n < 3), + * Stream.runCollect + * ) + * values // => [ 3, 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dropWhile: { + (predicate: (a: NoInfer, index: number) => boolean): (self: Stream) => Stream + (self: Stream, predicate: (a: NoInfer, index: number) => boolean): Stream +} = dual(2, ( + self: Stream, + predicate: (a: A, index: number) => boolean +): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let dropping = true + let index = 0 + const filtered: Pull.Pull, E> = Effect.flatMap(pull, (arr) => { + const found = arr.findIndex((a) => !predicate(a, index++)) + if (found === -1) return filtered + dropping = false + return Effect.succeed(arr.slice(found) as Arr.NonEmptyArray) + }) + return Effect.suspend(() => dropping ? filtered : pull) + }))) + +/** + * Drops elements while the filter succeeds. + * + * **When to use** + * + * Use when you need to remove a leading stream prefix based on a synchronous + * `Filter` result while preserving the remaining original stream elements. + * + * **Details** + * + * `Result.succeed` drops the current element. The first `Result.fail` stops + * dropping, emits that original element, and the rest of the source stream is + * emitted without further filtering. + * + * @see {@link dropWhile} for boolean predicate prefix dropping + * @see {@link takeWhileFilter} for keeping the accepted prefix as filter success values + * @see {@link dropWhileEffect} for effectful predicate prefix dropping + * + * @category filtering + * @since 4.0.0 + */ +export const dropWhileFilter: { + (filter: Filter.Filter, B, X>): (self: Stream) => Stream + (self: Stream, filter: Filter.Filter, B, X>): Stream +} = dual(2, ( + self: Stream, + filter: Filter.Filter, B, X> +): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let dropping = true + const filtered: Pull.Pull, E> = Effect.flatMap(pull, (arr) => { + const found = arr.findIndex((a) => Result.isFailure(filter(a))) + if (found === -1) return filtered + dropping = false + return Effect.succeed(arr.slice(found) as Arr.NonEmptyArray) + }) + return Effect.suspend(() => dropping ? filtered : pull) + }))) + +/** + * Drops elements while the specified effectful predicate evaluates to `true`. + * + * **Example** (Effectfully dropping while a predicate holds) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.dropWhileEffect((n) => Effect.succeed(n < 3)), + * Stream.runCollect + * ) + * result // => [ 3, 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dropWhileEffect: { + ( + predicate: (a: NoInfer, index: number) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + predicate: (a: A, index: number) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + predicate: (a: NoInfer, index: number) => Effect.Effect +): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let dropping = true + let index = 0 + const filtered: Pull.Pull, E | E2, void, R2> = Effect.gen(function*() { + while (true) { + const arr = yield* pull + for (let i = 0; i < arr.length; i++) { + const drop = yield* predicate(arr[i], index++) + if (drop) continue + dropping = false + return arr.slice(i) as Arr.NonEmptyArray + } + } + }) + return Effect.suspend((): Pull.Pull, E | E2, void, R | R2> => + dropping ? filtered : pull + ) + }))) + +/** + * Drops the last specified number of elements from this stream. + * + * **Details** + * + * Keeps the last `n` elements in memory to drop them on completion. + * + * **Example** (Dropping values from the right) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.dropRight(2), + * Stream.runCollect + * ) + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const dropRight: { + (n: number): (self: Stream) => Stream + (self: Stream, n: number): Stream +} = dual( + 2, + (self: Stream, n: number): Stream => { + if (n <= 0) return self + return transformPull(self, (pull, _scope) => + Effect.sync(() => { + const list = MutableList.make() + const emit: Pull.Pull, E> = Effect.flatMap(pull, (arr) => { + MutableList.appendAllUnsafe(list, arr) + const toTake = list.length - n + const items = MutableList.takeN(list, toTake) + return Arr.isArrayNonEmpty(items) ? Effect.succeed(items) : emit + }) + return emit + })) + } +) + +/** + * Exposes the underlying chunks as a stream of non-empty arrays. + * + * **Example** (Exposing stream chunks) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const chunks = yield* Stream.make(1, 2, 3, 4).pipe( + * Stream.rechunk(2), + * Stream.chunks, + * Stream.runCollect + * ) + * chunks // => [ [ 1, 2 ], [ 3, 4 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const chunks = (self: Stream): Stream, E, R> => + self.channel.pipe( + Channel.map(Arr.of), + fromChannel + ) + +/** + * Groups the stream into arrays of the specified size, preserving element order. + * + * **Details** + * + * The size is clamped to at least 1. + * + * **Example** (Rechunking stream elements) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.rechunk(2), + * Stream.chunks, + * Stream.runCollect + * ) + * result // => [ [ 1, 2 ], [ 3, 4 ], [ 5 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const rechunk: { + (size: number): (self: Stream) => Stream + (self: Stream, size: number): Stream +} = dual(2, (self: Stream, target: number): Stream => { + target = Math.max(1, target) + return transformPull(self, (pull, _scope) => + Effect.sync(() => { + let chunk = Arr.empty() as Arr.NonEmptyArray + let index = 0 + let current: Arr.NonEmptyReadonlyArray | undefined + let done = false + + return Effect.suspend(function loop(): Pull.Pull, E, void, R> { + if (done) return Cause.done() + else if (current === undefined) { + return Effect.flatMap(pull, (arr) => { + if (chunk.length === 0 && arr.length === target) { + return Effect.succeed(arr) + } else if (chunk.length + arr.length < target) { + chunk.push(...arr) + return loop() + } + current = arr + return loop() + }) + } + for (; index < current.length;) { + chunk.push(current[index++]) + if (chunk.length === target) { + const result = chunk + chunk = [] as any + return Effect.succeed(result) + } + } + index = 0 + current = undefined + return loop() + }).pipe( + Pull.catchDone(() => { + if (chunk.length === 0) return Cause.done() + const result = chunk + done = true + chunk = [] as any + return Effect.succeed(result) + }) + ) + })) +}) + +/** + * Emits a sliding window of `n` elements. + * + * **Example** (Emitting sliding windows) + * + * ```ts import.meta.vitest + * import { Effect, pipe, Stream } from "effect" + * + * await Effect.runPromise(Effect.gen(function*() { + * const result = yield* pipe( + * Stream.make(1, 2, 3, 4, 5), + * Stream.sliding(2), + * Stream.runCollect + * ) + * result // => [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ] + * })) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const sliding: { + (chunkSize: number): (self: Stream) => Stream, E, R> + (self: Stream, chunkSize: number): Stream, E, R> +} = dual( + 2, + (self: Stream, chunkSize: number): Stream, E, R> => + slidingSize(self, chunkSize, 1) +) + +/** + * Emits sliding windows of `chunkSize` elements, advancing by `stepSize`. + * + * **Example** (Emitting sliding windows with a step size) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const chunks = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.slidingSize(3, 2), + * Stream.runCollect + * ) + * chunks // => [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const slidingSize: { + (chunkSize: number, stepSize: number): (self: Stream) => Stream, E, R> + (self: Stream, chunkSize: number, stepSize: number): Stream, E, R> +} = dual( + 3, + (self: Stream, chunkSize: number, stepSize: number): Stream, E, R> => + transformPull(self, (upstream, _scope) => + Effect.sync(() => { + let cause: Cause.Cause | null = null + const list = MutableList.make() + let emitted = false + let skip = 0 + const pull: Pull.Pull< + Arr.NonEmptyReadonlyArray>, + E | Cause.Done + > = Effect.matchCauseEffect(upstream, { + onSuccess(arr) { + MutableList.appendAllUnsafe(list, arr) + if (skip > 0) { + const length = list.length + MutableList.takeNVoid(list, skip) + skip = Math.max(0, skip - length) + } + if (list.length < chunkSize) return pull + emitted = true + const chunks = [] as any as Arr.NonEmptyArray> + while (list.length >= chunkSize) { + if (chunkSize === stepSize) { + chunks.push(MutableList.takeN(list, chunkSize) as any) + } else { + chunks.push(MutableList.toArrayN(list, chunkSize) as any) + if (chunkSize === 1 && stepSize <= 0) { + MutableList.take(list) + } else { + const length = list.length + MutableList.takeNVoid(list, stepSize) + skip = Math.max(0, stepSize - length) + } + } + } + return Effect.succeed(chunks) + }, + onFailure(cause_) { + if (emitted) MutableList.takeNVoid(list, chunkSize - stepSize) + if (list.length === 0) return Effect.failCause(cause_) + cause = cause_ + return Effect.succeed(Arr.of(MutableList.takeAll(list) as any)) + } + }) + + return Effect.suspend(() => cause ? Effect.failCause(cause) : pull) + })) +) + +/** + * Splits the stream into non-empty groups whenever the predicate matches. + * + * **Details** + * + * Matching elements act as delimiters and are not included in the output. + * + * **Example** (Splitting on matching values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.range(0, 9).pipe( + * Stream.split((n) => n % 4 === 0), + * Stream.runCollect + * ) + * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const split: { + ( + refinement: Refinement, B> + ): (self: Stream) => Stream>, E, R> + (predicate: Predicate>): (self: Stream) => Stream, E, R> + ( + self: Stream, + refinement: Refinement + ): Stream>, E, R> + (self: Stream, predicate: Predicate): Stream, E, R> +} = dual(2, ( + self: Stream, + predicate: Predicate> +): Stream, E, R> => + mapAccumArray(self, Arr.empty, (acc, arr) => { + const out = Arr.empty>() + for (let i = 0; i < arr.length; i++) { + if (predicate(arr[i])) { + if (Arr.isArrayNonEmpty(acc)) { + out.push(acc) + acc = [] + } + } else { + acc.push(arr[i]) + } + } + return [acc, out] + }, { + onHalt(arr) { + return Arr.isArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr + } + })) + +/** + * Combines elements from this stream and the specified stream by repeatedly + * applying a stateful function that can pull from either side. + * + * **Details** + * + * Where possible, prefer `Stream.combineArray` for a more efficient + * implementation. + * + * **Example** (Combining streams with state) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.combine( + * Stream.make("A", "B", "C"), + * Stream.make(1, 2, 3), + * () => true, + * (takeLeft, pullLeft, pullRight) => + * takeLeft + * ? Effect.map(pullLeft, (value) => [`L:${value}`, false] as const) + * : Effect.map(pullRight, (value) => [`R:${value}`, true] as const) + * ) + * + * const program = Effect.gen(function*() { + * const output = yield* Stream.runCollect(stream) + * output // => [ 'L:A', 'R:1', 'L:B', 'R:2', 'L:C', 'R:3' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category merging + * @since 2.0.0 + */ +export const combine: { + ( + that: Stream, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, + pullRight: Pull.Pull + ) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + that: Stream, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, + pullRight: Pull.Pull + ) => Effect.Effect + ): Stream +} = dual(4, ( + self: Stream, + that: Stream, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, + pullRight: Pull.Pull + ) => Effect.Effect +): Stream => + Channel.combine( + Channel.flattenArray(self.channel), + Channel.flattenArray(that.channel), + s, + f + ).pipe( + Channel.map(Arr.of), + fromChannel + )) + +/** + * Combines two streams chunk-by-chunk with a stateful pull function. + * + * **When to use** + * + * Use to coordinate pulling chunks from two streams when each emitted chunk + * depends on both sides and local state. + * + * **Details** + * + * The combining function receives the current state and pull functions for the + * left and right streams. It returns the next non-empty chunk together with the + * next state. + * + * **Example** (Combining stream chunks with state) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2).pipe( + * Stream.combineArray( + * Stream.make(10, 20), + * () => true, + * (useLeft, pullLeft, pullRight) => + * Effect.gen(function*() { + * const array = useLeft ? yield* pullLeft : yield* pullRight + * return [array, !useLeft] as const + * }) + * ) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 1, 2, 10, 20 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const combineArray: { + ( + that: Stream, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, E, void>, + pullRight: Pull.Pull, E2, void> + ) => Effect.Effect, S], E3, R3> + ): (self: Stream) => Stream, R2 | R3 | R> + ( + self: Stream, + that: Stream, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, E, void>, + pullRight: Pull.Pull, E2, void> + ) => Effect.Effect, S], E3, R3> + ): Stream, R | R2 | R3> +} = dual(4, ( + self: Stream, + that: Stream, + s: LazyArg, + f: ( + s: S, + pullLeft: Pull.Pull, E, void>, + pullRight: Pull.Pull, E2, void> + ) => Effect.Effect, S], E3, R3> +): Stream, R | R2 | R3> => + fromChannel(Channel.combine( + self.channel, + that.channel, + s, + f + ))) + +/** + * Maps elements statefully, emitting zero or more outputs per input. + * + * **Example** (Statefully mapping stream values) + * + * ```ts import.meta.vitest + * import { Console, Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const totals = yield* Stream.make(0, 1, 2, 3, 4, 5, 6).pipe( + * Stream.mapAccum(() => 0, (total, n) => { + * const next = total + n + * return [next, [next]] as const + * }), + * Stream.runCollect + * ) + * + * totals // => [0, 1, 3, 6, 10, 15, 21] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const mapAccum: { + ( + initial: LazyArg, + f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + initial: LazyArg, + f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + initial: LazyArg, + f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } +): Stream => + fromChannel(Channel.mapAccum( + self.channel, + initial, + (state, arr) => { + const acc = Arr.empty() + for (let index = 0; index < arr.length; index++) { + const [newState, values] = f(state, arr[index]) + state = newState + acc.push(...values) + } + return [state, Arr.isArrayNonEmpty(acc) ? Arr.of(acc) : emptyArr] + }, + options?.onHalt ? + { + onHalt(state) { + const arr = options.onHalt!(state) + return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr + } + } : + undefined + ))) + +/** + * Maps over non-empty chunk arrays statefully, emitting zero or more values per chunk. + * + * **Details** + * + * The mapping function runs once per chunk and the state is threaded across chunks. + * + * **Example** (Statefully mapping stream chunks) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const output = yield* Stream.make(1, 2, 3, 4, 5, 6).pipe( + * Stream.rechunk(2), + * Stream.mapAccumArray(() => 0, (sum: number, chunk) => { + * const next = chunk.reduce((acc, n) => acc + n, sum) + * return [next, [next]] + * }), + * Stream.runCollect + * ) + * output // => [ 3, 10, 21 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 4.0.0 + */ +export const mapAccumArray: { + ( + initial: LazyArg, + f: (s: S, a: Arr.NonEmptyReadonlyArray) => readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + initial: LazyArg, + f: (s: S, a: Arr.NonEmptyReadonlyArray) => readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => Array) | undefined + } + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + initial: LazyArg, + f: (s: S, a: Arr.NonEmptyReadonlyArray) => readonly [state: S, values: ReadonlyArray], + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } +): Stream => + fromChannel(Channel.mapAccum( + self.channel, + initial, + (state, arr) => { + const [newState, values] = f(state, arr) + state = newState + return [state, Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : emptyArr] + }, + options?.onHalt ? + { + onHalt(state) { + const arr = options.onHalt!(state) + return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr + } + } : + undefined + ))) + +const emptyArr = Arr.empty() + +/** + * Maps each element statefully and effectfully, emitting zero or more output + * values per input. + * + * **When to use** + * + * Use when stateful element mapping needs Effects or can fail while emitting + * zero or more values per input element. + * + * **Details** + * + * The mapping effect receives the current state and element, then returns the + * next state plus the values to emit. The state is threaded through the + * stream. + * + * **Example** (Effectfully mapping stream values with state) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.make(1, 1, 1).pipe( + * Stream.mapAccumEffect(() => 0, (total, n) => + * Effect.succeed([total + n, [total + n]]) + * ), + * Stream.runCollect + * ) + * + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const mapAccumEffect: { + ( + initial: LazyArg, + f: (s: S, a: A) => Effect.Effect], E2, R2>, + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + initial: LazyArg, + f: (s: S, a: A) => Effect.Effect], E2, R2>, + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + initial: LazyArg, + f: (s: S, a: A) => Effect.Effect], E2, R2>, + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } +): Stream => + self.channel.pipe( + Channel.flattenArray, + Channel.mapAccum( + initial, + (state, a) => + Effect.map( + f(state, a), + ([state, values]) => [ + state, + Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : Arr.empty>() + ] + ), + options?.onHalt ? + { + onHalt(state) { + const arr = options.onHalt!(state) + return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr + } + } : + undefined + ), + fromChannel + )) + +/** + * Maps each non-empty input chunk statefully and effectfully, emitting zero or + * more output values per chunk. + * + * **When to use** + * + * Use when stateful mapping should process each emitted non-empty chunk with an + * Effect instead of each element separately. + * + * **Details** + * + * The mapping effect receives the current state and chunk, then returns the + * next state plus the values to emit. The state is threaded across chunks. + * + * **Example** (Effectfully mapping stream chunks with state) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const totals = yield* Stream.make(1, 2, 3, 4).pipe( + * Stream.rechunk(2), + * Stream.mapAccumArrayEffect(() => 0, (total, chunk) => + * Effect.gen(function*() { + * const next = chunk.reduce((sum, value) => sum + value, total) + * return [next, [next]] as const + * }) + * ), + * Stream.runCollect + * ) + * totals // => [ 3, 10 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 4.0.0 + */ +export const mapAccumArrayEffect: { + ( + initial: LazyArg, + f: (s: S, a: Arr.NonEmptyReadonlyArray) => Effect.Effect], E2, R2>, + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): (self: Stream) => Stream + ( + self: Stream, + initial: LazyArg, + f: (s: S, a: Arr.NonEmptyReadonlyArray) => Effect.Effect], E2, R2>, + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } + ): Stream +} = dual((args) => isStream(args[0]), ( + self: Stream, + initial: LazyArg, + f: (s: S, a: Arr.NonEmptyReadonlyArray) => Effect.Effect], E2, R2>, + options?: { + readonly onHalt?: ((state: S) => ReadonlyArray) | undefined + } +): Stream => + self.channel.pipe( + Channel.mapAccum( + initial, + (state, a) => + Effect.map( + f(state, a), + ([state, values]) => [ + state, + Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : emptyArr + ] + ), + options?.onHalt ? + { + onHalt(state) { + const arr = options.onHalt!(state) + return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr + } + } : + undefined + ), + fromChannel + )) + +/** + * Accumulates state across the stream, emitting the initial state and each updated state. + * + * **Example** (Scanning stream state) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.scan(0, (acc, n) => acc + n), + * Stream.runCollect + * ) + * values // => [ 0, 1, 3, 6 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category accumulation + * @since 2.0.0 + */ +export const scan: { + ( + initial: S, + f: (s: S, a: A) => S + ): (self: Stream) => Stream + ( + self: Stream, + initial: S, + f: (s: S, a: A) => S + ): Stream +} = dual(3, ( + self: Stream, + initial: S, + f: (s: S, a: A) => S +): Stream => + suspend(() => { + let isFirst = true + return fromChannel(Channel.mapAccum(self.channel, constant(initial), (state, arr) => { + const states = Arr.empty() as Arr.NonEmptyArray + if (isFirst) { + isFirst = false + states.push(state) + } + for (let index = 0; index < arr.length; index++) { + state = f(state, arr[index]) + states.push(state) + } + return [state, Arr.of(states)] + })) + })) + +/** + * Accumulates state effectfully and emits the initial state plus each accumulated state. + * + * **Example** (Effectfully scanning stream state) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const states = yield* Stream.make(1, 2, 3).pipe( + * Stream.scanEffect(0, (sum, n) => Effect.succeed(sum + n)), + * Stream.runCollect + * ) + * states // => [ 0, 1, 3, 6 ] + * }) + * await Effect.runPromise(program) + * ``` + * + * @category accumulation + * @since 2.0.0 + */ +export const scanEffect: { + ( + initial: S, + f: (s: S, a: A) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + initial: S, + f: (s: S, a: A) => Effect.Effect + ): Stream +} = dual(3, ( + self: Stream, + initial: S, + f: (s: S, a: A) => Effect.Effect +): Stream => + self.channel.pipe( + Channel.flattenArray, + Channel.scanEffect(initial, f), + Channel.map(Arr.of), + fromChannel + )) + +/** + * Drops earlier elements within the debounce window and emits only the latest element after the pause. + * + * **Example** (Debouncing stream elements) + * + * ```ts import.meta.vitest + * import { Duration, Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3).pipe(Stream.debounce(Duration.zero)) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 3 ] + * }) + * await Effect.runPromise(program) + * ``` + * + * @category rate limiting + * @since 2.0.0 + */ +export const debounce: { + (duration: Duration.Input): (self: Stream) => Stream + (self: Stream, duration: Duration.Input): Stream +} = dual( + 2, + (self: Stream, duration: Duration.Input): Stream => + transformPull( + self, + Effect.fnUntraced(function*(pull, scope) { + const clock = yield* Clock + const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) + let lastArr: Arr.NonEmptyReadonlyArray | undefined + let cause: Cause.Cause | undefined + let emitAtMs = Infinity + const pullLatch = Latch.makeUnsafe() + const emitLatch = Latch.makeUnsafe() + const endLatch = Latch.makeUnsafe() + + yield* pull.pipe( + pullLatch.whenOpen, + Effect.flatMap((arr) => { + emitLatch.openUnsafe() + lastArr = arr + emitAtMs = clock.currentTimeMillisUnsafe() + durationMs + return Effect.void + }), + Effect.forever({ disableYield: true }), + Effect.onError((cause_) => { + cause = cause_ + emitAtMs = clock.currentTimeMillisUnsafe() + emitLatch.openUnsafe() + endLatch.openUnsafe() + return Effect.void + }), + Effect.forkIn(scope) + ) + + const sleepLoop = Effect.suspend(function loop(): Pull.Pull, E, void, R> { + const now = clock.currentTimeMillisUnsafe() + const timeMs = emitAtMs < now ? durationMs : Math.min(durationMs, emitAtMs - now) + return Effect.flatMap(Effect.raceFirst(Effect.sleep(timeMs), endLatch.await), () => { + const now = clock.currentTimeMillisUnsafe() + if (now < emitAtMs) { + return loop() + } else if (lastArr) { + emitLatch.closeUnsafe() + pullLatch.closeUnsafe() + const eff = Effect.succeed(Arr.of(Arr.lastNonEmpty(lastArr))) + lastArr = undefined + return eff + } else if (cause) { + return Effect.failCause(cause!) + } + return loop() + }) + }) + + return Effect.suspend(() => { + if (cause) { + if (lastArr) { + const eff = Effect.succeed(Arr.of(Arr.lastNonEmpty(lastArr))) + lastArr = undefined + return eff + } + return Effect.failCause(cause) + } + pullLatch.openUnsafe() + return emitLatch.whenOpen(sleepLoop) + }) + }) + ) +) + +/** + * Rate-limits stream chunks with an effectful cost function. + * + * **When to use** + * + * Use to throttle chunks when computing each chunk's cost requires an effect. + * + * **Details** + * + * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, + * and each chunk consumes the cost returned by the effectful `cost` function. + * + * If using the "enforce" strategy, arrays that do not meet the bandwidth + * constraints are dropped. If using the "shape" strategy, arrays are delayed + * until they can be emitted without exceeding the bandwidth constraints. + * + * Defaults to the "shape" strategy. + * + * **Example** (Throttling stream chunks effectfully) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.range(0, 5).pipe( + * Stream.rechunk(1), + * Stream.throttleEffect({ + * cost: (arr) => Effect.succeed(arr.length), + * units: 1, + * duration: 0, + * strategy: "shape" + * }) + * ) + * + * await Effect.runPromise(Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => [ 0, 1, 2, 3, 4, 5 ] + * })) + * ``` + * + * @category rate limiting + * @since 2.0.0 + */ +export const throttleEffect: { + (options: { + readonly cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect + readonly units: number + readonly duration: Duration.Input + readonly burst?: number | undefined + readonly strategy?: "enforce" | "shape" | undefined + }): (self: Stream) => Stream + ( + self: Stream, + options: { + readonly cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect + readonly units: number + readonly duration: Duration.Input + readonly burst?: number | undefined + readonly strategy?: "enforce" | "shape" | undefined + } + ): Stream +} = dual( + 2, + ( + self: Stream, + options: { + readonly cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect + readonly units: number + readonly duration: Duration.Input + readonly burst?: number | undefined + readonly strategy?: "enforce" | "shape" | undefined + } + ): Stream => { + const burst = options.burst ?? 0 + if (options.strategy === "enforce") { + return throttleEnforceEffect(self, options.cost, options.units, options.duration, burst) + } + return throttleShapeEffect(self, options.cost, options.units, options.duration, burst) + } +) + +const throttleEnforceEffect = ( + self: Stream, + cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect, + units: number, + duration: Duration.Input, + burst: number +): Stream => + transformPull(self, (pull) => + Effect.clockWith((clock) => { + const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) + const max = units + burst < 0 ? Number.POSITIVE_INFINITY : units + burst + let tokens = units + let timestampMs = clock.currentTimeMillisUnsafe() + + return Effect.succeed( + Effect.flatMap(pull, function loop(arr): Pull.Pull, E | E2, void, R | R2> { + return Effect.flatMap(cost(arr), (weight) => { + const currentMs = clock.currentTimeMillisUnsafe() + const elapsed = currentMs - timestampMs + const cycles = elapsed / durationMs + const sum = tokens + (cycles * units) + const available = sum < 0 ? max : Math.min(sum, max) + + if (weight <= available) { + tokens = available - weight + timestampMs = currentMs + return Effect.succeed(arr) + } + + // Drop the array and continue + return Effect.flatMap(pull, loop) + }) + }) + ) + })) + +const throttleShapeEffect = ( + self: Stream, + cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect, + units: number, + duration: Duration.Input, + burst: number +): Stream => + transformPull(self, (pull) => + Effect.clockWith((clock) => { + const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) + const max = units + burst < 0 ? Number.POSITIVE_INFINITY : units + burst + let tokens = units + let timestampMs = clock.currentTimeMillisUnsafe() + + return Effect.succeed(Effect.flatMap(pull, (arr) => + Effect.flatMap(cost(arr), (weight) => { + const currentMs = clock.currentTimeMillisUnsafe() + const elapsed = currentMs - timestampMs + const cycles = elapsed / durationMs + const sum = tokens + (cycles * units) + const available = sum < 0 ? max : Math.min(sum, max) + const remaining = available - weight + + if (remaining >= 0) { + tokens = remaining + timestampMs = currentMs + return Effect.succeed(arr) + } + + // Calculate delay needed + const waitCycles = -remaining / units + const delayMs = Math.max(0, waitCycles * durationMs) + + if (delayMs > 0) { + return Effect.flatMap(Effect.sleep(delayMs), () => { + tokens = remaining + timestampMs = currentMs + return Effect.succeed(arr) + }) + } + + tokens = remaining + timestampMs = currentMs + return Effect.succeed(arr) + }))) + })) + +/** + * Rate-limits stream chunks with a synchronous cost function. + * + * **When to use** + * + * Use to throttle chunks when each chunk's cost can be computed synchronously. + * + * **Details** + * + * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, + * and each chunk consumes the cost returned by `cost`. + * + * If using the "enforce" strategy, arrays that do not meet the bandwidth + * constraints are dropped. If using the "shape" strategy, arrays are delayed + * until they can be emitted without exceeding the bandwidth constraints. + * + * Defaults to the "shape" strategy. + * + * **Example** (Throttling stream chunks) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.range(0, 5).pipe( + * Stream.rechunk(1), + * Stream.throttle({ + * cost: (arr) => arr.length, + * units: 1, + * duration: 0, + * strategy: "shape" + * }) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 0, 1, 2, 3, 4, 5 ] + * }) + * await Effect.runPromise(program) + * ``` + * + * @category rate limiting + * @since 2.0.0 + */ +export const throttle: { + (options: { + readonly cost: (arr: Arr.NonEmptyReadonlyArray) => number + readonly units: number + readonly duration: Duration.Input + readonly burst?: number | undefined + readonly strategy?: "enforce" | "shape" | undefined + }): (self: Stream) => Stream + ( + self: Stream, + options: { + readonly cost: (arr: Arr.NonEmptyReadonlyArray) => number + readonly units: number + readonly duration: Duration.Input + readonly burst?: number | undefined + readonly strategy?: "enforce" | "shape" | undefined + } + ): Stream +} = dual( + 2, + ( + self: Stream, + options: { + readonly cost: (arr: Arr.NonEmptyReadonlyArray) => number + readonly units: number + readonly duration: Duration.Input + readonly burst?: number | undefined + readonly strategy?: "enforce" | "shape" | undefined + } + ): Stream => + throttleEffect(self, { + ...options, + cost: (arr) => Effect.succeed(options.cost(arr)) + }) +) + +/** + * Partitions the stream into non-empty arrays of the specified size. + * + * **Details** + * + * The final array may be smaller if there are not enough elements to fill it. + * + * **Example** (Grouping elements by size) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const grouped = yield* Stream.range(1, 8).pipe( + * Stream.grouped(3), + * Stream.runCollect + * ) + * grouped // => [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const grouped: { + (n: number): (self: Stream) => Stream, E, R> + (self: Stream, n: number): Stream, E, R> +} = dual( + 2, + (self: Stream, n: number): Stream, E, R> => chunks(rechunk(self, n)) +) + +/** + * Partitions the stream into arrays, emitting when the chunk size is reached + * or the duration passes. + * + * **Example** (Grouping elements by size or time) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.groupedWithin(2, "5 seconds"), + * Stream.runCollect + * ) + * values // => [ [ 1, 2 ], [ 3 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const groupedWithin: { + ( + chunkSize: number, + duration: Duration.Input + ): (self: Stream) => Stream, E, R> + (self: Stream, chunkSize: number, duration: Duration.Input): Stream, E, R> +} = dual(3, ( + self: Stream, + chunkSize: number, + duration: Duration.Input +): Stream, E, R> => + aggregateWithin( + self, + Sink.take(chunkSize), + Schedule.spaced(duration) + )) + +/** + * Groups elements into keyed substreams using an effectful classifier. + * + * **Example** (Grouping elements into keyed substreams using an effectful classifier) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.groupBy((n) => + * Effect.succeed([n % 2 === 0 ? "even" : "odd", n] as const) + * ), + * Stream.mapEffect( + * Effect.fnUntraced(function*([key, stream]) { + * return [key, yield* Stream.runCollect(stream)] as const + * }), + * { concurrency: "unbounded" } + * ), + * Stream.runCollect + * ) + * + * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const groupBy: { + ( + f: (a: NoInfer) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } + ): (self: Stream) => Stream], E | E2, R | R2> + ( + self: Stream, + f: (a: NoInfer) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } + ): Stream], E | E2, R | R2> +} = dual((args) => isStream(args[0]), ( + self: Stream, + f: (a: NoInfer) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } +): Stream], E | E2, R | R2> => + groupByImpl( + self, + Effect.fnUntraced(function*(arr, queues, queueMap) { + for (let i = 0; i < arr.length; i++) { + const [key, value] = yield* f(arr[i]) + const oentry = MutableHashMap.get(queueMap, key) + const queue = Option.isSome(oentry) + ? oentry.value + : yield* Effect.scoped(RcMap.get(queues, key)) + yield* RcMap.touch(queues, key) + yield* Queue.offer(queue, value) + } + }), + options + )) + +/** + * Groups elements by a key and emits a stream per key. + * + * **Example** (Grouping elements by key) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( + * Stream.groupByKey((n) => n % 2 === 0 ? "even" : "odd"), + * Stream.mapEffect( + * ([key, stream]) => + * Stream.runCollect(stream).pipe( + * Effect.map((values) => [key, values] as const) + * ), + * { concurrency: "unbounded" } + * ), + * Stream.runCollect + * ) + * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category grouping + * @since 2.0.0 + */ +export const groupByKey: { + ( + f: (a: NoInfer) => K, + options?: { + readonly bufferSize?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } + ): (self: Stream) => Stream], E, R> + ( + self: Stream, + f: (a: NoInfer) => K, + options?: { + readonly bufferSize?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } + ): Stream], E, R> +} = dual((args) => isStream(args[0]), ( + self: Stream, + f: (a: NoInfer) => K, + options?: { + readonly bufferSize?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } +): Stream], E, R> => + suspend(() => { + const batch = MutableHashMap.empty>() + return groupByImpl( + self, + Effect.fnUntraced(function*(arr, queues, queueMap) { + for (let i = 0; i < arr.length; i++) { + const key = f(arr[i]) + const ovalues = MutableHashMap.get(batch, key) + if (Option.isNone(ovalues)) { + MutableHashMap.set(batch, key, [arr[i]]) + } else { + ovalues.value.push(arr[i]) + } + } + for (const [key, values] of batch) { + const oentry = MutableHashMap.get(queueMap, key) + const queue = Option.isSome(oentry) + ? oentry.value + : yield* Effect.scoped(RcMap.get(queues, key)) + yield* RcMap.touch(queues, key) + yield* Queue.offerAll(queue, values) + } + MutableHashMap.clear(batch) + }), + options + ) + })) + +const groupByImpl = ( + self: Stream, + f: ( + arr: Arr.NonEmptyReadonlyArray, + queues: RcMap.RcMap>, + queueMap: MutableHashMap.MutableHashMap> + ) => Effect.Effect, + options?: { + readonly bufferSize?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } +): Stream], E | E2, R | R2> => + transformPullBracket( + self, + Effect.fnUntraced(function*(pull, scope, forkedScope) { + const out = yield* Queue.unbounded], E | E2 | Cause.Done>() + yield* Scope.addFinalizer(scope, Queue.shutdown(out)) + + const queueMap = MutableHashMap.empty>() + const queues = yield* RcMap.make({ + lookup: (key: K) => + Effect.acquireRelease( + Queue.make({ capacity: options?.bufferSize ?? 4096 }).pipe( + Effect.tap((queue) => { + MutableHashMap.set(queueMap, key, queue) + return Queue.offer(out, [key, fromQueue(queue)]) + }) + ), + (queue) => { + MutableHashMap.remove(queueMap, key) + return Queue.end(queue) + } + ), + idleTimeToLive: options?.idleTimeToLive ?? Duration.infinity + }).pipe(Scope.provide(forkedScope)) + + yield* Effect.whileLoop({ + while: constTrue, + body: constant(Effect.flatMap(pull, (arr) => f(arr, queues, queueMap))), + step: constVoid + }).pipe( + Effect.catchCause((cause) => Queue.failCause(out, cause)), + Effect.forkIn(scope) + ) + + return Queue.takeAll(out) + }) + ) + +/** + * Groups consecutive elements that have equal keys into non-empty arrays. + * + * **When to use** + * + * Use when you already have a stream ordered by the grouping key and want to + * emit each consecutive run as a non-empty array while keeping later + * non-adjacent runs separate. + * + * **Details** + * + * The key is computed with `f`; adjacent elements whose keys are equal by + * `Equal.equals` are emitted as one `[key, group]`. Later non-adjacent runs + * with the same key are emitted separately. + * + * @see {@link groupByKey} for grouping all elements with the same key across the stream + * @see {@link groupBy} for custom grouped stream construction + * + * @category grouping + * @since 2.0.0 + */ +export const groupAdjacentBy: { + ( + f: (a: NoInfer) => K + ): (self: Stream) => Stream], E, R> + ( + self: Stream, + f: (a: NoInfer) => K + ): Stream], E, R> +} = dual(2, ( + self: Stream, + f: (a: NoInfer) => K +): Stream], E, R> => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let currentKey: K = undefined as any + let group: Arr.NonEmptyArray | undefined + let toEmit = Arr.empty]>() + const loop: Pull.Pull< + Arr.NonEmptyReadonlyArray]>, + E + > = pull.pipe( + Effect.flatMap((chunk) => { + for (let i = 0; i < chunk.length; i++) { + const item = chunk[i] + const key = f(item) + if (group === undefined) { + currentKey = key + group = [item] + continue + } else if (Equal.equals(key, currentKey)) { + group.push(item) + continue + } + toEmit.push([currentKey, group]) + currentKey = key + group = [item] + } + if (Arr.isArrayNonEmpty(toEmit)) { + const out = toEmit + toEmit = [] + return Effect.succeed(out) + } + return loop + }) + ) + let done = false + return Pull.catchDone(Effect.suspend(() => done ? Cause.done() : loop), () => { + done = true + const out = group + group = undefined + return out && Arr.isArrayNonEmpty(out) ? Effect.succeed(Arr.of([currentKey, out])) : Cause.done() + }) + }))) + +/** + * Applies a sink transducer to the stream and emits each sink result. + * + * **Example** (Transducing with a sink) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * const program = Effect.gen(function* () { + * const result = yield* Stream.make(1, 2, 3, 4).pipe( + * Stream.transduce(Sink.take(2)), + * Stream.runCollect + * ) + * + * result // => [ [ 1, 2 ], [ 3, 4 ], [] ] + * }) + * await Effect.runPromise(program) + * ``` + * + * @category aggregation + * @since 2.0.0 + */ +export const transduce = dual< + ( + sink: Sink.Sink + ) => (self: Stream) => Stream, + ( + self: Stream, + sink: Sink.Sink + ) => Stream +>( + 2, + ( + self: Stream, + sink: Sink.Sink + ): Stream => + transformPull(self, (upstream, scope) => + Effect.sync(() => { + let done: Exit.Exit | E> | undefined + let leftover: Arr.NonEmptyReadonlyArray | undefined + const upstreamWithLeftover = Effect.suspend(() => { + if (leftover !== undefined) { + const chunk = leftover + leftover = undefined + return Effect.succeed(chunk) + } + return upstream + }).pipe( + Effect.catch((error) => { + done = Exit.fail(error) + return Cause.done() + }) + ) + const pull = Effect.map( + Effect.suspend(() => sink.transform(upstreamWithLeftover, scope)), + ([value, leftover_]) => { + leftover = leftover_ + return Arr.of(value) + } + ) + return Effect.suspend((): Pull.Pull< + Arr.NonEmptyReadonlyArray, + E | E2, + void, + R2 + > => done ? done : pull) + })) +) + +/** + * Aggregates elements using the provided sink and emits each sink result as a stream element. + * + * **Details** + * + * The stream runs the upstream and downstream in separate fibers, so the sink can keep + * consuming input while downstream is busy processing the previous output. + * + * **Example** (Aggregating with a sink) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * await Effect.runPromise(Effect.gen(function* () { + * const aggregated = yield* Stream.runCollect( + * Stream.make(1, 2, 3, 4, 5, 6).pipe( + * Stream.aggregate( + * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)) + * ) + * ) + * ) + * aggregated // => [ 6, 15 ] + * })) + * ``` + * + * @category aggregation + * @since 2.0.0 + */ +export const aggregate: { + ( + sink: Sink.Sink + ): (self: Stream) => Stream + ( + self: Stream, + sink: Sink.Sink + ): Stream +} = dual(2, ( + self: Stream, + sink: Sink.Sink +): Stream => aggregateWithin(self, sink, Schedule.forever)) + +/** + * Aggregates elements with a sink, emitting each result when the sink completes or the schedule triggers. + * + * **Details** + * + * The schedule can flush the current aggregation even if the sink has not finished. + * + * **Example** (Aggregating with a sink and schedule) + * + * ```ts import.meta.vitest + * import { Effect, Schedule, Sink, Stream } from "effect" + * + * await Effect.runPromise(Effect.gen(function* () { + * const aggregated = yield* Stream.runCollect( + * Stream.make(1, 2, 3, 4, 5, 6).pipe( + * Stream.aggregateWithin( + * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)), + * Schedule.forever + * ) + * ) + * ) + * aggregated // => [ 6, 15 ] + * })) + * ``` + * + * @category aggregation + * @since 2.0.0 + */ +export const aggregateWithin: { + ( + sink: Sink.Sink, + schedule: Schedule.Schedule, E3, R3> + ): (self: Stream) => Stream + ( + self: Stream, + sink: Sink.Sink, + schedule: Schedule.Schedule, E3, R3> + ): Stream +} = dual(3, ( + self: Stream, + sink: Sink.Sink, + schedule: Schedule.Schedule, E3, R3> +): Stream => + fromChannel(Channel.fromTransformBracket(Effect.fnUntraced(function*(_upstream, _, scope) { + const pull = yield* Channel.toPullScoped(self.channel, _) + + const pullLatch = Latch.makeUnsafe(false) + const scheduleStep = Symbol() + const buffer = yield* Queue.make | typeof scheduleStep, E | Cause.Done>({ + capacity: 0 + }) + + // upstream -> buffer + yield* pull.pipe( + pullLatch.whenOpen, + Effect.flatMap((arr) => { + pullLatch.closeUnsafe() + return Queue.offer(buffer, arr) + }), + Effect.forever, // don't disable autoYield to prevent choking the schedule + Effect.catchCause((cause) => Queue.failCause(buffer, cause)), + Effect.forkIn(scope) + ) + + // schedule -> buffer + let lastOutput = Option.none() + let leftover: Arr.NonEmptyReadonlyArray | undefined + let sinkHasInput = false + const step = yield* Schedule.toStepWithSleep(schedule) + const stepLoop = Effect.suspend(function loop(): Pull.Pull { + return Effect.flatMap(step(lastOutput), () => !sinkHasInput ? loop() : Queue.offer(buffer, scheduleStep)) + }) + const stepToBuffer: Pull.Pull = stepLoop.pipe( + Effect.flatMap(() => Effect.never), + Pull.catchDone(() => Cause.done()) + ) + + // buffer -> sink + const pullFromBuffer: Pull.Pull< + Arr.NonEmptyReadonlyArray, + E + > = Queue.take(buffer).pipe( + Effect.flatMap((arr) => { + if (arr === scheduleStep) { + return Cause.done() + } + sinkHasInput = true + return Effect.succeed(arr) + }) + ) + + const sinkUpstream = Effect.suspend((): Pull.Pull, E> => { + if (leftover !== undefined) { + const chunk = leftover + leftover = undefined + sinkHasInput = true + return Effect.succeed(chunk) + } + pullLatch.openUnsafe() + return pullFromBuffer + }) + const catchSinkHalt = Effect.flatMap(([value, leftover_]: Sink.End) => { + // ignore the last output if the upstream only pulled a halt + if (!sinkHasInput && buffer.state._tag === "Done") return Cause.done() + lastOutput = Option.some(value) + leftover = leftover_ + return Effect.succeed(Arr.of(value)) + }) + + return Effect.suspend(() => { + // if the buffer has exited and there is no more data to process + if (buffer.state._tag === "Done" && leftover === undefined) { + return buffer.state.exit as Exit.Exit | E> + } + sinkHasInput = leftover !== undefined + return Effect.succeed(Effect.suspend(() => sink.transform(sinkUpstream as any, scope))) + }).pipe( + Effect.flatMap((pull) => Effect.raceFirst(catchSinkHalt(pull), stepToBuffer)) + ) + })))) + +/** + * Creates a fixed-size tuple of streams that each emit + * the same elements as the source stream. + * + * **Details** + * + * The source stream starts after all downstream streams have been subscribed. + * With the default suspend strategy, the source can only advance `capacity` + * chunks ahead of the slowest downstream stream. If a downstream stream is + * interrupted, it unsubscribes from the broadcast so it no longer contributes + * backpressure. + * + * **Example** (Broadcasting to two consumers) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const [left, right] = yield* Stream.make(1, 2, 3).pipe( + * Stream.broadcastN({ n: 2, capacity: 8 }) + * ) + * + * const values = yield* Effect.all([ + * Stream.runCollect(left), + * Stream.runCollect(right) + * ], { concurrency: "unbounded" }) + * + * values // => [ [ 1, 2, 3 ], [ 1, 2, 3 ] ] + * }) + * ) + * + * await Effect.runPromise(program) + * ``` + * + * @category broadcasting + * @since 4.0.0 + */ +export const broadcastN: { + ( + options: { + readonly n: N + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly n: N + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + } + ): (self: Stream) => Effect.Effect>, never, Scope.Scope | R> + ( + self: Stream, + options: { + readonly n: N + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly n: N + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + } + ): Effect.Effect>, never, Scope.Scope | R> +} = dual( + 2, + Effect.fnUntraced(function*( + self: Stream, + options: { + readonly n: N + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly n: N + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + } + ) { + const pubsub = yield* makePubSub>(options) + const streams = new Array(options.n) + const parentScope = yield* Scope.Scope + for (let i = 0; i < options.n; i++) { + const scope = Scope.forkUnsafe(parentScope) + const subscription = yield* PubSub.subscribe(pubsub).pipe( + Effect.provideService(Scope.Scope, scope) + ) + streams[i] = Channel.fromEffectTake(PubSub.take(subscription)).pipe( + Channel.onExit((exit) => Scope.close(scope, exit)), + fromChannel + ) + } + yield* Channel.runForEach(self.channel, (value) => PubSub.publish(pubsub, value)).pipe( + Effect.onExit((exit) => PubSub.publish(pubsub, exit)), + Effect.forkScoped + ) + return streams as TupleOf> + }) +) + +const makePubSub = ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } +) => + Effect.acquireRelease( + options.capacity === "unbounded" + ? PubSub.unbounded(options) + : options.strategy === "dropping" + ? PubSub.dropping(options) + : options.strategy === "sliding" + ? PubSub.sliding(options) + : PubSub.bounded(options), + PubSub.shutdown + ) + +/** + * Creates a PubSub-backed stream that multicasts the source to all subscribers. + * + * **Details** + * + * The returned stream is scoped and uses the provided PubSub capacity and replay settings. + * + * **Example** (Broadcasting a stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.scoped( + * Effect.gen(function* () { + * const broadcasted = yield* Stream.broadcast(Stream.fromArray([1, 2, 3]), { + * capacity: 8, + * replay: 3 + * }) + * + * const [left, right] = yield* Effect.all([ + * Stream.runCollect(broadcasted), + * Stream.runCollect(broadcasted) + * ], { concurrency: "unbounded" }) + * + * const result = [left, right] // => [[1, 2, 3], [1, 2, 3]] + * }) + * ) + * + * await Effect.runPromise(program) + * ``` + * + * @category broadcasting + * @since 2.0.0 + */ +export const broadcast: { + ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + } + ): (self: Stream) => Effect.Effect, never, Scope.Scope | R> + ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + } + ): Effect.Effect, never, Scope.Scope | R> +} = dual(2, ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + } +): Effect.Effect, never, Scope.Scope | R> => Effect.map(toPubSubTake(self, options), fromPubSubTake)) + +/** + * Returns a new Stream that multicasts the original stream, subscribing when the first consumer starts. + * + * **Details** + * + * The upstream continues running while there is at least one consumer and is finalized after the last one exits. + * If `idleTimeToLive` is set, the upstream is kept alive for that duration so a later subscriber can continue from + * the next element instead of restarting. + * + * **Example** (Sharing a stream) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Ref, Stream } from "effect" + * + * const result = await Effect.runPromise( + * Effect.scoped( + * Effect.gen(function*() { + * const firstReady = yield* Deferred.make() + * const secondReady = yield* Deferred.make() + * const acquisitions = yield* Ref.make(0) + * const source = Stream.fromEffect(Ref.update(acquisitions, (n) => n + 1)).pipe( + * Stream.drain, + * Stream.concat(Stream.make(0)), + * Stream.concat( + * Stream.fromEffect(Effect.all([Deferred.await(firstReady), Deferred.await(secondReady)])).pipe(Stream.drain) + * ), + * Stream.concat(Stream.make(1, 2, 3)) + * ) + * const shared = yield* Stream.share(source, { capacity: 16, replay: 1 }) + * const consume = (ready: Deferred.Deferred) => + * shared.pipe( + * Stream.tap((value) => value === 0 ? Deferred.succeed(ready, void 0) : Effect.void), + * Stream.filter((value) => value !== 0), + * Stream.runCollect + * ) + * + * const values = yield* Effect.all([consume(firstReady), consume(secondReady)], { concurrency: "unbounded" }) + * return { values, acquisitions: yield* Ref.get(acquisitions) } + * }) + * ) + * ) + * result // => { values: [[1, 2, 3], [1, 2, 3]], acquisitions: 1 } + * ``` + * + * @category broadcasting + * @since 3.8.0 + */ +export const share: { + ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } | { + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } + ): (self: Stream) => Effect.Effect, never, Scope.Scope | R> + ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } | { + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } + ): Effect.Effect, never, Scope.Scope | R> +} = dual(2, ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } | { + readonly capacity: number + readonly strategy?: "sliding" | "dropping" | "suspend" | undefined + readonly replay?: number | undefined + readonly idleTimeToLive?: Duration.Input | undefined + } +): Effect.Effect, never, Scope.Scope | R> => + Effect.map( + RcRef.make({ + acquire: broadcast(self, options), + idleTimeToLive: options.idleTimeToLive + }), + (ref) => unwrap(RcRef.get(ref)) + )) + +/** + * Pipes this stream through a channel that consumes and emits chunked elements. + * + * **Details** + * + * The channel receives `NonEmptyReadonlyArray` chunks and can transform both the + * output elements and error type. + * + * **Example** (Piping through a channel) + * + * ```ts import.meta.vitest + * import { Array, Channel, Effect, Stream } from "effect" + * + * type NumberChunk = readonly [number, ...Array] + * + * const doubleChunks = Channel.identity().pipe( + * Channel.map((chunk) => Array.map(chunk, (n) => n * 2)) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.fromArray([1, 2, 3]).pipe( + * Stream.rechunk(2), + * Stream.pipeThroughChannel(doubleChunks), + * Stream.runCollect + * ) + * result // => [ 2, 4, 6 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const pipeThroughChannel: { + ( + channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> + ): (self: Stream) => Stream + ( + self: Stream, + channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> + ): Stream +} = dual(2, ( + self: Stream, + channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> +): Stream => fromChannel(Channel.pipeTo(self.channel, channel))) + +/** + * Pipes values through the provided channel while preserving this stream's + * failures alongside any channel failures. + * + * **Details** + * + * Upstream failures are not passed to the channel, so the resulting stream can + * fail with either the original stream error or the channel error. + * + * **Example** (Piping through a channel with failures) + * + * ```ts import.meta.vitest + * import { Array, Channel, Effect, Stream } from "effect" + * + * type NumberChunk = readonly [number, ...Array] + * + * const stringifyChunks = Channel.identity().pipe( + * Channel.map((chunk) => Array.map(chunk, String)) + * ) + * + * await Effect.runPromise(Effect.gen(function*() { + * const result = yield* Stream.make(1, 2, 3).pipe( + * Stream.rechunk(2), + * Stream.pipeThroughChannelOrFail(stringifyChunks), + * Stream.runCollect + * ) + * + * result // => ["1", "2", "3"] + * })) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const pipeThroughChannelOrFail: { + ( + channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> + ): (self: Stream) => Stream + ( + self: Stream, + channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> + ): Stream +} = dual(2, ( + self: Stream, + channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> +): Stream => fromChannel(Channel.pipeToOrFail(self.channel, channel))) + +/** + * Pipes the stream through `Sink.toChannel`, emitting only the sink leftovers. + * + * **Details** + * + * If the sink completes mid-chunk, the remaining elements become the output stream. + * + * **Example** (Piping through a sink) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const leftovers = yield* Stream.make(1, 2, 3, 4).pipe( + * Stream.pipeThrough(Sink.take(2)), + * Stream.runCollect + * ) + * + * leftovers // => [ 3, 4 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const pipeThrough: { + (sink: Sink.Sink): (self: Stream) => Stream + (self: Stream, sink: Sink.Sink): Stream +} = dual( + 2, + (self: Stream, sink: Sink.Sink): Stream => + self.channel.pipe( + Channel.pipeToOrFail(Sink.toChannel(sink)), + Channel.concatWith(([_, leftover]) => leftover ? Channel.succeed(leftover) : Channel.empty), + fromChannel + ) +) + +/** + * Collects all elements into an array and emits it as a single element. + * + * **Example** (Collecting values into a stream element) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * + * const program = Effect.gen(function*() { + * const collected = yield* stream.pipe(Stream.collect, Stream.runCollect) + * collected[0] // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category accumulation + * @since 4.0.0 + */ +export const collect = (self: Stream): Stream, E, R> => fromEffect(runCollect(self)) + +/** + * Accumulates elements into a growing array, emitting the cumulative array for each input chunk. + * + * **Example** (Accumulating stream elements) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const accumulated = yield* Stream.runCollect( + * Stream.fromArray([1, 2, 3]).pipe( + * Stream.rechunk(1), + * Stream.accumulate + * ) + * ) + * accumulated // => [ [ 1 ], [ 1, 2 ], [ 1, 2, 3 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category accumulation + * @since 2.0.0 + */ +export const accumulate = (self: Stream): Stream, E, R> => + mapAccumArray(self, Arr.empty, (acc, as) => { + const combined = Arr.appendAll(acc, as) + return [combined, [combined]] + }) + +/** + * Emits only elements that differ from the previous one. + * + * **Example** (Emitting changed values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.fromIterable([1, 1, 2, 2, 3]).pipe( + * Stream.changes, + * Stream.runCollect + * ) + * + * values // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category deduplication + * @since 2.0.0 + */ +export const changes = (self: Stream): Stream => changesWith(self, Equal.equals) + +/** + * Returns a stream that only emits elements that are not equal to the previously emitted element, as determined by the specified predicate. + * + * **Example** (Emitting values that changed by equivalence) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make("A", "a", "B", "b", "b").pipe( + * Stream.changesWith((left, right) => left.toLowerCase() === right.toLowerCase()) + * ) + * + * await Effect.runPromise( + * Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 'A', 'B' ] + * }) + * ) + * ``` + * + * @category deduplication + * @since 2.0.0 + */ +export const changesWith: { + (f: (x: A, y: A) => boolean): (self: Stream) => Stream + (self: Stream, f: (x: A, y: A) => boolean): Stream +} = dual( + 2, + (self: Stream, f: (x: A, y: A) => boolean): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let first = true + let last: A + return Effect.flatMap(pull, function loop(arr): Pull.Pull, E> { + const out: Array = [] + let i = 0 + if (first) { + first = false + last = arr[0] + i = 1 + out.push(last) + } + for (; i < arr.length; i++) { + const a = arr[i] + if (f(a, last)) continue + last = a + out.push(a) + } + return Arr.isArrayNonEmpty(out) ? Effect.succeed(out) : Effect.flatMap(pull, loop) + }) + })) +) + +/** + * Emits only elements that differ from the previous element, using an effectful equality check. + * + * **Details** + * + * The predicate runs for each element after the first; returning `true` treats it as equal and skips it. + * + * **Example** (Effectfully emitting changed values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.make(1, 1, 2, 2, 3, 3).pipe( + * Stream.changesWithEffect((a, b) => Effect.succeed(a === b)) + * ) + * const result = yield* Stream.runCollect(stream) + * result // => [ 1, 2, 3 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category deduplication + * @since 2.0.0 + */ +export const changesWithEffect: { + ( + f: (x: A, y: A) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + f: (x: A, y: A) => Effect.Effect + ): Stream +} = dual( + 2, + ( + self: Stream, + f: (x: A, y: A) => Effect.Effect + ): Stream => + transformPull(self, (pull, _scope) => + Effect.sync(() => { + let first = true + let last: A + return Effect.flatMap( + pull, + Effect.fnUntraced(function* loop(arr): Generator< + Pull.Pull, + Arr.NonEmptyReadonlyArray, + any + > { + const out: Array = [] + let i = 0 + if (first) { + first = false + last = arr[0] + i = 1 + out.push(last) + } + for (; i < arr.length; i++) { + const a = arr[i] + if (yield* f(a, last)) continue + last = a + out.push(a) + } + return Arr.isArrayNonEmpty(out) ? out : yield* Effect.flatMap(pull, Effect.fnUntraced(loop)) + }) + ) + })) +) + +/** + * Decodes Uint8Array chunks into strings using TextDecoder with an optional encoding. + * + * **Example** (Decoding Uint8Array chunks into strings using TextDecoder with an optional encoding) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const encoder = new TextEncoder() + * const stream = Stream.make( + * encoder.encode("Hello"), + * encoder.encode(" World") + * ) + * + * const program = Effect.gen(function*() { + * const decoded = yield* stream.pipe( + * Stream.decodeText, + * Stream.runCollect + * ) + * decoded // => [ 'Hello', ' World' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category decoding + * @since 2.0.0 + */ +export const decodeText: < + Arg extends Stream | { + readonly encoding?: string | undefined + } | undefined = { + readonly encoding?: string | undefined + } +>( + streamOrOptions?: Arg, + options?: { + readonly encoding?: string | undefined + } | undefined +) => [Arg] extends [Stream] ? Stream + : (self: Stream) => Stream = dual( + (args) => isStream(args[0]), + (self: Stream, options?: { + readonly encoding?: string | undefined + }): Stream => + suspend(() => { + const decoder = new TextDecoder(options?.encoding) + return map(self, (chunk) => decoder.decode(chunk, { stream: true })) + }) + ) + +/** + * Encodes a stream of strings into UTF-8 `Uint8Array` chunks. + * + * **Example** (Encoding a stream of strings into UTF-8 Uint8Array chunks) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make("Hello", " ", "World") + * const program = Effect.gen(function*() { + * const encoded = Stream.encodeText(stream) + * const chunks = yield* Stream.runCollect(encoded) + * const bytes = chunks.map((chunk) => [...chunk]) + * bytes // => [ [ 72, 101, 108, 108, 111 ], [ 32 ], [ 87, 111, 114, 108, 100 ] ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category encoding + * @since 2.0.0 + */ +export const encodeText = (self: Stream): Stream => + suspend(() => { + const encoder = new TextEncoder() + return map(self, (chunk) => encoder.encode(chunk)) + }) + +/** + * Splits a stream of strings into lines, handling `\n`, `\r`, and `\r\n` delimiters across chunks. + * + * **Example** (Splitting streamed text into lines) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * await Effect.runPromise(Effect.gen(function* () { + * const lines = yield* Stream.runCollect( + * Stream.make("a\nb\r\n", "c\n").pipe(Stream.splitLines) + * ) + * lines // => [ 'a', 'b', 'c' ] + * })) + * ``` + * + * @category splitting + * @since 2.0.0 + */ +export const splitLines = (self: Stream): Stream => + self.channel.pipe( + Channel.pipeTo(Channel.splitLines()), + fromChannel + ) + +/** + * Inserts the provided element between emitted elements. + * + * **Example** (Interspersing stream elements) + * + * ```ts import.meta.vitest + * import { Console, Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.intersperse(0)) + * const result = yield* Stream.runCollect(stream) + * result // => [1, 0, 2, 0, 3, 0, 4] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const intersperse: { + (element: A2): (self: Stream) => Stream + (self: Stream, element: A2): Stream +} = dual(2, (self: Stream, element: A2): Stream => + mapArray(self, (arr, i) => { + const out: Arr.NonEmptyArray = i === 0 ? [] as any : [element] + const lastIndex = arr.length - 1 + for (let j = 0; j < arr.length; j++) { + if (j === lastIndex) { + out.push(arr[j]) + } else { + out.push(arr[j], element) + } + } + return out + })) + +/** + * Adds a start value, middle value, and end value around stream elements. + * + * **Details** + * + * The start and end values are always emitted, even when the stream is empty. + * + * **Example** (Interspersing stream affixes) + * + * ```ts import.meta.vitest + * import { Console, Effect, Stream } from "effect" + * + * const stream = Stream.make("a", "b", "c").pipe( + * Stream.intersperseAffixes({ start: "[", middle: ",", end: "]" }) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => ["[", "a", ",", "b", ",", "c", "]"] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const intersperseAffixes: { + ( + options: { readonly start: A2; readonly middle: A3; readonly end: A4 } + ): (self: Stream) => Stream + ( + self: Stream, + options: { readonly start: A2; readonly middle: A3; readonly end: A4 } + ): Stream +} = dual(2, ( + self: Stream, + options: { readonly start: A2; readonly middle: A3; readonly end: A4 } +): Stream => + succeed(options.start).pipe( + concat(intersperse(self, options.middle)), + concat(succeed(options.end)) + )) + +/** + * Interleaves this stream with the specified stream by alternating pulls from + * each stream; when one ends, the remaining values from the other stream are + * emitted. + * + * **Example** (Interleaving streams) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.interleave( + * Stream.make(2, 3), + * Stream.make(5, 6, 7) + * ) + * + * const program = Effect.gen(function*() { + * const collected = yield* Stream.runCollect(stream) + * collected // => [ 2, 5, 3, 6, 7 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category merging + * @since 2.0.0 + */ +export const interleave: { + (that: Stream): (self: Stream) => Stream + (self: Stream, that: Stream): Stream +} = dual( + 2, + (self: Stream, that: Stream): Stream => + interleaveWith( + self, + that, + fromIterable(Iterable.forever([true, false])) + ) +) + +/** + * Interleaves two streams deterministically by following a boolean decider stream. + * + * **Details** + * + * The decider controls how many elements are pulled; if one side ends, pulls for + * that side are ignored. + * + * **Example** (Interleaving two streams deterministically by following a boolean decider stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const left = Stream.make(1, 3, 5) + * const right = Stream.make(2, 4, 6) + * const decider = Stream.make(true, false, false, true, true) + * + * const values = yield* Stream.runCollect( + * Stream.interleaveWith(left, right, decider) + * ) + * + * values // => [ 1, 2, 4, 3, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category merging + * @since 2.0.0 + */ +export const interleaveWith: { + ( + that: Stream, + decider: Stream + ): (self: Stream) => Stream + ( + self: Stream, + that: Stream, + decider: Stream + ): Stream +} = dual(3, ( + self: Stream, + that: Stream, + decider: Stream +): Stream => + fromChannel(Channel.fromTransform(Effect.fnUntraced(function*(upstream, scope) { + const pullDecider = yield* Channel.toTransform(Channel.flattenArray(decider.channel))(upstream, scope) + const retry = Symbol() + type retry = typeof retry + let leftDone = false + let rightDone = false + const pullLeft = (yield* Channel.toTransform(Channel.flattenArray(self.channel))( + upstream, + scope + )).pipe( + Pull.catchDone(() => { + leftDone = true + return Effect.succeed(retry) + }) + ) + const pullRight = (yield* Channel.toTransform(Channel.flattenArray(that.channel))( + upstream, + scope + )).pipe( + Pull.catchDone(() => { + rightDone = true + return Effect.succeed(retry) + }) + ) + + return Effect.gen(function*() { + while (true) { + if (leftDone && rightDone) { + return yield* Cause.done() + } + const side = yield* pullDecider + if (side && leftDone) continue + if (!side && rightDone) continue + const elem = yield* (side ? pullLeft : pullRight) + if (elem === retry) continue + return Arr.of(elem) + } + }) + })))) + +/** + * Interrupts the evaluation of this stream when the provided effect + * completes. The given effect will be forked as part of this stream, and its + * success will be discarded. This combinator will also interrupt any + * in-progress element being pulled from upstream. + * + * **Details** + * + * If the effect completes with a failure before the stream completes, the + * returned stream will emit that failure. + * + * **Example** (Interrupting when an effect completes) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const interrupt = yield* Deferred.make() + * const stream = Stream.make(1, 2, 3).pipe( + * Stream.tap((value) => + * value === 2 + * ? Deferred.succeed(interrupt, void 0) + * : Effect.void + * ), + * Stream.interruptWhen(Deferred.await(interrupt)) + * ) + * + * const result = yield* Stream.runCollect(stream) + * result // => [ 1 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const interruptWhen: { + (effect: Effect.Effect): (self: Stream) => Stream + (self: Stream, effect: Effect.Effect): Stream +} = dual( + 2, + (self: Stream, effect: Effect.Effect): Stream => + fromChannel(Channel.interruptWhen(self.channel, effect)) +) + +/** + * Stops a stream after the current pull when an effect completes. + * + * **When to use** + * + * Use to stop before the next pull after an external signal completes. + * + * **Details** + * + * The effect is forked, its success value is discarded, and its failure fails + * the stream. + * + * **Gotchas** + * + * This does not interrupt or truncate an in-progress pull. A pull may emit + * multiple elements in a single chunk, in which case the entire chunk is + * emitted. Use {@link interruptWhen} when the stream should be interrupted + * immediately. + * + * **Example** (Halting a stream after an effect completes) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const halt = yield* Deferred.make() + * const values = yield* Stream.fromArray([1, 2, 3]).pipe( + * Stream.tap((value) => value === 2 ? Deferred.succeed(halt, void 0) : Effect.void), + * Stream.haltWhen(Deferred.await(halt)), + * Stream.runCollect + * ) + * values // => [ 1, 2 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category interruption + * @since 2.0.0 + */ +export const haltWhen: { + (effect: Effect.Effect): (self: Stream) => Stream + (self: Stream, effect: Effect.Effect): Stream +} = dual( + 2, + (self: Stream, effect: Effect.Effect): Stream => + fromChannel(Channel.haltWhen(self.channel, effect)) +) + +/** + * Runs the provided finalizer when the stream exits, passing the exit value. + * + * **Example** (Running a finalizer on exit) + * + * ```ts import.meta.vitest + * import { Effect, Exit, Stream } from "effect" + * + * const exits: Array = [] + * const stream = Stream.make(1, 2, 3).pipe( + * Stream.onExit((exit) => + * Exit.isSuccess(exit) + * ? Effect.sync(() => exits.push("success")) + * : Effect.sync(() => exits.push("failure")) + * ) + * ) + * + * await Effect.runPromise(Effect.gen(function*() { + * yield* Stream.runCollect(stream) + * })) + * exits // => ["success"] + * ``` + * + * @category resource management + * @since 4.0.0 + */ +export const onExit: { + ( + finalizer: (exit: Exit.Exit) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + finalizer: (exit: Exit.Exit) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + finalizer: (exit: Exit.Exit) => Effect.Effect +): Stream => fromChannel(Channel.onExit(self.channel, finalizer))) + +/** + * Runs the provided effect when the stream fails, passing the failure cause. + * + * **Gotchas** + * + * Note: Unlike `Effect.onError` there is no guarantee that the provided + * effect will not be interrupted. + * + * **Example** (Running an effect on errors) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Stream } from "effect" + * + * const errors: Array = [] + * const program = Effect.gen(function*() { + * const stream = Stream.make(1, 2, 3).pipe( + * Stream.concat(Stream.fail("boom")), + * Stream.onError((cause) => Effect.sync(() => errors.push(String(Cause.squash(cause))))) + * ) + * + * yield* Stream.runCollect(stream) + * }) + * + * await Effect.runPromise(Effect.exit(program)) + * errors // => ["boom"] + * ``` + * + * @category error handling + * @since 2.0.0 + */ +export const onError: { + ( + cleanup: (cause: Cause.Cause) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + cleanup: (cause: Cause.Cause) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + cleanup: (cause: Cause.Cause) => Effect.Effect +): Stream => fromChannel(Channel.onError(self.channel, cleanup))) + +/** + * Runs the provided effect before this stream starts. + * + * **Example** (Running an effect on start) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const program = Effect.gen(function*() { + * const stream = Stream.fromArray([1, 2, 3]).pipe( + * Stream.onStart(Effect.sync(() => events.push("started"))) + * ) + * + * const values = yield* Stream.runCollect(stream) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * events // => ["started"] + * ``` + * + * @category sequencing + * @since 3.6.0 + */ +export const onStart: { + ( + onStart: Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + onStart: Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + onStart: Effect.Effect +): Stream => fromChannel(Channel.onStart(self.channel, onStart))) + +/** + * Runs the provided effect with the first element emitted by the stream. + * + * **Example** (Running an effect on the first value) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const first: Array = [] + * await Effect.runPromise(Effect.gen(function* () { + * yield* Stream.fromArray([1, 2, 3]).pipe( + * Stream.onFirst((value) => Effect.sync(() => first.push(value))), + * Stream.runDrain + * ) + * })) + * first // => [1] + * ``` + * + * @category sequencing + * @since 4.0.0 + */ +export const onFirst: { + ( + onFirst: (element: NoInfer) => Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + onFirst: (element: NoInfer) => Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + onFirst: (element: NoInfer) => Effect.Effect +): Stream => fromChannel(Channel.onFirst(self.channel, (arr) => onFirst(arr[0])))) + +/** + * Runs the provided effect when the stream ends successfully. + * + * **Example** (Running an effect on end) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const program = Effect.gen(function*() { + * const values = yield* Stream.make(1, 2, 3).pipe( + * Stream.onEnd(Effect.sync(() => events.push("ended"))), + * Stream.runCollect + * ) + * values // => [1, 2, 3] + * }) + * + * await Effect.runPromise(program) + * events // => ["ended"] + * ``` + * + * @category sequencing + * @since 3.6.0 + */ +export const onEnd: { + ( + onEnd: Effect.Effect + ): (self: Stream) => Stream + ( + self: Stream, + onEnd: Effect.Effect + ): Stream +} = dual(2, ( + self: Stream, + onEnd: Effect.Effect +): Stream => fromChannel(Channel.onEnd(self.channel, onEnd))) + +/** + * Executes the provided finalizer after this stream's finalizers run. + * + * **Example** (Ensuring finalization) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const events: Array = [] + * const stream = Stream.fromArray([1, 2]).pipe( + * Stream.ensuring(Effect.sync(() => events.push("cleanup"))) + * ) + * + * const program = Effect.gen(function*() { + * const collected = yield* Stream.runCollect(stream) + * collected // => [1, 2] + * }) + * + * await Effect.runPromise(program) + * events // => ["cleanup"] + * ``` + * + * @category resource management + * @since 2.0.0 + */ +export const ensuring: { + (finalizer: Effect.Effect): (self: Stream) => Stream + (self: Stream, finalizer: Effect.Effect): Stream +} = dual( + 2, + (self: Stream, finalizer: Effect.Effect): Stream => + fromChannel(Channel.ensuring(self.channel, finalizer)) +) + +/** + * Provides a layer or context to the stream, removing the corresponding + * service requirements. Use `options.local` to build the layer every time; by + * default, layers are shared between provide calls. + * + * **Example** (Providing stream requirements) + * + * ```ts import.meta.vitest + * import { Console, Context, Effect, Layer, Stream } from "effect" + * + * class Env extends Context.Service()("Env") {} + * + * const layer = Layer.succeed(Env)({ name: "Ada" }) + * + * const stream = Stream.fromEffect( + * Effect.gen(function*() { + * const env = yield* Effect.service(Env) + * return `Hello, ${env.name}` + * }) + * ) + * + * const withEnv = stream.pipe(Stream.provide(layer)) + * + * await Effect.runPromise(Stream.runCollect(withEnv)) // => ["Hello, Ada"] + * ``` + * + * @category providing services + * @since 4.0.0 + */ +export const provide: { + ( + layer: Layer.Layer | Context.Context, + options?: { + readonly local?: boolean | undefined + } | undefined + ): ( + self: Stream + ) => Stream | RL> + ( + self: Stream, + layer: Layer.Layer | Context.Context, + options?: { + readonly local?: boolean | undefined + } | undefined + ): Stream | RL> +} = dual((args) => isStream(args[0]), ( + self: Stream, + layer: Layer.Layer | Context.Context, + options?: { + readonly local?: boolean | undefined + } | undefined +): Stream | RL> => fromChannel(Channel.provide(self.channel, layer, options))) + +/** + * Provides multiple services to the stream using a context. + * + * **Example** (Providing multiple services to the stream using a context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" + * + * class Config extends Context.Service()("Config") {} + * class Greeter extends Context.Service string }>()("Greeter") {} + * + * const context = Context.make(Config, { prefix: "Hello" }).pipe( + * Context.add(Greeter, { greet: (name: string) => `${name}!` }) + * ) + * + * const stream = Stream.fromEffect( + * Effect.gen(function*() { + * const config = yield* Effect.service(Config) + * const greeter = yield* Effect.service(Greeter) + * return greeter.greet(config.prefix) + * }) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(Stream.provideContext(stream, context)) + * result // => [ 'Hello!' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category providing services + * @since 2.0.0 + */ +export const provideContext: { + (context: Context.Context): (self: Stream) => Stream> + (self: Stream, context: Context.Context): Stream> +} = dual( + 2, + (self: Stream, context: Context.Context): Stream> => + fromChannel(Channel.provideContext(self.channel, context)) +) + +/** + * Provides the stream with a single required service, eliminating that + * requirement from its environment. + * + * **Example** (Providing a stream service) + * + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" + * + * class Greeter extends Context.Service string + * }>()("Greeter") {} + * + * const stream = Stream.fromEffect( + * Effect.service(Greeter).pipe( + * Effect.map((greeter) => greeter.greet("Ada")) + * ) + * ) + * + * const program = Effect.gen(function*() { + * const collected = yield* Stream.runCollect( + * stream.pipe( + * Stream.provideService(Greeter, { + * greet: (name) => `Hello, ${name}` + * }) + * ) + * ) + * collected // => [ 'Hello, Ada' ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category providing services + * @since 2.0.0 + */ +export const provideService: { + ( + key: Context.Key, + service: NoInfer + ): ( + self: Stream + ) => Stream> + ( + self: Stream, + key: Context.Key, + service: NoInfer + ): Stream> +} = dual(3, ( + self: Stream, + key: Context.Key, + service: NoInfer +): Stream> => fromChannel(Channel.provideService(self.channel, key, service))) + +/** + * Provides a service to the stream using an effect, removing the requirement and adding the effect's error and environment. + * + * **Example** (Providing a stream service effectfully) + * + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" + * + * class ApiConfig extends Context.Service()("ApiConfig") {} + * + * const stream = Stream.fromEffect( + * Effect.gen(function*() { + * const config = yield* Effect.service(ApiConfig) + * return config.baseUrl + * }) + * ) + * + * const events: Array = [] + * const withConfig = stream.pipe( + * Stream.provideServiceEffect( + * ApiConfig, + * Effect.succeed({ baseUrl: "https://example.com" }).pipe( + * Effect.tap(() => Effect.sync(() => events.push("loading"))) + * ) + * ) + * ) + * + * await Effect.runPromise(Stream.runCollect(withConfig)) // => ["https://example.com"] + * events // => ["loading"] + * ``` + * + * @category providing services + * @since 2.0.0 + */ +export const provideServiceEffect: { + ( + key: Context.Key, + service: Effect.Effect, ES, RS> + ): ( + self: Stream + ) => Stream | RS> + ( + self: Stream, + key: Context.Key, + service: Effect.Effect, ES, RS> + ): Stream | RS> +} = dual(3, ( + self: Stream, + key: Context.Key, + service: Effect.Effect, ES, RS> +): Stream | RS> => fromChannel(Channel.provideServiceEffect(self.channel, key, service))) + +/** + * Transforms the stream's required services by mapping the current context + * to a new one. + * + * **Example** (Updating the stream context) + * + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" + * + * class Logger extends Context.Service()("Logger") {} + * class Config extends Context.Service()("Config") {} + * + * const stream = Stream.fromEffect( + * Effect.gen(function*() { + * const logger = yield* Effect.service(Logger) + * const config = yield* Effect.service(Config) + * return `${logger.prefix}${config.name}` + * }) + * ) + * + * const updated = stream.pipe( + * Stream.updateContext((context: Context.Context) => + * Context.add(context, Config, { name: "World" }) + * ) + * ) + * + * const program = Effect.gen(function*() { + * const values = yield* Stream.runCollect(updated) + * values // => [ 'Hello World' ] + * }) + * + * await Effect.runPromise( + * Effect.provideService(program, Logger, { prefix: "Hello " }) + * ) + * ``` + * + * @category providing services + * @since 4.0.0 + */ +export const updateContext: { + ( + f: (context: Context.Context) => Context.Context + ): ( + self: Stream + ) => Stream + ( + self: Stream, + f: (context: Context.Context) => Context.Context + ): Stream +} = dual(2, ( + self: Stream, + f: (context: Context.Context) => Context.Context +): Stream => fromChannel(Channel.updateContext(self.channel, f))) + +/** + * Updates a single service in the stream environment by applying a function. + * + * **Example** (Updating a stream service) + * + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" + * + * class Counter extends Context.Service()("Counter") {} + * + * const stream = Stream.fromEffect(Effect.service(Counter)).pipe( + * Stream.updateService(Counter, (counter) => ({ count: counter.count + 1 })) + * ) + * + * const program = Effect.gen(function*() { + * const counters = yield* Stream.runCollect(stream) + * const message = `Updated count: ${counters[0].count}` // => "Updated count: 1" + * }) + * + * await Effect.runPromise(Effect.provideService(program, Counter, { count: 0 })) + * ``` + * + * @category providing services + * @since 2.0.0 + */ +export const updateService: { + ( + key: Context.Key, + f: (service: NoInfer) => S + ): ( + self: Stream + ) => Stream + ( + self: Stream, + key: Context.Key, + f: (service: NoInfer) => S + ): Stream +} = dual(3, ( + self: Stream, + service: Context.Key, + f: (service: NoInfer) => S +): Stream => + updateContext(self, (context) => + Context.add( + context, + service, + f(Context.get(context, service)) + ))) + +/** + * Wraps the stream with a new span for tracing. + * + * **Example** (Wrapping a stream in a span) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.withSpan("numbers")) + * + * await Effect.runPromise( + * Effect.gen(function*() { + * const values = yield* Stream.runCollect(stream) + * values // => [ 1, 2, 3 ] + * }) + * ) + * ``` + * + * @category tracing + * @since 2.0.0 + */ +export const withSpan: { + (name: string, options?: SpanOptions): (self: Stream) => Stream> + (self: Stream, name: string, options?: SpanOptions): Stream> +} = function() { + const dataFirst = isStream(arguments[0]) + const name = dataFirst ? arguments[1] : arguments[0] + const options = addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) + if (dataFirst) { + const self = arguments[0] as Stream + return fromChannel(Channel.withSpan(self.channel, name, options)) + } + return (self: Stream) => fromChannel(Channel.withSpan(self.channel, name, options)) +} as any + +/** + * Provides the entry point for do-notation style stream composition. + * + * **Example** (Starting stream do notation) + * + * ```ts import.meta.vitest + * import { Effect, pipe, Stream } from "effect" + * + * const program = pipe( + * Stream.Do, + * Stream.bind("value", () => Stream.fromArray([1, 2])), + * Stream.let("next", ({ value }) => value + 1) + * ) + * + * const effect = Effect.gen(function*() { + * const collected = yield* Stream.runCollect(program) + * collected // => [ { value: 1, next: 2 }, { value: 2, next: 3 } ] + * }) + * + * await Effect.runPromise(effect) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const Do: Stream<{}> = succeed({}) + +const let_: { + ( + name: Exclude, + f: (a: NoInfer) => B + ): (self: Stream) => Stream<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, E, R> + ( + self: Stream, + name: Exclude, + f: (a: NoInfer) => B + ): Stream<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, E, R> +} = dual(3, ( + self: Stream, + name: Exclude, + f: (a: NoInfer) => B +): Stream<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, E, R> => + map(self, (a) => ({ ...a, [name]: f(a) } as any))) +export { + /** + * Adds a computed field to the current Do-notation record. + * + * **Example** (Adding a computed field) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.Do.pipe( + * Stream.let("x", () => 2), + * Stream.let("y", ({ x }) => x * 3) + * ) + * + * const program = Effect.gen(function*() { + * const records = yield* Stream.runCollect(stream) + * records // => [ { x: 2, y: 6 } ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 2.0.0 + */ + let_ as let +} + +/** + * Binds the result of a stream to a field in the do-notation record. + * + * **Example** (Binding a stream value) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Stream.Do.pipe( + * Stream.bind("a", () => Stream.make(1, 2)), + * Stream.bind("b", ({ a }) => Stream.succeed(a + 1)) + * ) + * + * const result = Stream.runCollect(program) + * + * await Effect.runPromise(result) // => [{ a: 1, b: 2 }, { a: 2, b: 3 }] + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const bind: { + ( + tag: Exclude, + f: (_: NoInfer) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined + ): (self: Stream) => Stream<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, E2 | E, R2 | R> + ( + self: Stream, + tag: Exclude, + f: (_: NoInfer) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined + ): Stream<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, E | E2, R | R2> +} = dual((args) => isStream(args[0]), ( + self: Stream, + tag: Exclude, + f: (_: NoInfer) => Stream, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + } | undefined +): Stream<{ [K in N | keyof A]: K extends keyof A ? A[K] : B }, E | E2, R | R2> => + flatMap(self, (a) => map(f(a), (b) => ({ ...a, [tag]: b } as any)), options)) + +/** + * Binds an Effect-produced value into the do-notation record for each stream element. + * + * **Example** (Binding an effect value) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.Do.pipe( + * Stream.bind("value", () => Stream.make(1, 2)), + * Stream.bindEffect("double", ({ value }) => Effect.succeed(value * 2)) + * ) + * + * const program = Effect.gen(function*() { + * const result = yield* Stream.runCollect(stream) + * result // => [ { value: 1, double: 2 }, { value: 2, double: 4 } ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category sequencing + * @since 2.0.0 + */ +export const bindEffect: { + ( + tag: Exclude, + f: (_: NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + readonly unordered?: boolean | undefined + } + ): (self: Stream) => Stream<{ [K in keyof A | N]: K extends keyof A ? A[K] : B }, E | E2, R | R2> + ( + self: Stream, + tag: Exclude, + f: (_: NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + readonly unordered?: boolean | undefined + } + ): Stream<{ [K in keyof A | N]: K extends keyof A ? A[K] : B }, E | E2, R | R2> +} = dual((args) => isStream(args[0]), ( + self: Stream, + tag: Exclude, + f: (_: NoInfer) => Effect.Effect, + options?: { + readonly concurrency?: number | "unbounded" | undefined + readonly bufferSize?: number | undefined + readonly unordered?: boolean | undefined + } | undefined +): Stream<{ [K in keyof A | N]: K extends keyof A ? A[K] : B }, E | E2, R | R2> => + mapEffect(self, (a) => Effect.map(f(a), (b) => ({ ...a, [tag]: b } as any)), options)) + +/** + * Maps each element into a record keyed by the provided name. + * + * **Example** (Binding values to a record key) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3).pipe(Stream.bindTo("value")) + * + * await Effect.runPromise(Stream.runCollect(stream)) // => [{ value: 1 }, { value: 2 }, { value: 3 }] + * ``` + * + * @category mapping + * @since 2.0.0 + */ +export const bindTo: { + (name: N): (self: Stream) => Stream<{ [K in N]: A }, E, R> + (self: Stream, name: N): Stream<{ [K in N]: A }, E, R> +} = dual(2, ( + self: Stream, + name: N +): Stream<{ [K in N]: A }, E, R> => map(self, (a) => ({ [name]: a } as any))) + +/** + * Runs a stream with a sink and returns the sink result. + * + * **Example** (Running a stream with a sink) + * + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" + * + * const program = Stream.run(Stream.make(1, 2, 3), Sink.sum) + * + * await Effect.runPromise(program) // => 6 + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const run: { + ( + sink: Sink.Sink + ): (self: Stream) => Effect.Effect + ( + self: Stream, + sink: Sink.Sink + ): Effect.Effect +} = dual(2, ( + self: Stream, + sink: Sink.Sink +): Effect.Effect => + Effect.scopedWith((scope) => + Channel.toPullScoped(self.channel, scope).pipe( + Effect.flatMap((upstream) => sink.transform(upstream as any, scope)), + Effect.map(([a]) => a) + ) + )) + +/** + * Runs the stream and collects all elements into an array. + * + * **Example** (Collecting stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4, 5) + * + * const program = Effect.gen(function*() { + * const collected = yield* Stream.runCollect(stream) + * collected // => [ 1, 2, 3, 4, 5 ] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runCollect = (self: Stream): Effect.Effect, E, R> => + Channel.runFold( + self.channel, + () => [] as Array, + (acc, chunk) => { + for (let i = 0; i < chunk.length; i++) { + acc.push(chunk[i]) + } + return acc + } + ) + +/** + * Runs the stream and returns the number of elements emitted. + * + * **Example** (Counting stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4, 5) + * + * const program = Effect.gen(function* () { + * const count = yield* Stream.runCount(stream) + * count // => 5 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runCount = (self: Stream): Effect.Effect => + Channel.runFold(self.channel, () => 0, (acc, chunk) => acc + chunk.length) + +/** + * Runs the stream and returns the numeric sum of its elements. + * + * **Example** (Summing stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const total = yield* Stream.runSum(Stream.make(1, 2, 3)) + * total // => 6 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runSum = (self: Stream): Effect.Effect => + Channel.runFold(self.channel, () => 0, (acc, chunk) => { + for (let i = 0; i < chunk.length; i++) { + acc += chunk[i] + } + return acc + }) + +/** + * Runs the stream and folds elements using a pure reducer. + * + * **Example** (Folding stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const total = yield* Stream.runFold( + * Stream.make(1, 2, 3), + * () => 0, + * (acc, n) => acc + n + * ) + * total // => 6 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runFold: { + ( + initial: LazyArg, + f: (acc: Z, a: A) => Z + ): ( + self: Stream + ) => Effect.Effect + ( + self: Stream, + initial: LazyArg, + f: (acc: Z, a: A) => Z + ): Effect.Effect +} = dual(3, ( + self: Stream, + initial: LazyArg, + f: (acc: Z, a: A) => Z +): Effect.Effect => + Channel.runFold(self.channel, initial, (acc, arr) => { + for (let i = 0; i < arr.length; i++) { + acc = f(acc, arr[i]) + } + return acc + })) + +/** + * Runs the stream and folds elements using an effectful reducer. + * + * **When to use** + * + * Use when reducing stream elements needs Effects, services, or failures in the + * reducer. + * + * **Example** (Effectfully folding stream values) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const total = yield* Stream.runFoldEffect( + * Stream.make(1, 2, 3), + * () => 0, + * (acc, n) => Effect.succeed(acc + n) + * ) + * total // => 6 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runFoldEffect: { + ( + initial: LazyArg, + f: (acc: Z, a: A) => Effect.Effect + ): ( + self: Stream + ) => Effect.Effect + ( + self: Stream, + initial: LazyArg, + f: (acc: Z, a: A) => Effect.Effect + ): Effect.Effect +} = dual(3, ( + self: Stream, + initial: LazyArg, + f: (acc: Z, a: A) => Effect.Effect +): Effect.Effect => + Channel.runFoldEffect(self.channel, initial, (acc, arr) => { + let i = 0 + let s = acc + return Effect.map( + Effect.whileLoop({ + while: () => i < arr.length, + body: () => f(s, arr[i]), + step(z) { + s = z + i++ + } + }), + () => s + ) + })) + +/** + * Runs the stream and returns the first element as an `Option`. + * + * **Example** (Getting the first stream value) + * + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const head = yield* Stream.runHead(Stream.make(1, 2, 3)) + * Option.getOrThrow(head) // => 1 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runHead = (self: Stream): Effect.Effect, E, R> => + Effect.map(Channel.runHead(self.channel), Option.map(Arr.getUnsafe(0))) + +/** + * Runs the stream and returns the last element as an `Option`. + * + * **When to use** + * + * Use to consume a finite stream when only the final emitted element matters. + * + * **Details** + * + * `Option.some` contains the last emitted element. `Option.none` means the + * stream completed without emitting. + * + * **Gotchas** + * + * The returned effect waits for the stream to complete before it can produce a + * value. + * + * @see {@link runHead} for consuming only the first emitted element + * @see {@link runCollect} for collecting every emitted element + * @see {@link runDrain} for consuming the stream while discarding emitted elements + * + * @category destructors + * @since 2.0.0 + */ +export const runLast = (self: Stream): Effect.Effect, E, R> => + Effect.map(Channel.runLast(self.channel), Option.map(Arr.lastNonEmpty)) + +/** + * Runs the provided effectful callback for each element of the stream. + * + * **Example** (Running an effect for each value) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * const values: Array = [] + * + * const program = Effect.gen(function*() { + * yield* Stream.runForEach(stream, (n) => Effect.sync(() => values.push(`Processing: ${n}`))) + * }) + * + * await Effect.runPromise(program) + * values // => ["Processing: 1", "Processing: 2", "Processing: 3"] + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runForEach: { + ( + f: (a: A) => Effect.Effect + ): (self: Stream) => Effect.Effect + ( + self: Stream, + f: (a: A) => Effect.Effect + ): Effect.Effect +} = dual(2, ( + self: Stream, + f: (a: A) => Effect.Effect +): Effect.Effect => + Channel.runForEach(self.channel, (arr) => { + let i = 0 + return Effect.whileLoop({ + while: () => i < arr.length, + body: () => f(arr[i++]), + step: constVoid + }) + })) + +/** + * Runs the stream, applying the effectful predicate to each element and + * stopping when it returns `false`. + * + * **Example** (Running effects while a predicate holds) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const values: Array = [] + * const program = Effect.gen(function*() { + * const stream = Stream.make(1, 2, 3, 4, 5) + * + * yield* Stream.runForEachWhile(stream, (n) => + * Effect.gen(function*() { + * yield* Effect.sync(() => values.push(n)) + * return n < 3 + * }) + * ) + * }) + * + * await Effect.runPromise(program) + * values // => [1, 2, 3] + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runForEachWhile: { + ( + f: (a: A) => Effect.Effect + ): (self: Stream) => Effect.Effect + ( + self: Stream, + f: (a: A) => Effect.Effect + ): Effect.Effect +} = dual(2, ( + self: Stream, + f: (a: A) => Effect.Effect +): Effect.Effect => + Channel.runForEachWhile(self.channel, (arr) => { + let done = false + let i = 0 + return Effect.map( + Effect.whileLoop({ + while: () => !done && i < arr.length, + body: () => f(arr[i]), + step(b) { + i++ + if (!b) done = true + } + }), + () => !done + ) + })) + +/** + * Consumes the stream in chunks, passing each non-empty array to the callback. + * + * **Example** (Consuming stream chunks) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4, 5) + * const chunks: Array = [] + * const program = Effect.gen(function*() { + * yield* Stream.runForEachArray( + * stream, + * (chunk) => Effect.sync(() => chunks.push(chunk.join(", "))) + * ) + * }) + * + * await Effect.runPromise(program) + * chunks // => ["1, 2, 3, 4, 5"] + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const runForEachArray: { + ( + f: (a: Arr.NonEmptyReadonlyArray) => Effect.Effect + ): (self: Stream) => Effect.Effect + ( + self: Stream, + f: (a: Arr.NonEmptyReadonlyArray) => Effect.Effect + ): Effect.Effect +} = dual(2, ( + self: Stream, + f: (a: Arr.NonEmptyReadonlyArray) => Effect.Effect +): Effect.Effect => Channel.runForEach(self.channel, f)) + +/** + * Runs the stream for its effects, discarding emitted elements. + * + * **Example** (Draining a stream run) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const values: Array = [] + * const program = Effect.gen(function*() { + * const stream = Stream.make(1, 2, 3).pipe( + * Stream.mapEffect((n) => Effect.sync(() => values.push(n))) + * ) + * + * yield* Stream.runDrain(stream) + * }) + * + * await Effect.runPromise(program) + * values // => [1, 2, 3] + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runDrain = (self: Stream): Effect.Effect => Channel.runDrain(self.channel) + +/** + * Returns a scoped pull for manually consuming the stream's output chunks. + * + * **Details** + * + * The pull fails with `Cause.Done` when the stream ends and with the stream + * error on failure. + * + * **Example** (Creating a scoped pull) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const pull = yield* Stream.toPull(stream) + * const chunk = yield* pull + * chunk // => [ 1, 2, 3 ] + * }) + * ) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const toPull = ( + self: Stream +): Effect.Effect, E>, never, R | Scope.Scope> => Channel.toPull(self.channel) + +/** + * Concatenates all emitted strings into a single string. + * + * **Example** (Joining strings from a stream) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make("Hello", " ", "World", "!") + * const program = Effect.gen(function*() { + * const text = yield* Stream.mkString(stream) + * text // => "Hello World!" + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const mkString = (self: Stream): Effect.Effect => + Channel.runFold( + self.channel, + () => "", + (acc, chunk) => acc + chunk.join("") + ) + +/** + * Concatenates the stream's `Uint8Array` chunks into a single `ArrayBuffer`. + * + * **Example** (Joining byte chunks into an ArrayBuffer) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const program = Stream.make( + * new Uint8Array([1, 2]), + * new Uint8Array([3, 4]) + * ).pipe( + * Stream.mkArrayBuffer, + * Effect.map((buffer) => [...new Uint8Array(buffer)]) + * ) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4] + * ``` + * + * **Gotchas** + * + * This materializes the full content in memory. The source stream must not + * reuse or mutate emitted buffers, which are retained until collection completes. + * + * @category destructors + * @since 4.0.0 + */ +export const mkArrayBuffer = (self: Stream): Effect.Effect => + Effect.map(Channel.mkUint8Array(self.channel), (bytes) => bytes.buffer) + +/** + * Concatenates the stream's `Uint8Array` chunks into a single `Uint8Array`. + * + * **Example** (Joining Uint8Array chunks) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(new Uint8Array([1, 2]), new Uint8Array([3, 4])) + * const program = Effect.gen(function*() { + * const bytes = yield* Stream.mkUint8Array(stream) + * const values = Array.from(bytes) // => [1, 2, 3, 4] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * **Gotchas** + * + * This materializes the full content in memory. The source stream must not + * reuse or mutate emitted buffers, which are retained until collection completes. + * + * @category destructors + * @since 4.0.0 + */ +export const mkUint8Array = (self: Stream): Effect.Effect => + Channel.mkUint8Array(self.channel) + +/** + * Converts the stream to a `ReadableStream` using the provided services. + * + * **When to use** + * + * Use when bridging to Web Streams and you already have the `Context` required + * to run the stream outside an `Effect`. + * + * **Details** + * + * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. + * + * **Example** (Converting to a ReadableStream with services) + * + * ```ts import.meta.vitest + * import { Context, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4, 5) + * const readableStream = Stream.toReadableStreamWith(stream, Context.empty()) + * const values = await Array.fromAsync(readableStream) + * values // => [ 1, 2, 3, 4, 5 ] + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const toReadableStreamWith = dual< + ( + context: Context.Context, + options?: { readonly strategy?: QueuingStrategy | undefined } + ) => (self: Stream) => ReadableStream, + ( + self: Stream, + context: Context.Context, + options?: { readonly strategy?: QueuingStrategy | undefined } + ) => ReadableStream +>( + (args) => isStream(args[0]), + ( + self: Stream, + context: Context.Context, + options?: { readonly strategy?: QueuingStrategy | undefined } + ): ReadableStream => { + let currentResolve: (() => void) | undefined = undefined + let fiber: Fiber.Fiber | undefined = undefined + const latch = Latch.makeUnsafe(false) + + return new ReadableStream({ + start(controller) { + fiber = Effect.runFork(Effect.provideContext( + runForEachArray(self, (chunk) => + latch.whenOpen(Effect.sync(() => { + latch.closeUnsafe() + for (let i = 0; i < chunk.length; i++) { + controller.enqueue(chunk[i]) + } + currentResolve!() + currentResolve = undefined + }))), + context + )) + fiber.addObserver((exit) => { + if (exit._tag === "Failure") { + controller.error(Cause.squash(exit.cause)) + } else { + controller.close() + } + }) + }, + pull() { + return new Promise((resolve) => { + currentResolve = resolve + latch.openUnsafe() + }) + }, + cancel() { + if (!fiber) return + return Effect.runPromise(Effect.asVoid(Fiber.interrupt(fiber))) + } + }, options?.strategy) + } +) + +/** + * Converts a stream to a `ReadableStream`. + * + * **Details** + * + * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. + * + * **Example** (Converting a stream to a ReadableStream) + * + * ```ts import.meta.vitest + * import { Stream } from "effect" + * + * const readableStream = Stream.toReadableStream(Stream.make(1, 2, 3)) + * const values = await Array.fromAsync(readableStream) + * values // => [ 1, 2, 3 ] + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const toReadableStream: { + ( + options?: { readonly strategy?: QueuingStrategy | undefined } + ): ( + self: Stream + ) => ReadableStream + ( + self: Stream, + options?: { readonly strategy?: QueuingStrategy | undefined } + ): ReadableStream +} = dual( + (args) => isStream(args[0]), + ( + self: Stream, + options?: { readonly strategy?: QueuingStrategy | undefined } + ): ReadableStream => toReadableStreamWith(self, Context.empty(), options) +) + +/** + * Creates an Effect that builds a ReadableStream from the stream. + * + * **When to use** + * + * Use when bridging to Web Streams from inside an `Effect` so the required + * services can be captured from the current context. + * + * **Details** + * + * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. + * + * **Example** (Creating a ReadableStream effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3, 4, 5) + * + * const effect = Effect.gen(function*() { + * const readableStream = yield* Stream.toReadableStreamEffect(stream) + * readableStream instanceof ReadableStream // => true + * }) + * + * await Effect.runPromise(effect) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const toReadableStreamEffect: { + ( + options?: { readonly strategy?: QueuingStrategy | undefined } + ): ( + self: Stream + ) => Effect.Effect, never, R> + ( + self: Stream, + options?: { readonly strategy?: QueuingStrategy | undefined } + ): Effect.Effect, never, R> +} = dual( + (args) => isStream(args[0]), + ( + self: Stream, + options?: { readonly strategy?: QueuingStrategy | undefined } + ): Effect.Effect, never, R> => + Effect.map( + Effect.context(), + (context) => toReadableStreamWith(self, context, options) + ) +) + +/** + * Converts the stream to an `AsyncIterable` using the provided services. + * + * **When to use** + * + * Use when converting outside an Effect and you already have the `Context` + * needed to run the stream. + * + * **Example** (Converting to an AsyncIterable with services) + * + * ```ts import.meta.vitest + * import { Context, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * const iterable = Stream.toAsyncIterableWith(stream, Context.empty()) + * + * await Array.fromAsync(iterable) // => [1, 2, 3] + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const toAsyncIterableWith: { + (context: Context.Context): (self: Stream) => AsyncIterable + ( + self: Stream, + context: Context.Context + ): AsyncIterable +} = dual( + 2, + ( + self: Stream, + context: Context.Context + ): AsyncIterable => ({ + [Symbol.asyncIterator]() { + const runPromise = Effect.runPromiseWith(context) + const runFork = Effect.runForkWith(context) + const scope = Scope.makeUnsafe() + let pull: Pull.Pull, E, void, R> | undefined + let currentIter: Iterator | undefined + let currentFiber: Fiber.Fiber, E | Cause.Done> | undefined + let closePromise: Promise> | undefined + const close = (exit: Exit.Exit): Promise> => { + if (closePromise) return closePromise + const fiber = currentFiber + closePromise = runPromise(Effect.as( + Effect.andThen( + fiber ? Fiber.interrupt(fiber) : Effect.void, + Scope.close(scope, exit) + ), + { done: true, value: undefined } + )) + return closePromise + } + const closeAndReportError = async (exit: Exit.Exit): Promise => { + try { + await close(exit) + } catch (error) { + await runPromise(Effect.logError("Suppressed error while closing Stream async iterator", error)) + } + } + return { + async next(): Promise> { + if (closePromise) return closePromise + if (currentIter) { + const next = currentIter.next() + if (!next.done) return next + currentIter = undefined + } + const fiber = runFork( + pull ?? + Effect.flatMap(Channel.toPullScoped(self.channel, scope), (nextPull) => { + pull = nextPull + return nextPull + }) + ) + currentFiber = fiber + const exit = await runPromise(Fiber.await(fiber)) + if (currentFiber === fiber) { + currentFiber = undefined + } + if (Exit.isSuccess(exit)) { + currentIter = exit.value[Symbol.iterator]() + return currentIter.next() + } else if (Pull.isDoneCause(exit.cause)) { + return close(Exit.void) + } + if (closePromise && Cause.hasInterruptsOnly(exit.cause)) { + return closePromise + } + await closeAndReportError(exit) + throw Cause.squash(exit.cause) + }, + return() { + return close(Exit.void) + }, + async throw(error) { + await closeAndReportError(Exit.die(error)) + throw error + } + } + } + }) +) + +/** + * Creates an effect that yields an `AsyncIterable` using the current services. + * + * **When to use** + * + * Use when the `AsyncIterable` should be created inside Effect with the current + * context supplying the stream's services. + * + * **Example** (Creating an AsyncIterable effect) + * + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * + * const program = Effect.gen(function*() { + * const iterable = yield* Stream.toAsyncIterableEffect(stream) + * return yield* Effect.promise(() => Array.fromAsync(iterable)) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] + * ``` + * + * @category destructors + * @since 3.15.0 + */ +export const toAsyncIterableEffect = (self: Stream): Effect.Effect, never, R> => + Effect.map( + Effect.context(), + (context) => toAsyncIterableWith(self, context) + ) + +/** + * Converts a stream to an `AsyncIterable` for `for await...of` consumption. + * + * **Example** (Converting to an async iterable) + * + * ```ts import.meta.vitest + * import { Stream } from "effect" + * + * const stream = Stream.make(1, 2, 3) + * + * await Array.fromAsync(Stream.toAsyncIterable(stream)) // => [1, 2, 3] + * ``` + * + * @category destructors + * @since 3.15.0 + */ +export const toAsyncIterable = (self: Stream): AsyncIterable => + toAsyncIterableWith(self, Context.empty()) + +/** + * Runs the stream, publishing elements into the provided PubSub. + * + * **Details** + * + * `shutdownOnEnd` controls whether the PubSub is shut down when the stream ends. + * It only shuts down when set to `true`. + * + * **Example** (Running a stream into a PubSub) + * + * ```ts import.meta.vitest + * import { Effect, PubSub, Stream } from "effect" + * + * const program = Effect.scoped(Effect.gen(function* () { + * const pubsub = yield* PubSub.unbounded() + * const subscription = yield* PubSub.subscribe(pubsub) + * + * yield* Stream.runIntoPubSub(Stream.fromIterable([1, 2]), pubsub) + * + * const first = yield* PubSub.take(subscription) + * const second = yield* PubSub.take(subscription) + * + * first // => 1 + * second // => 2 + * })) + * + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runIntoPubSub: { + ( + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ): (self: Stream) => Effect.Effect + ( + self: Stream, + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined + ): Effect.Effect +} = dual((args) => isStream(args[0]), ( + self: Stream, + pubsub: PubSub.PubSub, + options?: { + readonly shutdownOnEnd?: boolean | undefined + } | undefined +): Effect.Effect => Channel.runIntoPubSubArray(self.channel, pubsub, options)) + +/** + * Converts a stream to a PubSub of emitted values for concurrent consumption. + * + * **Details** + * + * `shutdownOnEnd` indicates whether the PubSub should be shut down when the + * stream ends. By default this is `true`. + * + * **Example** (Converting a stream to a PubSub for concurrent consumption) + * + * ```ts import.meta.vitest + * import { Effect, PubSub, Stream } from "effect" + * + * const program = Effect.scoped(Effect.gen(function* () { + * const pubsub = yield* Stream.fromArray([1, 2]).pipe( + * Stream.toPubSub({ capacity: 8 }) + * ) + * const subscription = yield* PubSub.subscribe(pubsub) + * const first = yield* PubSub.take(subscription) + * + * first // => 1 + * })) + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const toPubSub: { + ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ): (self: Stream) => Effect.Effect, never, R | Scope.Scope> + ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ): Effect.Effect, never, R | Scope.Scope> +} = dual( + 2, + ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + readonly shutdownOnEnd?: boolean | undefined + } + ): Effect.Effect, never, R | Scope.Scope> => Channel.toPubSubArray(self.channel, options) +) + +/** + * Converts a stream to a PubSub of `Take` values for concurrent consumption. + * + * **Details** + * + * `Take` values include the stream's end and failure signals. + * + * **Example** (Converting to a PubSub of takes) + * + * ```ts import.meta.vitest + * import { Effect, PubSub, Stream } from "effect" + * + * const program = Effect.gen(function* () { + * const pubsub = yield* Stream.fromArray([1, 2, 3]).pipe( + * Stream.toPubSubTake({ capacity: 8 }) + * ) + * const subscription = yield* PubSub.subscribe(pubsub) + * const take = yield* PubSub.take(subscription) + * + * if (Array.isArray(take)) { + * take // => [ 1, 2, 3 ] + * } + * }) + * await Effect.runPromise(Effect.scoped(program)) + * ``` + * + * @category destructors + * @since 4.0.0 + */ +export const toPubSubTake: { + ( + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } + ): (self: Stream) => Effect.Effect>, never, R | Scope.Scope> + ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } + ): Effect.Effect>, never, R | Scope.Scope> +} = dual( + 2, + ( + self: Stream, + options: { + readonly capacity: "unbounded" + readonly replay?: number | undefined + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + readonly replay?: number | undefined + } + ): Effect.Effect>, never, R | Scope.Scope> => + Channel.toPubSubTake(self.channel, options) +) + +/** + * Creates a scoped dequeue that is fed by the stream for concurrent + * consumption. + * + * **Details** + * + * Elements are offered to the queue as the stream runs. Stream completion is + * signaled with `Cause.Done`, stream failures fail the queue, and the queue is + * shut down when the surrounding scope closes. + * + * **Example** (Converting a stream to a Queue for concurrent consumption) + * + * ```ts import.meta.vitest + * import { Effect, Queue, Stream } from "effect" + * + * const program = Effect.gen(function* () { + * const queue = yield* Stream.toQueue(Stream.fromIterable([1, 2, 3]), { capacity: 8 }) + * const chunk = yield* Queue.takeBetween(queue, 1, 3) + * chunk // => [ 1, 2, 3 ] + * }) + * await Effect.runPromise(Effect.scoped(program)) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const toQueue: { + ( + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): (self: Stream) => Effect.Effect, never, R | Scope.Scope> + ( + self: Stream, + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Effect.Effect, never, R | Scope.Scope> +} = dual( + 2, + ( + self: Stream, + options: { + readonly capacity: "unbounded" + } | { + readonly capacity: number + readonly strategy?: "dropping" | "sliding" | "suspend" | undefined + } + ): Effect.Effect, never, R | Scope.Scope> => + Channel.toQueueArray(self.channel, options) +) + +/** + * Runs the stream, offering each element to the provided queue and ending it + * with `Cause.Done` when the stream completes. + * + * **Example** (Running a stream into a queue) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Queue, Stream } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.bounded(4) + * + * yield* Effect.forkChild( + * Stream.runIntoQueue(Stream.fromIterable([1, 2, 3]), queue) + * ) + * + * const values = [ + * yield* Queue.take(queue), + * yield* Queue.take(queue), + * yield* Queue.take(queue) + * ] + * const done = yield* Effect.flip(Queue.take(queue)) + * + * values // => [ 1, 2, 3 ] + * done._tag === "Done" // => true + * }) + * await Effect.runPromise(program) + * ``` + * + * @category destructors + * @since 2.0.0 + */ +export const runIntoQueue: { + (queue: Queue.Queue): (self: Stream) => Effect.Effect + (self: Stream, queue: Queue.Queue): Effect.Effect +} = dual(2, ( + self: Stream, + queue: Queue.Queue +): Effect.Effect => Channel.runIntoQueueArray(self.channel, queue)) diff --git a/.repos/effect/packages/effect/src/String.ts b/.repos/effect/packages/effect/src/String.ts new file mode 100644 index 000000000..671ba710f --- /dev/null +++ b/.repos/effect/packages/effect/src/String.ts @@ -0,0 +1,1440 @@ +/** + * Works with TypeScript `string` values. + * + * This module exposes common string operations in a pipe-friendly style. The + * helpers cover checks, comparison, concatenation, trimming, casing, slicing, + * padding, replacement, normalization, safe character access, search helpers + * that return `Option`, and joining strings through a reducer. + * + * @since 2.0.0 + */ + +import type { NonEmptyArray } from "./Array.ts" +import * as Equ from "./Equivalence.ts" +import { dual } from "./Function.ts" +import * as readonlyArray from "./internal/array.ts" +import * as number from "./Number.ts" +import * as Option from "./Option.ts" +import * as order from "./Order.ts" +import type * as Ordering from "./Ordering.ts" +import type { Refinement } from "./Predicate.ts" +import * as predicate from "./Predicate.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Exposes the global string constructor. + * + * **When to use** + * + * Use to access native JavaScript string coercion or constructor behavior from + * the Effect module namespace. + * + * **Gotchas** + * + * Calling `String(value)` returns a primitive string. Calling + * `new String(value)` creates a boxed `String` object. + * + * @see {@link isString} for checking whether a value is a primitive string + * + * @category constructors + * @since 4.0.0 + */ +export const String = globalThis.String + +/** + * Checks whether a value is a `string`. + * + * **Example** (Checking for strings) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.isString("a") // => true + * String.isString(1) // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isString: Refinement = predicate.isString + +/** + * Provides an `Order` instance for comparing strings using lexicographic + * ordering. + * + * **Example** (Comparing strings lexicographically) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.Order("apple", "banana") // => -1 + * String.Order("banana", "apple") // => 1 + * String.Order("apple", "apple") // => 0 + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Order: order.Order = order.String + +/** + * Provides an `Equivalence` instance for strings using strict equality (`===`). + * + * **Example** (Comparing strings for equality) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.Equivalence("hello", "hello") // => true + * String.Equivalence("hello", "world") // => false + * ``` + * + * @category instances + * @since 2.0.0 + */ +export const Equivalence: Equ.Equivalence = Equ.String + +/** + * Provides the empty string `""`. + * + * **When to use** + * + * Use when you need the canonical empty string value from the `String` module. + * + * **Example** (Referencing the empty string) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.empty // => "" + * String.isEmpty(String.empty) // => true + * ``` + * + * @category constants + * @since 2.0.0 + */ +export const empty: "" = "" as const + +/** + * Concatenates two strings at the type level. + * + * **Example** (Concatenating string literal types) + * + * ```ts import.meta.vitest + * import type { String } from "effect" + * + * // Type-level concatenation + * type Result = String.Concat<"hello", "world"> // "helloworld" + * + * const witness: Result = "helloworld" + * ``` + * + * @category models + * @since 2.0.0 + */ +export type Concat = `${A}${B}` + +/** + * Concatenates two strings at runtime. + * + * **Example** (Concatenating strings) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * String.concat("hello", "world") // => "helloworld" + * pipe("hello", String.concat("world")) // => "helloworld" + * ``` + * + * @category combining + * @since 2.0.0 + */ +export const concat: { + (that: B): (self: A) => Concat + (self: A, that: B): Concat +} = dual(2, (self: string, that: string): string => self + that) + +/** + * Converts a string to uppercase. + * + * **Example** (Converting strings to uppercase) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("a", String.toUpperCase) // => "A" + * String.toUpperCase("hello") // => "HELLO" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const toUpperCase = (self: S): Uppercase => self.toUpperCase() as Uppercase + +/** + * Converts a string to lowercase. + * + * **Example** (Converting strings to lowercase) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("A", String.toLowerCase) // => "a" + * String.toLowerCase("HELLO") // => "hello" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const toLowerCase = (self: T): Lowercase => self.toLowerCase() as Lowercase + +/** + * Capitalizes the first character of a string. + * + * **Example** (Capitalizing a string) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("abc", String.capitalize) // => "Abc" + * String.capitalize("hello") // => "Hello" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const capitalize = (self: T): Capitalize => { + if (self.length === 0) return self as Capitalize + + return (toUpperCase(self[0]) + self.slice(1)) as Capitalize +} + +/** + * Uncapitalizes the first character of a string. + * + * **Example** (Uncapitalizing a string) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("ABC", String.uncapitalize) // => "aBC" + * String.uncapitalize("Hello") // => "hello" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const uncapitalize = (self: T): Uncapitalize => { + if (self.length === 0) return self as Uncapitalize + + return (toLowerCase(self[0]) + self.slice(1)) as Uncapitalize +} + +/** + * Replaces matches in a string using `String.prototype.replace`. + * + * **Details** + * + * String search values and non-global regular expressions replace the first + * match; global regular expressions replace every match. + * + * **Example** (Replacing a substring) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("abc", String.replace("b", "d")) // => "adc" + * pipe("hello world", String.replace("world", "Effect")) // => "hello Effect" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const replace = (searchValue: string | RegExp, replaceValue: string) => (self: string): string => + self.replace(searchValue, replaceValue) + +/** + * Type-level representation of trimming whitespace from both ends of a string. + * + * **Example** (Trimming whitespace at the type level) + * + * ```ts import.meta.vitest + * import type { String } from "effect" + * + * type Result = String.Trim<" hello "> // "hello" + * + * const witness: Result = "hello" + * ``` + * + * @category models + * @since 2.0.0 + */ +export type Trim = TrimEnd> + +/** + * Removes whitespace from both ends of a string. + * + * **Example** (Trimming whitespace) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.trim(" a ") // => "a" + * String.trim(" hello world ") // => "hello world" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const trim = (self: A): Trim => self.trim() as Trim + +/** + * Type-level representation of trimming whitespace from the start of a string. + * + * **Example** (Trimming leading whitespace at the type level) + * + * ```ts import.meta.vitest + * import type { String } from "effect" + * + * type Result = String.TrimStart<" hello"> // "hello" + * + * const witness: Result = "hello" + * ``` + * + * @category models + * @since 2.0.0 + */ +export type TrimStart = A extends `${" " | "\n" | "\t" | "\r"}${infer B}` ? TrimStart : A + +/** + * Removes whitespace from the start of a string. + * + * **Example** (Trimming leading whitespace) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.trimStart(" a ") // => "a " + * String.trimStart(" hello world") // => "hello world" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const trimStart = (self: A): TrimStart => self.trimStart() as TrimStart + +/** + * Type-level representation of trimming whitespace from the end of a string. + * + * **Example** (Trimming trailing whitespace at the type level) + * + * ```ts import.meta.vitest + * import type { String } from "effect" + * + * type Result = String.TrimEnd<"hello "> // "hello" + * + * const witness: Result = "hello" + * ``` + * + * @category models + * @since 2.0.0 + */ +export type TrimEnd = A extends `${infer B}${" " | "\n" | "\t" | "\r"}` ? TrimEnd : A + +/** + * Removes whitespace from the end of a string. + * + * **Example** (Trimming trailing whitespace) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.trimEnd(" a ") // => " a" + * String.trimEnd("hello world ") // => "hello world" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const trimEnd = (self: A): TrimEnd => self.trimEnd() as TrimEnd + +/** + * Extracts a section of a string and returns it as a new string. + * + * **Example** (Slicing strings) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("abcd", String.slice(1, 3)) // => "bc" + * pipe("hello world", String.slice(0, 5)) // => "hello" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const slice = (start?: number, end?: number) => (self: string): string => self.slice(start, end) + +/** + * Checks whether a `string` is empty. + * + * **Example** (Checking for empty strings) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.isEmpty("") // => true + * String.isEmpty("a") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isEmpty = (self: string): self is "" => self.length === 0 + +/** + * Checks whether a `string` is non-empty. + * + * **Example** (Checking for non-empty strings) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.isNonEmpty("") // => false + * String.isNonEmpty("a") // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isNonEmpty = (self: string): boolean => self.length > 0 + +/** + * Returns the JavaScript string length, measured in UTF-16 code units. + * + * **Example** (Getting string length) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.length("abc") // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const length = (self: string): number => self.length + +/** + * Splits a string into an array of substrings using a separator. + * + * **Example** (Splitting strings) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("abc", String.split("")) // => ["a", "b", "c"] + * pipe("", String.split("")) // => [""] + * String.split("hello,world", ",") // => ["hello", "world"] + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const split: { + (separator: string | RegExp): (self: string) => NonEmptyArray + (self: string, separator: string | RegExp): NonEmptyArray +} = dual(2, (self: string, separator: string | RegExp): NonEmptyArray => { + const out = self.split(separator) + return readonlyArray.isArrayNonEmpty(out) ? out : [self] +}) + +/** + * Returns `true` if `searchString` appears as a substring of `self`, at one or more positions that are + * greater than or equal to `position`; otherwise, returns `false`. + * + * **Example** (Checking for substrings) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("hello world", String.includes("world")) // => true + * pipe("hello world", String.includes("foo")) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const includes = (searchString: string, position?: number) => (self: string): boolean => + self.includes(searchString, position) + +/** + * Returns `true` if the string starts with the specified search string. + * + * **Example** (Checking string prefixes) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("hello world", String.startsWith("hello")) // => true + * pipe("hello world", String.startsWith("world")) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const startsWith = (searchString: string, position?: number) => (self: string): boolean => + self.startsWith(searchString, position) + +/** + * Returns `true` if the string ends with the specified search string. + * + * **Example** (Checking string suffixes) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("hello world", String.endsWith("world")) // => true + * pipe("hello world", String.endsWith("hello")) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const endsWith = (searchString: string, position?: number) => (self: string): boolean => + self.endsWith(searchString, position) + +/** + * Returns the character code at the specified index safely, or `None` if the index is out of bounds. + * + * **Example** (Reading character codes) + * + * ```ts import.meta.vitest + * import { Option, String } from "effect" + * + * String.charCodeAt("abc", 1) // => Option.some(98) + * String.charCodeAt("abc", 4) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const charCodeAt: { + (index: number): (self: string) => Option.Option + (self: string, index: number): Option.Option +} = dual( + 2, + (self: string, index: number): Option.Option => + Option.filter(Option.some(self.charCodeAt(index)), (charCode) => !isNaN(charCode)) +) + +/** + * Extracts characters from a string between two specified indices. + * + * **Example** (Extracting substrings) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("abcd", String.substring(1)) // => "bcd" + * pipe("abcd", String.substring(1, 3)) // => "bc" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const substring = (start: number, end?: number) => (self: string): string => self.substring(start, end) + +/** + * Returns the character at the specified relative index safely, or `None` if the index is out of bounds. + * + * **Example** (Accessing characters safely) + * + * ```ts import.meta.vitest + * import { Option, pipe, String } from "effect" + * + * pipe("abc", String.at(1)) // => Option.some("b") + * pipe("abc", String.at(4)) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const at: { + (index: number): (self: string) => Option.Option + (self: string, index: number): Option.Option +} = dual(2, (self: string, index: number): Option.Option => Option.fromUndefinedOr(self.at(index))) + +/** + * Returns the character at the specified non-negative index safely, or `None` if the index is out of bounds. + * + * **Example** (Reading characters safely) + * + * ```ts import.meta.vitest + * import { Option, pipe, String } from "effect" + * + * pipe("abc", String.charAt(1)) // => Option.some("b") + * pipe("abc", String.charAt(4)) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const charAt: { + (index: number): (self: string) => Option.Option + (self: string, index: number): Option.Option +} = dual( + 2, + (self: string, index: number): Option.Option => Option.filter(Option.some(self.charAt(index)), isNonEmpty) +) + +/** + * Returns the Unicode code point at the specified index safely, or `None` if the index is out of bounds. + * + * **Example** (Reading code points) + * + * ```ts import.meta.vitest + * import { Option, pipe, String } from "effect" + * + * pipe("abc", String.codePointAt(1)) // => Option.some(98) + * pipe("abc", String.codePointAt(10)) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const codePointAt: { + (index: number): (self: string) => Option.Option + (self: string, index: number): Option.Option +} = dual(2, (self: string, index: number): Option.Option => Option.fromUndefinedOr(self.codePointAt(index))) + +/** + * Returns the index of the first occurrence of a substring safely, or `None` if not found. + * + * **Example** (Finding the first substring index) + * + * ```ts import.meta.vitest + * import { Option, pipe, String } from "effect" + * + * pipe("abbbc", String.indexOf("b")) // => Option.some(1) + * pipe("abbbc", String.indexOf("z")) // => Option.none() + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const indexOf = (searchString: string) => (self: string): Option.Option => + Option.filter(Option.some(self.indexOf(searchString)), number.isGreaterThanOrEqualTo(0)) + +/** + * Returns the index of the last occurrence of a substring safely, or `None` if not found. + * + * **Example** (Finding the last substring index) + * + * ```ts import.meta.vitest + * import { Option, pipe, String } from "effect" + * + * pipe("abbbc", String.lastIndexOf("b")) // => Option.some(3) + * pipe("abbbc", String.lastIndexOf("d")) // => Option.none() + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const lastIndexOf = (searchString: string) => (self: string): Option.Option => + Option.filter(Option.some(self.lastIndexOf(searchString)), number.isGreaterThanOrEqualTo(0)) + +/** + * Computes locale-aware ordering for two strings, with optional locales and + * collator options, and returns the result as an `Ordering` (`-1`, `0`, or + * `1`). + * + * **Example** (Comparing strings by locale) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("a", String.localeCompare("b")) // => -1 + * pipe("b", String.localeCompare("a")) // => 1 + * pipe("a", String.localeCompare("a")) // => 0 + * ``` + * + * @category comparisons + * @since 2.0.0 + */ +export const localeCompare = + (that: string, locales?: Array, options?: Intl.CollatorOptions) => (self: string): Ordering.Ordering => + number.sign(self.localeCompare(that, locales, options)) + +/** + * Matches a string against a pattern safely and returns `Option.some` with the match + * array, or `Option.none` when the pattern does not match. + * + * **Example** (Matching regular expressions) + * + * ```ts import.meta.vitest + * import { Option, pipe, String } from "effect" + * + * pipe( + * "hello", + * String.match(/l+/), + * Option.map((match) => [match[0], match.index]) + * ) // => Option.some(["ll", 2]) + * pipe("hello", String.match(/x/)) // => Option.none() + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const match = (regExp: RegExp | string) => (self: string): Option.Option => + Option.fromNullOr(self.match(regExp)) + +/** + * Returns an iterator over all regular expression matches in the string using + * native `String.prototype.matchAll` semantics. + * + * **Example** (Iterating regular expression matches) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * const matches = pipe("hello world", String.matchAll(/l/g)) + * + * Array.from(matches, (match) => [match[0], match.index]) // => [["l", 2], ["l", 3], ["l", 9]] + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const matchAll = (regExp: RegExp) => (self: string): IterableIterator => self.matchAll(regExp) + +/** + * Normalizes a string according to the specified Unicode normalization form. + * + * **Example** (Normalizing Unicode strings) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * const str = "\u1E9B\u0323" + * Array.from(pipe(str, String.normalize()), (character) => character.codePointAt(0)) // => [0x1e9b, 0x323] + * Array.from(pipe(str, String.normalize("NFC")), (character) => character.codePointAt(0)) // => [0x1e9b, 0x323] + * Array.from( + * pipe(str, String.normalize("NFD")), + * (character) => character.codePointAt(0) + * ) // => [0x17f, 0x323, 0x307] + * Array.from(pipe(str, String.normalize("NFKC")), (character) => character.codePointAt(0)) // => [0x1e69] + * Array.from( + * pipe(str, String.normalize("NFKD")), + * (character) => character.codePointAt(0) + * ) // => [0x73, 0x323, 0x307] + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const normalize = (form?: "NFC" | "NFD" | "NFKC" | "NFKD") => (self: string): string => self.normalize(form) + +/** + * Pads the string from the end with a given fill string to a specified length. + * + * **Example** (Padding strings at the end) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("a", String.padEnd(5)) // => "a " + * pipe("a", String.padEnd(5, "_")) // => "a____" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const padEnd = (maxLength: number, fillString?: string) => (self: string): string => + self.padEnd(maxLength, fillString) + +/** + * Pads the string from the start with a given fill string to a specified length. + * + * **Example** (Padding strings at the start) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("a", String.padStart(5)) // => " a" + * pipe("a", String.padStart(5, "_")) // => "____a" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const padStart = (maxLength: number, fillString?: string) => (self: string): string => + self.padStart(maxLength, fillString) + +/** + * Repeats the string the specified number of times. + * + * **Example** (Repeating strings) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("a", String.repeat(5)) // => "aaaaa" + * pipe("hello", String.repeat(3)) // => "hellohellohello" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const repeat = (count: number) => (self: string): string => self.repeat(count) + +/** + * Replaces all occurrences of a substring or pattern in a string. + * + * **Example** (Replacing all matches) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * pipe("ababb", String.replaceAll("b", "c")) // => "acacc" + * pipe("ababb", String.replaceAll(/ba/g, "cc")) // => "accbb" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const replaceAll = (searchValue: string | RegExp, replaceValue: string) => (self: string): string => + self.replaceAll(searchValue, replaceValue) + +/** + * Returns the index of the first match for a string or regular expression safely, or + * `Option.none` when no match is found. + * + * **Example** (Searching strings) + * + * ```ts import.meta.vitest + * import { Option, String } from "effect" + * + * String.search("ababb", "b") // => Option.some(1) + * String.search("ababb", /abb/) // => Option.some(2) + * String.search("ababb", "d") // => Option.none() + * ``` + * + * @category searching + * @since 2.0.0 + */ +export const search: { + (regExp: RegExp | string): (self: string) => Option.Option + (self: string, regExp: RegExp | string): Option.Option +} = dual( + 2, + (self: string, regExp: RegExp | string): Option.Option => + Option.filter(Option.some(self.search(regExp)), number.isGreaterThanOrEqualTo(0)) +) + +/** + * Converts the string to lowercase according to the specified locale. + * + * **Example** (Lowercasing strings by locale) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * const str = "\u0130" + * pipe(str, String.toLocaleLowerCase("tr")) // => "i" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const toLocaleLowerCase = (locale?: string | Array) => (self: string): string => + self.toLocaleLowerCase(locale) + +/** + * Converts the string to uppercase according to the specified locale. + * + * **Example** (Uppercasing strings by locale) + * + * ```ts import.meta.vitest + * import { pipe, String } from "effect" + * + * const str = "i\u0307" + * pipe(str, String.toLocaleUpperCase("lt-LT")) // => "I" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const toLocaleUpperCase = (locale?: string | Array) => (self: string): string => + self.toLocaleUpperCase(locale) + +/** + * Keeps the specified number of characters from the start of a string. + * + * **Details** + * + * If `n` is larger than the available number of characters, the string will + * be returned whole. + * + * If `n` is not a positive number, an empty string will be returned. + * + * If `n` is a float, it will be rounded down to the nearest integer. + * + * **Example** (Taking characters from the start) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.takeLeft("Hello World", 5) // => "Hello" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const takeLeft: { + (n: number): (self: string) => string + (self: string, n: number): string +} = dual(2, (self: string, n: number): string => self.slice(0, Math.max(n, 0))) + +/** + * Keeps the specified number of characters from the end of a string. + * + * **Details** + * + * If `n` is larger than the available number of characters, the string will + * be returned whole. + * + * If `n` is not a positive number, an empty string will be returned. + * + * If `n` is a float, it will be rounded down to the nearest integer. + * + * **Example** (Taking characters from the end) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.takeRight("Hello World", 5) // => "World" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const takeRight: { + (n: number): (self: string) => string + (self: string, n: number): string +} = dual( + 2, + (self: string, n: number): string => self.slice(Math.max(0, self.length - Math.floor(n)), Infinity) +) + +const CR = 0x0d +const LF = 0x0a + +/** + * Returns an `IterableIterator` which yields each line contained within the + * string, trimming off the trailing newline character. + * + * **Example** (Iterating lines without separators) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * Array.from(String.linesIterator("hello\nworld\n")) // => ["hello", "world"] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +export const linesIterator = (self: string): LinesIterator => linesSeparated(self, true) + +/** + * Returns an `IterableIterator` which yields each line contained within the + * string as well as the trailing newline character. + * + * **Example** (Iterating lines with separators) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * Array.from(String.linesWithSeparators("hello\nworld\n")) // => ["hello\n", "world\n"] + * ``` + * + * @category splitting + * @since 2.0.0 + */ +export const linesWithSeparators = (s: string): LinesIterator => linesSeparated(s, false) + +/** + * Strips a leading margin prefix from every line using the supplied margin + * character. + * + * **Example** (Stripping custom margins) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.stripMarginWith(" |hello\n |world", "|") // => "hello\nworld" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const stripMarginWith: { + (marginChar: string): (self: string) => string + (self: string, marginChar: string): string +} = dual(2, (self: string, marginChar: string): string => { + let out = "" + + for (const line of linesWithSeparators(self)) { + let index = 0 + + while (index < line.length && line.charAt(index) <= " ") { + index = index + 1 + } + + const stripped = index < line.length && line.charAt(index) === marginChar + ? line.substring(index + 1) + : line + + out = out + stripped + } + + return out +}) + +/** + * Strips a leading `|` margin prefix from every line. + * + * **Example** (Stripping pipe margins) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.stripMargin(" |hello\n |world") // => "hello\nworld" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const stripMargin = (self: string): string => stripMarginWith(self, "|") + +/** + * Converts a snake_case string to camelCase. + * + * **Example** (Converting snake_case to camelCase) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.snakeToCamel("hello_world") // => "helloWorld" + * String.snakeToCamel("foo_bar_baz") // => "fooBarBaz" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const snakeToCamel = (self: string): string => { + if (self.length === 0) return self + let str = self[0] + for (let i = 1; i < self.length; i++) { + str += self[i] === "_" ? self[++i].toUpperCase() : self[i] + } + return str +} + +/** + * Converts a snake_case string to PascalCase. + * + * **Example** (Converting snake_case to PascalCase) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.snakeToPascal("hello_world") // => "HelloWorld" + * String.snakeToPascal("foo_bar_baz") // => "FooBarBaz" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const snakeToPascal = (self: string): string => { + if (self.length === 0) return self + let str = self[0].toUpperCase() + for (let i = 1; i < self.length; i++) { + str += self[i] === "_" ? self[++i].toUpperCase() : self[i] + } + return str +} + +/** + * Converts a snake_case string to kebab-case. + * + * **Example** (Converting snake_case to kebab-case) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.snakeToKebab("hello_world") // => "hello-world" + * String.snakeToKebab("foo_bar_baz") // => "foo-bar-baz" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const snakeToKebab = (self: string): string => self.replace(/_/g, "-") + +/** + * Converts a camelCase string to snake_case. + * + * **Example** (Converting camelCase to snake_case) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.camelToSnake("helloWorld") // => "hello_world" + * String.camelToSnake("fooBarBaz") // => "foo_bar_baz" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const camelToSnake = (self: string): string => self.replace(/([A-Z])/g, "_$1").toLowerCase() + +/** + * Converts a PascalCase string to snake_case. + * + * **Example** (Converting PascalCase to snake_case) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.pascalToSnake("HelloWorld") // => "hello_world" + * String.pascalToSnake("FooBarBaz") // => "foo_bar_baz" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const pascalToSnake = (self: string): string => + (self.slice(0, 1) + self.slice(1).replace(/([A-Z])/g, "_$1")).toLowerCase() + +/** + * Converts a kebab-case string to snake_case. + * + * **Example** (Converting kebab-case to snake_case) + * + * ```ts import.meta.vitest + * import { String } from "effect" + * + * String.kebabToSnake("hello-world") // => "hello_world" + * String.kebabToSnake("foo-bar-baz") // => "foo_bar_baz" + * ``` + * + * @category transforming + * @since 2.0.0 + */ +export const kebabToSnake = (self: string): string => self.replace(/-/g, "_") + +class LinesIterator implements IterableIterator { + private index: number + private readonly length: number + readonly s: string + readonly stripped: boolean + + constructor( + s: string, + stripped: boolean = false + ) { + this.s = s + this.stripped = stripped + this.index = 0 + this.length = s.length + } + + next(): IteratorResult { + if (this.done) { + return { done: true, value: undefined } + } + const start = this.index + while (!this.done && !isLineBreak(this.s[this.index]!)) { + this.index = this.index + 1 + } + let end = this.index + if (!this.done) { + const char = this.s[this.index]! + this.index = this.index + 1 + if (!this.done && isLineBreak2(char, this.s[this.index]!)) { + this.index = this.index + 1 + } + if (!this.stripped) { + end = this.index + } + } + return { done: false, value: this.s.substring(start, end) } + } + + [Symbol.iterator](): IterableIterator { + return new LinesIterator(this.s, this.stripped) + } + + private get done(): boolean { + return this.index >= this.length + } +} + +/** + * Checks whether the provided character is a line break character (i.e. either `"\r"` + * or `"\n"`). + */ +const isLineBreak = (char: string): boolean => { + const code = char.charCodeAt(0) + return code === CR || code === LF +} + +/** + * Checks whether the provided characters combine to form a carriage return/line-feed + * (i.e. `"\r\n"`). + */ +const isLineBreak2 = (char0: string, char1: string): boolean => char0.charCodeAt(0) === CR && char1.charCodeAt(0) === LF + +const linesSeparated = (self: string, stripped: boolean): LinesIterator => new LinesIterator(self, stripped) + +/** + * Normalizes a string by splitting it into word parts, transforming each part, + * and joining the parts with a configurable delimiter. + * + * **When to use** + * + * Use when you need custom word-case output with a delimiter or part transform + * that the fixed case helpers do not provide. + * + * @see {@link pascalCase} for fixed PascalCase output + * @see {@link camelCase} for fixed lower-initial camelCase output + * @see {@link constantCase} for fixed uppercase underscore-separated output + * @see {@link kebabCase} for fixed lowercase hyphen-separated output + * @see {@link snakeCase} for fixed lowercase underscore-separated output + * + * @category transforming + * @since 4.0.0 + */ +export const noCase: { + (options?: { + readonly splitRegExp?: RegExp | ReadonlyArray | undefined + readonly stripRegExp?: RegExp | ReadonlyArray | undefined + readonly delimiter?: string | undefined + readonly transform?: (part: string, index: number, parts: ReadonlyArray) => string + }): (self: string) => string + (self: string, options?: { + readonly splitRegExp?: RegExp | ReadonlyArray | undefined + readonly stripRegExp?: RegExp | ReadonlyArray | undefined + readonly delimiter?: string | undefined + readonly transform?: (part: string, index: number, parts: ReadonlyArray) => string + }): string +} = dual((args) => typeof args[0] === "string", (input: string, options?: { + readonly splitRegExp?: RegExp | ReadonlyArray | undefined + readonly stripRegExp?: RegExp | ReadonlyArray | undefined + readonly delimiter?: string | undefined + readonly transform?: (part: string, index: number, parts: ReadonlyArray) => string +}): string => { + const splitRegExp = toRegExpArray(options?.splitRegExp ?? SPLIT_REGEXP) + const stripRegExp = toRegExpArray(options?.stripRegExp ?? STRIP_REGEXP) + const delimiter = options?.delimiter ?? " " + const transform = options?.transform ?? toLowerCase + return normalizeCase(input, splitRegExp, stripRegExp, delimiter, transform) +}) + +const toRegExpArray = (regexp: RegExp | ReadonlyArray): ReadonlyArray => + predicate.isRegExp(regexp) ? [regexp] : regexp + +const normalizeCase = ( + input: string, + splitRegExp: ReadonlyArray, + stripRegExp: ReadonlyArray, + delimiter: string, + transform: (part: string, index: number, parts: ReadonlyArray) => string +): string => { + let result = input + for (const regexp of splitRegExp) { + result = result.replace(regexp, "$1\0$2") + } + for (const regexp of stripRegExp) { + result = result.replace(regexp, "\0") + } + let start = 0 + let end = result.length + // Trim the delimiter from around the output string. + while (result.charAt(start) === "\0") { + start++ + } + while (result.charAt(end - 1) === "\0") { + end-- + } + + // Transform each token independently. + return result.slice(start, end).split("\0").map(transform).join(delimiter) +} + +// Support camel case ("camelCase" -> "camel Case" and "CAMELCase" -> "CAMEL Case") +// and digit boundaries ("camel2case" -> "camel 2 case"). +const SPLIT_REGEXP = [/([a-z0-9])([A-Z])/g, /([A-Z])([A-Z][a-z])/g, /([A-Z])([0-9])/gi, /([0-9])([A-Z])/gi] + +// Config paths preserve digit groups such as "v2" while still supporting camel case. +const CONFIG_SPLIT_REGEXP = [/([a-z0-9])([A-Z])/g, /([A-Z])([A-Z][a-z])/g] + +// Remove all non-word characters. +const STRIP_REGEXP = /[^A-Z0-9]+/gi + +const pascalCaseTransform = (input: string): string => { + const firstChar = input.charAt(0) + const lowerChars = input.substring(1).toLowerCase() + return `${firstChar.toUpperCase()}${lowerChars}` +} + +/** + * Converts a string to PascalCase. + * + * **When to use** + * + * Use to normalize strings from spaces, separators, or camel/Pascal word + * boundaries into PascalCase. + * + * @see {@link camelCase} for lower-initial camelCase output + * @see {@link noCase} for configurable delimiters and part transforms + * @see {@link snakeToPascal} for converting known snake_case input only + * + * @category transforming + * @since 4.0.0 + */ +export const pascalCase: (self: string) => string = noCase({ + delimiter: "", + transform: pascalCaseTransform +}) + +const camelCaseTransform = (input: string, index: number): string => + index === 0 + ? input.toLowerCase() + : pascalCaseTransform(input) + +/** + * Converts a string to camelCase. + * + * **When to use** + * + * Use to normalize mixed word separators or existing PascalCase/camelCase text + * into lower-initial camelCase identifiers. + * + * @see {@link noCase} for configurable delimiters and part transforms + * @see {@link pascalCase} for upper-initial PascalCase output + * @see {@link snakeCase} for lowercase underscore-separated output + * @see {@link kebabCase} for lowercase hyphen-separated output + * @see {@link constantCase} for uppercase underscore-separated output + * + * @category transforming + * @since 4.0.0 + */ +export const camelCase: (self: string) => string = noCase({ + delimiter: "", + transform: camelCaseTransform +}) + +/** + * Converts a string to CONSTANT_CASE (uppercase with underscores). + * + * **When to use** + * + * Use to normalize words from mixed input formats into uppercase, + * underscore-separated identifiers. + * + * @see {@link snakeCase} for lowercase underscore-separated output + * @see {@link kebabCase} for lowercase hyphen-separated output + * @see {@link camelCase} for lower-initial camelCase output + * @see {@link pascalCase} for upper-initial PascalCase output + * @see {@link configCase} for configuration key casing that preserves numeric word groups + * @see {@link noCase} for configurable delimiters and part transforms + * + * @category transforming + * @since 4.0.0 + */ +export const constantCase: (self: string) => string = noCase({ + delimiter: "_", + transform: toUpperCase +}) + +/** + * Converts a string to CONFIG_CASE (uppercase with underscores) for + * configuration keys. + * + * **When to use** + * + * Use to normalize configuration path segments into environment-variable-like + * keys while preserving numeric word groups such as `v2`. + * + * **Details** + * + * Unlike {@link constantCase}, digit-letter boundaries are not split. For + * example, `"api-v2 xml"` becomes `"API_V2_XML"`. + * + * @see {@link constantCase} for standard uppercase underscore-separated output + * @category transforming + * @since 4.0.0 + */ +export const configCase: (self: string) => string = (self) => + normalizeCase(self, CONFIG_SPLIT_REGEXP, [STRIP_REGEXP], "_", toUpperCase) + +/** + * Converts a string to kebab-case (lowercase with hyphens). + * + * **When to use** + * + * Use to normalize free-form labels, identifiers, or keys into lowercase + * hyphen-separated text. + * + * @see {@link noCase} for configurable delimiters and part transforms + * @see {@link snakeCase} for lowercase underscore-separated output + * @see {@link constantCase} for uppercase underscore-separated output + * @see {@link camelCase} for lower-initial camelCase output + * @see {@link pascalCase} for upper-initial PascalCase output + * + * @category transforming + * @since 4.0.0 + */ +export const kebabCase: (self: string) => string = noCase({ + delimiter: "-" +}) + +/** + * Converts a string to snake_case (lowercase with underscores). + * + * **When to use** + * + * Use to normalize mixed-case or separator-delimited text into lowercase words + * joined with underscores. + * + * @see {@link noCase} for configurable lower-level normalization + * @see {@link kebabCase} for lowercase hyphen-separated output + * @see {@link constantCase} for uppercase underscore-separated output + * + * @category transforming + * @since 4.0.0 + */ +export const snakeCase: (self: string) => string = noCase({ + delimiter: "_" +}) + +/** + * Reducer for concatenating `string`s. + * + * **When to use** + * + * Use to concatenate many strings through APIs that consume a `Reducer`. + * + * **Details** + * + * The reducer starts from `""`, so combining an empty collection returns `""`. + * + * @see {@link concat} for concatenating two strings directly + * + * @category combining + * @since 4.0.0 + */ +export const ReducerConcat: Reducer.Reducer = Reducer.make((a, b) => a + b, "") diff --git a/.repos/effect/packages/effect/src/Struct.ts b/.repos/effect/packages/effect/src/Struct.ts new file mode 100644 index 000000000..14416763e --- /dev/null +++ b/.repos/effect/packages/effect/src/Struct.ts @@ -0,0 +1,983 @@ +/** + * Works with plain TypeScript objects, also called structs. + * + * The runtime helpers in this module create new objects instead of mutating + * their inputs. They cover common object workflows such as reading properties, + * listing typed keys, picking or omitting fields, assigning and renaming keys, + * transforming values, deriving comparison helpers, and creating records from a + * list of keys. The module also includes type-level helpers for simplifying and + * merging object shapes. + * + * @since 2.0.0 + */ + +import * as Combiner from "./Combiner.ts" +import * as Equivalence from "./Equivalence.ts" +import { dual } from "./Function.ts" +import * as InternalRecord from "./internal/record.ts" +import * as order from "./Order.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Flattens intersection types into a single object type for readability. + * + * **When to use** + * + * Use when hovering over a type shows `A & B & C` instead of the merged shape. + * + * **Details** + * + * This helper is purely cosmetic at the type level and has no runtime effect. + * It preserves `readonly` modifiers; use {@link Mutable} to strip them. + * + * **Example** (Flattening an intersection) + * + * ```ts import.meta.vitest + * import type { Struct } from "effect" + * + * type Original = { a: string } & { b: number } + * + * // Without Simplify, the type displays as `{ a: string } & { b: number }` + * type Simplified = Struct.Simplify + * // { a: string; b: number } + * + * const witness: Simplified = { a: "value", b: 1 } + * ``` + * + * @see {@link Mutable} – also flattens but removes `readonly` + * @see {@link Assign} – merges two types with right-side precedence + * @category utility types + * @since 4.0.0 + */ +export type Simplify = { [K in keyof T]: T[K] } & {} + +/** + * Removes `readonly` modifiers from all properties of an object type. + * + * **When to use** + * + * Use when you need a mutable version of a readonly interface. + * + * **Details** + * + * This helper is purely cosmetic at the type level and has no runtime effect. + * It also flattens intersections like {@link Simplify}. + * + * **Example** (Making a readonly type mutable) + * + * ```ts import.meta.vitest + * import type { Struct } from "effect" + * + * type ReadOnly = { readonly a: string; readonly b: number } + * type Writable = Struct.Mutable + * // { a: string; b: number } + * + * const witness: Writable = { a: "value", b: 1 } + * witness.b = 2 + * witness // => { a: "value", b: 2 } + * ``` + * + * @see {@link Simplify} – flattens intersections without removing `readonly` + * @category utility types + * @since 4.0.0 + */ +export type Mutable = { -readonly [K in keyof T]: T[K] } & {} + +/** + * Merges two object types with properties from `U` taking precedence over `T` + * on overlapping keys (like `Object.assign` at the type level). + * + * **When to use** + * + * Use when you need the type-level equivalent of `{ ...T, ...U }`. + * + * **Details** + * + * When no keys overlap, this returns a simple intersection for efficiency. + * When keys overlap, the type from `U` wins. + * + * **Example** (Merging two types with overlapping keys) + * + * ```ts import.meta.vitest + * import type { Struct } from "effect" + * + * type A = { a: string; b: number } + * type B = { b: boolean; c: string } + * type Merged = Struct.Assign + * // { a: string; b: boolean; c: string } + * + * const witness: Merged = { a: "value", b: true, c: "other" } + * ``` + * + * @see {@link assign} – the runtime equivalent + * @see {@link Simplify} – flatten the resulting intersection + * @category utility types + * @since 4.0.0 + */ +export type Assign = Simplify & U> + +/** + * Retrieves the value at `key` from a struct. + * + * **When to use** + * + * Use to extract a single property from a struct in a pipeline. + * + * **Details** + * + * The return type is narrowed to `S[K]`. + * + * **Example** (Extracting a property in a pipeline) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * pipe({ name: "Alice", age: 30 }, Struct.get("name")) // => "Alice" + * ``` + * + * @see {@link keys} – list all string keys of a struct + * @see {@link pick} – extract multiple properties into a new struct + * @category getters + * @since 2.0.0 + */ +export const get: { + (key: K): (self: S) => S[K] + (self: S, key: K): S[K] +} = dual(2, (self: S, key: K): S[K] => self[key]) + +/** + * Returns the string keys of a struct as a properly typed `Array`. + * + * **When to use** + * + * Use when you want a typed replacement for `Object.keys` that narrows the result + * to the known string keys of the struct. + * + * **Gotchas** + * + * Symbol keys are excluded; only string keys are returned. + * + * **Example** (Reading typed keys) + * + * ```ts import.meta.vitest + * import { Struct } from "effect" + * + * const user = { name: "Alice", age: 30, [Symbol.for("id")]: 1 } + * + * const k: Array<"name" | "age"> = Struct.keys(user) + * k // => ["name", "age"] + * ``` + * + * @see {@link get} – access a single key's value + * @see {@link pick} – select a subset of keys into a new struct + * @category getters + * @since 3.6.0 + */ +export const keys = (self: S): Array<(keyof S) & string> => + Object.keys(self) as Array<(keyof S) & string> + +/** + * Creates a new struct containing only the specified keys. + * + * **When to use** + * + * Use to narrow a struct down to a subset of its properties. + * + * **Gotchas** + * + * Keys not present in the struct are silently ignored. + * + * **Example** (Selecting specific properties) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * const user = { name: "Alice", age: 30, admin: true } + * pipe(user, Struct.pick(["name", "age"])) // => { name: "Alice", age: 30 } + * ``` + * + * @see {@link omit} – the inverse (exclude keys instead) + * @see {@link get} – extract a single value + * @category filtering + * @since 2.0.0 + */ +export const pick: { + >( + keys: Keys + ): (self: S) => Simplify> + >(self: S, keys: Keys): Simplify> +} = dual( + 2, + >(self: S, keys: Keys) => { + return buildStruct(self, (k, v) => (keys.includes(k) ? [k, v] : undefined)) + } +) + +/** + * Creates a new struct with the specified keys removed. + * + * **When to use** + * + * Use to exclude sensitive or irrelevant fields from a struct. + * + * **Gotchas** + * + * Keys not present in the struct are silently ignored. + * + * **Example** (Removing a property) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * const user = { name: "Alice", age: 30, password: "secret" } + * pipe(user, Struct.omit(["password"])) // => { name: "Alice", age: 30 } + * ``` + * + * @see {@link pick} – the inverse (keep only specified keys) + * @category filtering + * @since 2.0.0 + */ +export const omit: { + >( + keys: Keys + ): (self: S) => Simplify> + >(self: S, keys: Keys): Simplify> +} = dual( + 2, + >(self: S, keys: Keys) => { + return buildStruct(self, (k, v) => (!keys.includes(k) ? [k, v] : undefined)) + } +) + +/** + * Merges two structs into a new struct. When both structs share a key, the + * value from `that` (the second struct) wins. + * + * **When to use** + * + * Use when you want `{ ...self, ...that }` with proper types. + * + * **Details** + * + * The result type is `Simplify>`. + * + * **Example** (Merging structs with overlapping keys) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * const defaults = { theme: "light", lang: "en" } + * const overrides = { theme: "dark", fontSize: 14 } + * pipe(defaults, Struct.assign(overrides)) // => { theme: "dark", lang: "en", fontSize: 14 } + * ``` + * + * @see {@link Assign} – the type-level equivalent + * @see {@link evolve} – transform individual values instead of replacing them + * @category combining + * @since 4.0.0 + */ +export const assign: { + (that: O): (self: S) => Assign + (self: S, that: O): Assign +} = dual( + 2, + (self: S, that: O) => { + return { ...self, ...that } + } +) + +type Evolver = { readonly [K in keyof S]?: (a: S[K]) => unknown } + +type Evolved = Simplify< + { [K in keyof S]: K extends keyof E ? (E[K] extends (...a: any) => infer R ? R : S[K]) : S[K] } +> + +/** + * Transforms values of a struct selectively using per-key functions. Keys + * without a corresponding function are copied unchanged. + * + * **When to use** + * + * Use when you want to update specific fields while keeping the rest intact. + * + * **Details** + * + * Each transform function receives the current value and returns the new value; + * the return type can differ from the input type. + * + * **Example** (Transforming selected values) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * const result = pipe( + * { name: "alice", age: 30, active: true }, + * Struct.evolve({ + * name: (s) => s.toUpperCase(), + * age: (n) => n + 1 + * }) + * ) + * result // => { name: "ALICE", age: 31, active: true } + * ``` + * + * @see {@link evolveKeys} – transform keys instead of values + * @see {@link evolveEntries} – transform both keys and values + * @see {@link map} – apply the same transformation to all values + * @category transforming + * @since 2.0.0 + */ +export const evolve: { + >(e: E): (self: S) => Evolved + >(self: S, e: E): Evolved +} = dual( + 2, + >(self: S, e: E): Evolved => { + return buildStruct(self, (k, v) => [k, Object.hasOwn(e, k) ? (e as any)[k](v) : v]) + } +) + +type KeyEvolver = { readonly [K in keyof S]?: (k: K) => PropertyKey } + +type KeyEvolved = Simplify< + { [K in keyof S as K extends keyof E ? (E[K] extends ((k: K) => infer R extends PropertyKey) ? R : K) : K]: S[K] } +> + +/** + * Transforms keys of a struct selectively using per-key functions. Keys without + * a corresponding function are copied unchanged. + * + * **When to use** + * + * Use when you need computed key names, such as uppercasing or prefixing. + * + * **Details** + * + * Each transform function receives the key name and must return a new + * `PropertyKey`. + * + * **Example** (Renaming keys with functions) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * const result = pipe( + * { name: "Alice", age: 30 }, + * Struct.evolveKeys({ + * name: (k) => k.toUpperCase() + * }) + * ) + * result // => { NAME: "Alice", age: 30 } + * ``` + * + * @see {@link renameKeys} – rename keys with a static mapping + * @see {@link evolve} – transform values instead of keys + * @see {@link evolveEntries} – transform both keys and values + * @category transforming + * @since 4.0.0 + */ +export const evolveKeys: { + >(e: E): (self: S) => KeyEvolved + >(self: S, e: E): KeyEvolved +} = dual( + 2, + >(self: S, e: E): KeyEvolved => { + return buildStruct(self, (k, v) => [Object.hasOwn(e, k) ? (e as any)[k](k) : k, v]) + } +) + +type EntryEvolver = { readonly [K in keyof S]?: (k: K, v: S[K]) => [PropertyKey, unknown] } + +type EntryEvolved = { + [ + K in keyof S as K extends keyof E ? + E[K] extends ((k: K, v: S[K]) => [infer NK extends PropertyKey, infer _V]) ? NK : K + : K + ]: K extends keyof E ? E[K] extends ((k: K, v: S[K]) => [infer _NK, infer V]) ? V + : S[K] : + S[K] +} + +/** + * Transforms both keys and values of a struct selectively. Each per-key + * function receives `(key, value)` and must return a `[newKey, newValue]` + * tuple. Keys without a corresponding function are copied unchanged. + * + * **When to use** + * + * Use when you need to rename a key and change its value in one step. + * + * **Details** + * + * The return type is fully tracked at the type level. + * + * **Example** (Transforming keys and values together) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * const result = pipe( + * { amount: 100, label: "total" }, + * Struct.evolveEntries({ + * amount: (k, v) => [`${k}Cents`, v * 100], + * label: (k, v) => [k, v.toUpperCase()] + * }) + * ) + * result // => { amountCents: 10000, label: "TOTAL" } + * ``` + * + * @see {@link evolve} – transform values only + * @see {@link evolveKeys} – transform keys only + * @category transforming + * @since 4.0.0 + */ +export const evolveEntries: { + >(e: E): (self: S) => EntryEvolved + >(self: S, e: E): EntryEvolved +} = dual( + 2, + >(self: S, e: E): EntryEvolved => { + return buildStruct(self, (k, v) => (Object.hasOwn(e, k) ? (e as any)[k](k, v) : [k, v])) + } +) + +/** + * Renames keys in a struct using a static `{ oldKey: newKey }` mapping. Keys + * not mentioned in the mapping are copied unchanged. + * + * **When to use** + * + * Use when you need simple, declarative key renaming without custom logic. + * + * **Details** + * + * For computed key names, use {@link evolveKeys} instead. + * + * **Example** (Renaming keys) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * const result = pipe( + * { firstName: "Alice", lastName: "Smith", age: 30 }, + * Struct.renameKeys({ firstName: "first", lastName: "last" }) + * ) + * result // => { first: "Alice", last: "Smith", age: 30 } + * ``` + * + * @see {@link evolveKeys} – rename keys using functions + * @see {@link evolveEntries} – rename keys and transform values + * @category transforming + * @since 4.0.0 + */ +export const renameKeys: { + ( + mapping: M + ): (self: S) => { [K in keyof S as K extends keyof M ? M[K] extends PropertyKey ? M[K] : K : K]: S[K] } + ( + self: S, + mapping: M + ): { [K in keyof S as K extends keyof M ? M[K] extends PropertyKey ? M[K] : K : K]: S[K] } +} = dual(2, (self: S, mapping: M) => { + return buildStruct(self, (k, v) => [Object.hasOwn(mapping, k) ? mapping[k]! : k, v]) +}) + +/** + * Creates an `Equivalence` for a struct by providing an `Equivalence` for each + * property. Two structs are equivalent when all their corresponding properties + * are equivalent. + * + * **When to use** + * + * Use when you need equality for a record-like object to be decided field by + * field, with a custom equality rule for each property. + * + * **Details** + * + * This is an alias of `Equivalence.Struct`. Each property's equivalence is + * checked independently; all must return `true` for the overall result to be + * `true`. + * + * **Example** (Comparing structs for equivalence) + * + * ```ts import.meta.vitest + * import { Equivalence, Struct } from "effect" + * + * const PersonEquivalence = Struct.makeEquivalence({ + * name: Equivalence.strictEqual(), + * age: Equivalence.strictEqual() + * }) + * + * PersonEquivalence({ name: "Alice", age: 30 }, { name: "Alice", age: 30 }) // => true + * PersonEquivalence({ name: "Alice", age: 30 }, { name: "Bob", age: 30 }) // => false + * ``` + * + * @see {@link makeOrder} – create an `Order` for structs + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence = Equivalence.Struct + +/** + * Creates an `Order` for a struct by providing an `Order` for each property. + * Properties are compared in the order they appear in the fields object; the + * first non-zero comparison determines the result. + * + * **When to use** + * + * Use when you need to sort record-like objects lexicographically by several + * fields, with each field using its own ordering rule. + * + * **Details** + * + * This is an alias of `Order.Struct`. The order of keys in the `fields` object + * determines comparison priority. + * + * **Example** (Ordering structs by name then age) + * + * ```ts import.meta.vitest + * import { Number, String, Struct } from "effect" + * + * const PersonOrder = Struct.makeOrder({ + * name: String.Order, + * age: Number.Order + * }) + * + * PersonOrder({ name: "Alice", age: 30 }, { name: "Bob", age: 25 }) // => -1 + * ``` + * + * @see {@link makeEquivalence} – create an `Equivalence` for structs + * @category ordering + * @since 4.0.0 + */ +export const makeOrder = order.Struct + +/** + * Interface for type-level functions used by {@link map}, {@link mapPick}, and + * {@link mapOmit}. + * + * **When to use** + * + * Use when defining a typed function for {@link map}, {@link mapPick}, or + * {@link mapOmit}. + * + * **Details** + * + * Extend this interface with concrete `~lambda.in` and `~lambda.out` types to + * describe how a function transforms values at the type level. At runtime, + * create lambda values with {@link lambda}. + * + * **Example** (Defining a lambda type) + * + * ```ts import.meta.vitest + * import type { Struct } from "effect" + * + * interface ToString extends Struct.Lambda { + * readonly "~lambda.out": string + * } + * + * const witness: ToString = { "~lambda.in": 1, "~lambda.out": "1" } + * ``` + * + * @see {@link Apply} – apply a Lambda to a concrete type + * @see {@link lambda} – create a runtime lambda value + * @see {@link map} – use a lambda to transform all struct values + * @category utility types + * @since 4.0.0 + */ +export interface Lambda { + readonly "~lambda.in": unknown + readonly "~lambda.out": unknown +} + +/** + * Applies a {@link Lambda} type-level function to a value type `V`, producing + * the output type. + * + * **When to use** + * + * Use when you need to compute what type a Lambda would produce for a + * given input. + * + * **Details** + * + * This works by intersecting the Lambda with `{ "~lambda.in": V }` and reading + * `"~lambda.out"`. + * + * **Example** (Computing the output type of a lambda) + * + * ```ts import.meta.vitest + * import type { Struct } from "effect" + * + * interface ToString extends Struct.Lambda { + * readonly "~lambda.out": string + * } + * + * // string + * type Result = Struct.Apply + * + * const witness: Result = "value" + * ``` + * + * @see {@link Lambda} – the base interface + * @category utility types + * @since 4.0.0 + */ +export type Apply = (L & { readonly "~lambda.in": V })["~lambda.out"] + +/** + * Wraps a plain function as a {@link Lambda} value so it can be used with + * {@link map}, {@link mapPick}, and {@link mapOmit}. + * + * **When to use** + * + * Use to create a typed lambda for struct mapping APIs that need type-level + * input and output tracking. + * + * **Details** + * + * The type parameter `L` encodes both the input and output types at the type + * level, allowing the compiler to track how struct value types change. At + * runtime, the returned value is the same function; `lambda` only adjusts the + * type. + * + * **Example** (Wrapping values in arrays) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * interface AsArray extends Struct.Lambda { + * (self: A): Array + * readonly "~lambda.out": Array + * } + * + * const asArray = Struct.lambda((a) => [a]) + * const result = pipe({ x: 1, y: "hello" }, Struct.map(asArray)) + * result // => { x: [1], y: ["hello"] } + * ``` + * + * @see {@link Lambda} – the type-level interface + * @see {@link map} – apply a lambda to all struct values + * @category constructors + * @since 4.0.0 + */ +export const lambda = any>( + f: (a: Parameters[0]) => ReturnType +): L => f as any + +/** + * Applies a {@link Lambda} transformation to every value in a struct. + * + * **When to use** + * + * Use when you want to apply the same function to every value in a struct. + * + * **Details** + * + * The lambda must be created with {@link lambda} so the compiler can track the + * output types. + * + * **Example** (Wrapping every value in an array) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * interface AsArray extends Struct.Lambda { + * (self: A): Array + * readonly "~lambda.out": Array + * } + * + * const asArray = Struct.lambda((a) => [a]) + * const result = pipe({ width: 10, height: 20 }, Struct.map(asArray)) + * result // => { width: [10], height: [20] } + * ``` + * + * @see {@link mapPick} – apply a lambda only to selected keys + * @see {@link mapOmit} – apply a lambda to all keys except selected ones + * @see {@link evolve} – apply different functions to different keys + * @category mapping + * @since 4.0.0 + */ +export const map: { + ( + lambda: L + ): (self: S) => { [K in keyof S]: Apply } + ( + self: S, + lambda: L + ): { [K in keyof S]: Apply } +} = dual( + 2, + (self: S, lambda: L) => { + return buildStruct(self, (k, v) => [k, lambda(v)]) + } +) + +/** + * Applies a {@link Lambda} transformation only to the specified keys; all + * other keys are copied unchanged. + * + * **When to use** + * + * Use when you want to apply the same transformation to a subset of properties. + * + * **Example** (Wrapping only selected values in arrays) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * interface AsArray extends Struct.Lambda { + * (self: A): Array + * readonly "~lambda.out": Array + * } + * + * const asArray = Struct.lambda((a) => [a]) + * const result = pipe( + * { x: 1, y: 2, z: 3 }, + * Struct.mapPick(["x", "z"], asArray) + * ) + * result // => { x: [1], y: 2, z: [3] } + * ``` + * + * @see {@link map} – apply a lambda to all keys + * @see {@link mapOmit} – apply a lambda to all keys except selected ones + * @category mapping + * @since 4.0.0 + */ +export const mapPick: { + , L extends Lambda>( + keys: Keys, + lambda: L + ): ( + self: S + ) => { [K in keyof S]: K extends Keys[number] ? Apply : S[K] } + , L extends Lambda>( + self: S, + keys: Keys, + lambda: L + ): { [K in keyof S]: K extends Keys[number] ? Apply : S[K] } +} = dual( + 3, + , L extends Function>( + self: S, + keys: Keys, + lambda: L + ) => { + return buildStruct(self, (k, v) => [k, keys.includes(k) ? lambda(v) : v]) + } +) + +/** + * Applies a {@link Lambda} transformation to all keys except the specified + * ones; the excluded keys are copied unchanged. + * + * **When to use** + * + * Use when most keys should be transformed but a few should be preserved. + * + * **Example** (Wrapping all values except one in arrays) + * + * ```ts import.meta.vitest + * import { pipe, Struct } from "effect" + * + * interface AsArray extends Struct.Lambda { + * (self: A): Array + * readonly "~lambda.out": Array + * } + * + * const asArray = Struct.lambda((a) => [a]) + * const result = pipe( + * { x: 1, y: 2, z: 3 }, + * Struct.mapOmit(["y"], asArray) + * ) + * result // => { x: [1], y: 2, z: [3] } + * ``` + * + * @see {@link map} – apply a lambda to all keys + * @see {@link mapPick} – apply a lambda only to selected keys + * @category mapping + * @since 4.0.0 + */ +export const mapOmit: { + , L extends Lambda>( + keys: Keys, + lambda: L + ): ( + self: S + ) => { [K in keyof S]: K extends Keys[number] ? S[K] : Apply } + , L extends Lambda>( + self: S, + keys: Keys, + lambda: L + ): { [K in keyof S]: K extends Keys[number] ? S[K] : Apply } +} = dual( + 3, + , L extends Function>( + self: S, + keys: Keys, + lambda: L + ) => { + return buildStruct(self, (k, v) => [k, !keys.includes(k) ? lambda(v) : v]) + } +) + +/** + * Walk `source`; for each key decide what to emit via the small callback. + * + * The callback returns either + * • `undefined` → nothing is copied, or + * • `[newKey, newVal]` + * + * so every public API just supplies a different callback. + */ +function buildStruct< + S extends object, + f extends (k: keyof S, v: S[keyof S]) => [PropertyKey, unknown] | undefined +>( + source: S, + f: f +): any { + const out: Record = {} + for (const k of Reflect.ownKeys(source) as Array) { + if (!Object.prototype.propertyIsEnumerable.call(source, k)) continue + const res = f(k, source[k]) + if (res) { + const [nk, nv] = res + InternalRecord.assignProperty(out, nk, nv) + } + } + return out +} + +/** + * Creates a `Combiner` for a struct shape by providing a `Combiner` for each + * property. When two structs are combined, each property is merged using its + * corresponding combiner. + * + * **When to use** + * + * Use when you need to merge two same-shape records by combining each property + * independently, such as summing counters or concatenating strings. + * + * **Details** + * + * Pass `omitKeyWhen` to drop properties whose merged value matches a predicate, + * such as omitting zero counters. + * + * **Example** (Combining struct properties) + * + * ```ts import.meta.vitest + * import { Number, String, Struct } from "effect" + * + * const C = Struct.makeCombiner<{ readonly n: number; readonly s: string }>({ + * n: Number.ReducerSum, + * s: String.ReducerConcat + * }) + * + * C.combine({ n: 1, s: "hello" }, { n: 2, s: " world" }) // => { n: 3, s: "hello world" } + * ``` + * + * @see {@link makeReducer} – like `makeCombiner` but with an initial value + * @category combining + * @since 4.0.0 + */ +export function makeCombiner( + combiners: { readonly [K in keyof A]: Combiner.Combiner }, + options?: { + readonly omitKeyWhen?: ((a: A[keyof A]) => boolean) | undefined + } +): Combiner.Combiner { + const omitKeyWhen = options?.omitKeyWhen ?? (() => false) + return Combiner.make((self, that) => { + const keys = Reflect.ownKeys(combiners) as Array + const out = {} as A + for (const key of keys) { + const merge = combiners[key].combine(self[key], that[key]) + if (omitKeyWhen(merge)) continue + InternalRecord.assignProperty(out as object, key, merge) + } + return out + }) +} + +/** + * Creates a `Reducer` for a struct shape by providing a `Reducer` for each + * property. The initial value is derived from each property's + * `Reducer.initialValue`. When reducing a collection of structs, each property + * is combined independently. + * + * **When to use** + * + * Use when you need to fold same-shape records by accumulating each property + * independently into one summary record. + * + * **Details** + * + * Pass `omitKeyWhen` to drop properties whose reduced value matches a + * predicate. + * + * **Example** (Reducing a collection of structs) + * + * ```ts import.meta.vitest + * import { Number, String, Struct } from "effect" + * + * const R = Struct.makeReducer<{ readonly n: number; readonly s: string }>({ + * n: Number.ReducerSum, + * s: String.ReducerConcat + * }) + * + * const result = R.combineAll([ + * { n: 1, s: "a" }, + * { n: 2, s: "b" }, + * { n: 3, s: "c" } + * ]) + * result // => { n: 6, s: "abc" } + * ``` + * + * @see {@link makeCombiner} – like `makeReducer` but without an initial value + * @category folding + * @since 4.0.0 + */ +export function makeReducer( + reducers: { readonly [K in keyof A]: Reducer.Reducer }, + options?: { + readonly omitKeyWhen?: ((a: A[keyof A]) => boolean) | undefined + } +): Reducer.Reducer { + const combine = makeCombiner(reducers, options).combine + const initialValue = {} as A + for (const key of Reflect.ownKeys(reducers) as Array) { + const iv = reducers[key].initialValue + if (options?.omitKeyWhen?.(iv)) continue + InternalRecord.assignProperty(initialValue as object, key, iv) + } + return Reducer.make(combine, initialValue) +} + +/** + * Creates a record with the given keys and value. + * + * **When to use** + * + * Use to build an object where each provided key receives the same value. + * + * **Example** (Creating a record) + * + * ```ts import.meta.vitest + * import { Struct } from "effect" + * + * Struct.Record(["a", "b"], "value") // => { a: "value", b: "value" } + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export function Record, Value>( + keys: Keys, + value: Value +): Record { + const out: any = {} + for (const key of keys) { + InternalRecord.assignProperty(out, key, value) + } + return out +} diff --git a/.repos/effect/packages/effect/src/SubscriptionRef.ts b/.repos/effect/packages/effect/src/SubscriptionRef.ts new file mode 100644 index 000000000..1a4adf732 --- /dev/null +++ b/.repos/effect/packages/effect/src/SubscriptionRef.ts @@ -0,0 +1,1033 @@ +/** + * Stores mutable state and publishes changes as a stream. + * + * A `SubscriptionRef` stores the latest value, publishes the initial value, + * and publishes every committed update so subscribers can observe state over + * time. Updates are serialized so only one change is applied at a time. This + * module includes constructors, current-value reads, the `changes` stream, + * writes, updates, partial updates, and effectful update helpers. + * + * @since 2.0.0 + */ +import * as Effect from "./Effect.ts" +import { dual, identity } from "./Function.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as Option from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import * as PubSub from "./PubSub.ts" +import * as Semaphore from "./Semaphore.ts" +import * as Stream from "./Stream.ts" +import type { Invariant } from "./Types.ts" + +const TypeId = "~effect/SubscriptionRef" + +/** + * A mutable reference whose updates are serialized and published to + * subscribers. + * + * **When to use** + * + * Use to observe the current value and subsequent updates as a + * stream. + * + * @category models + * @since 2.0.0 + */ +export interface SubscriptionRef extends SubscriptionRef.Variance, Pipeable { + value: A + readonly semaphore: Semaphore.Semaphore + readonly pubsub: PubSub.PubSub +} + +/** + * Returns `true` if the provided value is a `SubscriptionRef`. + * + * **When to use** + * + * Use to narrow an unknown value before calling `SubscriptionRef` operations + * that require a subscription reference. + * + * @category guards + * @since 4.0.0 + */ +export const isSubscriptionRef: (u: unknown) => u is SubscriptionRef = ( + u: unknown +): u is SubscriptionRef => hasProperty(u, TypeId) + +/** + * The `SubscriptionRef` namespace containing type definitions associated with + * subscription references. + * + * @since 2.0.0 + */ +export declare namespace SubscriptionRef { + /** + * Type-level variance marker for the value type carried by a + * `SubscriptionRef`. + * + * @category models + * @since 2.0.0 + */ + export interface Variance { + readonly [TypeId]: { + readonly _A: Invariant + } + } +} + +const Proto = { + ...PipeInspectableProto, + [TypeId]: { + _A: identity + }, + toJSON(this: SubscriptionRef) { + return { + _id: "SubscriptionRef", + value: this.value + } + } +} + +/** + * Constructs a new `SubscriptionRef` from an initial value. + * + * **When to use** + * + * Use to create a `SubscriptionRef` when consumers need to read the latest + * value and subscribe to every update. + * + * **Details** + * + * The initial value is published during construction, so `changes` starts new + * subscribers with that value before future updates. + * + * @see {@link changes} for streaming the current value and subsequent updates + * @see {@link set} for replacing the value and notifying subscribers + * + * @category constructors + * @since 2.0.0 + */ +export const make = (value: A): Effect.Effect> => + Effect.map(PubSub.unbounded({ replay: 1 }), (pubsub) => { + const self = Object.create(Proto) + self.semaphore = Semaphore.makeUnsafe(1) + self.value = value + self.pubsub = pubsub + PubSub.publishUnsafe(self.pubsub, value) + return self + }) + +/** + * Creates a stream that emits the current value and all subsequent changes to + * the `SubscriptionRef`. + * + * **Details** + * + * The stream will first emit the current value, then emit all future changes + * as they occur. + * + * **Example** (Streaming changes) + * + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, Stream, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(0) + * const ready = yield* Deferred.make() + * + * const fiber = yield* SubscriptionRef.changes(ref).pipe( + * Stream.tap(() => Deferred.succeed(ready, void 0)), + * Stream.take(3), + * Stream.runCollect, + * Effect.forkChild + * ) + * + * yield* Deferred.await(ready) + * yield* SubscriptionRef.set(ref, 1) + * yield* SubscriptionRef.set(ref, 2) + * + * const values = yield* Fiber.join(fiber) + * return Array.from(values) + * }) + * + * await Effect.runPromise(program) // => [0, 1, 2] + * ``` + * + * @category subscriptions + * @since 4.0.0 + */ +export const changes = (self: SubscriptionRef): Stream.Stream => Stream.fromPubSub(self.pubsub) + +/** + * Retrieves the current value of the `SubscriptionRef` unsafely. + * + * **When to use** + * + * Use when you are in synchronous internals or test setup where concurrent + * updates are controlled. + * + * **Gotchas** + * + * This function directly accesses the underlying reference without any + * synchronization. It should only be used when you are certain there are no + * concurrent modifications. + * + * **Example** (Reading the current value unsafely) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(42) + * + * return SubscriptionRef.getUnsafe(ref) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const getUnsafe = (self: SubscriptionRef): A => self.value + +/** + * Retrieves the current value of the `SubscriptionRef`. + * + * **Example** (Reading the current value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(42) + * + * return yield* SubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const get = (self: SubscriptionRef): Effect.Effect => Effect.sync(() => self.value) + +/** + * Retrieves the current value and sets a new value atomically, notifying + * subscribers of the change. + * + * **Example** (Getting and setting a value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const oldValue = yield* SubscriptionRef.getAndSet(ref, 20) + * const newValue = yield* SubscriptionRef.get(ref) + * return [oldValue, newValue] + * }) + * + * await Effect.runPromise(program) // => [10, 20] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const getAndSet: { + (value: A): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, value: A): Effect.Effect +} = dual(2, (self: SubscriptionRef, value: A) => + self.semaphore.withPermit(Effect.sync(() => { + const current = self.value + setUnsafe(self, value) + return current + }))) + +const setUnsafe = (self: SubscriptionRef, value: A) => { + self.value = value + PubSub.publishUnsafe(self.pubsub, value) +} + +/** + * Retrieves the current value and updates it atomically with the result of + * applying a function, notifying subscribers of the change. + * + * **Example** (Getting and updating a value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const oldValue = yield* SubscriptionRef.getAndUpdate(ref, (n) => n * 2) + * const newValue = yield* SubscriptionRef.get(ref) + * return [oldValue, newValue] + * }) + * + * await Effect.runPromise(program) // => [10, 20] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const getAndUpdate: { + (update: (a: A) => A): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => A): Effect.Effect +} = dual(2, (self: SubscriptionRef, update: (a: A) => A) => + self.semaphore.withPermit(Effect.sync(() => { + const current = self.value + const newValue = update(current) + setUnsafe(self, newValue) + return current + }))) + +/** + * Retrieves the current value and updates it atomically with the result of + * applying an effectful function, notifying subscribers of the change. + * + * **Example** (Getting and updating with an effect) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const oldValue = yield* SubscriptionRef.getAndUpdateEffect( + * ref, + * (n) => Effect.succeed(n + 5) + * ) + * const newValue = yield* SubscriptionRef.get(ref) + * return [oldValue, newValue] + * }) + * + * await Effect.runPromise(program) // => [10, 15] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const getAndUpdateEffect: { + (update: (a: A) => Effect.Effect): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => Effect.Effect): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect +) => + self.semaphore.withPermit(Effect.suspend(() => { + const current = self.value + return Effect.map(update(current), (newValue) => { + setUnsafe(self, newValue) + return current + }) + }))) + +/** + * Retrieves the current value and optionally updates the reference. + * + * **When to use** + * + * Use to read the old `SubscriptionRef` value while applying a synchronous + * update only when a new value is available. + * + * **Details** + * + * If the function returns `Option.some`, the new value is set and published. If + * it returns `Option.none`, the reference is left unchanged and no update is + * published. + * + * **Example** (Getting and conditionally updating a value) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const oldValue = yield* SubscriptionRef.getAndUpdateSome( + * ref, + * (n) => n > 5 ? Option.some(n * 2) : Option.none() + * ) + * const newValue = yield* SubscriptionRef.get(ref) + * return [oldValue, newValue] + * }) + * + * await Effect.runPromise(program) // => [10, 20] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const getAndUpdateSome: { + (update: (a: A) => Option.Option): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => Option.Option): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Option.Option +) => + self.semaphore.withPermit(Effect.sync(() => { + const current = self.value + const option = update(current) + if (Option.isNone(option)) { + return current + } + setUnsafe(self, option.value) + return current + }))) + +/** + * Retrieves the current value and optionally updates the reference effectfully. + * + * **When to use** + * + * Use to read the old `SubscriptionRef` value while applying an effectful + * update only when a new value is available. + * + * **Details** + * + * If the effect succeeds with `Option.some`, the new value is set and + * published. If it succeeds with `Option.none`, the reference is left unchanged + * and no update is published. + * + * **Example** (Getting and conditionally updating with an effect) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const oldValue = yield* SubscriptionRef.getAndUpdateSomeEffect( + * ref, + * (n) => Effect.succeed(n > 5 ? Option.some(n + 3) : Option.none()) + * ) + * const newValue = yield* SubscriptionRef.get(ref) + * return [oldValue, newValue] + * }) + * + * await Effect.runPromise(program) // => [10, 13] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const getAndUpdateSomeEffect: { + ( + update: (a: A) => Effect.Effect, E, R> + ): (self: SubscriptionRef) => Effect.Effect + ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect, E, R> + ): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect, E, R> +) => + self.semaphore.withPermit(Effect.suspend(() => { + const current = self.value + return Effect.map(update(current), (option) => { + if (Option.isNone(option)) return current + setUnsafe(self, option.value) + return current + }) + }))) + +/** + * Modifies the `SubscriptionRef` atomically with a function that computes a + * return value and a new value, notifying subscribers of the change. + * + * **Example** (Modifying a value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const result = yield* SubscriptionRef.modify(ref, (n) => [ + * `Old value was ${n}`, + * n * 2 + * ]) + * const newValue = yield* SubscriptionRef.get(ref) + * return [result, newValue] + * }) + * + * await Effect.runPromise(program) // => ["Old value was 10", 20] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const modify: { + (modify: (a: A) => readonly [B, A]): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, f: (a: A) => readonly [B, A]): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + modify: (a: A) => readonly [B, A] +) => + self.semaphore.withPermit(Effect.sync(() => { + const [b, newValue] = modify(self.value) + setUnsafe(self, newValue) + return b + }))) + +/** + * Modifies the `SubscriptionRef` atomically with an effectful function that + * computes a return value and a new value, notifying subscribers of the + * change. + * + * **Example** (Modifying with an effect) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const result = yield* SubscriptionRef.modifyEffect( + * ref, + * (n) => Effect.succeed([`Doubled from ${n}`, n * 2] as const) + * ) + * const newValue = yield* SubscriptionRef.get(ref) + * return [result, newValue] + * }) + * + * await Effect.runPromise(program) // => ["Doubled from 10", 20] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const modifyEffect: { + ( + modify: (a: A) => Effect.Effect + ): (self: SubscriptionRef) => Effect.Effect + ( + self: SubscriptionRef, + modify: (a: A) => Effect.Effect + ): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + modify: (a: A) => Effect.Effect +): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => + Effect.map(modify(self.value), ([b, newValue]) => { + setUnsafe(self, newValue) + return b + }) + ))) + +/** + * Computes a return value and optionally updates the reference. + * + * **When to use** + * + * Use to return a separate result while synchronously deciding whether to + * publish a new `SubscriptionRef` value. + * + * **Details** + * + * If the function returns `Option.some` for the new value, the value is set and + * published. If it returns `Option.none`, the reference is left unchanged and + * no update is published. + * + * **Example** (Conditionally modifying a value) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const result = yield* SubscriptionRef.modifySome( + * ref, + * (n) => + * n > 5 ? ["Updated", Option.some(n * 2)] : ["Not updated", Option.none()] + * ) + * const newValue = yield* SubscriptionRef.get(ref) + * return [result, newValue] + * }) + * + * await Effect.runPromise(program) // => ["Updated", 20] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const modifySome: { + ( + modify: (a: A) => readonly [B, Option.Option] + ): (self: SubscriptionRef) => Effect.Effect + ( + self: SubscriptionRef, + modify: (a: A) => readonly [B, Option.Option] + ): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + modify: (a: A) => readonly [B, Option.Option] +) => + self.semaphore.withPermit(Effect.sync(() => { + const [b, option] = modify(self.value) + if (Option.isNone(option)) return b + setUnsafe(self, option.value) + return b + }))) + +/** + * Computes a return value and optionally updates the reference effectfully. + * + * **When to use** + * + * Use to return a separate result while effectfully deciding whether to publish + * a new `SubscriptionRef` value. + * + * **Details** + * + * If the effect succeeds with `Option.some`, the new value is set and + * published. If it succeeds with `Option.none`, the reference is left unchanged + * and no update is published. + * + * **Example** (Conditionally modifying with an effect) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * const result = yield* SubscriptionRef.modifySomeEffect( + * ref, + * (n) => + * Effect.succeed( + * n > 5 + * ? (["Updated", Option.some(n + 5)] as const) + * : (["Not updated", Option.none()] as const) + * ) + * ) + * const newValue = yield* SubscriptionRef.get(ref) + * return [result, newValue] + * }) + * + * await Effect.runPromise(program) // => ["Updated", 15] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const modifySomeEffect: { + ( + modify: (a: A) => Effect.Effect], E, R> + ): (self: SubscriptionRef) => Effect.Effect + ( + self: SubscriptionRef, + modify: (a: A) => Effect.Effect], E, R> + ): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + modify: (a: A) => Effect.Effect], E, R> +) => + self.semaphore.withPermit(Effect.suspend(() => + Effect.map(modify(self.value), ([b, option]) => { + if (Option.isNone(option)) return b + setUnsafe(self, option.value) + return b + }) + ))) + +/** + * Sets the value of the `SubscriptionRef`, notifying all subscribers of the + * change. + * + * **Example** (Setting a value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(0) + * + * yield* SubscriptionRef.set(ref, 42) + * + * return yield* SubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const set: { + (value: A): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, value: A): Effect.Effect +} = dual( + 2, + (self: SubscriptionRef, value: A) => self.semaphore.withPermit(Effect.sync(() => setUnsafe(self, value))) +) + +/** + * Sets the value of the `SubscriptionRef` and returns the new value, + * notifying all subscribers of the change. + * + * **Example** (Setting and reading the new value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(0) + * + * return yield* SubscriptionRef.setAndGet(ref, 42) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const setAndGet: { + (value: A): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, value: A): Effect.Effect +} = dual(2, (self: SubscriptionRef, value: A) => + self.semaphore.withPermit(Effect.sync(() => { + setUnsafe(self, value) + return value + }))) + +/** + * Updates the value of the `SubscriptionRef` with the result of applying a + * function, notifying subscribers of the change. + * + * **Example** (Updating a value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * yield* SubscriptionRef.update(ref, (n) => n * 2) + * + * return yield* SubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 20 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const update: { + (update: (a: A) => A): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => A): Effect.Effect +} = dual( + 2, + (self: SubscriptionRef, update: (a: A) => A) => + self.semaphore.withPermit(Effect.sync(() => setUnsafe(self, update(self.value)))) +) + +/** + * Updates the value of the `SubscriptionRef` with the result of applying an + * effectful function, notifying subscribers of the change. + * + * **Example** (Updating with an effect) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * yield* SubscriptionRef.updateEffect(ref, (n) => Effect.succeed(n + 5)) + * + * return yield* SubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 15 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateEffect: { + (update: (a: A) => Effect.Effect): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => Effect.Effect): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect +) => + self.semaphore.withPermit( + Effect.suspend(() => Effect.map(update(self.value), (newValue) => setUnsafe(self, newValue))) + )) + +/** + * Updates the value of the `SubscriptionRef` with the result of applying a + * function and returns the new value, notifying subscribers of the change. + * + * **Example** (Updating and reading the new value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * return yield* SubscriptionRef.updateAndGet(ref, (n) => n * 2) + * }) + * + * await Effect.runPromise(program) // => 20 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateAndGet: { + (update: (a: A) => A): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => A): Effect.Effect +} = dual(2, (self: SubscriptionRef, update: (a: A) => A) => + self.semaphore.withPermit(Effect.sync(() => { + const newValue = update(self.value) + setUnsafe(self, newValue) + return newValue + }))) + +/** + * Updates the value of the `SubscriptionRef` with the result of applying an + * effectful function and returns the new value, notifying subscribers of the + * change. + * + * **Example** (Updating with an effect and reading the new value) + * + * ```ts import.meta.vitest + * import { Effect, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * return yield* SubscriptionRef.updateAndGetEffect( + * ref, + * (n) => Effect.succeed(n + 5) + * ) + * }) + * + * await Effect.runPromise(program) // => 15 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateAndGetEffect: { + (update: (a: A) => Effect.Effect): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => Effect.Effect): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect +) => + self.semaphore.withPermit(Effect.suspend(() => + Effect.map(update(self.value), (newValue) => { + setUnsafe(self, newValue) + return newValue + }) + ))) + +/** + * Applies an update function to the current value. If it returns + * `Option.some`, sets and publishes that value; if it returns `Option.none`, + * leaves the reference unchanged and does not publish. + * + * **Example** (Conditionally updating a value) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * yield* SubscriptionRef.updateSome( + * ref, + * (n) => n > 5 ? Option.some(n * 2) : Option.none() + * ) + * + * return yield* SubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 20 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateSome: { + (update: (a: A) => Option.Option): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => Option.Option): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Option.Option +) => + self.semaphore.withPermit(Effect.sync(() => { + const option = update(self.value) + if (Option.isNone(option)) return + setUnsafe(self, option.value) + }))) + +/** + * Applies an effectful update only when it produces a new value. + * + * **When to use** + * + * Use to conditionally update a `SubscriptionRef` with an effectful function + * while discarding the resulting value. + * + * **Details** + * + * If the effect succeeds with `Option.some`, the new value is set and + * published. If it succeeds with `Option.none`, the reference is left unchanged + * and no update is published. + * + * **Example** (Conditionally updating with an effect) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * yield* SubscriptionRef.updateSomeEffect( + * ref, + * (n) => Effect.succeed(n > 5 ? Option.some(n + 3) : Option.none()) + * ) + * + * return yield* SubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 13 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateSomeEffect: { + ( + update: (a: A) => Effect.Effect, E, R> + ): (self: SubscriptionRef) => Effect.Effect + ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect, E, R> + ): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect, E, R> +) => + self.semaphore.withPermit(Effect.suspend(() => + Effect.map(update(self.value), (option) => { + if (Option.isNone(option)) return + setUnsafe(self, option.value) + }) + ))) + +/** + * Applies an optional update and returns the current value afterward. + * + * **When to use** + * + * Use to conditionally update a `SubscriptionRef` and read the value that is + * current after the update decision. + * + * **Details** + * + * If the function returns `Option.some`, the new value is set, published, and + * returned. If it returns `Option.none`, the unchanged current value is + * returned without publishing. + * + * **Example** (Conditionally updating and reading the new value) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * return yield* SubscriptionRef.updateSomeAndGet( + * ref, + * (n) => n > 5 ? Option.some(n * 2) : Option.none() + * ) + * }) + * + * await Effect.runPromise(program) // => 20 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateSomeAndGet: { + (update: (a: A) => Option.Option): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => Option.Option): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Option.Option +) => + self.semaphore.withPermit(Effect.sync(() => { + const current = self.value + const option = update(current) + if (Option.isNone(option)) return current + setUnsafe(self, option.value) + return option.value + }))) + +/** + * Applies an effectful optional update and returns the current value afterward. + * + * **When to use** + * + * Use to conditionally update a `SubscriptionRef` effectfully and read the + * value that is current after the update decision. + * + * **Details** + * + * If the effect succeeds with `Option.some`, the new value is set, published, + * and returned. If it succeeds with `Option.none`, the unchanged current value + * is returned without publishing. + * + * **Example** (Conditionally updating with an effect and reading the new value) + * + * ```ts import.meta.vitest + * import { Effect, Option, SubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* SubscriptionRef.make(10) + * + * return yield* SubscriptionRef.updateSomeAndGetEffect( + * ref, + * (n) => Effect.succeed(n > 5 ? Option.some(n + 3) : Option.none()) + * ) + * }) + * + * await Effect.runPromise(program) // => 13 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const updateSomeAndGetEffect: { + ( + update: (a: A) => Effect.Effect, E, R> + ): (self: SubscriptionRef) => Effect.Effect + (self: SubscriptionRef, update: (a: A) => Effect.Effect, E, R>): Effect.Effect +} = dual(2, ( + self: SubscriptionRef, + update: (a: A) => Effect.Effect, E, R> +) => + self.semaphore.withPermit(Effect.suspend(() => { + const current = self.value + return Effect.map(update(current), (option) => { + if (Option.isNone(option)) return current + setUnsafe(self, option.value) + return option.value + }) + }))) diff --git a/.repos/effect/packages/effect/src/Symbol.ts b/.repos/effect/packages/effect/src/Symbol.ts new file mode 100644 index 000000000..de444c25d --- /dev/null +++ b/.repos/effect/packages/effect/src/Symbol.ts @@ -0,0 +1,31 @@ +/** + * The `Symbol` module contains the runtime predicate for JavaScript primitive + * `symbol` values. It is most useful at boundaries where a value is `unknown` + * and must be narrowed before it can be used as a symbol key, identifier, or + * discriminant. + * + * @since 2.0.0 + */ + +import * as predicate from "./Predicate.ts" + +/** + * Checks whether a value is a `symbol`. + * + * **When to use** + * + * Use to validate unknown input before treating it as a JavaScript `symbol`. + * + * **Example** (Checking for symbols) + * + * ```ts import.meta.vitest + * import { Symbol } from "effect" + * + * Symbol.isSymbol(globalThis.Symbol.for("a")) // => true + * Symbol.isSymbol("a") // => false + * ``` + * + * @category guards + * @since 2.0.0 + */ +export const isSymbol: (u: unknown) => u is symbol = predicate.isSymbol diff --git a/.repos/effect/packages/effect/src/SynchronizedRef.ts b/.repos/effect/packages/effect/src/SynchronizedRef.ts new file mode 100644 index 000000000..5bbf95662 --- /dev/null +++ b/.repos/effect/packages/effect/src/SynchronizedRef.ts @@ -0,0 +1,668 @@ +/** + * Stores mutable state whose updates run one at a time. + * + * A `SynchronizedRef` behaves like a `Ref` for reading and simple state + * storage, but update and modify operations are serialized so each change sees + * a consistent current value. This is especially useful when the next value is + * computed by an effect, because the effectful transition is still protected + * from concurrent updates. This module includes constructors, reads, writes, + * updates, partial updates, and effectful update helpers. + * + * @since 2.0.0 + */ +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import { PipeInspectableProto } from "./internal/core.ts" +import * as Option from "./Option.ts" +import * as Ref from "./Ref.ts" +import * as Semaphore from "./Semaphore.ts" + +const TypeId = "~effect/SynchronizedRef" + +/** + * A mutable reference whose update and modify operations are serialized with an + * internal semaphore, including effectful transformations. + * + * **When to use** + * + * Use when shared state may be updated by multiple fibers and each update, + * including effectful state transitions, must observe one current value and run + * one at a time. + * + * @see {@link Ref.Ref} for a plain `Ref` when updates do not need effectful synchronization + * + * @category models + * @since 2.0.0 + */ +export interface SynchronizedRef extends Ref.Ref { + readonly [TypeId]: typeof TypeId + readonly backing: Ref.Ref + readonly semaphore: Semaphore.Semaphore +} + +const Proto = { + ...PipeInspectableProto, + [TypeId]: TypeId, + toJSON(this: SynchronizedRef) { + return { + _id: "SynchronizedRef", + value: this.backing.ref.current + } + } +} + +/** + * Creates a `SynchronizedRef` synchronously from an initial value. + * + * **When to use** + * + * Use when you need synchronous `SynchronizedRef` construction outside an + * Effect workflow. + * + * @category constructors + * @since 4.0.0 + */ +export const makeUnsafe = (value: A): SynchronizedRef => { + const self = Object.create(Proto) + self.semaphore = Semaphore.makeUnsafe(1) + self.backing = Ref.makeUnsafe(value) + return self +} + +/** + * Creates a `SynchronizedRef` from an initial value, wrapped in an `Effect`. + * + * **When to use** + * + * Use to create a `SynchronizedRef` inside an Effect program when later updates + * may run effects and must be serialized. + * + * **Details** + * + * The returned effect constructs a fresh `SynchronizedRef` by delegating to + * `makeUnsafe` when the effect is evaluated. + * + * @see {@link makeUnsafe} for synchronous construction when the caller controls safe initialization + * @see {@link Ref.make} for a plain `Ref` when updates do not need effectful synchronization + * + * @category constructors + * @since 2.0.0 + */ +export const make = (value: A): Effect.Effect> => Effect.sync(() => makeUnsafe(value)) + +/** + * Reads the current value synchronously, bypassing the `Effect` API and the + * ref's semaphore. + * + * **When to use** + * + * Use when you need immediate synchronous access to a `SynchronizedRef` value + * in low-level code that can safely read outside an `Effect`. + * + * @see {@link get} for the Effect-wrapped read when composing inside Effect programs + * + * @category getters + * @since 4.0.0 + */ +export const getUnsafe = (self: SynchronizedRef): A => self.backing.ref.current + +/** + * Returns an `Effect` that reads the current value of the `SynchronizedRef`. + * + * **When to use** + * + * Use to read the current value of a `SynchronizedRef` inside an `Effect` + * program without changing it. + * + * @see {@link getUnsafe} for synchronous reads when the caller controls safe access outside `Effect` + * + * @category getters + * @since 2.0.0 + */ +export const get = (self: SynchronizedRef): Effect.Effect => Effect.sync(() => getUnsafe(self)) + +/** + * Sets a new value atomically and returns the previous value, serialized by the + * ref's semaphore. + * + * **When to use** + * + * Use to replace a `SynchronizedRef` with a known value when the previous value + * is also needed. + * + * @see {@link set} for setting a value without returning the previous value + * @see {@link setAndGet} for setting a value and returning the new value + * @see {@link getAndUpdate} for deriving the new value from the current value + * + * @category mutations + * @since 2.0.0 + */ +export const getAndSet: { + (value: A): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, value: A): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, value: A): Effect.Effect => + self.semaphore.withPermit(Ref.getAndSet(self.backing, value)) +) + +/** + * Updates the current value atomically with a function and returns the previous + * value, serialized by the ref's semaphore. + * + * **When to use** + * + * Use to run a pure `SynchronizedRef` state update when the previous stored + * value is also needed. + * + * @see {@link update} for updating without returning a value + * @see {@link updateAndGet} for updating and returning the new value + * @see {@link getAndUpdateEffect} for effectful updates that return the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const getAndUpdate: { + (f: (a: A) => A): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => A): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => A): Effect.Effect => + self.semaphore.withPermit(Ref.getAndUpdate(self.backing, f)) +) + +/** + * Runs an effectful update atomically while holding the ref's semaphore, sets + * the new value if the effect succeeds, and returns the previous value. + * + * **When to use** + * + * Use when you need an effectful `SynchronizedRef` state transition to return + * the previous stored value. + * + * @see {@link getAndUpdate} for pure updates that return the previous value + * @see {@link updateEffect} for effectful updates without returning a value + * @see {@link updateAndGetEffect} for effectful updates that return the new value + * @see {@link modifyEffect} for effectful updates with a custom return value + * @see {@link getAndUpdateSomeEffect} for conditional effectful updates that return the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const getAndUpdateEffect: { + (f: (a: A) => Effect.Effect): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.map(f(value), (newValue) => { + self.backing.ref.current = newValue + return value + }) + })) +) + +/** + * Applies a partial update atomically and returns the previous value. If the + * function returns `Option.some`, the ref is updated; if it returns + * `Option.none`, the ref is left unchanged. + * + * **When to use** + * + * Use to return the previous `SynchronizedRef` value while applying a pure + * conditional update. + * + * @see {@link getAndUpdate} for always applying a pure update + * @see {@link updateSome} for applying a pure conditional update without returning the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const getAndUpdateSome: { + (pf: (a: A) => Option.Option): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, pf: (a: A) => Option.Option): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, pf: (a: A) => Option.Option): Effect.Effect => + self.semaphore.withPermit(Ref.getAndUpdateSome(self.backing, pf)) +) + +/** + * Runs an effectful partial update atomically while holding the ref's semaphore + * and returns the previous value. `Option.some` updates the ref; `Option.none` + * leaves it unchanged. + * + * **When to use** + * + * Use to return the previous `SynchronizedRef` value while running an effectful + * conditional update. + * + * @see {@link getAndUpdateSome} for the pure conditional variant + * @see {@link updateSomeEffect} for effectful conditional updates without returning the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const getAndUpdateSomeEffect: { + (pf: (a: A) => Effect.Effect, E, R>): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, pf: (a: A) => Effect.Effect, E, R>): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, pf: (a: A) => Effect.Effect, E, R>): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.flatMap(pf(value), (option) => { + if (Option.isNone(option)) { + return Effect.succeed(value) + } + self.backing.ref.current = option.value + return Effect.succeed(value) + }) + })) +) + +/** + * Computes a return value and a new ref value atomically, stores the new value, + * and returns the computed result. + * + * **When to use** + * + * Use to derive a separate result and the next stored `SynchronizedRef` value + * from the same current value in one serialized pure update. + * + * @see {@link modifyEffect} for effectfully deriving both the result and next stored value + * @see {@link modifySome} for deriving a result and optionally updating the stored value + * @see {@link updateAndGet} for returning the new stored value instead of a separate result + * + * @category mutations + * @since 2.0.0 + */ +export const modify: { + (f: (a: A) => readonly [B, A]): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => readonly [B, A]): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => readonly [B, A]): Effect.Effect => + self.semaphore.withPermit(Ref.modify(self.backing, f)) +) + +/** + * Runs an effectful modification atomically while holding the ref's semaphore, + * stores the new value if the effect succeeds, and returns the computed result. + * + * **When to use** + * + * Use to effectfully compute both a separate return value and the next stored + * `SynchronizedRef` value in one serialized update. + * + * @see {@link modify} for the pure variant + * @see {@link updateEffect} for effectfully storing a new value without a separate result + * + * @category mutations + * @since 2.0.0 + */ +export const modifyEffect: { + (f: (a: A) => Effect.Effect): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.map(f(value), ([b, a]) => { + self.backing.ref.current = a + return b + }) + })) +) + +/** + * Computes a return value and an optional new ref value atomically. + * `Option.some` updates the ref; `Option.none` leaves it unchanged. + * + * **When to use** + * + * Use to compute a return value while optionally updating a `SynchronizedRef` + * under its semaphore. + * + * @see {@link modify} for always storing a new value + * @see {@link updateSome} for optional updates without a separate return value + * + * @category mutations + * @since 2.0.0 + */ +export const modifySome: { + ( + pf: (a: A) => readonly [B, Option.Option] + ): (self: SynchronizedRef) => Effect.Effect + ( + self: SynchronizedRef, + pf: (a: A) => readonly [B, Option.Option] + ): Effect.Effect +} = dual( + 2, + ( + self: SynchronizedRef, + pf: (a: A) => readonly [B, Option.Option] + ): Effect.Effect => self.semaphore.withPermit(Ref.modifySome(self.backing, pf)) +) + +/** + * Runs an effectful modification atomically while holding the ref's semaphore. + * The effect computes a return value and an optional new ref value; + * `Option.some` updates the ref and `Option.none` leaves it unchanged. + * + * **When to use** + * + * Use to effectfully compute a return value while optionally updating the + * stored `SynchronizedRef` value. + * + * @see {@link modifySome} for the pure variant + * @see {@link updateSomeEffect} for effectful optional updates without a separate return value + * + * @category mutations + * @since 2.0.0 + */ +export const modifySomeEffect: { + ( + fallback: B, + pf: (a: A) => Effect.Effect], E, R> + ): (self: SynchronizedRef) => Effect.Effect + ( + self: SynchronizedRef, + pf: (a: A) => Effect.Effect], E, R> + ): Effect.Effect +} = dual( + 2, + ( + self: SynchronizedRef, + pf: (a: A) => Effect.Effect], E, R> + ): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.flatMap(pf(value), ([b, maybeA]) => { + if (Option.isNone(maybeA)) { + return Effect.succeed(b) + } + self.backing.ref.current = maybeA.value + return Effect.succeed(b) + }) + })) +) + +/** + * Sets the value of the `SynchronizedRef`, serialized by the ref's semaphore. + * + * **When to use** + * + * Use to replace the current value of a `SynchronizedRef` with a known value + * while keeping the write serialized with other synchronized updates. + * + * @see {@link getAndSet} for replacing the value when the previous value is needed + * @see {@link setAndGet} for replacing the value when the new value should be returned + * @see {@link update} for deriving the next value from the current value + * + * @category mutations + * @since 2.0.0 + */ +export const set: { + (value: A): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, value: A): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, value: A): Effect.Effect => + self.semaphore.withPermit(Ref.set(self.backing, value)) +) + +/** + * Sets the value of the `SynchronizedRef` and returns the new value. + * + * **When to use** + * + * Use to replace the current `SynchronizedRef` value with a known value and + * return that new value. + * + * @see {@link set} for setting without returning a value + * @see {@link getAndSet} for setting while returning the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const setAndGet: { + (value: A): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, value: A): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, value: A): Effect.Effect => + self.semaphore.withPermit(Ref.setAndGet(self.backing, value)) +) + +/** + * Updates the value of the `SynchronizedRef` with a function, serialized by the + * ref's semaphore. + * + * **When to use** + * + * Use to apply a pure state transition to a `SynchronizedRef` as a serialized + * `Effect`. + * + * @see {@link updateEffect} for effectfully deriving the next value + * @see {@link updateAndGet} for returning the new stored value + * @see {@link getAndUpdate} for returning the previous stored value + * + * @category mutations + * @since 2.0.0 + */ +export const update: { + (f: (a: A) => A): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => A): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => A): Effect.Effect => + self.semaphore.withPermit(Ref.update(self.backing, f)) +) + +/** + * Runs an effectful update while holding the ref's semaphore and stores the new + * value if the effect succeeds. + * + * **When to use** + * + * Use to run an effectful state transition on a `SynchronizedRef` when storing + * the new value is the only result you need. + * + * @see {@link update} for a pure state transition + * @see {@link getAndUpdateEffect} for returning the previous stored value + * @see {@link updateAndGetEffect} for returning the new stored value + * @see {@link modifyEffect} for returning a separate result while storing a new value + * @see {@link updateSomeEffect} for effectfully applying only some state transitions + * + * @category mutations + * @since 2.0.0 + */ +export const updateEffect: { + (f: (a: A) => Effect.Effect): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.map(f(value), (newValue) => { + self.backing.ref.current = newValue + }) + })) +) + +/** + * Updates the value of the `SynchronizedRef` with a function and returns the + * new value. + * + * **When to use** + * + * Use to apply a pure `SynchronizedRef` state transition and return the new + * stored value. + * + * @see {@link update} for updating without returning the new value + * @see {@link getAndUpdate} for updating while returning the previous value + * + * @category mutations + * @since 2.0.0 + */ +export const updateAndGet: { + (f: (a: A) => A): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => A): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => A): Effect.Effect => + self.semaphore.withPermit(Ref.updateAndGet(self.backing, f)) +) + +/** + * Runs an effectful update while holding the ref's semaphore, stores the new + * value if the effect succeeds, and returns that new value. + * + * **When to use** + * + * Use to run an effectful `SynchronizedRef` state transition and return the new + * stored value. + * + * @see {@link updateEffect} for effectful updates without returning the new value + * @see {@link updateAndGet} for the pure variant + * + * @category mutations + * @since 2.0.0 + */ +export const updateAndGetEffect: { + (f: (a: A) => Effect.Effect): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => Effect.Effect): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.map(f(value), (newValue) => { + self.backing.ref.current = newValue + return newValue + }) + })) +) + +/** + * Applies a partial update to the current value. `Option.some` stores the new + * value; `Option.none` leaves the ref unchanged. + * + * **When to use** + * + * Use to apply a pure conditional `SynchronizedRef` update without returning a + * value. + * + * @see {@link update} for always applying a pure update + * @see {@link updateSomeAndGet} for returning the resulting current value + * + * @category mutations + * @since 2.0.0 + */ +export const updateSome: { + (f: (a: A) => Option.Option): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, f: (a: A) => Option.Option): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, f: (a: A) => Option.Option): Effect.Effect => + self.semaphore.withPermit(Ref.updateSome(self.backing, f)) +) + +/** + * Runs an effectful partial update while holding the ref's semaphore. + * `Option.some` stores the new value; `Option.none` leaves the ref unchanged. + * + * **When to use** + * + * Use to run an effectful conditional `SynchronizedRef` update without + * returning a value. + * + * @see {@link updateSome} for the pure conditional variant + * @see {@link updateEffect} for effectful updates that always store a new value + * + * @category mutations + * @since 2.0.0 + */ +export const updateSomeEffect: { + ( + pf: (a: A) => Effect.Effect, E, R> + ): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, pf: (a: A) => Effect.Effect, E, R>): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, pf: (a: A) => Effect.Effect, E, R>): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.map(pf(value), (option) => { + if (Option.isNone(option)) { + return + } + self.backing.ref.current = option.value + }) + })) +) + +/** + * Applies a partial update and returns the resulting current value. + * `Option.some` stores and returns the new value; `Option.none` returns the + * unchanged value. + * + * **When to use** + * + * Use to apply a pure conditional `SynchronizedRef` update and return the + * resulting current value. + * + * @see {@link updateSome} for conditional updates without returning a value + * @see {@link updateAndGet} for always applying a pure update and returning the new value + * + * @category mutations + * @since 2.0.0 + */ +export const updateSomeAndGet: { + (pf: (a: A) => Option.Option): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, pf: (a: A) => Option.Option): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, pf: (a: A) => Option.Option): Effect.Effect => + self.semaphore.withPermit(Ref.updateSomeAndGet(self.backing, pf)) +) + +/** + * Runs an effectful partial update while holding the ref's semaphore and + * returns the resulting current value. `Option.some` stores and returns the new + * value; `Option.none` returns the unchanged value. + * + * **When to use** + * + * Use to run an effectful conditional `SynchronizedRef` update and return the + * resulting current value. + * + * @see {@link updateSomeEffect} for effectful conditional updates without returning a value + * @see {@link updateAndGetEffect} for effectful updates that always store and return a new value + * + * @category mutations + * @since 2.0.0 + */ +export const updateSomeAndGetEffect: { + (pf: (a: A) => Effect.Effect, E, R>): (self: SynchronizedRef) => Effect.Effect + (self: SynchronizedRef, pf: (a: A) => Effect.Effect, E, R>): Effect.Effect +} = dual( + 2, + (self: SynchronizedRef, pf: (a: A) => Effect.Effect, E, R>): Effect.Effect => + self.semaphore.withPermit(Effect.suspend(() => { + const value = getUnsafe(self) + return Effect.flatMap(pf(value), (option) => { + if (Option.isNone(option)) { + return Effect.succeed(value) + } + self.backing.ref.current = option.value + return Effect.succeed(option.value) + }) + })) +) diff --git a/.repos/effect/packages/effect/src/Take.ts b/.repos/effect/packages/effect/src/Take.ts new file mode 100644 index 000000000..48c2dd796 --- /dev/null +++ b/.repos/effect/packages/effect/src/Take.ts @@ -0,0 +1,46 @@ +/** + * The `Take` module provides the stored representation of one pull result from + * a stream-like producer. A `Take` is either a non-empty batch of + * emitted values, a failed `Exit`, or a successful `Exit` carrying the + * completion value. + * + * @since 2.0.0 + */ +import type { NonEmptyReadonlyArray } from "./Array.ts" +import * as Cause from "./Cause.ts" +import * as Effect from "./Effect.ts" +import * as Exit from "./Exit.ts" +import type * as Pull from "./Pull.ts" + +/** + * Represents one pull result: either a non-empty batch of values, a failure + * `Exit`, or a successful `Exit` that signals completion with a `Done` value. + * + * **When to use** + * + * Use to store, transfer, or interpret pull results later while preserving + * emitted values, failures, and normal completion. + * + * @see {@link toPull} for interpreting a `Take` as a `Pull` step + * + * @category models + * @since 2.0.0 + */ +export type Take = NonEmptyReadonlyArray | Exit.Exit + +/** + * Converts a `Take` into a `Pull`, succeeding with value batches, failing with + * failure exits, and translating successful exits into pull completion. + * + * **When to use** + * + * Use to interpret a stored or transferred `Take` as a `Pull` step while + * preserving emitted batches, ordinary failures, and completion values. + * + * @category converting + * @since 4.0.0 + */ +export const toPull = (take: Take): Pull.Pull, E, Done> => + Exit.isExit(take) + ? Exit.isSuccess(take) ? Cause.done(take.value) : (take as Exit.Exit) + : Effect.succeed(take) diff --git a/.repos/effect/packages/effect/src/Terminal.ts b/.repos/effect/packages/effect/src/Terminal.ts new file mode 100644 index 000000000..c2311d184 --- /dev/null +++ b/.repos/effect/packages/effect/src/Terminal.ts @@ -0,0 +1,188 @@ +/** + * Service definition for interactive terminal capabilities. Programs can query + * terminal dimensions, read a line of input, receive low-level key events, and + * display text without depending directly on a specific platform implementation. + * + * This module defines the `Terminal` service, input event shapes, key metadata, + * the `QuitError` used when a user cancels input, a guard for that error, and a + * constructor for custom terminal service implementations. + * + * @since 4.0.0 + */ +import type * as Cause from "./Cause.ts" +import * as Context from "./Context.ts" +import type * as Effect from "./Effect.ts" +import type * as Option from "./Option.ts" +import type { PlatformError } from "./PlatformError.ts" +import * as Predicate from "./Predicate.ts" +import type * as Queue from "./Queue.ts" +import * as Schema from "./Schema.ts" +import type * as Scope from "./Scope.ts" + +const TypeId = "~effect/platform/Terminal" + +/** + * A `Terminal` represents a command-line interface which can read input from a + * user and display messages to a user. + * + * @category services + * @since 4.0.0 + */ +export interface Terminal { + readonly [TypeId]: typeof TypeId + + /** + * The number of columns available on the platform's terminal interface. + */ + readonly columns: Effect.Effect + /** + * The number of rows available on the platform's terminal interface. + */ + + readonly rows: Effect.Effect + /** + * Reads input events from the default standard input. + */ + readonly readInput: Effect.Effect, never, Scope.Scope> + /** + * Reads a single line from the default standard input. + */ + readonly readLine: Effect.Effect + /** + * Displays text to the default standard output. + */ + readonly display: (text: string) => Effect.Effect +} + +/** + * Keyboard key metadata for terminal input, including the key name and + * modifier state. + * + * @category models + * @since 4.0.0 + */ +export interface Key { + /** + * The name of the key being pressed. + */ + readonly name: string + /** + * If set to `true`, then the user is also holding down the `Ctrl` key. + */ + readonly ctrl: boolean + /** + * If set to `true`, then the user is also holding down the `Meta` key. + */ + readonly meta: boolean + /** + * If set to `true`, then the user is also holding down the `Shift` key. + */ + readonly shift: boolean +} + +/** + * A terminal input event containing an optional raw character and the parsed + * key that was pressed. + * + * **When to use** + * + * Use when consuming low-level terminal input events from `Terminal.readInput` + * and you need both raw character input and parsed key metadata. + * + * @see {@link Key} for the parsed key metadata stored on each input event + * + * @category models + * @since 4.0.0 + */ +export interface UserInput { + /** + * The character read from the user (if any). + */ + readonly input: Option.Option + /** + * The key that the user pressed. + */ + readonly key: Key +} + +const QuitErrorTypeId = "effect/platform/Terminal/QuitError" + +/** + * Represents an error that occurs when a user attempts to + * quit out of a `Terminal` prompt for input (usually by entering `ctrl`+`c`). + * + * **When to use** + * + * Use when implementing terminal input or prompts that need to signal + * user-requested cancellation through the typed error channel. + * + * @see {@link isQuitError} for checking unknown errors when handling terminal cancellation + * + * @category errors + * @since 4.0.0 + */ +export class QuitError extends Schema.Error("QuitError")({ + _tag: Schema.tag("QuitError") +}) { + /** + * Marks this value as a terminal quit error for runtime guards. + * + * @since 4.0.0 + */ + readonly [QuitErrorTypeId] = QuitErrorTypeId +} + +/** + * Returns `true` if the provided value is a `Terminal.QuitError`. + * + * **When to use** + * + * Use to narrow unknown failures to `QuitError` when handling terminal input + * cancellation. + * + * **Details** + * + * Returns `true` when the value carries the `QuitError` runtime marker and + * narrows it to `QuitError`. + * + * @see {@link QuitError} for the error value produced when terminal input is quit + * + * @category guards + * @since 4.0.0 + */ +export const isQuitError = (u: unknown): u is QuitError => Predicate.hasProperty(u, QuitErrorTypeId) + +/** + * Service tag for command-line input and output services. + * + * **When to use** + * + * Use to access or provide platform terminal capabilities such as reading + * input, writing output, and inspecting terminal dimensions. + * + * @category services + * @since 4.0.0 + */ +export const Terminal: Context.Service = Context.Service("effect/platform/Terminal") + +/** + * Creates a `Terminal` service implementation. + * + * **When to use** + * + * Use to construct a custom `Terminal` service implementation from concrete + * terminal capabilities when writing a platform adapter, test implementation, + * or custom runtime service. + * + * **Details** + * + * The implementation object supplies `columns`, `rows`, `readInput`, + * `readLine`, and `display`; `make` attaches the `Terminal` service marker so + * the result can be provided through the `Terminal` context service. + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + impl: Omit +): Terminal => Terminal.of({ ...impl, [TypeId]: TypeId }) diff --git a/.repos/effect/packages/effect/src/Tracer.ts b/.repos/effect/packages/effect/src/Tracer.ts new file mode 100644 index 000000000..3ead5c1fb --- /dev/null +++ b/.repos/effect/packages/effect/src/Tracer.ts @@ -0,0 +1,719 @@ +/** + * Defines the low-level tracing model used by Effect. + * + * A span records the lifetime of an operation, including its name, parent, + * attributes, links, annotations, sampling decision, kind, and completion + * status. The module also defines the tracer service, parent-span context, + * external span support, trace propagation settings, and the default in-memory + * span implementation. + * + * @since 2.0.0 + */ +import * as Context from "./Context.ts" +import * as Encoding from "./Encoding.ts" +import type * as Exit from "./Exit.ts" +import type { Fiber } from "./Fiber.ts" +import { constFalse, type LazyArg } from "./Function.ts" +import type * as core from "./internal/core.ts" +import type { LogLevel } from "./LogLevel.ts" +import * as Option from "./Option.ts" + +/** + * A tracing backend used by Effect to create spans. Custom tracers implement + * `span` to allocate a span from the supplied name, parent, annotations, + * links, start time, kind, root flag, and sampling decision. + * + * @category services + * @since 2.0.0 + */ +export interface Tracer { + span(this: Tracer, options: { + readonly name: string + readonly parent: Option.Option + readonly annotations: Context.Context + readonly links: Array + readonly startTime: bigint + readonly kind: SpanKind + readonly root: boolean + readonly sampled: boolean + }): Span + readonly context?: + | ((primitive: EffectPrimitive, fiber: Fiber) => X) + | undefined +} + +const evaluate = "~effect/Effect/evaluate" satisfies core.evaluate + +/** + * A low-level Effect primitive that can be evaluated by a tracer-specific + * context for the current fiber. + * + * @category models + * @since 4.0.0 + */ +export interface EffectPrimitive { + [evaluate](this: EffectPrimitive, fiber: Fiber): X +} + +/** + * Lifecycle state of a span, where `Started` records the start time and + * `Ended` records the start time, end time, and exit value with which the span + * completed. + * + * **Example** (Creating span statuses) + * + * ```ts import.meta.vitest + * import { Exit } from "effect" + * import type { Tracer } from "effect" + * + * const startTime = 1_000_000_000n + * const endTime = 1_500_000_000n + * + * const startedStatus: Tracer.SpanStatus = { + * _tag: "Started", + * startTime + * } + * + * const endedStatus: Tracer.SpanStatus = { + * _tag: "Ended", + * startTime, + * endTime, + * exit: Exit.succeed("result") + * } + * + * startedStatus._tag // => "Started" + * endedStatus.endTime - endedStatus.startTime // => 500_000_000n + * ``` + * + * @category models + * @since 2.0.0 + */ +export type SpanStatus = { + _tag: "Started" + startTime: bigint +} | { + _tag: "Ended" + startTime: bigint + endTime: bigint + exit: Exit.Exit +} + +/** + * A span value that can participate in tracing, either an Effect-managed + * `Span` or an `ExternalSpan` propagated from another tracing system. + * + * **Example** (Accepting any span) + * + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" + * + * // Function that accepts any span type + * const getSpanIds = (span: Tracer.AnySpan) => Effect.succeed([span.spanId, span.traceId]) + * + * // Works with both Span and ExternalSpan + * const externalSpan = Tracer.externalSpan({ + * spanId: "span-123", + * traceId: "trace-456" + * }) + * + * await Effect.runPromise(getSpanIds(externalSpan)) // => ["span-123", "trace-456"] + * ``` + * + * @category models + * @since 2.0.0 + */ +export type AnySpan = Span | ExternalSpan + +/** + * Defines the string key for the parent-span context service. + * + * **When to use** + * + * Use when you need the raw context key for parent span lookup in lower-level + * tracing code. + * + * **Example** (Reading the parent span key) + * + * ```ts import.meta.vitest + * import { Tracer } from "effect" + * + * // The key used to identify parent spans in the context + * Tracer.ParentSpanKey // => "effect/Tracer/ParentSpan" + * ``` + * + * @category constants + * @since 4.0.0 + */ +export const ParentSpanKey = "effect/Tracer/ParentSpan" + +/** + * Context service containing the `Span` or `ExternalSpan` to use as the parent + * of newly-created child spans. + * + * **Example** (Accessing the parent span) + * + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" + * + * // Access the parent span from the context + * const program = Effect.gen(function*() { + * const parentSpan = yield* Effect.service(Tracer.ParentSpan) + * return parentSpan.spanId + * }) + * + * const parent = Tracer.externalSpan({ spanId: "span-123", traceId: "trace-456" }) + * await Effect.runPromise(Effect.provideService(program, Tracer.ParentSpan, parent)) // => "span-123" + * ``` + * + * @category services + * @since 2.0.0 + */ +export class ParentSpan extends Context.Service()(ParentSpanKey, { fiberCached: true }) {} + +/** + * Represents a span created outside Effect's tracer, carrying trace and span + * identifiers, sampling state, and annotations so it can be used as a parent or + * link in Effect tracing. + * + * **Example** (Creating an external span value) + * + * ```ts import.meta.vitest + * import { Context } from "effect" + * import type { Tracer } from "effect" + * + * // Create an external span from another tracing system + * const externalSpan: Tracer.ExternalSpan = { + * _tag: "ExternalSpan", + * spanId: "span-abc-123", + * traceId: "trace-xyz-789", + * sampled: true, + * annotations: Context.empty() + * } + * + * externalSpan.spanId // => "span-abc-123" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface ExternalSpan { + readonly _tag: "ExternalSpan" + readonly spanId: string + readonly traceId: string + readonly sampled: boolean + readonly annotations: Context.Context +} + +/** + * Options accepted by span-creating APIs, combining span metadata such as + * attributes, links, parent/root selection, kind, sampling, and trace level + * with stack trace capture settings. + * + * **Example** (Configuring span options) + * + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" + * + * // Create an effect with span options + * const options: Tracer.SpanOptions = { + * attributes: { "user.id": "123", "operation": "data-processing" }, + * kind: "internal", + * root: false, + * captureStackTrace: true + * } + * + * const program = Effect.succeed("Hello World").pipe( + * Effect.withSpan("my-operation", options) + * ) + * + * const spans: Array = [] + * const tracer = Tracer.make({ + * span(options) { + * const span = new Tracer.NativeSpan(options) + * spans.push(span) + * return span + * } + * }) + * const value = await Effect.runPromise(Effect.provideService(program, Tracer.Tracer, tracer)) // => "Hello World" + * + * spans[0]?.attributes.get("user.id") // => "123" + * spans[0]?.status._tag // => "Ended" + * ``` + * + * @category options + * @since 3.1.0 + */ +export interface SpanOptions extends SpanOptionsNoTrace, TraceOptions {} + +/** + * Span creation options that do not control stack trace capture, including + * attributes, links, parent or root selection, annotations, span kind, + * sampling, and the trace level used for filtering. + * + * @category options + * @since 4.0.0 + */ +export interface SpanOptionsNoTrace { + readonly attributes?: Record | undefined + readonly links?: ReadonlyArray | undefined + readonly parent?: AnySpan | undefined + readonly root?: boolean | undefined + readonly annotations?: Context.Context | undefined + readonly kind?: SpanKind | undefined + readonly sampled?: boolean | undefined + readonly level?: LogLevel | undefined +} + +/** + * Options that control stack trace capture for tracing wrappers. + * `captureStackTrace` can disable capture or provide a lazy stack string. + * + * @category options + * @since 4.0.0 + */ +export interface TraceOptions { + readonly captureStackTrace?: boolean | LazyArg | undefined +} + +/** + * OpenTelemetry-style role describing the kind of operation represented by a + * span: internal work, server handling, client calls, producing, or consuming. + * + * **Example** (Configuring span kinds) + * + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" + * + * // Different span kinds for different operations + * const program = Effect.succeed("handled").pipe( + * Effect.withSpan("handle-request", { + * kind: "server" as Tracer.SpanKind + * }) + * ) + * + * const spans: Array = [] + * const tracer = Tracer.make({ + * span(options) { + * const span = new Tracer.NativeSpan(options) + * spans.push(span) + * return span + * } + * }) + * const value = await Effect.runPromise(Effect.provideService(program, Tracer.Tracer, tracer)) // => "handled" + * + * spans[0]?.kind // => "server" + * ``` + * + * @category models + * @since 3.1.0 + */ +export type SpanKind = "internal" | "server" | "client" | "producer" | "consumer" + +/** + * A span created by an Effect tracer. It carries trace identity, parent, + * annotations, attributes, links, sampling and kind information, lifecycle + * status, and methods to end the span or add attributes, events, and links. + * + * **Example** (Working with spans) + * + * ```ts import.meta.vitest + * import { Context, Exit, Option } from "effect" + * import type { Tracer } from "effect" + * + * const attributes = new Map() + * const links: Array = [] + * const events: Array<[name: string, startTime: bigint, attributes: Record]> = [] + * let status: Tracer.SpanStatus = { + * _tag: "Started", + * startTime: 1_000_000_000n + * } + * + * const span: Tracer.Span = { + * _tag: "Span", + * name: "load-user", + * spanId: "span-1", + * traceId: "trace-1", + * parent: Option.none(), + * annotations: Context.empty(), + * get status() { + * return status + * }, + * attributes, + * links, + * sampled: true, + * kind: "internal", + * end(endTime, exit) { + * status = { _tag: "Ended", startTime: status.startTime, endTime, exit } + * }, + * attribute(key, value) { + * attributes.set(key, value) + * }, + * event(name, startTime, eventAttributes = {}) { + * events.push([name, startTime, eventAttributes]) + * }, + * addLinks(newLinks) { + * links.push(...newLinks) + * } + * } + * + * span.attribute("user.id", "123") + * span.event("loaded", 1_250_000_000n, { "cache.hit": true }) + * span.end(1_500_000_000n, Exit.succeed("user")) + * + * span.name // => "load-user" + * span.attributes.get("user.id") // => "123" + * span.status._tag // => "Ended" + * events // => [["loaded", 1_250_000_000n, { "cache.hit": true }]] + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Span { + readonly _tag: "Span" + readonly name: string + readonly spanId: string + readonly traceId: string + readonly parent: Option.Option + readonly annotations: Context.Context + readonly status: SpanStatus + readonly attributes: ReadonlyMap + readonly links: ReadonlyArray + readonly sampled: boolean + readonly kind: SpanKind + end(endTime: bigint, exit: Exit.Exit): void + attribute(key: string, value: unknown): void + event(name: string, startTime: bigint, attributes?: Record): void + addLinks(links: ReadonlyArray): void +} + +/** + * A relationship from one span to another span, with attributes describing the + * relationship. + * + * **Example** (Linking spans) + * + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" + * + * // Create a span link to connect spans + * const externalSpan = Tracer.externalSpan({ + * spanId: "external-span-123", + * traceId: "trace-456" + * }) + * + * const link: Tracer.SpanLink = { + * span: externalSpan, + * attributes: { "link.type": "follows-from", "service": "external-api" } + * } + * + * const program = Effect.succeed("result").pipe( + * Effect.withSpan("linked-operation", { links: [link] }) + * ) + * + * const spans: Array = [] + * const tracer = Tracer.make({ + * span(options) { + * const span = new Tracer.NativeSpan(options) + * spans.push(span) + * return span + * } + * }) + * const value = await Effect.runPromise(Effect.provideService(program, Tracer.Tracer, tracer)) // => "result" + * + * spans[0]?.links[0]?.span.spanId // => "external-span-123" + * spans[0]?.links[0]?.attributes["link.type"] // => "follows-from" + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface SpanLink { + readonly span: AnySpan + readonly attributes: Readonly> +} + +/** + * Creates a `Tracer` value from a tracer implementation object. + * + * **When to use** + * + * Use to create a custom tracing backend value that Effect can use when + * creating spans. + * + * **Details** + * + * `make` returns the supplied implementation object unchanged. The object must + * satisfy the `Tracer` contract, including a `span` method that returns a + * `Span`. + * + * @see {@link Span} for the span values returned by tracer implementations + * + * @category constructors + * @since 2.0.0 + */ +export const make = (options: Tracer): Tracer => options + +/** + * Creates an `ExternalSpan` from trace and span identifiers, defaulting + * `sampled` to `true` and annotations to an empty context when they are not + * provided. + * + * **Example** (Creating an external span) + * + * ```ts import.meta.vitest + * import { Effect, Option, Tracer } from "effect" + * + * // Create an external span from another tracing system + * const span = Tracer.externalSpan({ + * spanId: "span-abc-123", + * traceId: "trace-xyz-789", + * sampled: true + * }) + * + * // Use the external span as a parent + * const program = Effect.succeed("Hello").pipe( + * Effect.withSpan("child-operation", { parent: span }) + * ) + * + * const spans: Array = [] + * const tracer = Tracer.make({ + * span(options) { + * const span = new Tracer.NativeSpan(options) + * spans.push(span) + * return span + * } + * }) + * const value = await Effect.runPromise(Effect.provideService(program, Tracer.Tracer, tracer)) + * + * value // => "Hello" + * spans.map((span) => Option.getOrUndefined(span.parent)?.spanId) // => ["span-abc-123"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const externalSpan = ( + options: { + readonly spanId: string + readonly traceId: string + readonly sampled?: boolean | undefined + readonly annotations?: Context.Context | undefined + } +): ExternalSpan => ({ + _tag: "ExternalSpan", + spanId: options.spanId, + traceId: options.traceId, + sampled: options.sampled ?? true, + annotations: options.annotations ?? Context.empty() +}) + +/** + * Context reference for disabling trace propagation. + * + * **When to use** + * + * Use to prevent spans in a scope from propagating tracing context. + * + * **Details** + * + * When enabled on fiber or span annotations, new spans are created as + * non-propagating no-op spans and disabled spans are skipped when deriving a + * parent span. + * + * **Example** (Disabling span propagation) + * + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" + * + * // Disable span propagation for a specific effect + * const program = Tracer.DisablePropagation.pipe( + * Effect.provideService(Tracer.DisablePropagation, true) + * ) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category services + * @since 3.12.0 + */ +export const DisablePropagation = Context.Reference( + "effect/Tracer/DisablePropagation", + { defaultValue: constFalse } +) + +/** + * Context reference for controlling the current trace level for dynamic filtering. + * + * **When to use** + * + * Use to set the default trace level for spans in a scope when span options do + * not provide `level`. + * + * **Details** + * + * The default value is `"Info"`. Span creation uses `options.level ?? + * CurrentTraceLevel` before applying `MinimumTraceLevel`. + * + * @see {@link MinimumTraceLevel} for the threshold that decides whether spans at that level are sampled + * + * @category services + * @since 4.0.0 + */ +export const CurrentTraceLevel: Context.Reference = Context.Reference( + "effect/Tracer/CurrentTraceLevel", + { defaultValue: () => "Info" } +) + +/** + * Context reference for setting the minimum trace level threshold. Spans and their + * descendants below this level will have their sampling decision forced to + * false, preventing them from being exported. + * + * **When to use** + * + * Use to set the trace-level threshold that controls whether spans are sampled + * by default. + * + * **Details** + * + * The default value is `"All"`. Span creation compares the span level from + * `options.level ?? CurrentTraceLevel` against this threshold. + * + * **Gotchas** + * + * Explicit `options.sampled` bypasses threshold computation. + * + * @see {@link CurrentTraceLevel} for the default span level used when options do not specify one + * + * @category services + * @since 4.0.0 + */ +export const MinimumTraceLevel = Context.Reference< + LogLevel +>("effect/Tracer/MinimumTraceLevel", { defaultValue: () => "All" }) + +/** + * Defines the string key for the active tracer context reference. + * + * **When to use** + * + * Use when you need the raw context key for active tracer lookup in lower-level + * tracing code. + * + * @category constants + * @since 4.0.0 + */ +export const TracerKey = "effect/Tracer" + +/** + * Context reference for the active tracer service. By default it uses the + * native tracer, which creates `NativeSpan` instances. + * + * **Example** (Accessing the current tracer) + * + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" + * + * // Access the current tracer from the context + * const program = Effect.gen(function*() { + * const tracer = yield* Effect.service(Tracer.Tracer) + * // Or use the built-in tracer effect + * const tracerFromAccessor = yield* Effect.tracer + * return tracer === tracerFromAccessor + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category services + * @since 2.0.0 + */ +export const Tracer: Context.Reference = Context.Reference(TracerKey, { + fiberCached: true, + defaultValue: () => + make({ + span: (options) => new NativeSpan(options) + }) +}) + +/** + * Default in-memory `Span` implementation used by the native tracer. It + * generates span and trace identifiers, stores attributes, events, and links, + * and records `Started` or `Ended` status. + * + * **Details** + * + * The constructor initializes the span with `Started` status, inherits the + * parent trace id or generates a new one, and always generates a new span id. + * Attributes, events, links, and status are then mutated through `Span` methods. + * + * @see {@link Span} for the interface implemented by native spans + * + * @category models + * @since 4.0.0 + */ +export class NativeSpan implements Span { + readonly _tag = "Span" + readonly spanId: string + readonly traceId: string = "native" + readonly sampled: boolean + + readonly name: string + readonly parent: Option.Option + readonly annotations: Context.Context + readonly links: Array + readonly startTime: bigint + readonly kind: SpanKind + + status: SpanStatus + attributes: Map + events: Array<[name: string, startTime: bigint, attributes: Record]> = [] + + constructor(options: { + readonly name: string + readonly parent: Option.Option + readonly annotations: Context.Context + readonly links: Array + readonly startTime: bigint + readonly kind: SpanKind + readonly sampled: boolean + }) { + this.name = options.name + this.parent = options.parent + this.annotations = options.annotations + this.links = options.links + this.startTime = options.startTime + this.kind = options.kind + this.sampled = options.sampled + this.status = { + _tag: "Started", + startTime: options.startTime + } + this.attributes = new Map() + this.traceId = Option.getOrUndefined(options.parent)?.traceId ?? Encoding.randomHex(32) + this.spanId = Encoding.randomHex(16) + } + + end(endTime: bigint, exit: Exit.Exit): void { + this.status = { + _tag: "Ended", + endTime, + exit, + startTime: this.status.startTime + } + } + + attribute(key: string, value: unknown): void { + this.attributes.set(key, value) + } + + event(name: string, startTime: bigint, attributes?: Record): void { + this.events.push([name, startTime, attributes ?? {}]) + } + + addLinks(links: ReadonlyArray): void { + // oxlint-disable-next-line no-restricted-syntax + this.links.push(...links) + } +} diff --git a/.repos/effect/packages/effect/src/Trie.ts b/.repos/effect/packages/effect/src/Trie.ts new file mode 100644 index 000000000..c9203a880 --- /dev/null +++ b/.repos/effect/packages/effect/src/Trie.ts @@ -0,0 +1,807 @@ +/** + * Stores string-keyed values in an immutable prefix tree. + * + * A `Trie` is similar to a map whose keys are strings, but it is built + * for looking up keys by prefix. It is useful for autocomplete, route tables, + * dictionaries, and command lookup. Updates return new tries, and the module + * includes exact lookup, prefix lookup, longest-prefix lookup, iteration, + * mapping, filtering, reducing, and traversal helpers. + * + * @since 2.0.0 + */ +import type { Equal } from "./Equal.ts" +import type { Inspectable } from "./Inspectable.ts" +import * as TR from "./internal/trie.ts" +import type { Option } from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { Result } from "./Result.ts" +import type { Covariant, NoInfer } from "./Types.ts" + +const TypeId = TR.TrieTypeId + +/** + * An immutable string-keyed map optimized for prefix lookup. Iteration yields + * `[key, value]` pairs in key order, and update operations such as insert and + * remove return new `Trie` values. + * + * **Example** (Using a trie for prefix search) + * + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" + * + * // Create a trie with string-to-number mappings + * const trie: Trie.Trie = Trie.make( + * ["apple", 1], + * ["app", 2], + * ["application", 3], + * ["banana", 4] + * ) + * + * // Get values by exact key + * Trie.get(trie, "apple") // => Option.some(1) + * Trie.get(trie, "grape") // => Option.none() + * + * // Find all keys with a prefix + * Array.from(Trie.keysWithPrefix(trie, "app")) // => ["app", "apple", "application"] + * + * // Iterate over all entries (sorted alphabetically) + * Array.from(trie) // => [["app", 2], ["apple", 1], ["application", 3], ["banana", 4]] + * + * // Check if key exists + * Trie.has(trie, "app") // => true + * + * // Get size + * Trie.size(trie) // => 4 + * ``` + * + * @category models + * @since 2.0.0 + */ +export interface Trie extends Iterable<[string, Value]>, Equal, Pipeable, Inspectable { + readonly [TypeId]: { + readonly _Value: Covariant + } +} + +/** + * Creates an empty `Trie`. + * + * **Example** (Creating an empty trie) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty() + * + * Trie.size(trie) // => 0 + * Array.from(trie) // => [] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty: () => Trie = TR.empty + +/** + * Creates a new `Trie` from an iterable collection of key/value pairs (e.g. `Array<[string, V]>`). + * + * **Example** (Creating a trie from entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const iterable: Array = [["call", 0], ["me", 1], [ + * "mind", + * 2 + * ], ["mid", 3]] + * const trie = Trie.fromIterable(iterable) + * + * // The entries in the `Trie` are extracted in alphabetical order, regardless of the insertion order + * Array.from(trie) // => [["call", 0], ["me", 1], ["mid", 3], ["mind", 2]] + * trie // => Trie.make(["call", 0], ["me", 1], ["mind", 2], ["mid", 3]) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable: (entries: Iterable) => Trie = TR.fromIterable + +/** + * Constructs a new `Trie` from the specified entries (`[string, V]`). + * + * **Example** (Constructing a trie from entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.make(["ca", 0], ["me", 1]) + * + * Array.from(trie) // => [["ca", 0], ["me", 1]] + * trie // => Trie.fromIterable([["ca", 0], ["me", 1]]) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make: >( + ...entries: Entries +) => Trie = TR.make + +/** + * Inserts a new entry in the `Trie`. + * + * **Example** (Inserting entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie1 = Trie.empty().pipe( + * Trie.insert("call", 0) + * ) + * const trie2 = trie1.pipe(Trie.insert("me", 1)) + * const trie3 = trie2.pipe(Trie.insert("mind", 2)) + * const trie4 = trie3.pipe(Trie.insert("mid", 3)) + * + * Array.from(trie1) // => [["call", 0]] + * Array.from(trie2) // => [["call", 0], ["me", 1]] + * Array.from(trie3) // => [["call", 0], ["me", 1], ["mind", 2]] + * Array.from(trie4) // => [["call", 0], ["me", 1], ["mid", 3], ["mind", 2]] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const insert: { + (key: string, value: V): (self: Trie) => Trie + (self: Trie, key: string, value: V): Trie +} = TR.insert + +/** + * Returns an `IterableIterator` of the keys within the `Trie`. + * + * **Details** + * + * The keys are returned in alphabetical order, regardless of insertion order. + * + * **Example** (Reading keys in alphabetical order) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("cab", 0), + * Trie.insert("abc", 1), + * Trie.insert("bca", 2) + * ) + * + * Array.from(Trie.keys(trie)) // => ["abc", "bca", "cab"] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const keys: (self: Trie) => IterableIterator = TR.keys + +/** + * Returns an `IterableIterator` of the values within the `Trie`. + * + * **Details** + * + * Values are ordered based on their key in alphabetical order, regardless of insertion order. + * + * **Example** (Reading values by key order) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("call", 0), + * Trie.insert("me", 1), + * Trie.insert("and", 2) + * ) + * + * Array.from(Trie.values(trie)) // => [2, 0, 1] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const values: (self: Trie) => IterableIterator = TR.values + +/** + * Returns an `IterableIterator` of the entries within the `Trie`. + * + * **Details** + * + * The entries are returned by keys in alphabetical order, regardless of insertion order. + * + * **Example** (Reading entries in alphabetical order) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("call", 0), + * Trie.insert("me", 1) + * ) + * + * Array.from(Trie.entries(trie)) // => [["call", 0], ["me", 1]] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const entries: (self: Trie) => IterableIterator<[string, V]> = TR.entries + +/** + * Returns an `Array<[string, V]>` of the entries within the `Trie`. + * + * **Details** + * + * Equivalent to `Array.from(Trie.entries(trie))`. + * + * **Example** (Converting entries to an array) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("call", 0), + * Trie.insert("me", 1) + * ) + * Trie.toEntries(trie) // => [["call", 0], ["me", 1]] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const toEntries = (self: Trie): Array<[string, V]> => Array.from(entries(self)) + +/** + * Returns an `IterableIterator` of the keys within the `Trie` + * that have `prefix` as prefix (`prefix` included if it exists). + * + * **Example** (Finding keys with a prefix) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("she", 0), + * Trie.insert("shells", 1), + * Trie.insert("sea", 2), + * Trie.insert("shore", 3) + * ) + * + * Array.from(Trie.keysWithPrefix(trie, "she")) // => ["she", "shells"] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const keysWithPrefix: { + (prefix: string): (self: Trie) => IterableIterator + (self: Trie, prefix: string): IterableIterator +} = TR.keysWithPrefix + +/** + * Returns an `IterableIterator` of the values within the `Trie` + * that have `prefix` as prefix (`prefix` included if it exists). + * + * **Example** (Finding values with a prefix) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("she", 0), + * Trie.insert("shells", 1), + * Trie.insert("sea", 2), + * Trie.insert("shore", 3) + * ) + * + * Array.from(Trie.valuesWithPrefix(trie, "she")) // => [0, 1] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const valuesWithPrefix: { + (prefix: string): (self: Trie) => IterableIterator + (self: Trie, prefix: string): IterableIterator +} = TR.valuesWithPrefix + +/** + * Returns an `IterableIterator` of the entries within the `Trie` + * that have `prefix` as prefix (`prefix` included if it exists). + * + * **Example** (Finding entries with a prefix) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("she", 0), + * Trie.insert("shells", 1), + * Trie.insert("sea", 2), + * Trie.insert("shore", 3) + * ) + * + * Array.from(Trie.entriesWithPrefix(trie, "she")) // => [["she", 0], ["shells", 1]] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const entriesWithPrefix: { + (prefix: string): (self: Trie) => IterableIterator<[string, V]> + (self: Trie, prefix: string): IterableIterator<[string, V]> +} = TR.entriesWithPrefix + +/** + * Returns an `Array<[string, V]>` of the entries within the `Trie` whose keys + * start with `prefix`, including the entry for `prefix` itself when it exists. + * + * **Example** (Converting prefixed entries to an array) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("sea", 2), + * Trie.insert("she", 3) + * ) + * + * Trie.toEntriesWithPrefix(trie, "she") // => [["she", 3], ["shells", 0]] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const toEntriesWithPrefix: { + (prefix: string): (self: Trie) => Array<[string, V]> + (self: Trie, prefix: string): Array<[string, V]> +} = TR.toEntriesWithPrefix + +/** + * Returns the longest key/value in the `Trie` + * that is a prefix of that `key` if it exists, `None` otherwise. + * + * **Example** (Finding the longest prefix) + * + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2) + * ) + * + * Trie.longestPrefixOf(trie, "sell") // => Option.none() + * Trie.longestPrefixOf(trie, "sells") // => Option.some(["sells", 1]) + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const longestPrefixOf: { + (key: string): (self: Trie) => Option<[string, V]> + (self: Trie, key: string): Option<[string, V]> +} = TR.longestPrefixOf + +/** + * Returns the size of the `Trie` (number of entries in the `Trie`). + * + * **Example** (Getting the size) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("a", 0), + * Trie.insert("b", 1) + * ) + * + * Trie.size(trie) // => 2 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size: (self: Trie) => number = TR.size + +/** + * Looks up the value for the specified key in the `Trie` safely. + * + * **Example** (Looking up values safely) + * + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("call", 0), + * Trie.insert("me", 1), + * Trie.insert("mind", 2), + * Trie.insert("mid", 3) + * ) + * + * Trie.get(trie, "call") // => Option.some(0) + * Trie.get(trie, "me") // => Option.some(1) + * Trie.get(trie, "mind") // => Option.some(2) + * Trie.get(trie, "mid") // => Option.some(3) + * Trie.get(trie, "cale") // => Option.none() + * Trie.get(trie, "ma") // => Option.none() + * Trie.get(trie, "midn") // => Option.none() + * Trie.get(trie, "mea") // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const get: { + (key: string): (self: Trie) => Option + (self: Trie, key: string): Option +} = TR.get + +/** + * Checks whether the given key exists in the `Trie`. + * + * **Example** (Checking key membership) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("call", 0), + * Trie.insert("me", 1), + * Trie.insert("mind", 2), + * Trie.insert("mid", 3) + * ) + * + * Trie.has(trie, "call") // => true + * Trie.has(trie, "me") // => true + * Trie.has(trie, "mind") // => true + * Trie.has(trie, "mid") // => true + * Trie.has(trie, "cale") // => false + * Trie.has(trie, "ma") // => false + * Trie.has(trie, "midn") // => false + * Trie.has(trie, "mea") // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const has: { + (key: string): (self: Trie) => boolean + (self: Trie, key: string): boolean +} = TR.has + +/** + * Returns `true` when the `Trie` contains no entries. + * + * **Example** (Checking whether a trie is empty) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty() + * const trie1 = trie.pipe(Trie.insert("ma", 0)) + * + * Trie.isEmpty(trie) // => true + * Trie.isEmpty(trie1) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty: (self: Trie) => boolean = TR.isEmpty + +/** + * Looks up the value for the specified key in the `Trie` unsafely. + * + * **When to use** + * + * Use when the trie key is known to exist and a missing key should be treated + * as a programming error. + * + * **Gotchas** + * + * `getUnsafe` throws if the key is not found. Use `get` instead to safely get + * a value from the `Trie`. + * + * **Example** (Looking up values unsafely) + * + * ```ts import.meta.vitest + * import { Result, Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("call", 0), + * Trie.insert("me", 1) + * ) + * + * Result.try({ + * try: () => Trie.getUnsafe(trie, "mae"), + * catch: (error) => (error as Error).message + * }) // => Result.fail("Expected trie to contain key") + * ``` + * + * @category unsafe + * @since 4.0.0 + */ +export const getUnsafe: { + (key: string): (self: Trie) => V + (self: Trie, key: string): V +} = TR.getUnsafe + +/** + * Removes the entry for the specified key in the `Trie`. + * + * **Example** (Removing entries) + * + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("call", 0), + * Trie.insert("me", 1), + * Trie.insert("mind", 2), + * Trie.insert("mid", 3) + * ) + * + * const trie1 = trie.pipe(Trie.remove("call")) + * const trie2 = trie1.pipe(Trie.remove("mea")) + * + * Trie.get(trie, "call") // => Option.some(0) + * Trie.get(trie1, "call") // => Option.none() + * Trie.get(trie2, "call") // => Option.none() + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const remove: { + (key: string): (self: Trie) => Trie + (self: Trie, key: string): Trie +} = TR.remove + +/** + * Reduces a state over the entries of the `Trie`. + * + * **Example** (Reducing entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2) + * ) + * + * trie.pipe(Trie.reduce(0, (acc, n) => acc + n)) // => 3 + * trie.pipe(Trie.reduce(10, (acc, n) => acc + n)) // => 13 + * trie.pipe(Trie.reduce("", (acc, _, key) => acc + key)) // => "sellssheshells" + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + (zero: Z, f: (accumulator: Z, value: V, key: string) => Z): (self: Trie) => Z + (self: Trie, zero: Z, f: (accumulator: Z, value: V, key: string) => Z): Z +} = TR.reduce + +/** + * Maps over the entries of the `Trie` using the specified function. + * + * **Example** (Mapping entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2) + * ) + * + * Trie.map(trie, (v) => v + 1) // => Trie.make(["shells", 1], ["sells", 2], ["she", 3]) + * Trie.map(trie, (_, k) => k.length) // => Trie.make(["shells", 6], ["sells", 5], ["she", 3]) + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const map: { + (f: (value: V, key: string) => A): (self: Trie) => Trie + (self: Trie, f: (value: V, key: string) => A): Trie +} = TR.map + +/** + * Filters entries out of a `Trie` using the specified predicate. + * + * **Example** (Filtering entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2) + * ) + * + * Trie.filter(trie, (v) => v > 1) // => Trie.make(["she", 2]) + * Trie.filter(trie, (_, k) => k.length > 3) // => Trie.make(["shells", 0], ["sells", 1]) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filter: { + (f: (a: NoInfer, k: string) => a is B): (self: Trie) => Trie + (f: (a: NoInfer, k: string) => boolean): (self: Trie) => Trie + (self: Trie, f: (a: A, k: string) => a is B): Trie + (self: Trie, f: (a: A, k: string) => boolean): Trie +} = TR.filter + +/** + * Maps over the entries of the `Trie` using the specified filter and keeps + * only successful results. + * + * **Example** (Filtering and mapping entries) + * + * ```ts import.meta.vitest + * import { Result, Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2) + * ) + * + * Trie.filterMap(trie, (v) => v > 1 ? Result.succeed(v) : Result.failVoid) // => Trie.make(["she", 2]) + * Trie.filterMap( + * trie, + * (v, k) => k.length > 3 ? Result.succeed(v) : Result.failVoid + * ) // => Trie.make(["shells", 0], ["sells", 1]) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const filterMap: { + (f: (input: A, key: string) => Result): (self: Trie) => Trie + (self: Trie, f: (input: A, key: string) => Result): Trie +} = TR.filterMap + +/** + * Filters out `None` values from a `Trie` of `Options`s. + * + * **Example** (Compacting optional values) + * + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" + * + * const trie = Trie.empty>().pipe( + * Trie.insert("shells", Option.some(0)), + * Trie.insert("sells", Option.none()), + * Trie.insert("she", Option.some(2)) + * ) + * + * Trie.compact(trie) // => Trie.make(["shells", 0], ["she", 2]) + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const compact: (self: Trie>) => Trie = TR.compact + +/** + * Applies the specified function to the entries of the `Trie`. + * + * **Example** (Iterating over entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * let value = 0 + * + * Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2), + * Trie.forEach((n, key) => { + * value += n + key.length + * }) + * ) + * + * value // => 17 + * ``` + * + * @category traversing + * @since 2.0.0 + */ +export const forEach: { + (f: (value: V, key: string) => void): (self: Trie) => void + (self: Trie, f: (value: V, key: string) => void): void +} = TR.forEach + +/** + * Updates the value of the specified key within the `Trie` if it exists. + * + * **Example** (Modifying an existing value) + * + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2) + * ) + * + * trie.pipe(Trie.modify("she", (v) => v + 10), Trie.get("she")) // => Option.some(12) + * trie.pipe(Trie.modify("me", (v) => v)) // => trie + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const modify: { + (key: string, f: (v: V) => V): (self: Trie) => Trie + (self: Trie, key: string, f: (v: V) => V): Trie +} = TR.modify + +/** + * Removes all entries in the `Trie` which have the specified keys. + * + * **Example** (Removing multiple entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0), + * Trie.insert("sells", 1), + * Trie.insert("she", 2) + * ) + * + * trie.pipe(Trie.removeMany(["she", "sells"])) // => Trie.make(["shells", 0]) + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const removeMany: { + (keys: Iterable): (self: Trie) => Trie + (self: Trie, keys: Iterable): Trie +} = TR.removeMany + +/** + * Inserts multiple entries in the `Trie` at once. + * + * **Example** (Inserting multiple entries) + * + * ```ts import.meta.vitest + * import { Trie } from "effect" + * + * const trie = Trie.empty().pipe( + * Trie.insert("shells", 0) + * ) + * + * trie.pipe( + * Trie.insertMany([["sells", 1], ["she", 2]]) + * ) // => Trie.make(["shells", 0], ["sells", 1], ["she", 2]) + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const insertMany: { + (iter: Iterable<[string, V]>): (self: Trie) => Trie + (self: Trie, iter: Iterable<[string, V]>): Trie +} = TR.insertMany diff --git a/.repos/effect/packages/effect/src/Tuple.ts b/.repos/effect/packages/effect/src/Tuple.ts new file mode 100644 index 000000000..f929c9f0a --- /dev/null +++ b/.repos/effect/packages/effect/src/Tuple.ts @@ -0,0 +1,725 @@ +/** + * Works with fixed-length arrays, also called tuples. + * + * The runtime helpers in this module create new tuples instead of mutating + * their inputs, and the types preserve element positions where possible. The + * helpers cover tuple construction, indexed access, selecting or removing + * positions, appending values, transforming elements, renaming indices, mapping + * typed positions, and deriving comparison or combination helpers for tuple + * shapes. + * + * @since 2.0.0 + */ +import * as Combiner from "./Combiner.ts" +import * as Equivalence from "./Equivalence.ts" +import { dual } from "./Function.ts" +import * as order from "./Order.ts" +import * as Reducer from "./Reducer.ts" +import type { Apply, Lambda } from "./Struct.ts" + +/** + * Creates a tuple from the provided arguments. + * + * **When to use** + * + * Use when you need a properly typed tuple without writing `[a, b, c] as const` + * or another manual cast. + * + * **Details** + * + * The returned value has the exact tuple type, with each element's literal type + * preserved. + * + * **Example** (Creating a tuple) + * + * ```ts import.meta.vitest + * import { Tuple } from "effect" + * + * Tuple.make(10, 20, "red") // => [10, 20, "red"] + * ``` + * + * @see {@link get} – access a single element by index + * @see {@link appendElement} – append an element to a tuple + * @category constructors + * @since 2.0.0 + */ +export const make = >(...elements: [...Elements]): [...Elements] => + elements + +type Indices> = Exclude["length"], T["length"]> + +/** + * Retrieves the element at the specified index from a tuple. + * + * **When to use** + * + * Use when a single tuple element should be extracted in a pipeline. + * + * **Details** + * + * The index is constrained to valid tuple positions at the type level. + * + * **Example** (Extracting an element by index) + * + * ```ts import.meta.vitest + * import { pipe, Tuple } from "effect" + * + * pipe(Tuple.make(1, true, "hello"), Tuple.get(2)) // => "hello" + * ``` + * + * @see {@link make} – create a tuple + * @see {@link pick} – extract multiple elements into a new tuple + * @category getters + * @since 4.0.0 + */ +export const get: { + , I extends Indices & keyof T>(index: I): (self: T) => T[I] + , I extends Indices & keyof T>(self: T, index: I): T[I] +} = dual(2, , I extends keyof T>(self: T, index: I): T[I] => self[index]) + +type _BuildTuple< + T extends ReadonlyArray, + K, + Acc extends ReadonlyArray = [], + I extends ReadonlyArray = [] // current index counter +> = I["length"] extends T["length"] ? Acc + : _BuildTuple< + T, + K, + // If current index is in K, keep the element; otherwise skip it + I["length"] extends K ? [...Acc, T[I["length"]]] : Acc, + [...I, unknown] + > + +type PickTuple, I extends ReadonlyArray>> = { + -readonly [K in keyof I]: T[I[K] & keyof T] +} + +/** + * Creates a new tuple containing only the elements at the specified indices. + * + * **When to use** + * + * Use to select a subset of elements from a tuple by position. + * + * **Details** + * + * The result order matches the order of the provided indices. + * + * **Example** (Selecting elements by index) + * + * ```ts import.meta.vitest + * import { Tuple } from "effect" + * + * Tuple.pick(["a", "b", "c", "d"], [0, 2, 3]) // => ["a", "c", "d"] + * ``` + * + * @see {@link omit} – the inverse (exclude indices instead) + * @see {@link get} – extract a single element + * @category filtering + * @since 4.0.0 + */ +export const pick: { + , const I extends ReadonlyArray>>( + indices: I + ): (self: T) => PickTuple + , const I extends ReadonlyArray>>( + self: T, + indices: I + ): PickTuple +} = dual( + 2, + >( + self: T, + indices: ReadonlyArray + ) => { + return indices.map((i) => self[i]) + } +) + +type OmitTuple, K> = _BuildTuple, K>> + +/** + * Creates a new tuple with the elements at the specified indices removed. + * + * **When to use** + * + * Use to drop elements from a tuple by position. + * + * **Details** + * + * Elements not at the specified indices are kept in their original order. + * + * **Example** (Removing elements by index) + * + * ```ts import.meta.vitest + * import { Tuple } from "effect" + * + * Tuple.omit(["a", "b", "c", "d"], [1, 3]) // => ["a", "c"] + * ``` + * + * @see {@link pick} – the inverse (keep only specified indices) + * @category filtering + * @since 4.0.0 + */ +export const omit: { + , const I extends ReadonlyArray>>( + indices: I + ): (self: T) => OmitTuple + , const I extends ReadonlyArray>>( + self: T, + indices: I + ): OmitTuple +} = dual( + 2, + >( + self: T, + indices: ReadonlyArray + ) => { + const toDrop = new Set(indices) + return self.filter((_, i) => !toDrop.has(i)) + } +) + +/** + * Appends a single element to the end of a tuple. + * + * **When to use** + * + * Use when you need the appended value to remain part of the tuple's type-level + * shape and preserve literal element positions. + * + * **Details** + * + * The result type is `[...T, E]`, preserving all existing element types. + * + * **Example** (Appending an element) + * + * ```ts import.meta.vitest + * import { pipe, Tuple } from "effect" + * + * pipe(Tuple.make(1, 2), Tuple.appendElement("end")) // => [1, 2, "end"] + * ``` + * + * @see {@link appendElements} – append multiple elements (another tuple) + * @category combining + * @since 2.0.0 + */ +export const appendElement: { + (element: E): >(self: T) => [...T, E] + , const E>(self: T, element: E): [...T, E] +} = dual(2, , E>(self: T, element: E): [...T, E] => [...self, element]) + +/** + * Concatenates two tuples into a single tuple. + * + * **When to use** + * + * Use to append all elements from one tuple to another tuple. + * + * **Details** + * + * The result type is `[...T1, ...T2]`, preserving all element types from both + * tuples. Neither input tuple is mutated; a fresh tuple is returned. + * + * **Example** (Concatenating tuples) + * + * ```ts import.meta.vitest + * import { pipe, Tuple } from "effect" + * + * pipe(Tuple.make(1, 2), Tuple.appendElements(["a", "b"] as const)) // => [1, 2, "a", "b"] + * ``` + * + * @see {@link appendElement} – append a single element + * @category combining + * @since 4.0.0 + */ +export const appendElements: { + >( + that: T2 + ): >(self: T1) => [...T1, ...T2] + , const T2 extends ReadonlyArray>(self: T1, that: T2): [...T1, ...T2] +} = dual( + 2, + , T2 extends ReadonlyArray>( + self: T1, + that: T2 + ): [...T1, ...T2] => [...self, ...that] +) + +type Evolver = { readonly [I in keyof T]?: ((a: T[I]) => unknown) | undefined } + +type Evolved = { [I in keyof T]: I extends keyof E ? (E[I] extends (...a: any) => infer R ? R : T[I]) : T[I] } + +/** + * Transforms elements of a tuple by providing an array of transform functions. + * Each function applies to the element at the same position. Positions beyond + * the array's length are copied unchanged. + * + * **When to use** + * + * Use when you want to update the first N elements while keeping the rest. + * + * **Details** + * + * Each transform function receives the current value and can return a different + * type. + * + * **Example** (Transforming selected elements) + * + * ```ts import.meta.vitest + * import { pipe, Tuple } from "effect" + * + * pipe( + * Tuple.make("hello", 42, true), + * Tuple.evolve([ + * (s) => s.toUpperCase(), + * (n) => n * 2 + * ]) + * ) // => ["HELLO", 84, true] + * ``` + * + * @see {@link map} – apply the same transformation to all elements + * @see {@link renameIndices} – swap element positions + * @category mapping + * @since 4.0.0 + */ +export const evolve: { + , const E extends Evolver>(evolver: E): (self: T) => Evolved + , const E extends Evolver>(self: T, evolver: E): Evolved +} = dual( + 2, + , const E extends Evolver>(self: T, evolver: E) => { + return self.map((e, i) => (evolver[i] !== undefined ? evolver[i](e) : e)) + } +) + +/** + * Renames tuple indices by providing an array of stringified source + * indices. Each position in the array specifies which index to read from + * (e.g., `["2", "1", "0"]` reverses a 3-element tuple). + * + * **When to use** + * + * Use to reorder tuple elements while preserving index-specific types. + * + * **Details** + * + * The mapping returns a tuple in the requested index order. + * + * **Gotchas** + * + * The mapping uses stringified source indices, not arbitrary names. + * + * **Example** (Swapping elements) + * + * ```ts import.meta.vitest + * import { pipe, Tuple } from "effect" + * + * pipe( + * Tuple.make("a", "b", "c"), + * Tuple.renameIndices(["2", "1", "0"]) + * ) // => ["c", "b", "a"] + * ``` + * + * @see {@link evolve} – transform element values instead of positions + * @category transforming + * @since 4.0.0 + */ +export const renameIndices: { + , const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>( + mapping: M + ): (self: T) => { [I in keyof T]: I extends keyof M ? M[I] extends keyof T ? T[M[I]] : T[I] : T[I] } + , const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>( + self: T, + mapping: M + ): { [I in keyof T]: I extends keyof M ? M[I] extends keyof T ? T[M[I]] : T[I] : T[I] } +} = dual( + 2, + , const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>( + self: T, + mapping: M + ) => { + return self.map((e, i) => mapping[i] !== undefined ? self[mapping[i]] : e) + } +) + +/** + * Applies a `Struct.Lambda` transformation to every element in a tuple. + * + * **When to use** + * + * Use when you want to apply the same transformation to every tuple element. + * + * **Details** + * + * The lambda lets the compiler track the output type for each element. + * + * **Gotchas** + * + * The lambda must be created with `Struct.lambda`; a plain function will not + * type-check. + * + * **Example** (Wrapping every element in an array) + * + * ```ts import.meta.vitest + * import { pipe, Struct, Tuple } from "effect" + * + * interface AsArray extends Struct.Lambda { + * (self: A): Array + * readonly "~lambda.out": Array + * } + * + * const asArray = Struct.lambda((a) => [a]) + * pipe(Tuple.make(1, "hello", true), Tuple.map(asArray)) // => [[1], ["hello"], [true]] + * ``` + * + * @see {@link mapPick} – apply a lambda only to selected indices + * @see {@link mapOmit} – apply a lambda to all indices except selected ones + * @see {@link evolve} – apply different functions to different indices + * @category mapping + * @since 3.9.0 + */ +export const map: { + ( + lambda: L + ): >( + self: T + ) => { [K in keyof T]: Apply } + , L extends Lambda>( + self: T, + lambda: L + ): { [K in keyof T]: Apply } +} = dual( + 2, + , L extends Function>(self: T, lambda: L) => { + return self.map((e) => lambda(e)) + } +) + +/** + * Applies a `Struct.Lambda` transformation only to the elements at the + * specified indices; all other elements are copied unchanged. + * + * **When to use** + * + * Use when you want to apply the same transformation to a subset of + * positions. + * + * **Example** (Wrapping only selected elements in arrays) + * + * ```ts import.meta.vitest + * import { pipe, Struct, Tuple } from "effect" + * + * interface AsArray extends Struct.Lambda { + * (self: A): Array + * readonly "~lambda.out": Array + * } + * + * const asArray = Struct.lambda((a) => [a]) + * pipe( + * Tuple.make(1, "hello", true), + * Tuple.mapPick([0, 2], asArray) + * ) // => [[1], "hello", [true]] + * ``` + * + * @see {@link map} – apply a lambda to all elements + * @see {@link mapOmit} – apply a lambda to all elements except selected ones + * @category mapping + * @since 4.0.0 + */ +export const mapPick: { + , const I extends ReadonlyArray>, L extends Lambda>( + indices: I, + lambda: L + ): ( + self: T + ) => { [K in keyof T]: K extends `${I[number]}` ? Apply : T[K] } + , const I extends ReadonlyArray>, L extends Lambda>( + self: T, + indices: I, + lambda: L + ): { [K in keyof T]: K extends `${I[number]}` ? Apply : T[K] } +} = dual( + 3, + , L extends Function>( + self: T, + indices: ReadonlyArray, + lambda: L + ) => { + const toPick = new Set(indices) + return self.map((e, i) => (toPick.has(i) ? lambda(e) : e)) + } +) + +/** + * Applies a `Struct.Lambda` transformation to all elements except those at the + * specified indices; the excluded elements are copied unchanged. + * + * **When to use** + * + * Use when most elements should be transformed but a few should be + * preserved. + * + * **Example** (Wrapping all elements except one in arrays) + * + * ```ts import.meta.vitest + * import { pipe, Struct, Tuple } from "effect" + * + * interface AsArray extends Struct.Lambda { + * (self: A): Array + * readonly "~lambda.out": Array + * } + * + * const asArray = Struct.lambda((a) => [a]) + * pipe( + * Tuple.make(1, "hello", true), + * Tuple.mapOmit([1], asArray) + * ) // => [[1], "hello", [true]] + * ``` + * + * @see {@link map} – apply a lambda to all elements + * @see {@link mapPick} – apply a lambda only to selected indices + * @category mapping + * @since 4.0.0 + */ +export const mapOmit: { + , const I extends ReadonlyArray>, L extends Lambda>( + indices: I, + lambda: L + ): ( + self: T + ) => { [K in keyof T]: K extends `${I[number]}` ? T[K] : Apply } + , const I extends ReadonlyArray>, L extends Lambda>( + self: T, + indices: I, + lambda: L + ): { [K in keyof T]: K extends `${I[number]}` ? T[K] : Apply } +} = dual( + 3, + , L extends Function>( + self: T, + indices: ReadonlyArray, + lambda: L + ) => { + const toOmit = new Set(indices) + return self.map((e, i) => (toOmit.has(i) ? e : lambda(e))) + } +) + +/** + * Creates an `Equivalence` for tuples by comparing corresponding elements + * using the provided per-position `Equivalence`s. Two tuples are equivalent + * when all their corresponding elements are equivalent. + * + * **When to use** + * + * Use when you need an `Equivalence` to compare tuples element-by-element. + * + * **Details** + * + * This is an alias of `Equivalence.Tuple`. + * + * **Example** (Comparing tuples for equivalence) + * + * ```ts import.meta.vitest + * import { Equivalence, Tuple } from "effect" + * + * const eq = Tuple.makeEquivalence([ + * Equivalence.strictEqual(), + * Equivalence.strictEqual() + * ]) + * + * eq(["Alice", 30], ["Alice", 30]) // => true + * eq(["Alice", 30], ["Bob", 30]) // => false + * ``` + * + * @see {@link makeOrder} – create an `Order` for tuples + * @category instances + * @since 4.0.0 + */ +export const makeEquivalence = Equivalence.Tuple + +/** + * Creates an `Order` for tuples by comparing corresponding elements using the + * provided per-position `Order`s. Elements are compared left-to-right; the + * first non-zero comparison determines the result. + * + * **When to use** + * + * Use when you need to sort fixed-position arrays lexicographically, with each + * position using its own ordering rule. + * + * **Details** + * + * This is an alias of `Order.Tuple`. + * + * **Example** (Ordering tuples) + * + * ```ts import.meta.vitest + * import { Number, String, Tuple } from "effect" + * + * const ord = Tuple.makeOrder([String.Order, Number.Order]) + * + * ord(["Alice", 30], ["Bob", 25]) // => -1 + * ord(["Alice", 30], ["Alice", 30]) // => 0 + * ``` + * + * @see {@link makeEquivalence} – create an `Equivalence` for tuples + * @category ordering + * @since 4.0.0 + */ +export const makeOrder = order.Tuple + +export { + /** + * Checks whether an array has exactly `N` elements, narrowing the type to a + * fixed-length tuple. + * + * **When to use** + * + * Use to guard that an array has exactly the tuple length expected at + * runtime. + * + * **Details** + * + * This is a re-export of `Predicate.isTupleOf`. It narrows the type to + * `TupleOf` in the truthy branch. + * + * **Gotchas** + * + * This only checks `.length`; it does not validate element types. + * + * **Example** (Checking exact length) + * + * ```ts import.meta.vitest + * import { Tuple } from "effect" + * + * const arr: Array = [1, 2, 3] + * if (Tuple.isTupleOf(arr, 3)) { + * arr // => [1, 2, 3] + * } + * ``` + * + * @see `isTupleOfAtLeast` – check for a minimum length + * @category guards + * @since 3.3.0 + */ + isTupleOf, + /** + * Checks whether an array has at least `N` elements, narrowing the type to a + * tuple with a minimum length. + * + * **When to use** + * + * Use to guard that an array has at least the tuple length expected at + * runtime. + * + * **Details** + * + * This is a re-export of `Predicate.isTupleOfAtLeast`. It narrows the type to + * `TupleOfAtLeast` in the truthy branch. + * + * **Gotchas** + * + * This only checks `.length`; it does not validate element types. + * + * **Example** (Checking minimum length) + * + * ```ts import.meta.vitest + * import { Tuple } from "effect" + * + * const arr: Array = [1, 2, 3, 4] + * if (Tuple.isTupleOfAtLeast(arr, 3)) { + * arr // => [1, 2, 3, 4] + * } + * ``` + * + * @see `isTupleOf` – check for an exact length + * @category guards + * @since 3.3.0 + */ + isTupleOfAtLeast +} from "./Predicate.ts" + +/** + * Creates a `Combiner` for a tuple shape by providing a `Combiner` for each + * position. When two tuples are combined, each element is merged using its + * corresponding combiner. + * + * **When to use** + * + * Use when you need to merge two same-shape tuples by combining each position + * independently, such as summing counters or concatenating strings. + * + * **Example** (Combining tuple elements) + * + * ```ts import.meta.vitest + * import { Number, String, Tuple } from "effect" + * + * const C = Tuple.makeCombiner([ + * Number.ReducerSum, + * String.ReducerConcat + * ]) + * + * C.combine([1, "hello"], [2, " world"]) // => [3, "hello world"] + * ``` + * + * @see {@link makeReducer} – like `makeCombiner` but with an initial value + * @category combining + * @since 4.0.0 + */ +export function makeCombiner>( + combiners: { readonly [K in keyof A]: Combiner.Combiner } +): Combiner.Combiner { + return Combiner.make((self, that) => { + const out = [] + for (let i = 0; i < self.length; i++) { + out.push(combiners[i].combine(self[i], that[i])) + } + return out as any + }) +} + +/** + * Creates a `Reducer` for a tuple shape by providing a `Reducer` for each + * position. The initial value is derived from each position's + * `Reducer.initialValue`. When reducing a collection of tuples, each element + * is combined independently. + * + * **When to use** + * + * Use when you need to fold same-shape tuples by accumulating each position + * independently into one summary tuple. + * + * **Example** (Reducing a collection of tuples) + * + * ```ts import.meta.vitest + * import { Number, String, Tuple } from "effect" + * + * const R = Tuple.makeReducer([ + * Number.ReducerSum, + * String.ReducerConcat + * ]) + * + * R.combineAll([ + * [1, "a"], + * [2, "b"], + * [3, "c"] + * ]) // => [6, "abc"] + * ``` + * + * @see {@link makeCombiner} – like `makeReducer` but without an initial value + * @category folding + * @since 4.0.0 + */ +export function makeReducer>( + reducers: { readonly [K in keyof A]: Reducer.Reducer } +): Reducer.Reducer { + const combine = makeCombiner(reducers).combine + const initialValue = [] + for (let i = 0; i < reducers.length; i++) { + initialValue.push(reducers[i].initialValue) + } + return Reducer.make(combine, initialValue as unknown as A) +} diff --git a/.repos/effect/packages/effect/src/TxChunk.ts b/.repos/effect/packages/effect/src/TxChunk.ts new file mode 100644 index 000000000..e83aa2c09 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxChunk.ts @@ -0,0 +1,852 @@ +/** + * Stores a `Chunk` inside transactional state. + * + * A `TxChunk` keeps its current `Chunk` in a `TxRef`, so reads and + * updates can be committed atomically with other transactional operations. This + * module offers a transactional version of common chunk workflows, including + * creating collections, reading or replacing the current chunk, adding or + * removing values, checking size, slicing, mapping, filtering, and combining + * chunks. + * + * @since 4.0.0 + */ +import * as Chunk from "./Chunk.ts" +import * as Effect from "./Effect.ts" +import { format } from "./Formatter.ts" +import { dual } from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import * as TxRef from "./TxRef.ts" +import type { NoInfer } from "./Types.ts" + +const TypeId = "~effect/transactions/TxChunk" + +/** + * TxChunk is a transactional chunk data structure that provides Software Transactional Memory (STM) + * semantics for chunk operations. + * + * **Details** + * + * Accessed values are tracked by the transaction in order to detect conflicts and to track changes. + * A transaction will retry whenever a conflict is detected or whenever the transaction explicitly + * calls `Effect.txRetry` and any of the accessed TxChunk values change. + * + * **Example** (Using a transactional chunk) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a transactional chunk + * const txChunk: TxChunk.TxChunk = yield* TxChunk.fromIterable([ + * 1, + * 2, + * 3 + * ]) + * + * // Single operations - no explicit transaction needed + * yield* TxChunk.append(txChunk, 4) + * const result = yield* TxChunk.get(txChunk) + * + * // Multi-step atomic operation - use explicit transaction + * yield* Effect.tx( + * Effect.gen(function*() { + * yield* TxChunk.prepend(txChunk, 0) + * yield* TxChunk.append(txChunk, 5) + * }) + * ) + * + * const finalResult = yield* TxChunk.get(txChunk) + * return [Chunk.toArray(result), Chunk.toArray(finalResult)] + * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4], [0, 1, 2, 3, 4, 5]] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxChunk extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + readonly ref: TxRef.TxRef> +} + +const TxChunkProto = { + [NodeInspectSymbol](this: TxChunk) { + return this.toJSON() + }, + toString(this: TxChunk) { + return `TxChunk(${format(toJson((this).ref))})` + }, + toJSON(this: TxChunk) { + return { + _id: "TxChunk", + ref: toJson((this).ref) + } + }, + pipe(this: TxChunk) { + return pipeArguments(this, arguments) + } +} + +/** + * Creates a new `TxChunk` with the specified initial chunk. + * + * **Details** + * + * This function returns a new TxChunk reference containing the provided initial chunk. No existing + * TxChunk instances are modified. + * + * **Example** (Creating a TxChunk from a chunk) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a TxChunk with initial values + * const initialChunk = Chunk.fromIterable([1, 2, 3]) + * const txChunk = yield* TxChunk.make(initialChunk) + * + * // Read the value - automatically transactional + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const make = (initial: Chunk.Chunk): Effect.Effect> => + Effect.map(TxRef.make(initial), (ref) => makeUnsafe(ref)) + +/** + * Creates a new empty `TxChunk`. + * + * **Details** + * + * This function returns a new TxChunk reference that is initially empty. No existing TxChunk + * instances are modified. + * + * **Example** (Creating an empty TxChunk) + * + * ```ts import.meta.vitest + * import { Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * // Create an empty TxChunk + * const txChunk = yield* TxChunk.empty() + * + * // Check if it's empty - automatically transactional + * const isEmpty = yield* TxChunk.isEmpty(txChunk) + * + * // Add elements - automatically transactional + * yield* TxChunk.append(txChunk, 42) + * + * const isStillEmpty = yield* TxChunk.isEmpty(txChunk) + * return [isEmpty, isStillEmpty] + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const empty = (): Effect.Effect> => + Effect.map(TxRef.make(Chunk.empty()), (ref) => makeUnsafe(ref)) + +/** + * Creates a new `TxChunk` from an iterable. + * + * **Details** + * + * This function returns a new TxChunk reference containing elements from the provided iterable. No + * existing TxChunk instances are modified. + * + * **Example** (Creating from an iterable) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * // Create TxChunk from array + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5]) + * + * // Read the contents - automatically transactional + * const chunk = yield* TxChunk.get(txChunk) + * + * // Multi-step atomic modification - use explicit transaction + * yield* Effect.tx( + * Effect.gen(function*() { + * yield* TxChunk.append(txChunk, 6) + * yield* TxChunk.prepend(txChunk, 0) + * }) + * ) + * + * const updated = yield* TxChunk.get(txChunk) + * return [Chunk.toArray(chunk), Chunk.toArray(updated)] + * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5, 6]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromIterable = (iterable: Iterable): Effect.Effect> => + Effect.map(TxRef.make(Chunk.fromIterable(iterable)), (ref) => makeUnsafe(ref)) + +/** + * Creates a new `TxChunk` with the specified TxRef. + * + * **Details** + * + * This function returns a new TxChunk reference wrapping the provided TxRef. No existing TxChunk + * instances are modified. + * + * **Example** (Wrapping an existing TxRef) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk, TxRef } from "effect" + * + * // Create a TxChunk from an existing TxRef (advanced usage) + * const ref = TxRef.makeUnsafe(Chunk.fromIterable([1, 2, 3])) + * const txChunk = TxChunk.makeUnsafe(ref) + * Chunk.toArray(await Effect.runPromise(TxChunk.get(txChunk))) // => [1, 2, 3] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeUnsafe = (ref: TxRef.TxRef>): TxChunk => { + const txChunk = Object.create(TxChunkProto) + txChunk[TypeId] = TypeId + txChunk.ref = ref + return txChunk +} + +/** + * Modifies the value of the `TxChunk` using the provided function. + * + * **Details** + * + * This function mutates the original TxChunk by updating its internal state. It does not return a + * new TxChunk reference. + * + * **Example** (Modifying while returning a value) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * + * // Modify and return both old size and new chunk + * const oldSize = yield* TxChunk.modify(txChunk, (chunk) => [ + * Chunk.size(chunk), // return value (old size) + * Chunk.append(chunk, 4) // new value + * ]) + * + * const newChunk = yield* TxChunk.get(txChunk) + * return [oldSize, Chunk.toArray(newChunk)] + * }) + * + * await Effect.runPromise(program) // => [3, [1, 2, 3, 4]] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const modify: { + ( + f: (current: Chunk.Chunk>) => [returnValue: R, newValue: Chunk.Chunk] + ): (self: TxChunk) => Effect.Effect + ( + self: TxChunk, + f: (current: Chunk.Chunk) => [returnValue: R, newValue: Chunk.Chunk] + ): Effect.Effect +} = dual( + 2, + ( + self: TxChunk, + f: (current: Chunk.Chunk) => [returnValue: R, newValue: Chunk.Chunk] + ): Effect.Effect => TxRef.modify(self.ref, f) +) + +/** + * Updates the value of the `TxChunk` using the provided function. + * + * **Details** + * + * This function mutates the original TxChunk by updating its internal state. It does not return a + * new TxChunk reference. + * + * **Example** (Updating the stored chunk) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * + * // Update the chunk by reversing it atomically + * yield* TxChunk.update(txChunk, (chunk) => Chunk.reverse(chunk)) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [3, 2, 1] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const update: { + (f: (current: Chunk.Chunk>) => Chunk.Chunk): (self: TxChunk) => Effect.Effect + (self: TxChunk, f: (current: Chunk.Chunk) => Chunk.Chunk): Effect.Effect +} = dual( + 2, + ( + self: TxChunk, + f: (current: Chunk.Chunk) => Chunk.Chunk + ): Effect.Effect => TxRef.update(self.ref, f) +) + +/** + * Reads the current chunk from the `TxChunk`. + * + * **Example** (Reading the current chunk) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * + * // Read the current value within a transaction + * const chunk = yield* TxChunk.get(txChunk) + * return [Chunk.toArray(chunk), Chunk.size(chunk)] + * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3], 3] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const get = (self: TxChunk): Effect.Effect> => TxRef.get(self.ref) + +/** + * Sets the value of the `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by replacing its internal state with the provided + * chunk. It does not return a new TxChunk reference. + * + * **Example** (Replacing the stored chunk) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * + * // Replace the entire chunk content + * const newChunk = Chunk.fromIterable([10, 20, 30, 40]) + * yield* TxChunk.set(txChunk, newChunk) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [10, 20, 30, 40] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const set: { + (chunk: Chunk.Chunk): (self: TxChunk) => Effect.Effect + (self: TxChunk, chunk: Chunk.Chunk): Effect.Effect +} = dual( + 2, + (self: TxChunk, chunk: Chunk.Chunk): Effect.Effect => TxRef.set(self.ref, chunk) +) + +/** + * Appends an element to the end of the `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by adding the element to the end. It does not return a + * new TxChunk reference. + * + * **Example** (Appending an element) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * + * // Add element to the end atomically + * yield* TxChunk.append(txChunk, 4) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const append: { + (element: A): (self: TxChunk) => Effect.Effect + (self: TxChunk, element: A): Effect.Effect +} = dual( + 2, + (self: TxChunk, element: A): Effect.Effect => update(self, (current) => Chunk.append(current, element)) +) + +/** + * Prepends an element to the beginning of the `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by adding the element to the beginning. It does not + * return a new TxChunk reference. + * + * **Example** (Prepending an element) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([2, 3, 4]) + * + * // Add element to the beginning atomically + * yield* TxChunk.prepend(txChunk, 1) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const prepend: { + (element: A): (self: TxChunk) => Effect.Effect + (self: TxChunk, element: A): Effect.Effect +} = dual( + 2, + (self: TxChunk, element: A): Effect.Effect => update(self, (current) => Chunk.prepend(current, element)) +) + +/** + * Gets the size of the `TxChunk`. + * + * **Example** (Getting the size) + * + * ```ts import.meta.vitest + * import { Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5]) + * + * // Get the current size - automatically transactional + * const currentSize = yield* TxChunk.size(txChunk) + * + * // Size is tracked for conflict detection + * yield* TxChunk.append(txChunk, 6) + * const newSize = yield* TxChunk.size(txChunk) + * return [currentSize, newSize] + * }) + * + * await Effect.runPromise(program) // => [5, 6] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const size = (self: TxChunk): Effect.Effect => + modify(self, (current) => [Chunk.size(current), current]) + +/** + * Checks whether the `TxChunk` is empty. + * + * **Example** (Checking for an empty chunk) + * + * ```ts import.meta.vitest + * import { Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const emptyChunk = yield* TxChunk.empty() + * const nonEmptyChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * + * // Check if chunks are empty - automatically transactional + * const isEmpty1 = yield* TxChunk.isEmpty(emptyChunk) + * const isEmpty2 = yield* TxChunk.isEmpty(nonEmptyChunk) + * + * return [isEmpty1, isEmpty2] + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isEmpty = (self: TxChunk): Effect.Effect => + modify(self, (current) => [Chunk.isEmpty(current), current]) + +/** + * Checks whether the `TxChunk` is non-empty. + * + * **Example** (Checking for a non-empty chunk) + * + * ```ts import.meta.vitest + * import { Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const emptyChunk = yield* TxChunk.empty() + * const nonEmptyChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * + * // Check if chunks are non-empty - automatically transactional + * const isNonEmpty1 = yield* TxChunk.isNonEmpty(emptyChunk) + * const isNonEmpty2 = yield* TxChunk.isNonEmpty(nonEmptyChunk) + * + * return [isNonEmpty1, isNonEmpty2] + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isNonEmpty = (self: TxChunk): Effect.Effect => + modify(self, (current) => [Chunk.isNonEmpty(current), current]) + +/** + * Takes the first `n` elements from the `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by keeping only the first n elements. It does not + * return a new TxChunk reference. + * + * **Example** (Taking leading elements) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5]) + * + * // Take only the first 3 elements - automatically transactional + * yield* TxChunk.take(txChunk, 3) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const take: { + (n: number): (self: TxChunk) => Effect.Effect + (self: TxChunk, n: number): Effect.Effect +} = dual( + 2, + (self: TxChunk, n: number): Effect.Effect => update(self, (current) => Chunk.take(current, n)) +) + +/** + * Drops the first `n` elements from the `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by removing the first n elements. It does not return a + * new TxChunk reference. + * + * **Example** (Dropping leading elements) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5]) + * + * // Drop the first 2 elements - automatically transactional + * yield* TxChunk.drop(txChunk, 2) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [3, 4, 5] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const drop: { + (n: number): (self: TxChunk) => Effect.Effect + (self: TxChunk, n: number): Effect.Effect +} = dual( + 2, + (self: TxChunk, n: number): Effect.Effect => update(self, (current) => Chunk.drop(current, n)) +) + +/** + * Takes a slice of the `TxChunk` from `start` to `end` (exclusive). + * + * **Details** + * + * This function mutates the original TxChunk by keeping only the elements in the specified range. It + * does not return a new TxChunk reference. + * + * **Example** (Taking a slice) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5, 6, 7]) + * + * // Take elements from index 2 to 5 (exclusive) - automatically transactional + * yield* TxChunk.slice(txChunk, 2, 5) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [3, 4, 5] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const slice: { + (start: number, end: number): (self: TxChunk) => Effect.Effect + (self: TxChunk, start: number, end: number): Effect.Effect +} = dual( + 3, + (self: TxChunk, start: number, end: number): Effect.Effect => + update(self, (current) => Chunk.take(Chunk.drop(current, start), end - start)) +) + +/** + * Maps each element of the `TxChunk` using a function that returns the same + * element type. + * + * **Details** + * + * This function mutates the original `TxChunk` by transforming each element in place. It does not + * return a new `TxChunk` reference. + * + * **Example** (Mapping elements) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4]) + * + * // Transform each element atomically (must maintain same type) + * yield* TxChunk.map(txChunk, (n) => n * 2) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [2, 4, 6, 8] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const map: { + (f: (a: NoInfer) => A): (self: TxChunk) => Effect.Effect + (self: TxChunk, f: (a: A) => A): Effect.Effect +} = dual( + 2, + (self: TxChunk, f: (a: A) => A): Effect.Effect => update(self, (current) => Chunk.map(current, f)) +) + +/** + * Filters the `TxChunk` keeping only elements that satisfy the predicate. + * + * **Details** + * + * This function mutates the original TxChunk by removing elements that don't match the predicate. It + * does not return a new TxChunk reference. + * + * **Example** (Filtering elements) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5, 6]) + * + * // Keep only even numbers atomically + * yield* TxChunk.filter(txChunk, (n) => n % 2 === 0) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [2, 4, 6] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const filter: { + (refinement: (a: A) => a is B): (self: TxChunk) => Effect.Effect + (predicate: (a: A) => boolean): (self: TxChunk) => Effect.Effect + (self: TxChunk, refinement: (a: A) => a is B): Effect.Effect + (self: TxChunk, predicate: (a: A) => boolean): Effect.Effect +} = dual( + 2, + (self: TxChunk, predicate: (a: A) => boolean): Effect.Effect => + update(self, (current) => Chunk.filter(current, predicate)) +) + +/** + * Concatenates another chunk to the end of the `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by appending all elements from the other chunk. It does + * not return a new TxChunk reference. + * + * **Example** (Appending another chunk) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([1, 2, 3]) + * const otherChunk = Chunk.fromIterable([4, 5, 6]) + * + * // Append all elements from another chunk atomically + * yield* TxChunk.appendAll(txChunk, otherChunk) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5, 6] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const appendAll: { + (other: Chunk.Chunk): (self: TxChunk) => Effect.Effect + (self: TxChunk, other: Chunk.Chunk): Effect.Effect +} = dual( + 2, + (self: TxChunk, other: Chunk.Chunk): Effect.Effect => + update(self, (current) => Chunk.appendAll(current, other)) +) + +/** + * Concatenates another chunk to the beginning of the `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by prepending all elements from the other chunk. It + * does not return a new TxChunk reference. + * + * **Example** (Prepending another chunk) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk = yield* TxChunk.fromIterable([4, 5, 6]) + * const otherChunk = Chunk.fromIterable([1, 2, 3]) + * + * // Prepend all elements from another chunk atomically + * yield* TxChunk.prependAll(txChunk, otherChunk) + * + * const result = yield* TxChunk.get(txChunk) + * return Chunk.toArray(result) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5, 6] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const prependAll: { + (other: Chunk.Chunk): (self: TxChunk) => Effect.Effect + (self: TxChunk, other: Chunk.Chunk): Effect.Effect +} = dual( + 2, + (self: TxChunk, other: Chunk.Chunk): Effect.Effect => + update(self, (current) => Chunk.prependAll(current, other)) +) + +/** + * Concatenates another `TxChunk` to the end of this `TxChunk`. + * + * **Details** + * + * This function mutates the original TxChunk by appending all elements from the other TxChunk. It + * does not return a new TxChunk reference. + * + * **Example** (Concatenating TxChunks) + * + * ```ts import.meta.vitest + * import { Chunk, Effect, TxChunk } from "effect" + * + * const program = Effect.gen(function*() { + * const txChunk1 = yield* TxChunk.fromIterable([1, 2, 3]) + * const txChunk2 = yield* TxChunk.fromIterable([4, 5, 6]) + * + * // Concatenate atomically within a transaction + * yield* TxChunk.concat(txChunk1, txChunk2) + * + * const result = yield* TxChunk.get(txChunk1) + * + * // Original txChunk2 is unchanged + * const original = yield* TxChunk.get(txChunk2) + * return [Chunk.toArray(result), Chunk.toArray(original)] + * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4, 5, 6], [4, 5, 6]] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const concat: { + (other: TxChunk): (self: TxChunk) => Effect.Effect + (self: TxChunk, other: TxChunk): Effect.Effect +} = dual( + 2, + (self: TxChunk, other: TxChunk): Effect.Effect => + Effect.gen(function*() { + const otherChunk = yield* get(other) + yield* appendAll(self, otherChunk) + }).pipe(Effect.tx) +) diff --git a/.repos/effect/packages/effect/src/TxDeferred.ts b/.repos/effect/packages/effect/src/TxDeferred.ts new file mode 100644 index 000000000..637e61494 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxDeferred.ts @@ -0,0 +1,327 @@ +/** + * Transactional deferred values for coordinating Effect transactions. + * + * A `TxDeferred` is a write-once cell whose completion is a + * `Result` stored in transactional state. Readers can wait for the value + * from inside a transaction: while the cell is empty the transaction retries, + * and when another transaction completes the deferred the waiting transaction + * can resume with either the success value or the typed failure. + * + * @since 4.0.0 + */ + +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import type { Option } from "./Option.ts" +import * as O from "./Option.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import type { Result } from "./Result.ts" +import * as Res from "./Result.ts" +import * as TxRef from "./TxRef.ts" + +const TypeId = "~effect/transactions/TxDeferred" + +/** + * A transactional deferred is a write-once cell readable within transactions. + * Readers block (retry the transaction) until a value is committed, and writers + * succeed only on the first call; subsequent writes return `false`. + * + * **When to use** + * + * Use to coordinate transaction-local readers and one-time completion with a + * success or failure result. + * + * **Example** (Completing a transactional deferred) + * + * ```ts import.meta.vitest + * import { Effect, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * + * // Complete the deferred + * const first = yield* TxDeferred.succeed(deferred, 42) + * + * // Second write is a no-op + * const second = yield* TxDeferred.succeed(deferred, 99) + * + * // Read the value + * const value = yield* TxDeferred.await(deferred) + * return [first, second, value] + * }) + * + * await Effect.runPromise(program) // => [true, false, 42] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxDeferred extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + readonly ref: TxRef.TxRef>> +} + +const TxDeferredProto: Omit, typeof TypeId | "ref"> = { + [NodeInspectSymbol](this: TxDeferred) { + return toJson(this) + }, + toJSON(this: TxDeferred) { + return { + _id: "TxDeferred" + } + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +const makeTxDeferred = (ref: TxRef.TxRef>>): TxDeferred => { + const self = Object.create(TxDeferredProto) + self[TypeId] = TypeId + self.ref = ref + return self +} + +/** + * Creates a new empty `TxDeferred`. + * + * **When to use** + * + * Use to create a transactional deferred that can be completed exactly once. + * + * **Example** (Creating a transactional deferred) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * return yield* TxDeferred.poll(deferred) + * }) + * + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (): Effect.Effect> => + Effect.map(TxRef.make>>(O.none()), makeTxDeferred) + +/** + * Reads the deferred value. Retries the transaction if the deferred has not + * been completed yet. + * + * **Example** (Awaiting a deferred value) + * + * ```ts import.meta.vitest + * import { Effect, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * yield* TxDeferred.succeed(deferred, 42) + * return yield* TxDeferred.await(deferred) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category getters + * @since 4.0.0 + */ +const await_ = (self: TxDeferred): Effect.Effect => + Effect.gen(function*() { + const option = yield* TxRef.get(self.ref) + if (O.isNone(option)) { + return yield* Effect.txRetry + } + return Res.isSuccess(option.value) + ? option.value.success + : yield* Effect.fail(option.value.failure) + }).pipe(Effect.tx) + +export { + /** + * Reads the deferred value. Retries the transaction if the deferred has not + * been completed yet. + * + * **When to use** + * + * Use to read the success value of a `TxDeferred` while retrying until the + * deferred is completed. + * + * @see {@link poll} for inspecting the current completion state without retrying the transaction + * + * @category getters + * @since 4.0.0 + */ + await_ as await +} + +/** + * Reads the current state of the deferred without retrying. Returns `None` if + * not yet completed. + * + * **When to use** + * + * Use to inspect a `TxDeferred` without retrying when it is not completed yet. + * + * **Example** (Polling a deferred) + * + * ```ts import.meta.vitest + * import { Effect, Option, Result, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * const before = yield* TxDeferred.poll(deferred) + * + * yield* TxDeferred.succeed(deferred, 42) + * const after = yield* TxDeferred.poll(deferred) + * return [before, after] + * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(Result.succeed(42))] + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const poll = (self: TxDeferred): Effect.Effect>> => TxRef.get(self.ref) + +/** + * Completes the deferred with a `Result`. Returns `true` if this was the first + * completion, `false` if already completed. + * + * **When to use** + * + * Use to complete a `TxDeferred` with an already computed `Result`. + * + * **Example** (Completing with a result) + * + * ```ts import.meta.vitest + * import { Effect, Result, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * const first = yield* TxDeferred.done(deferred, Result.succeed(42)) + * const second = yield* TxDeferred.done(deferred, Result.succeed(99)) + * return [first, second] + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const done: { + (result: Result): (self: TxDeferred) => Effect.Effect + (self: TxDeferred, result: Result): Effect.Effect +} = dual( + 2, + (self: TxDeferred, result: Result): Effect.Effect => + TxRef.modify(self.ref, (current) => { + if (O.isSome(current)) { + return [false, current] + } + return [true, O.some(result)] + }) +) + +/** + * Completes the deferred with a success value. Returns `true` if this was the + * first completion, `false` if already completed. + * + * **When to use** + * + * Use to complete a `TxDeferred` with a successful value. + * + * **Example** (Completing with a success value) + * + * ```ts import.meta.vitest + * import { Effect, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * const first = yield* TxDeferred.succeed(deferred, 42) + * const second = yield* TxDeferred.succeed(deferred, 99) + * return [first, second] + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const succeed: { + (value: A): (self: TxDeferred) => Effect.Effect + (self: TxDeferred, value: A): Effect.Effect +} = dual( + 2, + (self: TxDeferred, value: A): Effect.Effect => done(self, Res.succeed(value)) +) + +/** + * Completes the deferred with a failure. Returns `true` if this was the first + * completion, `false` if already completed. + * + * **When to use** + * + * Use to complete a `TxDeferred` with a typed failure value. + * + * **Example** (Completing with a failure) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Option, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * const first = yield* TxDeferred.fail(deferred, "boom") + * const second = yield* TxDeferred.fail(deferred, "boom2") + * const exit = yield* Effect.exit(TxDeferred.await(deferred)) + * return [first, second, exit, Exit.getCause(exit)] + * }) + * + * await Effect.runPromise(program) // => [true, false, Exit.fail("boom"), Option.some(Cause.fail("boom"))] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const fail: { + (error: E): (self: TxDeferred) => Effect.Effect + (self: TxDeferred, error: E): Effect.Effect +} = dual( + 2, + (self: TxDeferred, error: E): Effect.Effect => done(self, Res.fail(error)) +) + +/** + * Determines if the provided value is a `TxDeferred`. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a transactional deferred. + * + * **Example** (Checking transactional deferreds) + * + * ```ts import.meta.vitest + * import { Effect, TxDeferred } from "effect" + * + * const program = Effect.gen(function*() { + * const deferred = yield* TxDeferred.make() + * return [TxDeferred.isTxDeferred(deferred), TxDeferred.isTxDeferred("not a deferred")] + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxDeferred = (u: unknown): u is TxDeferred => hasProperty(u, TypeId) diff --git a/.repos/effect/packages/effect/src/TxHashMap.ts b/.repos/effect/packages/effect/src/TxHashMap.ts new file mode 100644 index 000000000..9671b41da --- /dev/null +++ b/.repos/effect/packages/effect/src/TxHashMap.ts @@ -0,0 +1,2109 @@ +/** + * Transactional hash maps for storing and updating key-value pairs inside + * Effect transactions. + * + * A `TxHashMap` stores an immutable `HashMap` in a `TxRef`, so map reads and + * writes can commit atomically with other transactional operations. Use it for + * shared registries, counters, indexes, and other maps that need safe + * read-modify-write sequences alongside related transactional state. + * + * @since 2.0.0 + */ + +import * as Effect from "./Effect.ts" +import { format } from "./Formatter.ts" +import { dual } from "./Function.ts" +import * as HashMap from "./HashMap.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.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 { Result } from "./Result.ts" +import * as TxRef from "./TxRef.ts" + +const TypeId = "~effect/transactions/TxHashMap" + +const TxHashMapProto = { + [TypeId]: TypeId, + [NodeInspectSymbol](this: TxHashMap) { + return toJson(this) + }, + toString(this: TxHashMap) { + return `TxHashMap(${format(toJson((this).ref))})` + }, + toJSON(this: TxHashMap) { + return { + _id: "TxHashMap", + ref: toJson((this).ref) + } + }, + pipe(this: TxHashMap) { + return pipeArguments(this, arguments) + } +} + +/** + * A TxHashMap is a transactional hash map data structure that provides atomic operations + * on key-value pairs within Effect transactions. It uses an immutable HashMap internally + * with TxRef for transactional semantics, ensuring all operations are performed atomically. + * + * **Example** (Using transactional hash maps) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a transactional hash map + * const txMap = yield* TxHashMap.make(["user1", "Alice"], ["user2", "Bob"]) + * + * // Single operations are automatically transactional + * yield* TxHashMap.set(txMap, "user3", "Charlie") + * yield* TxHashMap.get(txMap, "user1") // => Option.some("Alice") + * + * // Multi-step atomic operations + * yield* Effect.tx( + * Effect.gen(function*() { + * const currentUser = yield* TxHashMap.get(txMap, "user1") + * if (currentUser._tag === "Some") { + * yield* TxHashMap.set(txMap, "user1", currentUser.value + "_updated") + * yield* TxHashMap.remove(txMap, "user2") + * } + * }) + * ) + * + * return yield* TxHashMap.size(txMap) + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxHashMap extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + readonly ref: TxRef.TxRef> +} + +/** + * The TxHashMap namespace contains type-level utilities and helper types + * for working with TxHashMap instances. + * + * **Example** (Reusing extracted TxHashMap types) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a transactional inventory map + * const inventory = yield* TxHashMap.make( + * ["laptop", { stock: 5, price: 999 }], + * ["mouse", { stock: 20, price: 29 }] + * ) + * + * // Extract types for reuse + * type ProductId = TxHashMap.TxHashMap.Key // string + * type Product = TxHashMap.TxHashMap.Value // { stock: number, price: number } + * type InventoryEntry = TxHashMap.TxHashMap.Entry // [string, Product] + * + * // Use extracted types in functions + * const updateStock = (id: ProductId, newStock: number) => + * TxHashMap.modify( + * inventory, + * id, + * (product) => ({ ...product, stock: newStock }) + * ) + * + * yield* updateStock("laptop", 3) + * return yield* TxHashMap.get(inventory, "laptop") + * }) + * + * await Effect.runPromise(program) // => Option.some({ stock: 3, price: 999 }) + * ``` + * + * @since 4.0.0 + */ +export declare namespace TxHashMap { + /** + * Extracts the key type from a TxHashMap type. + * + * **Example** (Extracting key types) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a user map to extract key type from + * const userMap = yield* TxHashMap.make( + * ["alice", { name: "Alice", age: 30 }], + * ["bob", { name: "Bob", age: 25 }] + * ) + * + * // Extract the key type (string) + * type UserKey = TxHashMap.TxHashMap.Key + * + * // Use the extracted type in functions + * const getUserById = (id: UserKey) => TxHashMap.get(userMap, id) + * return yield* getUserById("alice") + * }) + * + * await Effect.runPromise(program) // => Option.some({ name: "Alice", age: 30 }) + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Key> = T extends TxHashMap ? K : never + + /** + * Extracts the value type from a TxHashMap type. + * + * **Example** (Extracting value types) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a product catalog TxHashMap + * const catalog = yield* TxHashMap.make( + * ["laptop", { price: 999, category: "electronics" }], + * ["book", { price: 29, category: "education" }] + * ) + * + * // Extract the value type (Product) + * type Product = TxHashMap.TxHashMap.Value + * + * // Use the extracted type for type-safe operations + * const processProduct = (product: Product) => { + * return `${product.category}: $${product.price}` + * } + * + * return Option.map(yield* TxHashMap.get(catalog, "laptop"), processProduct) + * }) + * + * await Effect.runPromise(program) // => Option.some("electronics: $999") + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Value> = T extends TxHashMap ? V : never + + /** + * Extracts the entry type from a TxHashMap type. + * + * **Example** (Extracting entry types) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a configuration TxHashMap + * const config = yield* TxHashMap.make( + * ["api_url", "https://api.example.com"], + * ["timeout", "5000"], + * ["retries", "3"] + * ) + * + * // Extract the entry type [string, string] + * type ConfigEntry = TxHashMap.TxHashMap.Entry + * + * // Use the extracted type for processing entries + * const processEntry = ([key, value]: ConfigEntry) => { + * return `${key}=${value}` + * } + * + * // Get all entries and process them + * return (yield* TxHashMap.entries(config)).map(processEntry).sort() + * }) + * + * await Effect.runPromise(program) // => ["api_url=https://api.example.com", "retries=3", "timeout=5000"] + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Entry> = T extends TxHashMap ? readonly [K, V] : never +} + +/** + * Creates an empty TxHashMap. + * + * **Example** (Creating an empty map) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create an empty transactional hash map + * const emptyMap = yield* TxHashMap.empty() + * + * // Verify it's empty + * yield* TxHashMap.isEmpty(emptyMap) // => true + * yield* TxHashMap.size(emptyMap) // => 0 + * + * // Start adding elements + * yield* TxHashMap.set(emptyMap, "first", 1) + * return yield* TxHashMap.size(emptyMap) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (): Effect.Effect> => + Effect.gen(function*() { + const ref = yield* TxRef.make(HashMap.empty()) + return Object.assign(Object.create(TxHashMapProto), { ref }) + }) + +/** + * Creates a TxHashMap from the provided key-value pairs. + * + * **Example** (Creating a map from entries) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a user directory + * const userMap = yield* TxHashMap.make( + * ["alice", { name: "Alice Smith", role: "admin" }], + * ["bob", { name: "Bob Johnson", role: "user" }], + * ["charlie", { name: "Charlie Brown", role: "user" }] + * ) + * + * // Check the initial size + * yield* TxHashMap.size(userMap) // => 3 + * + * // Access users + * yield* TxHashMap.get(userMap, "alice") // => Option.some({ name: "Alice Smith", role: "admin" }) + * return yield* TxHashMap.get(userMap, "david") + * }) + * + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = ( + ...entries: Array +): Effect.Effect> => + Effect.gen(function*() { + const hashMap = HashMap.make(...entries) + const ref = yield* TxRef.make(hashMap) + return Object.assign(Object.create(TxHashMapProto), { ref }) + }) + +/** + * Creates a TxHashMap from an iterable of key-value pairs. + * + * **Example** (Creating a map from an iterable) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create from various iterable sources + * const configEntries = [ + * ["database.host", "localhost"], + * ["database.port", "5432"], + * ["cache.enabled", "true"], + * ["logging.level", "info"] + * ] as const + * + * const configMap = yield* TxHashMap.fromIterable(configEntries) + * + * // Verify the configuration was loaded + * yield* TxHashMap.size(configMap) // => 4 + * yield* TxHashMap.get(configMap, "database.host") // => Option.some("localhost") + * + * // Can also create from Map, Set of tuples, etc. + * const jsMap = new Map([["key1", "value1"], ["key2", "value2"]]) + * return yield* TxHashMap.fromIterable(jsMap) + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = ( + entries: Iterable +): Effect.Effect> => + Effect.gen(function*() { + const hashMap = HashMap.fromIterable(entries) + const ref = yield* TxRef.make(hashMap) + return Object.assign(Object.create(TxHashMapProto), { ref }) + }) + +/** + * Looks up the value for the specified key in the TxHashMap. + * + * **Example** (Looking up values safely) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const userMap = yield* TxHashMap.make( + * ["alice", { name: "Alice", role: "admin" }], + * ["bob", { name: "Bob", role: "user" }] + * ) + * + * // Safe lookup - returns Option + * yield* TxHashMap.get(userMap, "alice") // => Option.some({ name: "Alice", role: "admin" }) + * yield* TxHashMap.get(userMap, "charlie") // => Option.none() + * + * // Use with pipe syntax for type-safe access + * return yield* TxHashMap.get(userMap, "bob") + * }) + * + * await Effect.runPromise(program) // => Option.some({ name: "Bob", role: "user" }) + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const get: { + (key: K1): (self: TxHashMap) => Effect.Effect> + (self: TxHashMap, key: K1): Effect.Effect> +} = dual( + 2, + (self: TxHashMap, key: K1): Effect.Effect> => + Effect.gen(function*() { + const map = yield* TxRef.get(self.ref) + return HashMap.get(map, key) + }) +) + +/** + * Sets the value for the specified key in the TxHashMap. + * + * **Details** + * + * This function mutates the original TxHashMap by updating its internal state. + * It does not return a new TxHashMap reference. + * + * **Example** (Setting values) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const inventory = yield* TxHashMap.make( + * ["laptop", 5], + * ["mouse", 20] + * ) + * + * // Update existing item + * yield* TxHashMap.set(inventory, "laptop", 3) + * yield* TxHashMap.get(inventory, "laptop") // => Option.some(3) + * + * // Add new item + * yield* TxHashMap.set(inventory, "keyboard", 15) + * yield* TxHashMap.get(inventory, "keyboard") // => Option.some(15) + * + * // Use with pipe syntax + * yield* TxHashMap.set("tablet", 8)(inventory) + * return yield* TxHashMap.get(inventory, "tablet") + * }) + * + * await Effect.runPromise(program) // => Option.some(8) + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const set: { + (key: K, value: V): (self: TxHashMap) => Effect.Effect + (self: TxHashMap, key: K, value: V): Effect.Effect +} = dual( + 3, + (self: TxHashMap, key: K, value: V): Effect.Effect => + TxRef.update(self.ref, (map) => HashMap.set(map, key, value)) +) + +/** + * Checks whether the specified key exists in the TxHashMap. + * + * **Example** (Checking for keys) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const permissions = yield* TxHashMap.make( + * ["alice", ["read", "write"]], + * ["bob", ["read"]], + * ["charlie", ["admin"]] + * ) + * + * // Check if users exist + * yield* TxHashMap.has(permissions, "alice") // => true + * yield* TxHashMap.has(permissions, "david") // => false + * + * // Use direct method call for type-safe access + * return yield* TxHashMap.has(permissions, "bob") + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const has: { + (key: K1): (self: TxHashMap) => Effect.Effect + (self: TxHashMap, key: K1): Effect.Effect +} = dual( + 2, + (self: TxHashMap, key: K1): Effect.Effect => + Effect.gen(function*() { + const map = yield* TxRef.get(self.ref) + return HashMap.has(map, key) + }) +) + +/** + * Removes the specified key from the TxHashMap. + * + * **Details** + * + * This function mutates the original TxHashMap by removing the specified + * key-value pair. It does not return a new TxHashMap reference. + * + * **Example** (Removing keys) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const cache = yield* TxHashMap.make( + * ["user:1", { name: "Alice", lastSeen: "2024-01-01" }], + * ["user:2", { name: "Bob", lastSeen: "2024-01-02" }], + * ["user:3", { name: "Charlie", lastSeen: "2023-12-30" }] + * ) + * + * // Remove expired user + * yield* TxHashMap.remove(cache, "user:3") // => true + * + * // Try to remove non-existent key + * yield* TxHashMap.remove(cache, "user:999") // => false + * + * // Verify removal + * yield* TxHashMap.has(cache, "user:3") // => false + * return yield* TxHashMap.size(cache) + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const remove: { + (key: K1): (self: TxHashMap) => Effect.Effect + (self: TxHashMap, key: K1): Effect.Effect +} = dual( + 2, + (self: TxHashMap, key: K1): Effect.Effect => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const existed = HashMap.has(currentMap, key) + if (existed) { + yield* TxRef.set(self.ref, HashMap.remove(currentMap, key)) + } + return existed + }).pipe(Effect.tx) +) + +/** + * Removes all entries from the TxHashMap. + * + * **Details** + * + * This function mutates the original TxHashMap by clearing all key-value pairs. + * It does not return a new TxHashMap reference. + * + * **Example** (Clearing all entries) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const sessionMap = yield* TxHashMap.make( + * ["session1", { userId: "alice", expires: "2024-01-01T12:00:00Z" }], + * ["session2", { userId: "bob", expires: "2024-01-01T13:00:00Z" }], + * ["session3", { userId: "charlie", expires: "2024-01-01T14:00:00Z" }] + * ) + * + * // Check initial state + * yield* TxHashMap.size(sessionMap) // => 3 + * + * // Clear all sessions (e.g., during maintenance) + * yield* TxHashMap.clear(sessionMap) + * + * // Verify cleared + * yield* TxHashMap.size(sessionMap) // => 0 + * return yield* TxHashMap.isEmpty(sessionMap) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const clear = (self: TxHashMap): Effect.Effect => TxRef.set(self.ref, HashMap.empty()) + +/** + * Returns the number of entries in the TxHashMap. + * + * **Example** (Counting entries) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const metrics = yield* TxHashMap.make( + * ["requests", 1000], + * ["errors", 5], + * ["users", 50] + * ) + * + * yield* TxHashMap.size(metrics) // => 3 + * + * // Add more metrics + * yield* TxHashMap.set(metrics, "response_time", 250) + * yield* TxHashMap.size(metrics) // => 4 + * + * // Remove a metric + * yield* TxHashMap.remove(metrics, "errors") + * return yield* TxHashMap.size(metrics) + * }) + * + * await Effect.runPromise(program) // => 3 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const size = (self: TxHashMap): Effect.Effect => + Effect.gen(function*() { + const map = yield* TxRef.get(self.ref) + return HashMap.size(map) + }) + +/** + * Checks whether the TxHashMap is empty. + * + * **Example** (Checking for an empty map) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Start with empty map + * const cache = yield* TxHashMap.empty() + * yield* TxHashMap.isEmpty(cache) // => true + * + * // Add an item + * yield* TxHashMap.set(cache, "key1", "value1") + * yield* TxHashMap.isEmpty(cache) // => false + * + * // Clear and check again + * yield* TxHashMap.clear(cache) + * return yield* TxHashMap.isEmpty(cache) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: TxHashMap): Effect.Effect => + Effect.gen(function*() { + const map = yield* TxRef.get(self.ref) + return HashMap.isEmpty(map) + }) + +/** + * Checks whether the TxHashMap is non-empty. + * + * **Example** (Checking for a non-empty map) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const inventory = yield* TxHashMap.make(["laptop", 5]) + * + * yield* TxHashMap.isNonEmpty(inventory) // => true + * + * // Clear inventory + * yield* TxHashMap.clear(inventory) + * return yield* TxHashMap.isNonEmpty(inventory) + * }) + * + * await Effect.runPromise(program) // => false + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isNonEmpty = (self: TxHashMap): Effect.Effect => + Effect.map(isEmpty(self), (empty) => !empty) + +/** + * Updates the value for the specified key if it exists, returning the previous value in `Some`; returns `None` and leaves the map unchanged when the key is absent. + * + * **Details** + * + * This function mutates the original TxHashMap by updating the value at the + * specified key. It does not return a new TxHashMap reference. + * + * **Example** (Updating existing values) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const counters = yield* TxHashMap.make( + * ["downloads", 100], + * ["views", 250] + * ) + * + * // Increment existing counter + * const oldDownloads = yield* TxHashMap.modify( + * counters, + * "downloads", + * (count) => count + 1 + * ) + * oldDownloads // => Option.some(100) + * + * yield* TxHashMap.get(counters, "downloads") // => Option.some(101) + * + * // Try to modify non-existent key + * const nonExistent = yield* TxHashMap.modify( + * counters, + * "clicks", + * (count) => count + 1 + * ) + * nonExistent // => Option.none() + * + * // Update views counter with direct method call + * yield* TxHashMap.modify(counters, "views", (views) => views * 2) + * return yield* TxHashMap.get(counters, "views") + * }) + * + * await Effect.runPromise(program) // => Option.some(500) + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const modify: { + ( + key: K, + f: (value: V) => V + ): (self: TxHashMap) => Effect.Effect> + (self: TxHashMap, key: K, f: (value: V) => V): Effect.Effect> +} = dual( + 3, + ( + self: TxHashMap, + key: K, + f: (value: V) => V + ): Effect.Effect> => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const currentValue = HashMap.get(currentMap, key) + if (Option.isSome(currentValue)) { + const newValue = f(currentValue.value) + yield* TxRef.set(self.ref, HashMap.set(currentMap, key, newValue)) + return currentValue + } + return Option.none() + }).pipe(Effect.tx) +) + +/** + * Updates the value for the specified key using an Option-based update function. + * + * **Details** + * + * This function mutates the original TxHashMap by updating, adding, or removing + * the key-value pair based on the function result. It does not return a new + * TxHashMap reference. + * + * **Example** (Updating values with Option) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const storage = yield* TxHashMap.make([ + * "file1.txt", + * "content1" + * ], ["access_count", 0]) + * const increment = Option.map((value: string | number) => typeof value === "number" ? value + 1 : value) + * + * // Increment existing counter + * yield* TxHashMap.modifyAt(storage, "access_count", increment) + * yield* TxHashMap.get(storage, "access_count") // => Option.some(1) + * + * // Increment existing counter again + * yield* TxHashMap.modifyAt(storage, "access_count", increment) + * yield* TxHashMap.get(storage, "access_count") // => Option.some(2) + * + * // Update an existing string entry + * yield* TxHashMap.modifyAt( + * storage, + * "file1.txt", + * Option.map((value) => typeof value === "string" ? `${value}.bak` : value) + * ) + * return yield* TxHashMap.get(storage, "file1.txt") + * }) + * + * await Effect.runPromise(program) // => Option.some("content1.bak") + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const modifyAt: { + ( + key: K, + f: (value: Option.Option) => Option.Option + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + key: K, + f: (value: Option.Option) => Option.Option + ): Effect.Effect +} = dual( + 3, + ( + self: TxHashMap, + key: K, + f: (value: Option.Option) => Option.Option + ): Effect.Effect => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const currentValue = HashMap.get(currentMap, key) + const newValue = f(currentValue) + + if (Option.isSome(newValue)) { + yield* TxRef.set(self.ref, HashMap.set(currentMap, key, newValue.value)) + } else if (Option.isSome(currentValue)) { + yield* TxRef.set(self.ref, HashMap.remove(currentMap, key)) + } + }).pipe(Effect.tx) +) + +/** + * Returns an array of all keys in the TxHashMap. + * + * **Example** (Reading keys) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const userRoles = yield* TxHashMap.make( + * ["alice", "admin"], + * ["bob", "user"], + * ["charlie", "moderator"] + * ) + * + * const usernames = (yield* TxHashMap.keys(userRoles)).sort() + * usernames // => ["alice", "bob", "charlie"] + * + * // Useful for iteration + * const assignments: Array = [] + * for (const username of usernames) { + * const role = yield* TxHashMap.get(userRoles, username) + * if (role._tag === "Some") { + * assignments.push(`${username}: ${role.value}`) + * } + * } + * return assignments + * }) + * + * await Effect.runPromise(program) // => ["alice: admin", "bob: user", "charlie: moderator"] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const keys = (self: TxHashMap): Effect.Effect> => + Effect.gen(function*() { + const map = yield* TxRef.get(self.ref) + return Array.from(HashMap.keys(map)) + }) + +/** + * Returns an array of all values in the TxHashMap. + * + * **Example** (Reading values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const scores = yield* TxHashMap.make( + * ["alice", 95], + * ["bob", 87], + * ["charlie", 92] + * ) + * + * const allScores = (yield* TxHashMap.values(scores)).sort((a, b) => a - b) + * allScores // => [87, 92, 95] + * + * // Calculate average + * const average = allScores.reduce((sum, score) => sum + score, 0) / + * allScores.length + * average.toFixed(2) // => "91.33" + * + * // Find maximum + * return Math.max(...allScores) + * }) + * + * await Effect.runPromise(program) // => 95 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const values = (self: TxHashMap): Effect.Effect> => + Effect.gen(function*() { + const map = yield* TxRef.get(self.ref) + return HashMap.toValues(map) + }) + +/** + * Returns an array of all key-value pairs in the TxHashMap. + * + * **Example** (Reading entries) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const config = yield* TxHashMap.make( + * ["host", "localhost"], + * ["port", "3000"], + * ["ssl", "false"] + * ) + * + * return (yield* TxHashMap.entries(config)).toSorted(([left], [right]) => left.localeCompare(right)) + * }) + * + * await Effect.runPromise(program) // => [["host", "localhost"], ["port", "3000"], ["ssl", "false"]] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const entries = ( + self: TxHashMap +): Effect.Effect> => + Effect.gen(function*() { + const map = yield* TxRef.get(self.ref) + return HashMap.toEntries(map) + }) + +/** + * Returns an immutable snapshot of the current TxHashMap state. + * + * **Example** (Taking immutable snapshots) + * + * ```ts import.meta.vitest + * import { Effect, HashMap, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const liveData = yield* TxHashMap.make( + * ["temperature", 22.5], + * ["humidity", 45.2], + * ["pressure", 1013.25] + * ) + * + * // Take snapshot for reporting + * const snapshot = yield* TxHashMap.snapshot(liveData) + * + * // Continue modifying live data + * yield* TxHashMap.set(liveData, "temperature", 23.1) + * yield* TxHashMap.set(liveData, "wind_speed", 5.3) + * + * // Snapshot remains unchanged + * HashMap.size(snapshot) // => 3 + * HashMap.get(snapshot, "temperature") // => Option.some(22.5) + * + * // Can use regular HashMap operations on snapshot + * return HashMap.get(snapshot, "humidity") + * }) + * + * await Effect.runPromise(program) // => Option.some(45.2) + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const snapshot = ( + self: TxHashMap +): Effect.Effect> => TxRef.get(self.ref) + +/** + * Merges another HashMap into this TxHashMap. If both maps contain the same key, + * the value from the other map will be used. + * + * **Details** + * + * This function mutates the original TxHashMap by merging the provided HashMap + * into it. It does not return a new TxHashMap reference. + * + * **Example** (Merging HashMaps) + * + * ```ts import.meta.vitest + * import { Effect, HashMap, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create initial user preferences + * const userPrefs = yield* TxHashMap.make( + * ["theme", "light"], + * ["language", "en"], + * ["notifications", "enabled"] + * ) + * + * // New preferences to merge in + * const newSettings = HashMap.make( + * ["theme", "dark"], // will override existing + * ["timezone", "UTC"], // new setting + * ["sound", "enabled"] // new setting + * ) + * + * // Merge the new settings + * yield* TxHashMap.union(userPrefs, newSettings) + * + * // Check the merged result + * yield* TxHashMap.get(userPrefs, "theme") // => Option.some("dark") + * yield* TxHashMap.get(userPrefs, "language") // => Option.some("en") + * yield* TxHashMap.get(userPrefs, "timezone") // => Option.some("UTC") + * return yield* TxHashMap.size(userPrefs) + * }) + * + * await Effect.runPromise(program) // => 5 + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const union: { + ( + other: HashMap.HashMap + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + other: HashMap.HashMap + ): Effect.Effect +} = dual( + 2, + ( + self: TxHashMap, + other: HashMap.HashMap + ): Effect.Effect => TxRef.update(self.ref, (map) => HashMap.union(map, other)) +) + +/** + * Removes multiple keys from the TxHashMap. + * + * **Details** + * + * This function mutates the original TxHashMap by removing all specified keys. + * It does not return a new TxHashMap reference. + * + * **Example** (Removing multiple keys) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a cache with temporary data + * const cache = yield* TxHashMap.make( + * ["session_1", { user: "alice", expires: "2024-01-01" }], + * ["session_2", { user: "bob", expires: "2024-01-01" }], + * ["session_3", { user: "charlie", expires: "2024-12-31" }], + * ["temp_data_1", { value: "temporary" }], + * ["temp_data_2", { value: "also_temporary" }] + * ) + * + * yield* TxHashMap.size(cache) // => 5 + * + * // Remove expired sessions and temporary data + * const keysToRemove = ["session_1", "session_2", "temp_data_1", "temp_data_2"] + * yield* TxHashMap.removeMany(cache, keysToRemove) + * + * yield* TxHashMap.size(cache) // => 1 + * + * // Verify only the valid session remains + * yield* TxHashMap.get(cache, "session_3") // => Option.some({ user: "charlie", expires: "2024-12-31" }) + * + * // Can also remove from Set, Array, or any iterable + * const moreKeysToRemove = new Set(["session_3"]) + * yield* TxHashMap.removeMany(cache, moreKeysToRemove) + * return yield* TxHashMap.isEmpty(cache) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const removeMany: { + (keys: Iterable): (self: TxHashMap) => Effect.Effect + (self: TxHashMap, keys: Iterable): Effect.Effect +} = dual( + 2, + (self: TxHashMap, keys: Iterable): Effect.Effect => + TxRef.update(self.ref, (map) => HashMap.removeMany(map, keys)) +) + +/** + * Sets multiple key-value pairs in the TxHashMap. + * + * **Details** + * + * This function mutates the original TxHashMap by setting all provided key-value + * pairs. It does not return a new TxHashMap reference. + * + * **Example** (Setting multiple entries) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create an empty product catalog + * const catalog = yield* TxHashMap.empty< + * string, + * { price: number; stock: number } + * >() + * + * // Bulk load initial products + * const initialProducts: Array< + * readonly [string, { price: number; stock: number }] + * > = [ + * ["laptop", { price: 999, stock: 5 }], + * ["mouse", { price: 29, stock: 50 }], + * ["keyboard", { price: 79, stock: 20 }], + * ["monitor", { price: 299, stock: 8 }] + * ] + * + * yield* TxHashMap.setMany(catalog, initialProducts) + * + * yield* TxHashMap.size(catalog) // => 4 + * + * // Update prices with a new batch + * const priceUpdates: Array< + * readonly [string, { price: number; stock: number }] + * > = [ + * ["laptop", { price: 899, stock: 5 }], // sale price + * ["mouse", { price: 25, stock: 50 }], // sale price + * ["webcam", { price: 89, stock: 12 }] // new product + * ] + * + * yield* TxHashMap.setMany(catalog, priceUpdates) + * + * yield* TxHashMap.size(catalog) // => 5 + * + * // Verify the updates + * yield* TxHashMap.get(catalog, "laptop") // => Option.some({ price: 899, stock: 5 }) + * + * // Can also use Map, Set of tuples, or any iterable of entries + * const jsMap = new Map([["tablet", { price: 399, stock: 3 }]]) + * yield* TxHashMap.setMany(catalog, jsMap) + * return yield* TxHashMap.get(catalog, "tablet") + * }) + * + * await Effect.runPromise(program) // => Option.some({ price: 399, stock: 3 }) + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const setMany: { + ( + entries: Iterable + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + entries: Iterable + ): Effect.Effect +} = dual( + 2, + ( + self: TxHashMap, + entries: Iterable + ): Effect.Effect => TxRef.update(self.ref, (map) => HashMap.setMany(map, entries)) +) + +/** + * Returns `true` if the specified value is a `TxHashMap`, `false` otherwise. + * + * **Example** (Checking TxHashMap values) + * + * ```ts import.meta.vitest + * import { Effect, Exit, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const txMap = yield* TxHashMap.make(["key", "value"]) + * + * TxHashMap.isTxHashMap(txMap) // => true + * TxHashMap.isTxHashMap({}) // => false + * TxHashMap.isTxHashMap(null) // => false + * TxHashMap.isTxHashMap("not a map") // => false + * + * // Useful for type guards in runtime checks + * const validateInput = (value: unknown) => { + * if (TxHashMap.isTxHashMap(value)) { + * // TypeScript now knows this is a TxHashMap + * return Effect.succeed("Valid TxHashMap") + * } + * return Effect.fail("Invalid input") + * } + * + * yield* Effect.exit(validateInput(null)) // => Exit.fail("Invalid input") + * return yield* Effect.exit(validateInput(txMap)) + * }) + * + * await Effect.runPromise(program) // => Exit.succeed("Valid TxHashMap") + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxHashMap = (value: unknown): value is TxHashMap => { + return hasProperty(value, TypeId) +} + +/** + * Looks up the value for the specified key using a caller-supplied hash. + * + * **Gotchas** + * + * The supplied hash must be the hash for the same key, such as a precomputed + * `Hash.hash(key)` value. If the hash does not match the key, an existing entry + * may not be found. + * + * **Example** (Looking up values with precomputed hashes) + * + * ```ts import.meta.vitest + * import { Effect, Hash, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a cache with user sessions + * const cache = yield* TxHashMap.make( + * ["session_abc123", { userId: "user1", lastActive: 1_700_000_000_000 }], + * ["session_def456", { userId: "user2", lastActive: 1_700_000_060_000 }] + * ) + * + * // When you have precomputed hash (e.g., from another lookup) + * const sessionId = "session_abc123" + * const precomputedHash = Hash.string(sessionId) + * + * // Use hash-optimized lookup for performance in hot paths + * const session = yield* TxHashMap.getHash(cache, sessionId, precomputedHash) + * session // => Option.some({ userId: "user1", lastActive: 1_700_000_000_000 }) + * + * // This avoids recomputing the hash when you already have it + * return yield* TxHashMap.getHash( + * cache, + * "invalid", + * Hash.string("invalid") + * ) + * }) + * + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const getHash: { + ( + key: K1, + hash: number + ): (self: TxHashMap) => Effect.Effect> + ( + self: TxHashMap, + key: K1, + hash: number + ): Effect.Effect> +} = dual( + 3, + ( + self: TxHashMap, + key: K1, + hash: number + ): Effect.Effect> => TxRef.get(self.ref).pipe(Effect.map((map) => HashMap.getHash(map, key, hash))) +) + +/** + * Checks whether the specified key has an entry using a caller-supplied hash. + * + * **Gotchas** + * + * The supplied hash must be the hash for the same key, such as a precomputed + * `Hash.hash(key)` value. If the hash does not match the key, an existing entry + * may not be found. + * + * **Example** (Checking keys with precomputed hashes) + * + * ```ts import.meta.vitest + * import { Effect, Hash, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create an access control map + * const permissions = yield* TxHashMap.make( + * ["admin", { read: true, write: true, delete: true }], + * ["user", { read: true, write: false, delete: false }] + * ) + * + * // When checking permissions frequently with same roles + * const role = "admin" + * const roleHash = Hash.string(role) + * + * // Use hash-optimized existence check + * yield* TxHashMap.hasHash(permissions, role, roleHash) // => true + * + * // Check non-existent role + * yield* TxHashMap.hasHash( + * permissions, + * "guest", + * Hash.string("guest") + * ) // => false + * + * // Useful in hot paths where hash is computed once and reused + * const roles = ["admin", "user", "moderator"] + * const roleHashes = roles.map((role) => [role, Hash.string(role)] as const) + * const results: Array = [] + * for (const [role, hash] of roleHashes) { + * const exists = yield* TxHashMap.hasHash(permissions, role, hash) + * results.push(`Role ${role}: ${exists}`) + * } + * return results + * }) + * + * await Effect.runPromise(program) // => ["Role admin: true", "Role user: true", "Role moderator: false"] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const hasHash: { + ( + key: K1, + hash: number + ): (self: TxHashMap) => Effect.Effect + (self: TxHashMap, key: K1, hash: number): Effect.Effect +} = dual( + 3, + ( + self: TxHashMap, + key: K1, + hash: number + ): Effect.Effect => TxRef.get(self.ref).pipe(Effect.map((map) => HashMap.hasHash(map, key, hash))) +) + +/** + * Transforms all values in the TxHashMap using the provided function, preserving keys. + * + * **Details** + * + * This function returns a new TxHashMap reference with the transformed values. + * The original TxHashMap is not modified. + * + * **Example** (Mapping values) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a user profile map + * const profiles = yield* TxHashMap.make( + * ["alice", { name: "Alice", age: 30, active: true }], + * ["bob", { name: "Bob", age: 25, active: false }], + * ["charlie", { name: "Charlie", age: 35, active: true }] + * ) + * + * // Transform to extract just names with greeting + * const greetings = yield* TxHashMap.map( + * profiles, + * (profile, userId) => `Hello, ${profile.name}! (User: ${userId})` + * ) + * + * // Check the transformed values + * yield* TxHashMap.get(greetings, "alice") // => Option.some("Hello, Alice! (User: alice)") + * + * // Data-last usage with pipe + * const ages = yield* profiles.pipe( + * TxHashMap.map((profile) => profile.age) + * ) + * + * yield* TxHashMap.get(ages, "alice") // => Option.some(30) + * + * // Original map is unchanged + * return yield* TxHashMap.get(profiles, "alice") + * }) + * + * await Effect.runPromise(program) // => Option.some({ name: "Alice", age: 30, active: true }) + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const map: { + ( + f: (value: V, key: K) => A + ): (self: TxHashMap) => Effect.Effect> + ( + self: TxHashMap, + f: (value: V, key: K) => A + ): Effect.Effect> +} = dual( + 2, + ( + self: TxHashMap, + f: (value: V, key: K) => A + ): Effect.Effect> => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const mappedMap = HashMap.map(currentMap, f) + return yield* fromHashMap(mappedMap) + }).pipe(Effect.tx) +) + +/** + * Filters the TxHashMap to keep only entries that satisfy the provided predicate. + * + * **Details** + * + * This function returns a new TxHashMap reference containing only the entries + * that match the condition. The original TxHashMap is not modified. + * + * **Example** (Filtering entries) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a product inventory + * const inventory = yield* TxHashMap.make( + * ["laptop", { price: 999, stock: 5, category: "electronics" }], + * ["mouse", { price: 29, stock: 50, category: "electronics" }], + * ["book", { price: 15, stock: 100, category: "books" }], + * ["phone", { price: 699, stock: 0, category: "electronics" }] + * ) + * + * // Filter to get only electronics in stock + * const electronicsInStock = yield* TxHashMap.filter( + * inventory, + * (product) => product.category === "electronics" && product.stock > 0 + * ) + * + * yield* TxHashMap.size(electronicsInStock) // => 2 + * + * // Data-last usage with pipe + * const expensiveItems = yield* inventory.pipe( + * TxHashMap.filter((product) => product.price > 500) + * ) + * + * yield* TxHashMap.size(expensiveItems) // => 2 + * + * // Type guard usage + * return yield* TxHashMap.filter( + * inventory, + * (product): product is typeof product & { price: number } => + * product.price > 50 + * ) + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const filter: { + ( + predicate: (value: V, key: K) => value is B + ): (self: TxHashMap) => Effect.Effect> + ( + predicate: (value: V, key: K) => boolean + ): (self: TxHashMap) => Effect.Effect> + ( + self: TxHashMap, + predicate: (value: V, key: K) => value is B + ): Effect.Effect> + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect> +} = dual( + 2, + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect> => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const filteredMap = HashMap.filter(currentMap, predicate) + return yield* fromHashMap(filteredMap) + }).pipe(Effect.tx) +) + +/** + * Reduces the TxHashMap entries to a single value by applying a reducer function. + * Iterates over all key-value pairs and accumulates them into a final result. + * + * **Example** (Reducing entries) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a sales data map + * const sales = yield* TxHashMap.make( + * ["Q1", 15000], + * ["Q2", 18000], + * ["Q3", 22000], + * ["Q4", 25000] + * ) + * + * // Calculate total sales + * const totalSales = yield* TxHashMap.reduce( + * sales, + * 0, + * (total, amount) => total + amount + * ) + * totalSales // => 80000 + * + * // Data-last usage with pipe + * const quarterlyReport = yield* sales.pipe( + * TxHashMap.reduce( + * { quarters: 0, total: 0, max: 0 }, + * (report, amount, quarter) => ({ + * quarters: report.quarters + 1, + * total: report.total + amount, + * max: Math.max(report.max, amount) + * }) + * ) + * ) + * return quarterlyReport + * }) + * + * await Effect.runPromise(program) // => { quarters: 4, total: 80000, max: 25000 } + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const reduce: { + ( + zero: A, + f: (accumulator: A, value: V, key: K) => A + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + zero: A, + f: (accumulator: A, value: V, key: K) => A + ): Effect.Effect +} = dual( + 3, + ( + self: TxHashMap, + zero: A, + f: (accumulator: A, value: V, key: K) => A + ): Effect.Effect => TxRef.get(self.ref).pipe(Effect.map((map) => HashMap.reduce(map, zero, f))) +) + +/** + * Combines filtering and mapping in a single operation. Applies a filter to each + * entry, keeping only successful results and transforming them. + * + * **Details** + * + * This function returns a new TxHashMap reference containing only the transformed + * entries that succeeded. The original TxHashMap is not modified. + * + * **Example** (Filtering and mapping entries) + * + * ```ts import.meta.vitest + * import { Effect, Option, Result, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a mixed data map + * const userData = yield* TxHashMap.make( + * ["alice", { age: "30", role: "admin", active: true }], + * ["bob", { age: "invalid", role: "user", active: true }], + * ["charlie", { age: "25", role: "admin", active: false }], + * ["diana", { age: "28", role: "user", active: true }] + * ) + * + * // Extract valid ages for active admin users only + * const activeAdminAges = yield* TxHashMap.filterMap( + * userData, + * (user, username) => { + * if (!user.active || user.role !== "admin") return Result.failVoid + * const age = parseInt(user.age) + * if (isNaN(age)) return Result.failVoid + * return Result.succeed({ + * username, + * age, + * seniority: age > 27 ? "senior" : "junior" + * }) + * } + * ) + * + * const aliceData = yield* TxHashMap.get(activeAdminAges, "alice") + * aliceData // => Option.some({ username: "alice", age: 30, seniority: "senior" }) + * yield* TxHashMap.get(activeAdminAges, "charlie") // => Option.none() + * + * // Data-last usage with pipe + * const validAges = yield* userData.pipe( + * TxHashMap.filterMap((user) => { + * const age = parseInt(user.age) + * return isNaN(age) ? Result.failVoid : Result.succeed(age) + * }) + * ) + * + * return yield* TxHashMap.size(validAges) + * }) + * + * await Effect.runPromise(program) // => 3 + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const filterMap: { + ( + f: (input: V, key: K) => Result + ): (self: TxHashMap) => Effect.Effect> + ( + self: TxHashMap, + f: (input: V, key: K) => Result + ): Effect.Effect> +} = dual( + 2, + ( + self: TxHashMap, + f: (input: V, key: K) => Result + ): Effect.Effect> => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const filteredMap = HashMap.filterMap(currentMap, f) + return yield* fromHashMap(filteredMap) + }).pipe(Effect.tx) +) + +/** + * Checks whether any entry in the TxHashMap matches the given predicate. + * + * **Example** (Checking entries with a predicate) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a user status map + * const currentTime = 1_700_000_000_000 + * const userStatuses = yield* TxHashMap.make( + * ["alice", { status: "online", lastSeen: currentTime }], + * ["bob", { status: "offline", lastSeen: currentTime - 3_600_000 }], + * ["charlie", { status: "online", lastSeen: currentTime }] + * ) + * + * // Check if any users are online + * yield* TxHashMap.hasBy( + * userStatuses, + * (user) => user.status === "online" + * ) // => true + * + * // Check if any users have specific username pattern + * yield* TxHashMap.hasBy( + * userStatuses, + * (user, username) => username.startsWith("admin") + * ) // => false + * + * // Data-last usage with pipe + * return yield* userStatuses.pipe( + * TxHashMap.hasBy((user) => currentTime - user.lastSeen < 1_800_000) // 30 minutes + * ) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const hasBy: { + ( + predicate: (value: V, key: K) => boolean + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect +} = dual( + 2, + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect => TxRef.get(self.ref).pipe(Effect.map((map) => HashMap.hasBy(map, predicate))) +) + +/** + * Finds the first entry in the TxHashMap that matches the given predicate. + * Returns the key-value pair as a tuple wrapped in an Option. + * + * **Example** (Finding the first matching entry) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a task priority map + * const tasks = yield* TxHashMap.make( + * ["task1", { priority: 1, assignee: "alice", completed: false }], + * ["task2", { priority: 3, assignee: "bob", completed: true }], + * ["task3", { priority: 2, assignee: "alice", completed: false }] + * ) + * + * // Find first high-priority incomplete task + * const highPriorityTask = yield* TxHashMap.findFirst( + * tasks, + * (task) => task.priority >= 2 && !task.completed + * ) + * + * highPriorityTask // => Option.some(["task3", { priority: 2, assignee: "alice", completed: false }]) + * + * // Find first task assigned to specific user + * return yield* tasks.pipe( + * TxHashMap.findFirst((task) => task.assignee === "alice") + * ) + * }) + * + * await Effect.runPromise(program) // => Option.some(["task1", { priority: 1, assignee: "alice", completed: false }]) + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const findFirst: { + ( + predicate: (value: V, key: K) => boolean + ): (self: TxHashMap) => Effect.Effect> + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect> +} = dual( + 2, + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect> => + TxRef.get(self.ref).pipe(Effect.map((map) => HashMap.findFirst(map, predicate))) +) + +/** + * Checks whether at least one entry in the TxHashMap satisfies the given predicate. + * + * **Example** (Checking whether some entries match) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a product inventory + * const inventory = yield* TxHashMap.make( + * ["laptop", { price: 999, stock: 5 }], + * ["mouse", { price: 29, stock: 50 }], + * ["keyboard", { price: 79, stock: 0 }] + * ) + * + * // Check if any products are expensive + * yield* TxHashMap.some( + * inventory, + * (product) => product.price > 500 + * ) // => true + * + * // Check if any products are out of stock + * yield* TxHashMap.some( + * inventory, + * (product) => product.stock === 0 + * ) // => true + * + * // Data-last usage with pipe + * return yield* inventory.pipe( + * TxHashMap.some((product) => product.price < 50) + * ) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const some: { + ( + predicate: (value: V, key: K) => boolean + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect +} = dual( + 2, + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect => TxRef.get(self.ref).pipe(Effect.map((map) => HashMap.some(map, predicate))) +) + +/** + * Checks whether all entries in the TxHashMap satisfy the given predicate. + * + * **Example** (Checking whether every entry matches) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a user permissions map + * const permissions = yield* TxHashMap.make( + * ["alice", { canRead: true, canWrite: true, canDelete: false }], + * ["bob", { canRead: true, canWrite: false, canDelete: false }], + * ["charlie", { canRead: true, canWrite: true, canDelete: true }] + * ) + * + * // Check if all users can read + * yield* TxHashMap.every( + * permissions, + * (perms) => perms.canRead + * ) // => true + * + * // Check if all users can write + * yield* TxHashMap.every( + * permissions, + * (perms) => perms.canWrite + * ) // => false + * + * // Data-last usage with pipe + * return yield* permissions.pipe( + * TxHashMap.every((perms, username) => perms.canRead && username.length > 2) + * ) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const every: { + ( + predicate: (value: V, key: K) => boolean + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect +} = dual( + 2, + ( + self: TxHashMap, + predicate: (value: V, key: K) => boolean + ): Effect.Effect => TxRef.get(self.ref).pipe(Effect.map((map) => HashMap.every(map, predicate))) +) + +/** + * Executes a side-effect function for each entry in the TxHashMap. + * The function receives the value and key as parameters and can perform effects. + * + * **Example** (Running effects for each entry) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a log processing map + * const logs = yield* TxHashMap.make( + * ["error.log", { size: 1024, level: "error" }], + * ["access.log", { size: 2048, level: "info" }], + * ["debug.log", { size: 512, level: "debug" }] + * ) + * + * const messages: Array = [] + * yield* TxHashMap.forEach(logs, (logInfo, filename) => + * Effect.sync(() => { + * messages.push(`${filename}: ${logInfo.size} bytes (${logInfo.level})`) + * })) + * + * return messages.sort() + * }) + * + * const result = await Effect.runPromise(program) + * result // => ["access.log: 2048 bytes (info)", "debug.log: 512 bytes (debug)", "error.log: 1024 bytes (error)"] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const forEach: { + ( + f: (value: V, key: K) => Effect.Effect + ): (self: TxHashMap) => Effect.Effect + ( + self: TxHashMap, + f: (value: V, key: K) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: TxHashMap, + f: (value: V, key: K) => Effect.Effect + ): Effect.Effect => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const entries = HashMap.toEntries(currentMap) + yield* Effect.forEach(entries, ([key, value]) => f(value, key)) + }) +) + +/** + * Maps each entry effectfully to a `TxHashMap` and flattens the produced maps. + * + * **Details** + * + * This function returns a new TxHashMap reference with the flattened results. + * The original TxHashMap is not modified. + * + * **Example** (Flat mapping entries) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a department-employee map + * const departments = yield* TxHashMap.make( + * ["engineering", ["alice", "bob"]], + * ["marketing", ["charlie", "diana"]] + * ) + * + * // Expand each department into individual employee entries with metadata + * const employeeDetails = yield* TxHashMap.flatMap( + * departments, + * (employees, department) => + * Effect.gen(function*() { + * const employeeMap = yield* TxHashMap.empty< + * string, + * { department: string; role: string } + * >() + * for (let i = 0; i < employees.length; i++) { + * const employee = employees[i] + * const role = i === 0 ? "lead" : "member" + * yield* TxHashMap.set(employeeMap, employee, { department, role }) + * } + * return employeeMap + * }) + * ) + * + * // Check the flattened result + * yield* TxHashMap.get(employeeDetails, "alice") // => Option.some({ department: "engineering", role: "lead" }) + * yield* TxHashMap.get(employeeDetails, "charlie") // => Option.some({ department: "marketing", role: "lead" }) + * return yield* TxHashMap.size(employeeDetails) + * }) + * + * await Effect.runPromise(program) // => 4 + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const flatMap: { + ( + f: (value: V, key: K) => Effect.Effect> + ): (self: TxHashMap) => Effect.Effect> + ( + self: TxHashMap, + f: (value: V, key: K) => Effect.Effect> + ): Effect.Effect> +} = dual( + 2, + ( + self: TxHashMap, + f: (value: V, key: K) => Effect.Effect> + ): Effect.Effect> => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const result = yield* empty() + + const mapEntries = HashMap.toEntries(currentMap) + for (const [key, value] of mapEntries) { + const newMap = yield* f(value, key) + const newEntries = yield* entries(newMap) + yield* setMany(result, newEntries) + } + + return result + }).pipe(Effect.tx) +) + +/** + * Removes all None values from a TxHashMap containing Option values. + * + * **Details** + * + * This function returns a new TxHashMap reference with only the Some values + * unwrapped. The original TxHashMap is not modified. + * + * **Example** (Compacting optional values) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a map with optional user data + * const userData = yield* TxHashMap.make< + * string, + * Option.Option<{ age: number; email?: string }> + * >( + * ["alice", Option.some({ age: 30, email: "alice@example.com" })], + * ["bob", Option.none()], // incomplete data + * ["charlie", Option.some({ age: 25 })], + * ["diana", Option.none()], // missing data + * ["eve", Option.some({ age: 28, email: "eve@example.com" })] + * ) + * + * // Remove all None values and unwrap Some values + * const validUsers = yield* TxHashMap.compact(userData) + * + * yield* TxHashMap.size(validUsers) // => 3 + * + * yield* TxHashMap.get(validUsers, "alice") // => Option.some({ age: 30, email: "alice@example.com" }) + * yield* TxHashMap.get(validUsers, "bob") // => Option.none() + * + * // Useful for cleaning up optional data processing results + * const userAges = yield* TxHashMap.map(validUsers, (user) => user.age) + * return (yield* TxHashMap.entries(userAges)).toSorted(([left], [right]) => left.localeCompare(right)) + * }) + * + * await Effect.runPromise(program) // => [["alice", 30], ["charlie", 25], ["eve", 28]] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const compact = ( + self: TxHashMap> +): Effect.Effect> => + Effect.gen(function*() { + const currentMap = yield* TxRef.get(self.ref) + const compactedMap = HashMap.compact(currentMap) + return yield* fromHashMap(compactedMap) + }).pipe(Effect.tx) + +/** + * Returns an array of all key-value pairs in the TxHashMap. + * This is an alias for the `entries` function, providing API consistency with HashMap. + * + * **Example** (Converting to entries) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const settings = yield* TxHashMap.make( + * ["theme", "dark"], + * ["language", "en-US"], + * ["timezone", "UTC"] + * ) + * + * // Get all entries as an array + * const sortedEntries = (yield* TxHashMap.toEntries(settings)) + * .toSorted(([left], [right]) => left.localeCompare(right)) + * sortedEntries // => [["language", "en-US"], ["theme", "dark"], ["timezone", "UTC"]] + * + * // Convert to an object + * return Object.fromEntries(sortedEntries) + * }) + * + * await Effect.runPromise(program) // => { language: "en-US", theme: "dark", timezone: "UTC" } + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const toEntries = ( + self: TxHashMap +): Effect.Effect> => entries(self) + +/** + * Returns an array of all values in the TxHashMap. + * This is an alias for the `values` function, providing API consistency with HashMap. + * + * **Example** (Converting to values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" + * + * const program = Effect.gen(function*() { + * const inventory = yield* TxHashMap.make( + * ["laptop", { price: 999, stock: 5 }], + * ["mouse", { price: 29, stock: 50 }], + * ["keyboard", { price: 79, stock: 20 }] + * ) + * + * // Get all product information + * const products = yield* TxHashMap.toValues(inventory) + * products.length // => 3 + * + * // Calculate total inventory value + * const totalValue = products.reduce( + * (sum, product) => sum + (product.price * product.stock), + * 0 + * ) + * totalValue // => 8025 + * + * // Find products with low stock + * return products.filter((product) => product.stock < 10).length + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const toValues = (self: TxHashMap): Effect.Effect> => values(self) + +/** + * Helper function to create a TxHashMap from an existing HashMap + */ +const fromHashMap = (hashMap: HashMap.HashMap): Effect.Effect> => + Effect.gen(function*() { + const ref = yield* TxRef.make(hashMap) + return Object.assign(Object.create(TxHashMapProto), { ref }) + }) diff --git a/.repos/effect/packages/effect/src/TxHashSet.ts b/.repos/effect/packages/effect/src/TxHashSet.ts new file mode 100644 index 000000000..5ed9e793b --- /dev/null +++ b/.repos/effect/packages/effect/src/TxHashSet.ts @@ -0,0 +1,941 @@ +/** + * Transactional hash sets for storing unique values inside Effect + * transactions. + * + * A `TxHashSet` keeps an immutable `HashSet` inside a `TxRef`, so membership + * checks and updates can commit atomically with other transactional operations. + * Use it when several pieces of shared transactional state must change + * together, such as adding a value only after checking related state. The + * module includes the usual set operations, including adding, removing, + * membership checks, set algebra, mapping, filtering, reducing, and conversion + * back to `HashSet`. + * + * @since 2.0.0 + */ + +import * as Effect from "./Effect.ts" +import { format } from "./Formatter.ts" +import { dual } from "./Function.ts" +import * as HashSet from "./HashSet.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty, type Predicate, type Refinement } from "./Predicate.ts" +import * as TxRef from "./TxRef.ts" +import type { NoInfer } from "./Types.ts" + +const TypeId = "~effect/transactions/TxHashSet" + +const TxHashSetProto = { + [TypeId]: TypeId, + [NodeInspectSymbol](this: TxHashSet) { + return toJson(this) + }, + toString(this: TxHashSet) { + return `TxHashSet(${format(toJson((this).ref))})` + }, + toJSON(this: TxHashSet) { + return { + _id: "TxHashSet", + ref: toJson((this).ref) + } + }, + pipe(this: TxHashSet) { + return pipeArguments(this, arguments) + } +} + +/** + * A TxHashSet is a transactional hash set data structure that provides atomic operations on unique values within Effect transactions. It uses an immutable HashSet internally with TxRef for transactional semantics, ensuring all operations are performed atomically. + * + * **Details** + * + * Mutation operations such as `add`, `remove`, and `clear` update the original TxHashSet and return `Effect` or `Effect`. Transform operations such as `union`, `intersection`, `difference`, `map`, and `filter` create new TxHashSet instances and leave the original TxHashSet unchanged. + * + * **Example** (Using transactional hash sets) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a transactional hash set + * const txSet = yield* TxHashSet.make("apple", "banana", "cherry") + * + * // Single operations are automatically transactional + * yield* TxHashSet.add(txSet, "grape") + * yield* TxHashSet.has(txSet, "apple") // => true + * + * // Multi-step atomic operations + * yield* Effect.tx( + * Effect.gen(function*() { + * const hasCherry = yield* TxHashSet.has(txSet, "cherry") + * if (hasCherry) { + * yield* TxHashSet.remove(txSet, "cherry") + * yield* TxHashSet.add(txSet, "orange") + * } + * }) + * ) + * + * yield* TxHashSet.size(txSet) // => 4 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxHashSet extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + readonly ref: TxRef.TxRef> +} + +/** + * The TxHashSet namespace contains type-level utilities and helper types + * for working with TxHashSet instances. + * + * **Example** (Extracting value types inside transactions) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a transactional color set + * const colors = yield* TxHashSet.make("red", "green", "blue") + * + * // Extract the value type for reuse + * type Color = TxHashSet.TxHashSet.Value // string + * + * // Use extracted type in functions + * const addColor = (color: Color) => TxHashSet.add(colors, color) + * + * yield* addColor("yellow") + * yield* TxHashSet.has(colors, "yellow") // => true + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @since 4.0.0 + */ +export declare namespace TxHashSet { + /** + * Extracts the value type from a `TxHashSet` type. + * + * **Example** (Extracting a TxHashSet value type) + * + * ```ts import.meta.vitest + * import type { TxHashSet } from "effect" + * + * type FruitSet = TxHashSet.TxHashSet<"apple" | "banana" | "cherry"> + * + * // Extract the value type + * type Fruit = TxHashSet.TxHashSet.Value // "apple" | "banana" | "cherry" + * + * const processFruit = (fruit: Fruit) => { + * return `Processing ${fruit}` + * } + * + * processFruit("apple") // => "Processing apple" + * ``` + * + * @category utility types + * @since 4.0.0 + */ + export type Value = T extends TxHashSet ? V : never +} + +const makeTxHashSet = (ref: TxRef.TxRef>): TxHashSet => { + const self = Object.create(TxHashSetProto) + self.ref = ref + return self +} + +/** + * Creates an empty TxHashSet. + * + * **Example** (Creating an empty transactional hash set) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const txSet = yield* TxHashSet.empty() + * + * yield* TxHashSet.size(txSet) // => 0 + * yield* TxHashSet.isEmpty(txSet) // => true + * + * // Add some values + * yield* TxHashSet.add(txSet, "hello") + * yield* TxHashSet.add(txSet, "world") + * yield* TxHashSet.size(txSet) // => 2 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (): Effect.Effect> => + Effect.gen(function*() { + const ref = yield* TxRef.make(HashSet.empty()) + return makeTxHashSet(ref) + }) + +/** + * Creates a TxHashSet from a variable number of values. + * + * **Example** (Creating transactional hash sets from values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const fruits = yield* TxHashSet.make("apple", "banana", "cherry") + * yield* TxHashSet.size(fruits) // => 3 + * + * const numbers = yield* TxHashSet.make(1, 2, 3, 2, 1) // Duplicates ignored + * yield* TxHashSet.size(numbers) // => 3 + * + * const mixed = yield* TxHashSet.make("hello", 42, true) + * yield* TxHashSet.size(mixed) // => 3 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = >( + ...values: Values +): Effect.Effect> => + Effect.gen(function*() { + const hashSet = HashSet.make(...values) + const ref = yield* TxRef.make(hashSet) + return makeTxHashSet(ref) + }) + +/** + * Creates a TxHashSet from an iterable collection of values. + * + * **Example** (Creating a transactional hash set from an iterable) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const fromArray = yield* TxHashSet.fromIterable(["a", "b", "c", "b", "a"]) + * yield* TxHashSet.size(fromArray) // => 3 + * + * const fromSet = yield* TxHashSet.fromIterable(new Set([1, 2, 3])) + * yield* TxHashSet.size(fromSet) // => 3 + * + * const fromString = yield* TxHashSet.fromIterable("hello") + * Array.from(yield* TxHashSet.toHashSet(fromString)).sort() // => ["e", "h", "l", "o"] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable = (values: Iterable): Effect.Effect> => + Effect.gen(function*() { + const hashSet = HashSet.fromIterable(values) + const ref = yield* TxRef.make(hashSet) + return makeTxHashSet(ref) + }) + +/** + * Creates a TxHashSet from an existing HashSet. + * + * **Example** (Creating a transactional hash set from a HashSet) + * + * ```ts import.meta.vitest + * import { Effect, HashSet, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const hashSet = HashSet.make("x", "y", "z") + * const txSet = yield* TxHashSet.fromHashSet(hashSet) + * + * yield* TxHashSet.size(txSet) // => 3 + * yield* TxHashSet.has(txSet, "y") // => true + * + * // Original hashSet is unchanged when txSet is modified + * yield* TxHashSet.add(txSet, "w") + * HashSet.size(hashSet) // => 3 + * yield* TxHashSet.size(txSet) // => 4 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromHashSet = (hashSet: HashSet.HashSet): Effect.Effect> => + Effect.gen(function*() { + const ref = yield* TxRef.make(hashSet) + return makeTxHashSet(ref) + }) + +/** + * Checks whether a value is a TxHashSet. + * + * **Example** (Checking for a TxHashSet) + * + * ```ts import.meta.vitest + * import { Effect, HashSet, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const txSet = yield* TxHashSet.make(1, 2, 3) + * const hashSet = HashSet.make(1, 2, 3) + * const array = [1, 2, 3] + * + * TxHashSet.isTxHashSet(txSet) // => true + * TxHashSet.isTxHashSet(hashSet) // => false + * TxHashSet.isTxHashSet(array) // => false + * TxHashSet.isTxHashSet(null) // => false + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxHashSet = (u: unknown): u is TxHashSet => hasProperty(u, TypeId) + +/** + * Adds a value to the TxHashSet. If the value already exists, the operation has no effect. + * + * **Details** + * + * This function mutates the original TxHashSet by adding the specified value. It does not return a new TxHashSet reference. + * + * **Example** (Adding values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const txSet = yield* TxHashSet.make("a", "b") + * + * yield* TxHashSet.add(txSet, "c") + * yield* TxHashSet.size(txSet) // => 3 + * yield* TxHashSet.has(txSet, "c") // => true + * + * // Adding existing value has no effect + * yield* TxHashSet.add(txSet, "a") + * yield* TxHashSet.size(txSet) // => 3 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const add: { + (value: V): (self: TxHashSet) => Effect.Effect + (self: TxHashSet, value: V): Effect.Effect +} = dual< + (value: V) => (self: TxHashSet) => Effect.Effect, + (self: TxHashSet, value: V) => Effect.Effect +>(2, (self: TxHashSet, value: V) => TxRef.update(self.ref, (set) => HashSet.add(set, value))) + +/** + * Removes a value from the TxHashSet. + * + * **Details** + * + * This function mutates the original TxHashSet by removing the specified value. It does not return a new TxHashSet reference. + * + * **Example** (Removing values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const txSet = yield* TxHashSet.make("a", "b", "c") + * + * yield* TxHashSet.remove(txSet, "b") // => true + * yield* TxHashSet.size(txSet) // => 2 + * yield* TxHashSet.has(txSet, "b") // => false + * + * // Removing non-existent value returns false + * yield* TxHashSet.remove(txSet, "d") // => false + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const remove: { + (value: V): (self: TxHashSet) => Effect.Effect + (self: TxHashSet, value: V): Effect.Effect +} = dual< + (value: V) => (self: TxHashSet) => Effect.Effect, + (self: TxHashSet, value: V) => Effect.Effect +>(2, (self: TxHashSet, value: V) => + Effect.gen(function*() { + const currentSet = yield* TxRef.get(self.ref) + const existed = HashSet.has(currentSet, value) + if (existed) { + yield* TxRef.set(self.ref, HashSet.remove(currentSet, value)) + } + return existed + }).pipe(Effect.tx)) + +/** + * Checks whether the TxHashSet contains the specified value. + * + * **Example** (Checking membership) + * + * ```ts import.meta.vitest + * import { Effect, Equal, Hash, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const txSet = yield* TxHashSet.make("apple", "banana", "cherry") + * + * yield* TxHashSet.has(txSet, "apple") // => true + * yield* TxHashSet.has(txSet, "grape") // => false + * + * // Works with any type that implements Equal + * class Person implements Equal.Equal { + * constructor(readonly name: string) {} + * + * [Equal.symbol](other: unknown) { + * return other instanceof Person && this.name === other.name + * } + * + * [Hash.symbol](): number { + * return Hash.string(this.name) + * } + * } + * + * const people = yield* TxHashSet.make(new Person("Alice"), new Person("Bob")) + * yield* TxHashSet.has(people, new Person("Alice")) // => true + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const has: { + (value: V): (self: TxHashSet) => Effect.Effect + (self: TxHashSet, value: V): Effect.Effect +} = dual< + (value: V) => (self: TxHashSet) => Effect.Effect, + (self: TxHashSet, value: V) => Effect.Effect +>(2, (self: TxHashSet, value: V) => + Effect.gen(function*() { + const set = yield* TxRef.get(self.ref) + return HashSet.has(set, value) + })) + +/** + * Returns the number of values in the TxHashSet. + * + * **Example** (Getting the set size) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const empty = yield* TxHashSet.empty() + * yield* TxHashSet.size(empty) // => 0 + * + * const small = yield* TxHashSet.make("a", "b") + * yield* TxHashSet.size(small) // => 2 + * + * const fromIterable = yield* TxHashSet.fromIterable(["x", "y", "z", "x", "y"]) + * yield* TxHashSet.size(fromIterable) // => 3 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: TxHashSet): Effect.Effect => + Effect.gen(function*() { + const set = yield* TxRef.get(self.ref) + return HashSet.size(set) + }) + +/** + * Checks whether the TxHashSet is empty. + * + * **Example** (Checking whether a set is empty) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const empty = yield* TxHashSet.empty() + * yield* TxHashSet.isEmpty(empty) // => true + * + * const nonEmpty = yield* TxHashSet.make("a") + * yield* TxHashSet.isEmpty(nonEmpty) // => false + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: TxHashSet): Effect.Effect => + Effect.gen(function*() { + const set = yield* TxRef.get(self.ref) + return HashSet.isEmpty(set) + }) + +/** + * Removes all values from the TxHashSet. + * + * **Details** + * + * This function mutates the original TxHashSet by clearing all values. It does not return a new TxHashSet reference. + * + * **Example** (Clearing all values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const txSet = yield* TxHashSet.make("a", "b", "c") + * yield* TxHashSet.size(txSet) // => 3 + * + * yield* TxHashSet.clear(txSet) + * yield* TxHashSet.size(txSet) // => 0 + * yield* TxHashSet.isEmpty(txSet) // => true + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mutations + * @since 4.0.0 + */ +export const clear = (self: TxHashSet): Effect.Effect => TxRef.set(self.ref, HashSet.empty()) + +/** + * Creates the union of two TxHashSets, returning a new TxHashSet. + * + * **Example** (Combining sets with union) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set1 = yield* TxHashSet.make("a", "b") + * const set2 = yield* TxHashSet.make("b", "c") + * const combined = yield* TxHashSet.union(set1, set2) + * + * Array.from(yield* TxHashSet.toHashSet(combined)).sort() // => ["a", "b", "c"] + * yield* TxHashSet.size(combined) // => 3 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const union: { + (that: TxHashSet): (self: TxHashSet) => Effect.Effect> + (self: TxHashSet, that: TxHashSet): Effect.Effect> +} = dual< + ( + that: TxHashSet + ) => (self: TxHashSet) => Effect.Effect>, + ( + self: TxHashSet, + that: TxHashSet + ) => Effect.Effect> +>(2, (self: TxHashSet, that: TxHashSet) => + Effect.gen(function*() { + const set1 = yield* TxRef.get(self.ref) + const set2 = yield* TxRef.get(that.ref) + const combined = HashSet.union(set1, set2) + return yield* fromHashSet(combined) + }).pipe(Effect.tx)) + +/** + * Creates the intersection of two TxHashSets, returning a new TxHashSet. + * + * **Example** (Finding common values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set1 = yield* TxHashSet.make("a", "b", "c") + * const set2 = yield* TxHashSet.make("b", "c", "d") + * const common = yield* TxHashSet.intersection(set1, set2) + * + * Array.from(yield* TxHashSet.toHashSet(common)).sort() // => ["b", "c"] + * yield* TxHashSet.size(common) // => 2 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const intersection: { + (that: TxHashSet): (self: TxHashSet) => Effect.Effect> + (self: TxHashSet, that: TxHashSet): Effect.Effect> +} = dual< + ( + that: TxHashSet + ) => (self: TxHashSet) => Effect.Effect>, + ( + self: TxHashSet, + that: TxHashSet + ) => Effect.Effect> +>(2, (self: TxHashSet, that: TxHashSet) => + Effect.gen(function*() { + const set1 = yield* TxRef.get(self.ref) + const set2 = yield* TxRef.get(that.ref) + const common = HashSet.intersection(set1, set2) + return yield* fromHashSet(common) + }).pipe(Effect.tx)) + +/** + * Creates the difference of two TxHashSets (elements in the first set that are not in the second), returning a new TxHashSet. + * + * **Example** (Finding values absent from another set) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const set1 = yield* TxHashSet.make("a", "b", "c") + * const set2 = yield* TxHashSet.make("b", "d") + * const diff = yield* TxHashSet.difference(set1, set2) + * + * Array.from(yield* TxHashSet.toHashSet(diff)).sort() // => ["a", "c"] + * yield* TxHashSet.size(diff) // => 2 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const difference: { + (that: TxHashSet): (self: TxHashSet) => Effect.Effect> + (self: TxHashSet, that: TxHashSet): Effect.Effect> +} = dual< + ( + that: TxHashSet + ) => (self: TxHashSet) => Effect.Effect>, + ( + self: TxHashSet, + that: TxHashSet + ) => Effect.Effect> +>(2, (self: TxHashSet, that: TxHashSet) => + Effect.gen(function*() { + const set1 = yield* TxRef.get(self.ref) + const set2 = yield* TxRef.get(that.ref) + const diff = HashSet.difference(set1, set2) + return yield* fromHashSet(diff) + }).pipe(Effect.tx)) + +/** + * Checks whether a TxHashSet is a subset of another TxHashSet. + * + * **Example** (Checking subset relationships) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const small = yield* TxHashSet.make("a", "b") + * const large = yield* TxHashSet.make("a", "b", "c", "d") + * const other = yield* TxHashSet.make("x", "y") + * + * yield* TxHashSet.isSubset(small, large) // => true + * yield* TxHashSet.isSubset(large, small) // => false + * yield* TxHashSet.isSubset(small, other) // => false + * yield* TxHashSet.isSubset(small, small) // => true + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isSubset: { + (that: TxHashSet): (self: TxHashSet) => Effect.Effect + (self: TxHashSet, that: TxHashSet): Effect.Effect +} = dual< + (that: TxHashSet) => (self: TxHashSet) => Effect.Effect, + (self: TxHashSet, that: TxHashSet) => Effect.Effect +>(2, (self: TxHashSet, that: TxHashSet) => + Effect.gen(function*() { + const set1 = yield* TxRef.get(self.ref) + const set2 = yield* TxRef.get(that.ref) + return HashSet.isSubset(set1, set2) + }).pipe(Effect.tx)) + +/** + * Checks whether at least one value in the TxHashSet satisfies the predicate. + * + * **Example** (Testing whether some values match) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const numbers = yield* TxHashSet.make(1, 2, 3, 4, 5) + * + * yield* TxHashSet.some(numbers, (n) => n > 3) // => true + * yield* TxHashSet.some(numbers, (n) => n > 10) // => false + * + * const empty = yield* TxHashSet.empty() + * yield* TxHashSet.some(empty, (n) => n > 0) // => false + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const some: { + (predicate: Predicate): (self: TxHashSet) => Effect.Effect + (self: TxHashSet, predicate: Predicate): Effect.Effect +} = dual< + (predicate: Predicate) => (self: TxHashSet) => Effect.Effect, + (self: TxHashSet, predicate: Predicate) => Effect.Effect +>(2, (self: TxHashSet, predicate: Predicate) => + Effect.gen(function*() { + const set = yield* TxRef.get(self.ref) + return HashSet.some(set, predicate) + })) + +/** + * Checks whether all values in the TxHashSet satisfy the predicate. + * + * **Example** (Testing whether every value matches) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const numbers = yield* TxHashSet.make(2, 4, 6, 8) + * + * yield* TxHashSet.every(numbers, (n) => n % 2 === 0) // => true + * yield* TxHashSet.every(numbers, (n) => n > 5) // => false + * + * const empty = yield* TxHashSet.empty() + * yield* TxHashSet.every(empty, (n) => n > 0) // => true + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const every: { + (predicate: Predicate): (self: TxHashSet) => Effect.Effect + (self: TxHashSet, predicate: Predicate): Effect.Effect +} = dual< + (predicate: Predicate) => (self: TxHashSet) => Effect.Effect, + (self: TxHashSet, predicate: Predicate) => Effect.Effect +>(2, (self: TxHashSet, predicate: Predicate) => + Effect.gen(function*() { + const set = yield* TxRef.get(self.ref) + return HashSet.every(set, predicate) + })) + +/** + * Maps each value in the TxHashSet using the provided function, returning a new TxHashSet. + * + * **Example** (Mapping values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const numbers = yield* TxHashSet.make(1, 2, 3) + * const doubled = yield* TxHashSet.map(numbers, (n) => n * 2) + * + * Array.from(yield* TxHashSet.toHashSet(doubled)).sort() // => [2, 4, 6] + * yield* TxHashSet.size(doubled) // => 3 + * + * // Mapping can reduce size if function produces duplicates + * const strings = yield* TxHashSet.make("apple", "banana", "cherry") + * const lengths = yield* TxHashSet.map(strings, (s) => s.length) + * Array.from(yield* TxHashSet.toHashSet(lengths)).sort() // => [5, 6] + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category mapping + * @since 4.0.0 + */ +export const map: { + (f: (value: V) => U): (self: TxHashSet) => Effect.Effect> + (self: TxHashSet, f: (value: V) => U): Effect.Effect> +} = dual< + (f: (value: V) => U) => (self: TxHashSet) => Effect.Effect>, + (self: TxHashSet, f: (value: V) => U) => Effect.Effect> +>(2, (self: TxHashSet, f: (value: V) => U) => + Effect.gen(function*() { + const currentSet = yield* TxRef.get(self.ref) + const mappedSet = HashSet.map(currentSet, f) + return yield* fromHashSet(mappedSet) + }).pipe(Effect.tx)) + +/** + * Filters the TxHashSet keeping only values that satisfy the predicate, returning a new TxHashSet. + * + * **Example** (Filtering values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const numbers = yield* TxHashSet.make(1, 2, 3, 4, 5, 6) + * const evens = yield* TxHashSet.filter(numbers, (n) => n % 2 === 0) + * + * Array.from(yield* TxHashSet.toHashSet(evens)).sort() // => [2, 4, 6] + * yield* TxHashSet.size(evens) // => 3 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category filtering + * @since 4.0.0 + */ +export const filter: { + ( + refinement: Refinement, U> + ): (self: TxHashSet) => Effect.Effect> + ( + predicate: Predicate> + ): (self: TxHashSet) => Effect.Effect> + ( + self: TxHashSet, + refinement: Refinement + ): Effect.Effect> + (self: TxHashSet, predicate: Predicate): Effect.Effect> +} = dual< + { + ( + refinement: Refinement, U> + ): (self: TxHashSet) => Effect.Effect> + ( + predicate: Predicate> + ): (self: TxHashSet) => Effect.Effect> + }, + { + ( + self: TxHashSet, + refinement: Refinement + ): Effect.Effect> + (self: TxHashSet, predicate: Predicate): Effect.Effect> + } +>(2, (self: TxHashSet, predicate: Predicate) => + Effect.gen(function*() { + const currentSet = yield* TxRef.get(self.ref) + const filteredSet = HashSet.filter(currentSet, predicate) + return yield* fromHashSet(filteredSet) + }).pipe(Effect.tx)) + +/** + * Reduces the TxHashSet to a single value by iterating through the values and applying an accumulator function. + * + * **Example** (Reducing values) + * + * ```ts import.meta.vitest + * import { Effect, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const numbers = yield* TxHashSet.make(1, 2, 3, 4, 5) + * yield* TxHashSet.reduce(numbers, 0, (acc, n) => acc + n) // => 15 + * + * const strings = yield* TxHashSet.make("a", "b", "c") + * String(yield* TxHashSet.reduce(strings, "", (acc, s) => acc + s)).split("").sort().join("") // => "abc" + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + ( + zero: U, + f: (accumulator: U, value: V) => U + ): (self: TxHashSet) => Effect.Effect + ( + self: TxHashSet, + zero: U, + f: (accumulator: U, value: V) => U + ): Effect.Effect +} = dual< + ( + zero: U, + f: (accumulator: U, value: V) => U + ) => (self: TxHashSet) => Effect.Effect, + ( + self: TxHashSet, + zero: U, + f: (accumulator: U, value: V) => U + ) => Effect.Effect +>(3, (self: TxHashSet, zero: U, f: (accumulator: U, value: V) => U) => + Effect.gen(function*() { + const set = yield* TxRef.get(self.ref) + return HashSet.reduce(set, zero, f) + })) + +/** + * Converts the TxHashSet to an immutable HashSet snapshot. + * + * **Example** (Taking a HashSet snapshot) + * + * ```ts import.meta.vitest + * import { Effect, HashSet, TxHashSet } from "effect" + * + * const program = Effect.gen(function*() { + * const txSet = yield* TxHashSet.make("x", "y", "z") + * const hashSet = yield* TxHashSet.toHashSet(txSet) + * + * HashSet.size(hashSet) // => 3 + * HashSet.has(hashSet, "y") // => true + * + * // hashSet is a snapshot - modifications to txSet don't affect it + * yield* TxHashSet.add(txSet, "w") + * HashSet.size(hashSet) // => 3 + * yield* TxHashSet.size(txSet) // => 4 + * }) + * + * await Effect.runPromise(program) + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const toHashSet = (self: TxHashSet): Effect.Effect> => TxRef.get(self.ref) diff --git a/.repos/effect/packages/effect/src/TxPriorityQueue.ts b/.repos/effect/packages/effect/src/TxPriorityQueue.ts new file mode 100644 index 000000000..179e7e096 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxPriorityQueue.ts @@ -0,0 +1,586 @@ +/** + * Transactional priority queues whose state is stored in a `TxRef`. Elements + * are kept in the order defined by the `Order` supplied at construction time, + * and dequeue operations return the first element according to that ordering. + * + * Use `TxPriorityQueue` when multiple fibers coordinate through a shared queue + * and queue operations need to compose with other transactional state changes. + * The retrying `peek` and `take` operations wait transactionally when the queue + * is empty, so they can be combined with other transactional reads and writes in + * one atomic workflow. + * + * @since 4.0.0 + */ + +import type { Chunk } from "./Chunk.ts" +import * as C from "./Chunk.ts" +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import type { Option } from "./Option.ts" +import * as O from "./Option.ts" +import type { Order } from "./Order.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty, type Predicate } from "./Predicate.ts" +import * as TxRef from "./TxRef.ts" + +const TypeId = "~effect/transactions/TxPriorityQueue" + +/** + * A transactional priority queue backed by a sorted `Chunk`. + * + * **Details** + * + * Elements are stored in ascending order according to the `Order` provided at + * construction time. `take` returns the smallest element, `peek` observes it + * without removing. + * + * **Example** (Dequeuing values by priority) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * yield* TxPriorityQueue.offer(pq, 3) + * yield* TxPriorityQueue.offer(pq, 1) + * yield* TxPriorityQueue.offer(pq, 2) + * return yield* TxPriorityQueue.take(pq) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxPriorityQueue extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + readonly ref: TxRef.TxRef> + readonly ord: Order +} + +const TxPriorityQueueProto: Omit, typeof TypeId | "ref" | "ord"> = { + [NodeInspectSymbol](this: TxPriorityQueue) { + return toJson(this) + }, + toJSON(this: TxPriorityQueue) { + return { + _id: "TxPriorityQueue" + } + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +const makeTxPriorityQueue = (ref: TxRef.TxRef>, ord: Order): TxPriorityQueue => { + const self = Object.create(TxPriorityQueueProto) + self[TypeId] = TypeId + self.ref = ref + self.ord = ord + return self +} + +const insertSorted = (chunk: Chunk, value: A, ord: Order): Chunk => { + const arr = C.toArray(chunk) as Array + let lo = 0 + let hi = arr.length + while (lo < hi) { + const mid = (lo + hi) >>> 1 + if (ord(arr[mid], value) <= 0) { + lo = mid + 1 + } else { + hi = mid + } + } + const out = Array(arr.length + 1) as Array + for (let i = 0; i < lo; i++) out[i] = arr[i] + out[lo] = value + for (let i = lo; i < arr.length; i++) out[i + 1] = arr[i] + return C.fromIterable(out) +} + +/** + * Creates an empty `TxPriorityQueue` with the given ordering. + * + * **Example** (Creating an empty priority queue) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * return yield* TxPriorityQueue.isEmpty(pq) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const empty = (order: Order): Effect.Effect> => + Effect.map(TxRef.make>(C.empty()), (ref) => makeTxPriorityQueue(ref, order)) + +/** + * Creates a `TxPriorityQueue` from an iterable of elements. + * + * **Example** (Creating a priority queue from an iterable) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [3, 1, 2]) + * return yield* TxPriorityQueue.take(pq) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const fromIterable: { + (order: Order): (iterable: Iterable) => Effect.Effect> + (order: Order, iterable: Iterable): Effect.Effect> +} = dual( + 2, + (order: Order, iterable: Iterable): Effect.Effect> => { + const arr = Array.from(iterable).sort((a, b) => order(a, b)) + return Effect.map( + TxRef.make>(C.fromIterable(arr)), + (ref) => makeTxPriorityQueue(ref, order) + ) + } +) + +/** + * Creates a `TxPriorityQueue` from variadic elements. + * + * **Example** (Creating a priority queue from variadic values) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.make(Order.Number)(3, 1, 2) + * return yield* TxPriorityQueue.take(pq) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (order: Order) => (...elements: Array): Effect.Effect> => + fromIterable(order, elements) + +/** + * Returns the number of elements in the queue. + * + * **Example** (Getting the queue size) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [1, 2, 3]) + * return yield* TxPriorityQueue.size(pq) + * }) + * + * await Effect.runPromise(program) // => 3 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: TxPriorityQueue): Effect.Effect => Effect.map(TxRef.get(self.ref), C.size) + +/** + * Returns `true` if the queue is empty. + * + * **Example** (Checking whether a queue is empty) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * return yield* TxPriorityQueue.isEmpty(pq) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: TxPriorityQueue): Effect.Effect => Effect.map(size(self), (n) => n === 0) + +/** + * Returns `true` if the queue has at least one element. + * + * **Example** (Checking whether a queue has elements) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [1]) + * return yield* TxPriorityQueue.isNonEmpty(pq) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isNonEmpty = (self: TxPriorityQueue): Effect.Effect => Effect.map(size(self), (n) => n > 0) + +/** + * Observes the smallest element without removing it. + * + * **When to use** + * + * Use to inspect the next prioritized value and retry transactionally while + * the queue is empty. + * + * **Example** (Peeking at the next value) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [3, 1, 2]) + * return yield* TxPriorityQueue.peek(pq) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const peek = (self: TxPriorityQueue): Effect.Effect => + Effect.gen(function*() { + const chunk = yield* TxRef.get(self.ref) + const head = C.head(chunk) + if (O.isNone(head)) { + return yield* Effect.txRetry + } + return head.value + }).pipe(Effect.tx) + +/** + * Observes the smallest element without removing it, returning `None` when the + * queue is empty. + * + * **When to use** + * + * Use to inspect the next prioritized value without retrying on an empty queue. + * + * **Example** (Peeking without retrying) + * + * ```ts import.meta.vitest + * import { Effect, Option, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * return yield* TxPriorityQueue.peekOption(pq) + * }) + * + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const peekOption = (self: TxPriorityQueue): Effect.Effect> => + Effect.map(TxRef.get(self.ref), C.head) + +/** + * Inserts an element into the queue in sorted position. + * + * **Example** (Offering a value) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * yield* TxPriorityQueue.offer(pq, 2) + * yield* TxPriorityQueue.offer(pq, 1) + * return yield* TxPriorityQueue.take(pq) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const offer: { + (value: A): (self: TxPriorityQueue) => Effect.Effect + (self: TxPriorityQueue, value: A): Effect.Effect +} = dual( + 2, + (self: TxPriorityQueue, value: A): Effect.Effect => + TxRef.update(self.ref, (chunk) => insertSorted(chunk, value, self.ord)) +) + +/** + * Inserts all elements from an iterable into the queue. + * + * **Example** (Offering multiple values) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * yield* TxPriorityQueue.offerAll(pq, [3, 1, 2]) + * return yield* TxPriorityQueue.take(pq) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const offerAll: { + (values: Iterable): (self: TxPriorityQueue) => Effect.Effect + (self: TxPriorityQueue, values: Iterable): Effect.Effect +} = dual( + 2, + (self: TxPriorityQueue, values: Iterable): Effect.Effect => + TxRef.update(self.ref, (chunk) => { + const arr = [...C.toArray(chunk), ...values].sort((a, b) => self.ord(a, b)) + return C.fromIterable(arr) + }) +) + +/** + * Takes the smallest element from the queue. Retries if the queue is empty. + * + * **Example** (Taking the next value) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [3, 1, 2]) + * return yield* TxPriorityQueue.take(pq) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const take = (self: TxPriorityQueue): Effect.Effect => + Effect.gen(function*() { + const chunk = yield* TxRef.get(self.ref) + const head = C.head(chunk) + if (O.isNone(head)) { + return yield* Effect.txRetry + } + yield* TxRef.set(self.ref, C.drop(chunk, 1)) + return head.value + }).pipe(Effect.tx) + +/** + * Takes all elements from the queue, returning them in priority order. + * + * **Example** (Taking all values in priority order) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [3, 1, 2]) + * return yield* TxPriorityQueue.takeAll(pq) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const takeAll = (self: TxPriorityQueue): Effect.Effect> => + Effect.map( + TxRef.modify(self.ref, (chunk) => [chunk, C.empty()]), + C.toArray + ) + +/** + * Tries to take the smallest element. Returns `None` if the queue is empty. + * + * **Example** (Taking without retrying) + * + * ```ts import.meta.vitest + * import { Effect, Option, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * return yield* TxPriorityQueue.takeOption(pq) + * }) + * + * await Effect.runPromise(program) // => Option.none() + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const takeOption = (self: TxPriorityQueue): Effect.Effect> => + TxRef.modify(self.ref, (chunk) => { + const head = C.head(chunk) + if (O.isNone(head)) { + return [O.none(), chunk] + } + return [O.some(head.value), C.drop(chunk, 1)] + }) + +/** + * Takes up to `n` elements from the queue in priority order. + * + * **Example** (Taking up to a limit) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [5, 3, 1, 4, 2]) + * return yield* TxPriorityQueue.takeUpTo(pq, 2) + * }) + * + * await Effect.runPromise(program) // => [1, 2] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const takeUpTo: { + (n: number): (self: TxPriorityQueue) => Effect.Effect> + (self: TxPriorityQueue, n: number): Effect.Effect> +} = dual( + 2, + (self: TxPriorityQueue, n: number): Effect.Effect> => + Effect.map( + TxRef.modify(self.ref, (chunk) => { + const taken = C.take(chunk, n) + const rest = C.drop(chunk, n) + return [taken, rest] + }), + C.toArray + ) +) + +/** + * Removes elements matching the predicate. + * + * **Example** (Removing matching values) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [1, 2, 3, 4, 5]) + * yield* TxPriorityQueue.removeIf(pq, (n) => n % 2 === 0) + * return yield* TxPriorityQueue.takeAll(pq) + * }) + * + * await Effect.runPromise(program) // => [1, 3, 5] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const removeIf: { + (predicate: Predicate): (self: TxPriorityQueue) => Effect.Effect + (self: TxPriorityQueue, predicate: Predicate): Effect.Effect +} = dual( + 2, + (self: TxPriorityQueue, predicate: Predicate): Effect.Effect => + TxRef.update(self.ref, (chunk) => C.filter(chunk, (a) => !predicate(a))) +) + +/** + * Keeps only elements matching the predicate. + * + * **Example** (Retaining matching values) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [1, 2, 3, 4, 5]) + * yield* TxPriorityQueue.retainIf(pq, (n) => n % 2 === 0) + * return yield* TxPriorityQueue.takeAll(pq) + * }) + * + * await Effect.runPromise(program) // => [2, 4] + * ``` + * + * @category filtering + * @since 2.0.0 + */ +export const retainIf: { + (predicate: Predicate): (self: TxPriorityQueue) => Effect.Effect + (self: TxPriorityQueue, predicate: Predicate): Effect.Effect +} = dual( + 2, + (self: TxPriorityQueue, predicate: Predicate): Effect.Effect => + TxRef.update(self.ref, (chunk) => C.filter(chunk, predicate)) +) + +/** + * Returns all elements in priority order without removing them. + * + * **Example** (Reading values in priority order) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [3, 1, 2]) + * return yield* TxPriorityQueue.toArray(pq) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] + * ``` + * + * @category converting + * @since 2.0.0 + */ +export const toArray = (self: TxPriorityQueue): Effect.Effect> => + Effect.map(TxRef.get(self.ref), C.toArray) + +/** + * Determines if the provided value is a `TxPriorityQueue`. + * + * **Example** (Checking for a TxPriorityQueue) + * + * ```ts import.meta.vitest + * import { Effect, Order, TxPriorityQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const pq = yield* TxPriorityQueue.empty(Order.Number) + * return [TxPriorityQueue.isTxPriorityQueue(pq), TxPriorityQueue.isTxPriorityQueue("nope")] + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxPriorityQueue = (u: unknown): u is TxPriorityQueue => hasProperty(u, TypeId) diff --git a/.repos/effect/packages/effect/src/TxPubSub.ts b/.repos/effect/packages/effect/src/TxPubSub.ts new file mode 100644 index 000000000..697bc59ad --- /dev/null +++ b/.repos/effect/packages/effect/src/TxPubSub.ts @@ -0,0 +1,705 @@ +/** + * Broadcasts values to subscribers inside Effect transactions. + * + * A `TxPubSub` is a transactional publish/subscribe hub. Each subscriber + * owns a `TxQueue`, and each published value is offered to the subscriber + * queues that are registered at the time of publication. This module includes + * bounded, dropping, sliding, and unbounded hubs, publishing helpers, scoped + * subscriptions, shutdown operations, and a guard. + * + * @since 4.0.0 + */ +import * as Arr from "./Array.ts" +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Scope from "./Scope.ts" +import * as TxQueue from "./TxQueue.ts" +import * as TxRef from "./TxRef.ts" + +const TypeId = "~effect/transactions/TxPubSub" + +/** + * A TxPubSub represents a transactional publish/subscribe hub that broadcasts messages + * to all current subscribers using Software Transactional Memory (STM) semantics. + * + * **Example** (Subscribing to a transactional pub/sub) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publish(hub, "hello") + * return yield* TxQueue.take(sub) + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => "hello" + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxPubSub extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + /** @internal */ + readonly subscribersRef: TxRef.TxRef>> + /** @internal */ + readonly shutdownRef: TxRef.TxRef + readonly strategy: "bounded" | "unbounded" | "dropping" | "sliding" + readonly capacity: number +} + +const TxPubSubProto: Omit, typeof TypeId | "subscribersRef" | "shutdownRef" | "strategy" | "capacity"> = { + [NodeInspectSymbol](this: TxPubSub) { + return toJson(this) + }, + toJSON(this: TxPubSub) { + return { + _id: "TxPubSub", + strategy: this.strategy, + capacity: this.capacity + } + }, + toString(this: TxPubSub) { + return `TxPubSub(${this.strategy}, ${this.capacity})` + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +const makeTxPubSub = ( + subscribersRef: TxRef.TxRef>>, + shutdownRef: TxRef.TxRef, + strategy: "bounded" | "unbounded" | "dropping" | "sliding", + cap: number +): TxPubSub => { + const self = Object.create(TxPubSubProto) + self[TypeId] = TypeId + self.subscribersRef = subscribersRef + self.shutdownRef = shutdownRef + self.strategy = strategy + self.capacity = cap + return self +} + +// ============================================================================= +// Constructors +// ============================================================================= + +/** + * Creates a bounded TxPubSub with the specified capacity. When a subscriber's + * queue is full, the publisher will retry the transaction until space is available. + * + * **Example** (Creating a bounded pub/sub) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.bounded(16) + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publish(hub, 42) + * return yield* TxQueue.take(sub) + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const bounded = (capacity: number): Effect.Effect> => + Effect.gen(function*() { + const subscribersRef = yield* TxRef.make>>([]) + const shutdownRef = yield* TxRef.make(false) + return makeTxPubSub(subscribersRef, shutdownRef, "bounded", capacity) + }).pipe(Effect.tx) + +/** + * Creates a dropping TxPubSub with the specified capacity. When a subscriber's + * queue is full, the message is dropped for that subscriber. + * + * **Example** (Creating a dropping pub/sub) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.dropping(2) + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publish(hub, 1) + * yield* TxPubSub.publish(hub, 2) + * yield* TxPubSub.publish(hub, 3) // dropped + * const v1 = yield* TxQueue.take(sub) + * const v2 = yield* TxQueue.take(sub) + * return [v1, v2] + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => [1, 2] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const dropping = (capacity: number): Effect.Effect> => + Effect.gen(function*() { + const subscribersRef = yield* TxRef.make>>([]) + const shutdownRef = yield* TxRef.make(false) + return makeTxPubSub(subscribersRef, shutdownRef, "dropping", capacity) + }).pipe(Effect.tx) + +/** + * Creates a sliding TxPubSub with the specified capacity. When a subscriber's + * queue is full, the oldest message in that subscriber's queue is dropped. + * + * **Example** (Creating a sliding pub/sub) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.sliding(2) + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publish(hub, 1) + * yield* TxPubSub.publish(hub, 2) + * yield* TxPubSub.publish(hub, 3) // evicts 1 + * return yield* TxQueue.take(sub) + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const sliding = (capacity: number): Effect.Effect> => + Effect.gen(function*() { + const subscribersRef = yield* TxRef.make>>([]) + const shutdownRef = yield* TxRef.make(false) + return makeTxPubSub(subscribersRef, shutdownRef, "sliding", capacity) + }).pipe(Effect.tx) + +/** + * Creates an unbounded TxPubSub with unlimited capacity. Messages are always accepted. + * + * **Example** (Creating an unbounded pub/sub) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publish(hub, "msg") + * return yield* TxQueue.take(sub) + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => "msg" + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unbounded = (): Effect.Effect> => + Effect.gen(function*() { + const subscribersRef = yield* TxRef.make>>([]) + const shutdownRef = yield* TxRef.make(false) + return makeTxPubSub(subscribersRef, shutdownRef, "unbounded", Number.POSITIVE_INFINITY) + }).pipe(Effect.tx) + +// ============================================================================= +// Getters +// ============================================================================= + +/** + * Returns the capacity of the TxPubSub. + * + * **Example** (Reading pub/sub capacity) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.bounded(16) + * return TxPubSub.capacity(hub) + * }) + * + * await Effect.runPromise(program) // => 16 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const capacity = (self: TxPubSub): number => self.capacity + +/** + * Returns the current number of messages across all subscriber queues (the max). + * + * **Example** (Reading subscriber queue size) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publish(hub, 1) + * yield* TxPubSub.publish(hub, 2) + * return yield* TxPubSub.size(hub) + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const size = (self: TxPubSub): Effect.Effect => + Effect.gen(function*() { + const subscribers = yield* TxRef.get(self.subscribersRef) + let maxSize = 0 + for (const queue of subscribers) { + const s = yield* TxQueue.size(queue) + if (s > maxSize) maxSize = s + } + return maxSize + }).pipe(Effect.tx) + +/** + * Checks whether the TxPubSub has no pending messages (all subscriber queues are empty). + * + * **Example** (Checking whether a pub/sub is empty) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * return yield* TxPubSub.isEmpty(hub) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: TxPubSub): Effect.Effect => Effect.map(size(self), (s) => s === 0) + +/** + * Checks whether any subscriber queue is at capacity. + * + * **Example** (Checking whether a pub/sub is full) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.bounded(2) + * return yield* TxPubSub.isFull(hub) + * }) + * + * await Effect.runPromise(program) // => false + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isFull = (self: TxPubSub): Effect.Effect => + Effect.gen(function*() { + if (self.capacity === Number.POSITIVE_INFINITY) return false + const subscribers = yield* TxRef.get(self.subscribersRef) + for (const queue of subscribers) { + if (yield* TxQueue.isFull(queue)) return true + } + return false + }).pipe(Effect.tx) + +/** + * Checks whether the TxPubSub has been shut down. + * + * **Example** (Checking whether a pub/sub is shut down) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * const before = yield* TxPubSub.isShutdown(hub) + * yield* TxPubSub.shutdown(hub) + * return [before, yield* TxPubSub.isShutdown(hub)] + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isShutdown = (self: TxPubSub): Effect.Effect => TxRef.get(self.shutdownRef) + +// ============================================================================= +// Mutations +// ============================================================================= + +/** + * Publishes a message to all current subscribers. + * + * **Details** + * + * Returns `true` if the message was delivered to all subscribers, or `false` if the hub is shut down or the message was dropped for any subscriber. For the bounded strategy, the transaction retries if any subscriber queue is full. For the sliding strategy, full subscriber queues drop their oldest messages. For the dropping strategy, full subscriber queues drop the new message and the operation returns `false`. + * + * **Example** (Publishing a message to subscribers) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * + * // No subscribers - publish is a no-op + * const r1 = yield* TxPubSub.publish(hub, "no one listening") + * + * const msg = yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publish(hub, "hello") + * return yield* TxQueue.take(sub) + * }) + * ) + * return [r1, msg] + * }) + * + * await Effect.runPromise(program) // => [true, "hello"] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const publish: { + (value: A): (self: TxPubSub) => Effect.Effect + (self: TxPubSub, value: A): Effect.Effect +} = dual( + 2, + (self: TxPubSub, value: A): Effect.Effect => + Effect.gen(function*() { + if (yield* TxRef.get(self.shutdownRef)) return false + + const subscribers = yield* TxRef.get(self.subscribersRef) + let allAccepted = true + + for (const queue of subscribers) { + const accepted = yield* TxQueue.offer(queue, value) + if (!accepted) allAccepted = false + } + + return allAccepted + }).pipe(Effect.tx) +) + +/** + * Publishes all messages from an iterable to all current subscribers. + * + * **Details** + * + * Returns `true` if all messages were delivered to all subscribers. + * + * **Example** (Publishing multiple messages to subscribers) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxPubSub.subscribe(hub) + * yield* TxPubSub.publishAll(hub, [1, 2, 3]) + * const v1 = yield* TxQueue.take(sub) + * const v2 = yield* TxQueue.take(sub) + * const v3 = yield* TxQueue.take(sub) + * return [v1, v2, v3] + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const publishAll: { + (values: Iterable): (self: TxPubSub) => Effect.Effect + (self: TxPubSub, values: Iterable): Effect.Effect +} = dual( + 2, + (self: TxPubSub, values: Iterable): Effect.Effect => { + const valuesArray = Arr.fromIterable(values) + return Effect.gen(function*() { + if (yield* TxRef.get(self.shutdownRef)) return false + + let allAccepted = true + for (const value of valuesArray) { + const accepted = yield* publish(self, value) + if (!accepted) allAccepted = false + } + return allAccepted + }).pipe(Effect.tx) + } +) + +/** + * Subscribes to the TxPubSub, returning a scoped `TxQueue` for messages published after subscription. + * + * **Details** + * + * The returned queue uses the hub's capacity strategy: bounded subscriptions backpressure publishers when full, dropping subscriptions may miss new messages when full, and sliding subscriptions may evict older queued messages. The subscription is automatically removed when the scope is closed. + * + * **Example** (Subscribing multiple queues) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub1 = yield* TxPubSub.subscribe(hub) + * const sub2 = yield* TxPubSub.subscribe(hub) + * + * yield* TxPubSub.publish(hub, "broadcast") + * + * const msg1 = yield* TxQueue.take(sub1) + * const msg2 = yield* TxQueue.take(sub2) + * return [msg1, msg2] + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => ["broadcast", "broadcast"] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const subscribe = (self: TxPubSub): Effect.Effect, never, Scope.Scope> => + Effect.acquireRelease( + Effect.tx(acquireSubscriber(self)), + (queue) => Effect.tx(releaseSubscriber(self, queue)) + ) + +/** + * Creates a subscriber queue and registers it with the pub/sub. + * + * **When to use** + * + * Use to create and register a subscriber queue inside a larger transaction + * when registration must be atomic with other Tx operations. + * + * **Details** + * + * This is the transactional acquire step of `subscribe`, exposed so that callers can compose it with other Tx operations in a single transaction, such as `TxSubscriptionRef.changes`. + * + * @see {@link subscribe} for the scoped acquire and release wrapper when no custom transaction composition is needed + * @see {@link releaseSubscriber} to remove and shut down a queue returned by `acquireSubscriber` + * + * @category mutations + * @since 4.0.0 + */ +export const acquireSubscriber = ( + self: TxPubSub +): Effect.Effect, never, Effect.Transaction> => + Effect.gen(function*() { + const queue = yield* makeSubscriberQueue(self.strategy, self.capacity) + yield* TxRef.update(self.subscribersRef, (subs) => [...subs, queue]) + return queue + }) + +/** + * Removes a subscriber queue from the pub/sub and shuts it down. + * + * **When to use** + * + * Use to release a manually acquired subscriber queue inside a larger + * transaction, removing it from the pub/sub and shutting it down together with + * related transactional cleanup. + * + * **Details** + * + * This is the transactional release step of `subscribe`, exposed so that callers can compose it with other Tx operations in a single transaction. + * + * **Gotchas** + * + * The supplied queue is shut down after being removed, so callers should pass a + * queue acquired for this pub/sub. + * + * @see {@link acquireSubscriber} for the matching transactional acquire step + * @see {@link subscribe} for the scoped acquire and release wrapper + * + * @category mutations + * @since 4.0.0 + */ +export const releaseSubscriber: { + (queue: TxQueue.TxQueue): (self: TxPubSub) => Effect.Effect + (self: TxPubSub, queue: TxQueue.TxQueue): Effect.Effect +} = dual( + 2, + ( + self: TxPubSub, + queue: TxQueue.TxQueue + ): Effect.Effect => + Effect.gen(function*() { + yield* TxRef.update(self.subscribersRef, (subs) => subs.filter((q) => q !== queue)) + yield* TxQueue.shutdown(queue) + }) +) + +const makeSubscriberQueue = ( + strategy: "bounded" | "unbounded" | "dropping" | "sliding", + cap: number +): Effect.Effect> => { + switch (strategy) { + case "bounded": + return TxQueue.bounded(cap) + case "dropping": + return TxQueue.dropping(cap) + case "sliding": + return TxQueue.sliding(cap) + case "unbounded": + return TxQueue.unbounded() + } +} + +/** + * Shuts down the TxPubSub and all subscriber queues registered at the time of shutdown. + * + * **Details** + * + * After shutdown, `publish` and `publishAll` return `false`, and `awaitShutdown` completes. The operation is idempotent. + * + * **Gotchas** + * + * Subscribers acquired after shutdown are not automatically shut down by this call. + * + * **Example** (Shutting down a pub/sub) + * + * ```ts import.meta.vitest + * import { Effect, TxPubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * yield* TxPubSub.shutdown(hub) + * + * const shut = yield* TxPubSub.isShutdown(hub) + * const accepted = yield* TxPubSub.publish(hub, 1) + * return [shut, accepted] + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const shutdown = (self: TxPubSub): Effect.Effect => + Effect.gen(function*() { + const alreadyShutdown = yield* TxRef.get(self.shutdownRef) + if (alreadyShutdown) return + + yield* TxRef.set(self.shutdownRef, true) + const subscribers = yield* TxRef.get(self.subscribersRef) + for (const queue of subscribers) { + yield* TxQueue.shutdown(queue) + } + }).pipe(Effect.tx) + +/** + * Waits for the TxPubSub to be shut down. + * + * **Example** (Waiting for shutdown) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, TxPubSub } from "effect" + * + * const program = Effect.gen(function*() { + * const hub = yield* TxPubSub.unbounded() + * + * const fiber = yield* Effect.forkChild(TxPubSub.awaitShutdown(hub)) + * yield* TxPubSub.shutdown(hub) + * yield* Fiber.await(fiber) + * return yield* TxPubSub.isShutdown(hub) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const awaitShutdown = (self: TxPubSub): Effect.Effect => + Effect.gen(function*() { + const shut = yield* TxRef.get(self.shutdownRef) + if (shut) return + return yield* Effect.txRetry + }).pipe(Effect.tx) + +// ============================================================================= +// Guards +// ============================================================================= + +/** + * Checks whether the given value is a TxPubSub. + * + * **Example** (Checking for a TxPubSub) + * + * ```ts import.meta.vitest + * import { TxPubSub } from "effect" + * + * const someValue: unknown = {} + * TxPubSub.isTxPubSub(someValue) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxPubSub = (u: unknown): u is TxPubSub => hasProperty(u, TypeId) diff --git a/.repos/effect/packages/effect/src/TxQueue.ts b/.repos/effect/packages/effect/src/TxQueue.ts new file mode 100644 index 000000000..e4e7f0c80 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxQueue.ts @@ -0,0 +1,1507 @@ +/** + * Transactional queues whose state changes participate in Effect transactions. + * + * A `TxQueue` stores values of type `A`, exposes write-only `TxEnqueue` + * and read-only `TxDequeue` handles, and can complete, fail, or shut down with + * causes observed by consumers. Queue operations can retry transactionally when + * they cannot proceed, such as taking from an empty open queue or offering to a + * full bounded queue. This makes the queue useful for coordinating producers + * and consumers alongside other transactional state changes. + * + * @since 4.0.0 + */ +import type * as Arr from "./Array.ts" +import * as Cause from "./Cause.ts" +import * as Chunk from "./Chunk.ts" +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import * as Option from "./Option.ts" +import { hasProperty } from "./Predicate.ts" +import { type ExcludeDone, isDoneCause } from "./Pull.ts" +import * as TxChunk from "./TxChunk.ts" +import * as TxRef from "./TxRef.ts" +import type * as Types from "./Types.ts" + +/** + * Represents the state of a transactional queue with sophisticated lifecycle management. + * + * **Details** + * + * The queue progresses through three states: + * - **Open**: Accepting offers and serving takes normally + * - **Closing**: No new offers accepted, serving remaining items until empty + * - **Done**: Terminal state with completion cause, no further operations possible + * + * **Example** (Inspecting queue lifecycle states) + * + * ```ts import.meta.vitest + * import type { TxQueue } from "effect" + * + * const state: TxQueue.State = { _tag: "Open" } + * state._tag // => "Open" + * ``` + * + * @category models + * @since 4.0.0 + */ +export type State<_A, E> = + | { + readonly _tag: "Open" + } + | { + readonly _tag: "Closing" + readonly cause: Cause.Cause + } + | { + readonly _tag: "Done" + readonly cause: Cause.Cause + } + +const EnqueueTypeId = "~effect/transactions/TxQueue/Enqueue" +const DequeueTypeId = "~effect/transactions/TxQueue/Dequeue" +const TypeId = "~effect/transactions/TxQueue" + +/** + * Namespace containing type definitions for TxEnqueue variance annotations. + * + * @since 4.0.0 + */ +export declare namespace TxEnqueue { + /** + * Variance annotation interface for TxEnqueue contravariance. + * + * @category models + * @since 4.0.0 + */ + export interface Variance { + readonly _A: Types.Contravariant + readonly _E: Types.Contravariant + } +} + +/** + * Namespace containing type definitions for TxDequeue variance annotations. + * + * @since 4.0.0 + */ +export declare namespace TxDequeue { + /** + * Variance annotation interface for TxDequeue covariance. + * + * @category models + * @since 4.0.0 + */ + export interface Variance { + readonly _A: Types.Covariant + readonly _E: Types.Covariant + } +} + +/** + * Namespace containing type definitions for TxQueue variance annotations. + * + * @since 4.0.0 + */ +export declare namespace TxQueue { + /** + * Variance annotation interface for TxQueue invariance. + * + * @category models + * @since 4.0.0 + */ + export interface Variance { + readonly _A: Types.Invariant + readonly _E: Types.Invariant + } +} + +/** + * Represents the shared state of a transactional queue that can be inspected. + * This interface contains the core properties needed for queue state inspection + * operations like size, capacity, and completion status. + * + * @category models + * @since 4.0.0 + */ +export interface TxQueueState extends Inspectable { + readonly strategy: "bounded" | "unbounded" | "dropping" | "sliding" + readonly capacity: number + readonly items: TxChunk.TxChunk + readonly stateRef: TxRef.TxRef> +} + +/** + * A TxEnqueue represents the write-only interface of a transactional queue, providing + * operations for adding elements (enqueue operations) and inspecting queue state. + * + * **Example** (Offering values through enqueue handles) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * import type { Cause } from "effect" + * + * const program = Effect.gen(function*() { + * // Queue without error channel + * const queue = yield* TxQueue.bounded(10) + * const accepted = yield* TxQueue.offer(queue, 42) + * + * // Queue with error channel for completion signaling + * const faultTolerantQueue = yield* TxQueue.bounded(10) + * yield* TxQueue.offerAll(faultTolerantQueue, [1, 2, 3]) + * yield* TxQueue.fail(faultTolerantQueue, "processing complete") + * + * // Works with Done for clean completion + * const completableQueue = yield* TxQueue.bounded< + * string, + * Cause.Done + * >(5) + * yield* TxQueue.offer(completableQueue, "task") + * yield* TxQueue.end(completableQueue) + * + * return accepted + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxEnqueue extends TxQueueState { + readonly [EnqueueTypeId]: TxEnqueue.Variance +} + +/** + * A TxDequeue represents the read-only interface of a transactional queue, providing + * operations for consuming elements (dequeue operations) and inspecting queue state. + * + * **Example** (Taking values through dequeue handles) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * // Queue without error channel + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offer(queue, 42) + * const item = yield* TxQueue.take(queue) + * + * // Queue with error channel - errors propagate through E-channel + * const faultTolerantQueue = yield* TxQueue.bounded(10) + * yield* TxQueue.fail(faultTolerantQueue, "processing failed") + * + * // All dequeue operations now fail with the error directly + * const takeResult = yield* Effect.flip(TxQueue.take(faultTolerantQueue)) // "processing failed" + * const peekResult = yield* Effect.flip(TxQueue.peek(faultTolerantQueue)) // "processing failed" + * return [item, takeResult, peekResult] as const + * }) + * + * await Effect.runPromise(program) // => [42, "processing failed", "processing failed"] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxDequeue extends TxQueueState { + readonly [DequeueTypeId]: TxDequeue.Variance +} + +/** + * A TxQueue represents a transactional queue data structure that provides both + * enqueue and dequeue operations with Software Transactional Memory (STM) semantics. + * + * **Example** (Combining enqueue and dequeue operations) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a bounded transactional queue (E defaults to never) + * const queue = yield* TxQueue.bounded(10) + * + * // Single operations - automatically transactional + * const accepted = yield* TxQueue.offer(queue, 42) + * const item = yield* TxQueue.take(queue) // Effect + * + * // Queue with error channel + * const faultTolerantQueue = yield* TxQueue.bounded(10) + * + * // Operations can handle queue-level failures + * yield* TxQueue.fail(faultTolerantQueue, "queue failed") + * const result = yield* Effect.flip(TxQueue.take(faultTolerantQueue)) + * return [accepted, item, result] as const + * }) + * + * await Effect.runPromise(program) // => [true, 42, "queue failed"] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxQueue extends TxEnqueue, TxDequeue { + readonly [TypeId]: TxQueue.Variance +} + +/** + * Checks whether the given value is a TxEnqueue. + * + * **Example** (Checking enqueue handles) + * + * ```ts import.meta.vitest + * import { TxQueue } from "effect" + * + * const someValue: unknown = {} + * TxQueue.isTxEnqueue(someValue) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxEnqueue = (u: unknown): u is TxEnqueue => hasProperty(u, EnqueueTypeId) + +/** + * Checks whether the given value is a TxDequeue. + * + * **Example** (Checking dequeue handles) + * + * ```ts import.meta.vitest + * import { TxQueue } from "effect" + * + * const someValue: unknown = {} + * TxQueue.isTxDequeue(someValue) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxDequeue = (u: unknown): u is TxDequeue => hasProperty(u, DequeueTypeId) + +/** + * Checks whether the given value is a TxQueue. + * + * **Example** (Checking queue handles) + * + * ```ts import.meta.vitest + * import { TxQueue } from "effect" + * + * const someValue: unknown = {} + * TxQueue.isTxQueue(someValue) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxQueue = (u: unknown): u is TxQueue => hasProperty(u, TypeId) + +// ============================================================================= +// Proto +// ============================================================================= + +const TxQueueProto = { + [EnqueueTypeId]: { _A: (_: never) => _, _E: (_: never) => _ }, + [DequeueTypeId]: { _A: (_: never) => _, _E: (_: never) => _ }, + [TypeId]: { _A: (_: never) => _, _E: (_: never) => _ }, + [NodeInspectSymbol](this: TxQueue) { + return toJson(this) + }, + toString(this: TxQueue) { + return `TxQueue(${this.strategy}, ${this.capacity})` + }, + toJSON(this: TxQueue) { + return { + _id: "TxQueue", + strategy: this.strategy, + capacity: this.capacity + } + } +} + +// ============================================================================= +// Constructors +// ============================================================================= + +/** + * Creates a new bounded `TxQueue` with the specified capacity. + * + * **Details** + * + * This function returns a new TxQueue reference with the specified capacity. No existing TxQueue instances are modified. + * + * **Example** (Creating bounded queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a bounded queue (E defaults to never) + * const queue = yield* TxQueue.bounded(10) + * + * // Create a bounded queue with error channel + * const faultTolerantQueue = yield* TxQueue.bounded(10) + * + * // Offer items - will succeed until capacity is reached + * yield* TxQueue.offer(queue, 1) + * yield* TxQueue.offer(queue, 2) + * + * return yield* TxQueue.take(queue) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const bounded = ( + capacity: number +): Effect.Effect> => + Effect.gen(function*() { + const items = yield* TxChunk.empty() + const stateRef = yield* TxRef.make>({ _tag: "Open" }) + + const txQueue = Object.create(TxQueueProto) + txQueue.strategy = "bounded" + txQueue.capacity = capacity + txQueue.items = items + txQueue.stateRef = stateRef + return txQueue + }).pipe(Effect.tx) + +/** + * Creates a new unbounded `TxQueue` with unlimited capacity. + * + * **Details** + * + * This function returns a new TxQueue reference with unlimited capacity. No existing TxQueue instances are modified. + * + * **Example** (Creating unbounded queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * // Create an unbounded queue (E defaults to never) + * const queue = yield* TxQueue.unbounded() + * + * // Create an unbounded queue with error channel + * const faultTolerantQueue = yield* TxQueue.unbounded() + * + * // Can offer unlimited items + * yield* TxQueue.offer(queue, "hello") + * yield* TxQueue.offer(queue, "world") + * + * return yield* TxQueue.size(queue) + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const unbounded = (): Effect.Effect> => + Effect.gen(function*() { + const items = yield* TxChunk.empty() + const stateRef = yield* TxRef.make>({ _tag: "Open" }) + + const txQueue = Object.create(TxQueueProto) + txQueue.strategy = "unbounded" + txQueue.capacity = Number.POSITIVE_INFINITY + txQueue.items = items + txQueue.stateRef = stateRef + return txQueue + }).pipe(Effect.tx) + +/** + * Creates a new dropping `TxQueue` with the specified capacity that drops new items when full. + * + * **Details** + * + * This function returns a new TxQueue reference with dropping strategy. No existing TxQueue instances are modified. + * + * **Example** (Creating dropping queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a dropping queue with capacity 2 + * const queue = yield* TxQueue.dropping(2) + * + * // Fill to capacity + * yield* TxQueue.offer(queue, 1) + * yield* TxQueue.offer(queue, 2) + * + * // This will be dropped (returns false) + * return yield* TxQueue.offer(queue, 3) + * }) + * + * await Effect.runPromise(program) // => false + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const dropping = ( + capacity: number +): Effect.Effect> => + Effect.gen(function*() { + const items = yield* TxChunk.empty() + const stateRef = yield* TxRef.make>({ _tag: "Open" }) + + const txQueue = Object.create(TxQueueProto) + txQueue.strategy = "dropping" + txQueue.capacity = capacity + txQueue.items = items + txQueue.stateRef = stateRef + return txQueue + }).pipe(Effect.tx) + +/** + * Creates a new sliding `TxQueue` with the specified capacity that evicts old items when full. + * + * **Details** + * + * This function returns a new TxQueue reference with sliding strategy. No existing TxQueue instances are modified. + * + * **Example** (Creating sliding queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a sliding queue with capacity 2 + * const queue = yield* TxQueue.sliding(2) + * + * // Fill to capacity + * yield* TxQueue.offer(queue, 1) + * yield* TxQueue.offer(queue, 2) + * + * // This will evict item 1 and add 3 + * yield* TxQueue.offer(queue, 3) + * + * return yield* TxQueue.take(queue) + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const sliding = ( + capacity: number +): Effect.Effect> => + Effect.gen(function*() { + const items = yield* TxChunk.empty() + const stateRef = yield* TxRef.make>({ _tag: "Open" }) + + const txQueue = Object.create(TxQueueProto) + txQueue.strategy = "sliding" + txQueue.capacity = capacity + txQueue.items = items + txQueue.stateRef = stateRef + return txQueue + }).pipe(Effect.tx) + +// ============================================================================= +// Core Queue Operations +// ============================================================================= + +/** + * Offers an item to the queue and returns whether it was accepted. + * + * **Details** + * + * Open unbounded queues always accept; open bounded queues retry while full; dropping queues return `false` when full; sliding queues evict the oldest item when full. Closing or done queues return `false`. This function mutates the original TxQueue by adding the item according to the queue's strategy. It does not return a new TxQueue reference. + * + * **Example** (Offering a value) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * // Offer an item - returns true if accepted + * return yield* TxQueue.offer(queue, 42) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const offer: { + (value: A): (self: TxEnqueue) => Effect.Effect + (self: TxEnqueue, value: A): Effect.Effect +} = dual( + 2, + (self: TxEnqueue, value: A): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + if (state._tag === "Done" || state._tag === "Closing") { + return false + } + + const currentSize = yield* size(self) + + // Unbounded - always accept + if (self.strategy === "unbounded") { + yield* TxChunk.append(self.items, value) + return true + } + + // For bounded queues, check capacity + if (currentSize < self.capacity) { + yield* TxChunk.append(self.items, value) + return true + } + + // Queue is at capacity, strategy-specific behavior + if (self.strategy === "dropping") { + return false // Drop the new item + } + + if (self.strategy === "sliding") { + yield* TxChunk.drop(self.items, 1) // Remove oldest item + yield* TxChunk.append(self.items, value) // Add new item + return true + } + + // bounded strategy - block until space is available + return yield* Effect.txRetry + }).pipe(Effect.tx) +) + +/** + * Offers multiple items to the queue, returning the items that were not + * accepted. + * + * **Details** + * + * Each item follows `offer` semantics: bounded queues retry while full, dropping queues reject new items when full, sliding queues evict old items to accept new items, and closing or done queues reject all items. This function mutates the original TxQueue by adding items according to the queue's strategy. It does not return a new TxQueue reference. + * + * **Example** (Offering multiple values) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * // Offer multiple items - returns rejected items as array + * return yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) + * }) + * + * await Effect.runPromise(program) // => [] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const offerAll: { + (values: Iterable): (self: TxEnqueue) => Effect.Effect> + (self: TxEnqueue, values: Iterable): Effect.Effect> +} = dual( + 2, + (self: TxEnqueue, values: Iterable): Effect.Effect> => { + const valuesArray = Array.from(values) + + return Effect.gen(function*() { + const rejected: Array = [] + + for (const value of valuesArray) { + const accepted = yield* offer(self, value) + if (!accepted) { + rejected.push(value) + } + } + + return rejected + }).pipe(Effect.tx) + } +) + +/** + * Takes the next item from the queue, retrying the transaction while the queue + * is empty. + * + * **Details** + * + * If the queue is done, the effect fails with the queue's completion cause. This function mutates the original TxQueue by removing the first item. It does not return a new TxQueue reference. + * + * **Example** (Taking a value) + * + * ```ts import.meta.vitest + * import { Effect, Exit, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offer(queue, 42) + * + * // Take an item - blocks if empty + * const item = yield* TxQueue.take(queue) + * + * // When queue fails, take fails with the same error + * yield* TxQueue.fail(queue, "queue error") + * const result = yield* Effect.exit(TxQueue.take(queue)) + * return [item, result] as const + * }) + * + * await Effect.runPromise(program) // => [42, Exit.fail("queue error")] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const take = (self: TxDequeue): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + // Check if queue is done - forward the cause directly + if (state._tag === "Done") { + return yield* Effect.failCause(state.cause) + } + + // If no items available, retry transaction + if (yield* isEmpty(self)) { + return yield* Effect.txRetry + } + + // Take item from queue + const chunk = yield* TxChunk.get(self.items) + const head = Chunk.head(chunk) + if (Option.isNone(head)) { + return yield* Effect.txRetry + } + + yield* TxChunk.drop(self.items, 1) + + // Check if we need to transition Closing → Done + if (state._tag === "Closing" && (yield* isEmpty(self))) { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + } + + return head.value + }).pipe(Effect.tx) + +/** + * Tries to take an item from the queue without blocking. + * + * **Example** (Polling without blocking) + * + * ```ts import.meta.vitest + * import { Effect, Option, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * // Poll returns Option.none if empty + * const maybe = yield* TxQueue.poll(queue) + * + * yield* TxQueue.offer(queue, 42) + * const item = yield* TxQueue.poll(queue) + * return [maybe, item] as const + * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const poll = (self: TxDequeue): Effect.Effect> => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + if (state._tag === "Done") { + return Option.none() + } + + const chunk = yield* TxChunk.get(self.items) + const head = Chunk.head(chunk) + if (Option.isNone(head)) { + return Option.none() + } + + yield* TxChunk.drop(self.items, 1) + + if (state._tag === "Closing" && (yield* isEmpty(self))) { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + } + + return Option.some(head.value) + }).pipe(Effect.tx) + +/** + * Takes all items from the queue. Blocks if the queue is empty. + * + * **Details** + * + * If the queue is already in a failed state, the error is propagated through the E-channel. This follows the same patterns as `take` and waits when there are no elements. It returns a non-empty array because it blocks until at least one item is available. This function mutates the original TxQueue by removing all items. It does not return a new TxQueue reference. + * + * **Example** (Taking all queued values) + * + * ```ts import.meta.vitest + * import { Effect, Exit, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) + * + * // Take all items atomically - returns NonEmptyArray + * return yield* TxQueue.takeAll(queue) + * }) + * + * // Error propagation example + * const errorExample = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(5) + * yield* TxQueue.offerAll(queue, [1, 2]) + * yield* TxQueue.fail(queue, "processing error") + * + * // takeAll() propagates the queue error through E-channel + * return yield* Effect.exit(TxQueue.takeAll(queue)) + * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] + * await Effect.runPromise(errorExample) // => Exit.fail("processing error") + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const takeAll = (self: TxDequeue): Effect.Effect, E> => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + // Handle done queue + if (state._tag === "Done") { + return yield* Effect.failCause(state.cause) + } + + // Wait if empty - same pattern as take() + if (yield* isEmpty(self)) { + return yield* Effect.txRetry + } + + const chunk = yield* TxChunk.get(self.items) + + // Take all items (guaranteed non-empty due to isEmpty check above) + const items = Chunk.toArray(chunk) as Arr.NonEmptyArray + yield* TxChunk.set(self.items, Chunk.empty()) + + // Check if we need to transition Closing → Done + if (state._tag === "Closing") { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + } + + return items + }).pipe(Effect.tx) + +/** + * Takes up to `n` items from the queue in a single transaction. + * + * **Details** + * + * For an open queue, waits until `min(n, capacity)` items are available, then removes that many items. If `n` is less than or equal to zero, returns an empty array without modifying the queue. If the queue is closing, drains the currently available items and transitions to `Done`. If the queue is already done, the effect fails with the queue's completion cause. This function mutates the original TxQueue by removing the taken items. It does not return a new TxQueue reference. + * + * **Example** (Taking a fixed number of values) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(5) + * yield* TxQueue.offerAll(queue, [1, 2, 3, 4]) + * + * const items = yield* TxQueue.takeN(queue, 4) + * + * // This requests more than capacity (5), so takes all available (up to 5) + * yield* TxQueue.offerAll(queue, [5, 6, 7, 8, 9]) + * const all = yield* TxQueue.takeN(queue, 10) + * return [items, all] as const + * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4], [5, 6, 7, 8, 9]] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const takeN: { + (n: number): (self: TxDequeue) => Effect.Effect, E> + (self: TxDequeue, n: number): Effect.Effect, E> +} = dual( + 2, + (self: TxDequeue, n: number): Effect.Effect, E> => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + // Check if queue is done - forward the cause directly + if (state._tag === "Done") { + return yield* Effect.failCause(state.cause) + } + + const currentSize = yield* size(self) + + // Determine how many items we can/should take + const requestedCount = n + const maxPossible = Math.min(requestedCount, self.capacity) + + // If we can't get the requested amount due to capacity constraints, + // take what the capacity allows. Otherwise, wait for the full amount. + const shouldWaitForFull = requestedCount <= self.capacity + const minimumRequired = shouldWaitForFull ? requestedCount : maxPossible + + // If we don't have enough items available + if (currentSize < minimumRequired) { + // If queue is closing, transition to done and return what we have + if (state._tag === "Closing") { + if (yield* isEmpty(self)) { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + return [] + } + // Take all remaining items when closing + const chunk = yield* TxChunk.get(self.items) + const taken = Chunk.toArray(chunk) + yield* TxChunk.set(self.items, Chunk.empty()) + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + return taken + } + + // Queue is still open but not enough items - retry transaction + return yield* Effect.txRetry + } + + // Take the determined number of items + const toTake = minimumRequired + const chunk = yield* TxChunk.get(self.items) + const taken = Chunk.take(chunk, toTake) + yield* TxChunk.drop(self.items, toTake) + + // Check if we need to transition Closing → Done + if (state._tag === "Closing" && (yield* isEmpty(self))) { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + } + + return Chunk.toArray(taken) + }).pipe(Effect.tx) +) + +/** + * Takes between `min` and `max` currently available items, waiting for `min` on + * an open queue. + * + * **Details** + * + * If the queue is closing, drains the currently available items even when fewer than `min` are available and transitions to `Done`. Invalid ranges (`min <= 0`, `max <= 0`, or `min > max`) return an empty array. If the queue is already done, the effect fails with the queue's completion cause. + * + * **Example** (Taking batches within bounds) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5, 6, 7, 8]) + * + * // Take between 2 and 5 items + * const batch1 = yield* TxQueue.takeBetween(queue, 2, 5) + * + * // Take between 1 and 10 items (but only 3 remain) + * const batch2 = yield* TxQueue.takeBetween(queue, 1, 10) + * + * // Would wait for at least 1 item to be available + * // const batch3 = yield* TxQueue.takeBetween(queue, 1, 3) + * return [batch1, batch2] as const + * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4, 5], [6, 7, 8]] + * ``` + * + * @category taking + * @since 2.0.0 + */ +export const takeBetween: { + (min: number, max: number): (self: TxDequeue) => Effect.Effect, E> + (self: TxDequeue, min: number, max: number): Effect.Effect, E> +} = dual( + 3, + (self: TxDequeue, min: number, max: number): Effect.Effect, E> => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + // Check if queue is done - forward the cause directly + if (state._tag === "Done") { + return yield* Effect.failCause(state.cause) + } + + // Validate parameters + if (min <= 0 || max <= 0 || min > max) { + return [] + } + + const currentSize = yield* size(self) + + // If we have less than minimum required items + if (currentSize < min) { + // If queue is closing, transition to done and return what we have + if (state._tag === "Closing") { + if (yield* isEmpty(self)) { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + return [] + } + // Take all remaining items when closing (if >= min or all available) + const chunk = yield* TxChunk.get(self.items) + const taken = Chunk.toArray(chunk) + yield* TxChunk.set(self.items, Chunk.empty()) + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + return taken + } + + // Queue is still open but not enough items - retry transaction + return yield* Effect.txRetry + } + + // We have at least the minimum, take up to the maximum + const toTake = Math.min(currentSize, max) + const chunk = yield* TxChunk.get(self.items) + const taken = Chunk.take(chunk, toTake) + yield* TxChunk.drop(self.items, toTake) + + // Check if we need to transition Closing → Done + if (state._tag === "Closing" && (yield* isEmpty(self))) { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + } + + return Chunk.toArray(taken) + }).pipe(Effect.tx) +) + +/** + * Waits transactionally for the next item and returns it without removing it. + * + * **Details** + * + * If the queue is open but empty, the transaction retries until an item is available or the queue completes. If the queue is done, the queue's completion cause is propagated through the error channel. + * + * **Example** (Peeking without removing values) + * + * ```ts import.meta.vitest + * import { Effect, Exit, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offer(queue, 42) + * + * // Peek at the next item without removing it + * const item = yield* TxQueue.peek(queue) + * + * // Item is still in the queue + * const size = yield* TxQueue.size(queue) + * return [item, size] as const + * }) + * + * // Error handling example + * const errorExample = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(5) + * yield* TxQueue.fail(queue, "queue failed") + * + * // peek() propagates the queue error through E-channel + * return yield* Effect.exit(TxQueue.peek(queue)) + * }) + * + * await Effect.runPromise(program) // => [42, 1] + * await Effect.runPromise(errorExample) // => Exit.fail("queue failed") + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const peek = (self: TxDequeue): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + if (state._tag === "Done") { + return yield* Effect.failCause(state.cause) + } + + const chunk = yield* TxChunk.get(self.items) + const head = Chunk.head(chunk) + if (Option.isNone(head)) { + return yield* Effect.txRetry + } + + return head.value + }).pipe(Effect.tx) + +/** + * Gets the current size of the queue. + * + * **Example** (Reading queue size) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offerAll(queue, [1, 2, 3]) + * + * return yield* TxQueue.size(queue) + * }) + * + * await Effect.runPromise(program) // => 3 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const size = (self: TxQueueState): Effect.Effect => TxChunk.size(self.items) + +/** + * Checks whether the queue is empty. + * + * **Example** (Checking whether a queue is empty) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * const empty = yield* TxQueue.isEmpty(queue) + * + * yield* TxQueue.offer(queue, 42) + * const stillEmpty = yield* TxQueue.isEmpty(queue) + * return [empty, stillEmpty] as const + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isEmpty = (self: TxQueueState): Effect.Effect => TxChunk.isEmpty(self.items) + +/** + * Checks whether the queue is at capacity. + * + * **Example** (Checking whether a queue is full) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(2) + * + * const full = yield* TxQueue.isFull(queue) + * + * yield* TxQueue.offerAll(queue, [1, 2]) + * const nowFull = yield* TxQueue.isFull(queue) + * return [full, nowFull] as const + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isFull = (self: TxQueueState): Effect.Effect => + self.capacity === Number.POSITIVE_INFINITY + ? Effect.succeed(false) + : Effect.map(size(self), (currentSize) => currentSize >= self.capacity) + +/** + * Interrupts the queue gracefully with the current fiber's interruption cause. + * + * **Details** + * + * If the queue still contains items, it enters the closing state so buffered items can be drained before consumers observe the interruption. If it is empty, it transitions directly to done. Returns `false` if the queue was already closing or done. + * + * **Example** (Interrupting queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offer(queue, 42) + * + * // Interrupt gracefully - allows remaining items to be consumed + * return yield* TxQueue.interrupt(queue) + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const interrupt = (self: TxEnqueue): Effect.Effect => + Effect.withFiber((fiber) => failCause(self, Cause.interrupt(fiber.id))) + +/** + * Fails the queue with the specified error, discarding any buffered items. + * + * **Details** + * + * The queue transitions directly to done with `Cause.fail(error)`. Returns `false` if the queue was already closing or done. + * + * **Example** (Failing queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * // Fail the queue with an error + * return yield* TxQueue.fail(queue, "connection lost") + * }) + * + * await Effect.runPromise(program) // => true + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const fail: { + (error: E): (self: TxEnqueue) => Effect.Effect + (self: TxEnqueue, error: E): Effect.Effect +} = dual( + 2, + (self: TxEnqueue, error: E): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + if (state._tag !== "Open") { + return false // Already closing/done + } + + // Fail transitions directly to Done, clearing items + yield* TxChunk.set(self.items, Chunk.empty()) + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: Cause.fail(error) }) + + return true + }).pipe(Effect.tx) +) + +/** + * Completes the queue with the specified cause. + * + * **Details** + * + * If the queue is empty, it transitions directly to done. If it still contains items, it enters the closing state so buffered items can be drained before the cause is observed. Returns `false` if the queue was already closing or done. + * + * **Example** (Failing queues with causes) + * + * ```ts import.meta.vitest + * import { Cause, Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * // Complete with specific cause + * const cause = Cause.interrupt() + * const result = yield* TxQueue.failCause(queue, cause) + * return [cause, result] as const + * }) + * + * await Effect.runPromise(program) // => [Cause.interrupt(), true] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const failCause: { + (cause: Cause.Cause): (self: TxEnqueue) => Effect.Effect + (self: TxEnqueue, cause: Cause.Cause): Effect.Effect +} = dual( + 2, + (self: TxEnqueue, cause: Cause.Cause): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + if (state._tag !== "Open") { + return false // Already closing/done + } + + if (yield* isEmpty(self)) { + // Can transition directly to Done + yield* TxRef.set(self.stateRef, { _tag: "Done", cause }) + } else { + // Need to go through Closing state + yield* TxRef.set(self.stateRef, { _tag: "Closing", cause }) + } + + return true + }).pipe(Effect.tx) +) + +/** + * Ends a queue by signaling completion with a `Cause.Done` error. + * + * **Details** + * + * This is a convenience wrapper around `failCause` for queues whose error channel can contain `Cause.Done`. If buffered items remain, the queue enters the closing state and those items may still be consumed before later `take` or `peek` operations fail with `Cause.Done`. + * + * **Example** (Ending queues) + * + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * // Signal the end of the queue + * const result = yield* TxQueue.end(queue) + * + * // All operations will now fail with Done + * const takeResult = yield* Effect.exit(TxQueue.take(queue)) + * + * const peekResult = yield* Effect.exit(TxQueue.peek(queue)) + * return [result, takeResult, peekResult] as const + * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail(Cause.Done()), Exit.fail(Cause.Done())] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const end = (self: TxEnqueue): Effect.Effect => + failCause(self, Cause.fail(Cause.Done())) + +/** + * Removes and returns all currently buffered elements. + * + * **Details** + * + * If the queue is closing, draining its buffered elements transitions it to done. If the queue is already done with a `Cause.Done` error, returns an empty array. If the queue is done for any other cause, including interruption or failure, that cause is propagated. + * + * **Example** (Clearing queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) + * + * const sizeBefore = yield* TxQueue.size(queue) + * + * const cleared = yield* TxQueue.clear(queue) + * + * const sizeAfter = yield* TxQueue.size(queue) + * return [sizeBefore, cleared, sizeAfter] as const + * }) + * + * await Effect.runPromise(program) // => [5, [1, 2, 3, 4, 5], 0] + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const clear = (self: TxEnqueue): Effect.Effect, ExcludeDone> => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + if (state._tag === "Done") { + // Return empty array only for halt causes (like Cause.Done) + if (isDoneCause(state.cause)) { + return [] + } + return yield* Effect.failCause(state.cause) + } + const chunk = yield* TxChunk.get(self.items) + yield* TxChunk.set(self.items, Chunk.empty()) + if (state._tag === "Closing") { + yield* TxRef.set(self.stateRef, { _tag: "Done", cause: state.cause }) + } + return Chunk.toArray(chunk) + }).pipe(Effect.tx) + +/** + * Shuts down the queue immediately by clearing all items and interrupting it (legacy compatibility). + * + * **Details** + * + * This operation clears all items from the queue using `clear`, then interrupts the queue using `interrupt`. This function mutates the original TxQueue by clearing its contents and marking it as shutdown. It does not return a new TxQueue reference. + * + * **Example** (Shutting down queues) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) + * + * const sizeBefore = yield* TxQueue.size(queue) + * + * yield* TxQueue.shutdown(queue) + * + * const sizeAfter = yield* TxQueue.size(queue) + * + * const isShutdown = yield* TxQueue.isShutdown(queue) + * return [sizeBefore, sizeAfter, isShutdown] as const + * }) + * + * await Effect.runPromise(program) // => [5, 0, true] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const shutdown = (self: TxEnqueue): Effect.Effect => + Effect.gen(function*() { + yield* Effect.ignoreCause(clear(self)) + return yield* interrupt(self) + }).pipe(Effect.tx) + +/** + * Checks whether the queue is in the open state. + * + * **Example** (Checking open state) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * const open = yield* TxQueue.isOpen(queue) + * + * yield* TxQueue.interrupt(queue) + * const stillOpen = yield* TxQueue.isOpen(queue) + * return [open, stillOpen] as const + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isOpen = (self: TxQueueState): Effect.Effect => + Effect.map(TxRef.get(self.stateRef), (state) => state._tag === "Open") + +/** + * Checks whether the queue is in the closing state. + * + * **Example** (Checking closing state) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * yield* TxQueue.offer(queue, 42) + * + * const closing = yield* TxQueue.isClosing(queue) + * + * yield* TxQueue.interrupt(queue) + * const nowClosing = yield* TxQueue.isClosing(queue) + * return [closing, nowClosing] as const + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isClosing = (self: TxQueueState): Effect.Effect => + Effect.map(TxRef.get(self.stateRef), (state) => state._tag === "Closing") + +/** + * Checks whether the queue is done (completed or failed). + * + * **Example** (Checking done state) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * const done = yield* TxQueue.isDone(queue) + * + * yield* TxQueue.interrupt(queue) + * const nowDone = yield* TxQueue.isDone(queue) + * return [done, nowDone] as const + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 4.0.0 + */ +export const isDone = (self: TxQueueState): Effect.Effect => + Effect.map(TxRef.get(self.stateRef), (state) => state._tag === "Done") + +/** + * Checks whether the queue is shutdown (legacy compatibility). + * + * **Example** (Checking shutdown state) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * const isShutdown = yield* TxQueue.isShutdown(queue) + * + * yield* TxQueue.shutdown(queue) + * const nowShutdown = yield* TxQueue.isShutdown(queue) + * return [isShutdown, nowShutdown] as const + * }) + * + * await Effect.runPromise(program) // => [false, true] + * ``` + * + * @category predicates + * @since 2.0.0 + */ +export const isShutdown = (self: TxQueueState): Effect.Effect => isDone(self) + +/** + * Waits for the queue to complete (either successfully or with failure). + * + * **Example** (Awaiting queue completion) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, TxQueue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* TxQueue.bounded(10) + * + * const waiter = yield* Effect.forkChild(TxQueue.awaitCompletion(queue)) + * yield* TxQueue.interrupt(queue) + * + * yield* Fiber.join(waiter) + * return "Queue completed successfully" + * }) + * + * await Effect.runPromise(program) // => "Queue completed successfully" + * ``` + * + * @category combinators + * @since 4.0.0 + */ +export const awaitCompletion = (self: TxQueueState): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + if (state._tag === "Done") { + return void 0 + } + + // Not done yet, retry transaction + return yield* Effect.txRetry + }).pipe(Effect.tx) diff --git a/.repos/effect/packages/effect/src/TxReentrantLock.ts b/.repos/effect/packages/effect/src/TxReentrantLock.ts new file mode 100644 index 000000000..0e0f90ec6 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxReentrantLock.ts @@ -0,0 +1,684 @@ +/** + * Coordinates shared access inside transactions with read and write locks. + * + * A `TxReentrantLock` lets many fibers hold read locks at the same time, or one + * fiber hold a write lock for exclusive access. Lock ownership is tracked by + * fiber, so a fiber that already holds the lock can acquire it again and later + * release each acquisition. Attempts that cannot proceed retry transactionally + * until the lock becomes available. This module includes manual, scoped, and + * wrapper-style operations for read and write locking. + * + * @since 4.0.0 + */ +import * as Effect from "./Effect.ts" +import * as HashMap from "./HashMap.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.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 * as Scope from "./Scope.ts" +import * as TxRef from "./TxRef.ts" + +const TypeId = "~effect/transactions/TxReentrantLock" + +/** + * @category models + * @since 4.0.0 + */ +interface LockState { + readonly readers: HashMap.HashMap + readonly writer: Option.Option +} + +const emptyState: LockState = { + readers: HashMap.empty(), + writer: Option.none() +} + +/** + * A TxReentrantLock provides a transactional read/write lock with reentrant semantics. + * Multiple readers can hold the lock concurrently, or a single writer can hold exclusive + * access. A fiber holding the write lock may acquire additional read/write locks (reentrancy). + * + * **Example** (Using read and write locks) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * + * // Multiple readers can proceed concurrently + * const read = yield* TxReentrantLock.withReadLock(lock, Effect.succeed("reading")) + * + * // Writer gets exclusive access + * const write = yield* TxReentrantLock.withWriteLock(lock, Effect.succeed("writing")) + * return [read, write] + * }) + * + * await Effect.runPromise(program) // => ["reading", "writing"] + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxReentrantLock extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + /** @internal */ + readonly stateRef: TxRef.TxRef +} + +const TxReentrantLockProto: Omit = { + [NodeInspectSymbol](this: TxReentrantLock) { + return toJson(this) + }, + toJSON(this: TxReentrantLock) { + return { _id: "TxReentrantLock" } + }, + toString() { + return "TxReentrantLock" + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +// ============================================================================= +// Constructors +// ============================================================================= + +/** + * Creates a new TxReentrantLock. + * + * **Example** (Creating a reentrant lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.locked(lock) + * }) + * + * await Effect.runPromise(program) // => false + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (): Effect.Effect => + Effect.gen(function*() { + const stateRef = yield* TxRef.make(emptyState) + const self = Object.create(TxReentrantLockProto) + self[TypeId] = TypeId + self.stateRef = stateRef + return self + }).pipe(Effect.tx) + +// ============================================================================= +// Mutations +// ============================================================================= + +/** + * Acquires a read lock. Blocks if another fiber holds the write lock. + * If the current fiber already holds the write lock, the read lock is granted (reentrancy). + * Returns the current number of read locks held by this fiber. + * + * **Example** (Acquiring a read lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * const count = yield* TxReentrantLock.acquireRead(lock) + * yield* TxReentrantLock.releaseRead(lock) + * return count + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const acquireRead = (self: TxReentrantLock): Effect.Effect => + Effect.withFiber((fiber) => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + const fiberId = fiber.id + + // If another fiber holds the write lock, retry + if (Option.isSome(state.writer) && state.writer.value[0] !== fiberId) { + return yield* Effect.txRetry + } + + // Grant read lock + const currentCount = Option.getOrElse(HashMap.get(state.readers, fiberId), () => 0) + const newCount = currentCount + 1 + yield* TxRef.set(self.stateRef, { + ...state, + readers: HashMap.set(state.readers, fiberId, newCount) + }) + return newCount + }).pipe(Effect.tx) + ) + +/** + * Acquires the write lock for the current fiber. + * + * **When to use** + * + * Use to enter an exclusive section manually when `withWriteLock` is not the + * right shape. + * + * **Details** + * + * Blocks if any other fiber holds a read or write lock. If the current fiber + * already holds the write lock, the count is incremented. If the current fiber + * holds a read lock, the write lock is granted as an upgrade. + * + * Returns the current number of write locks held by this fiber. + * + * **Example** (Acquiring a write lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * const count = yield* TxReentrantLock.acquireWrite(lock) + * yield* TxReentrantLock.releaseWrite(lock) + * return count + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const acquireWrite = (self: TxReentrantLock): Effect.Effect => + Effect.withFiber((fiber) => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + const fiberId = fiber.id + + // If another fiber holds the write lock, retry + if (Option.isSome(state.writer) && state.writer.value[0] !== fiberId) { + return yield* Effect.txRetry + } + + // If other fibers hold read locks, retry + for (const [readerId] of state.readers) { + if (readerId !== fiberId && Option.getOrElse(HashMap.get(state.readers, readerId), () => 0) > 0) { + return yield* Effect.txRetry + } + } + + // Grant write lock + if (Option.isSome(state.writer)) { + // Reentrant: increment write count + const newCount = state.writer.value[1] + 1 + yield* TxRef.set(self.stateRef, { + ...state, + writer: Option.some([fiberId, newCount] as const) + }) + return newCount + } + + // First write lock acquisition + yield* TxRef.set(self.stateRef, { + ...state, + writer: Option.some([fiberId, 1] as const) + }) + return 1 + }).pipe(Effect.tx) + ) + +/** + * Releases one read lock held by the current fiber. + * + * **When to use** + * + * Use to leave a manually acquired read lock. + * + * **Details** + * + * Returns the remaining number of read locks held by this fiber. + * + * **Example** (Releasing a read lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * yield* TxReentrantLock.acquireRead(lock) + * return yield* TxReentrantLock.releaseRead(lock) + * }) + * + * await Effect.runPromise(program) // => 0 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +const releaseReadFor = (self: TxReentrantLock, fiberId: number): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + const currentCount = Option.getOrElse(HashMap.get(state.readers, fiberId), () => 0) + + if (currentCount <= 0) return 0 + + const newCount = currentCount - 1 + const newReaders = newCount === 0 + ? HashMap.remove(state.readers, fiberId) + : HashMap.set(state.readers, fiberId, newCount) + + yield* TxRef.set(self.stateRef, { ...state, readers: newReaders }) + return newCount + }).pipe(Effect.tx) + +export const releaseRead = (self: TxReentrantLock): Effect.Effect => + Effect.withFiber((fiber) => releaseReadFor(self, fiber.id)) + +/** + * Releases one write lock held by the current fiber. + * + * **When to use** + * + * Use to leave a manually acquired write lock. + * + * **Details** + * + * Returns the remaining number of write locks held by this fiber. + * + * **Example** (Releasing a write lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * yield* TxReentrantLock.acquireWrite(lock) + * return yield* TxReentrantLock.releaseWrite(lock) + * }) + * + * await Effect.runPromise(program) // => 0 + * ``` + * + * @category mutations + * @since 2.0.0 + */ +const releaseWriteFor = (self: TxReentrantLock, fiberId: number): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + + if (Option.isNone(state.writer) || state.writer.value[0] !== fiberId) return 0 + + const newCount = state.writer.value[1] - 1 + const newWriter = newCount <= 0 + ? Option.none() + : Option.some([fiberId, newCount] as const) + + yield* TxRef.set(self.stateRef, { ...state, writer: newWriter }) + return newCount + }).pipe(Effect.tx) + +export const releaseWrite = (self: TxReentrantLock): Effect.Effect => + Effect.withFiber((fiber) => releaseWriteFor(self, fiber.id)) + +/** + * Acquires a read lock for the duration of the scope. + * The lock is automatically released when the scope closes. + * + * **Example** (Holding a scoped read lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * + * const held = yield* Effect.scoped( + * Effect.gen(function*() { + * yield* TxReentrantLock.readLock(lock) + * // read lock is held for the duration of the scope + * return yield* TxReentrantLock.readLocks(lock) + * }) + * ) + * // read lock is released + * return [held, yield* TxReentrantLock.readLocks(lock)] + * }) + * + * await Effect.runPromise(program) // => [1, 0] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const readLock = (self: TxReentrantLock): Effect.Effect => + Effect.withFiber((fiber) => + Effect.acquireRelease( + acquireRead(self), + () => releaseReadFor(self, fiber.id) + ) + ) + +/** + * Acquires a write lock for the duration of the scope. + * The lock is automatically released when the scope closes. + * + * **Example** (Holding a scoped write lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * + * const held = yield* Effect.scoped( + * Effect.gen(function*() { + * yield* TxReentrantLock.writeLock(lock) + * // write lock is held for the duration of the scope + * return yield* TxReentrantLock.writeLocks(lock) + * }) + * ) + * // write lock is released + * return [held, yield* TxReentrantLock.writeLocks(lock)] + * }) + * + * await Effect.runPromise(program) // => [1, 0] + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const writeLock = (self: TxReentrantLock): Effect.Effect => + Effect.withFiber((fiber) => + Effect.acquireRelease( + acquireWrite(self), + () => releaseWriteFor(self, fiber.id) + ) + ) + +/** + * Runs the provided effect while holding a read lock. The lock is automatically + * released after the effect completes, fails, or is interrupted. + * + * **Example** (Running an effect with a read lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.withReadLock( + * lock, + * Effect.succeed("read data") + * ) + * }) + * + * await Effect.runPromise(program) // => "read data" + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const withReadLock: { + (effect: Effect.Effect): (self: TxReentrantLock) => Effect.Effect + (self: TxReentrantLock, effect: Effect.Effect): Effect.Effect +} = ((...args: Array) => { + if (args.length === 1) { + const [effect] = args + return (self: TxReentrantLock) => + Effect.acquireUseRelease( + acquireRead(self), + () => effect, + () => releaseRead(self) + ) + } + const [self, effect] = args + return Effect.acquireUseRelease( + acquireRead(self), + () => effect, + () => releaseRead(self) + ) +}) as any + +/** + * Runs the provided effect while holding a write lock. The lock is automatically + * released after the effect completes, fails, or is interrupted. + * + * **Example** (Running an effect with a write lock) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.withWriteLock( + * lock, + * Effect.succeed("wrote data") + * ) + * }) + * + * await Effect.runPromise(program) // => "wrote data" + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const withWriteLock: { + (effect: Effect.Effect): (self: TxReentrantLock) => Effect.Effect + (self: TxReentrantLock, effect: Effect.Effect): Effect.Effect +} = ((...args: Array) => { + if (args.length === 1) { + const [effect] = args + return (self: TxReentrantLock) => + Effect.acquireUseRelease( + acquireWrite(self), + () => effect, + () => releaseWrite(self) + ) + } + const [self, effect] = args + return Effect.acquireUseRelease( + acquireWrite(self), + () => effect, + () => releaseWrite(self) + ) +}) as any + +/** + * Runs an effect while holding a write lock. + * + * **When to use** + * + * Use when you need to run an effect with exclusive write access through a + * `TxReentrantLock` and prefer the concise lock helper. + * + * **Example** (Running an effect with exclusive access) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.withLock( + * lock, + * Effect.succeed("exclusive operation") + * ) + * }) + * + * await Effect.runPromise(program) // => "exclusive operation" + * ``` + * + * @category mutations + * @since 2.0.0 + */ +export const withLock: { + (effect: Effect.Effect): (self: TxReentrantLock) => Effect.Effect + (self: TxReentrantLock, effect: Effect.Effect): Effect.Effect +} = withWriteLock + +// ============================================================================= +// Getters +// ============================================================================= + +/** + * Returns the total number of read locks held across all fibers. + * + * **Example** (Counting read locks) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * yield* TxReentrantLock.acquireRead(lock) + * const count = yield* TxReentrantLock.readLocks(lock) + * yield* TxReentrantLock.releaseRead(lock) + * return count + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const readLocks = (self: TxReentrantLock): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + let total = 0 + for (const [, count] of state.readers) { + total += count + } + return total + }) + +/** + * Returns the number of write locks held (0 or the reentrant count). + * + * **Example** (Counting write locks) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.writeLocks(lock) + * }) + * + * await Effect.runPromise(program) // => 0 + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const writeLocks = (self: TxReentrantLock): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + return Option.isSome(state.writer) ? state.writer.value[1] : 0 + }) + +/** + * Checks whether the lock is held by any fiber (read or write). + * + * **Example** (Checking whether a lock is held) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.locked(lock) + * }) + * + * await Effect.runPromise(program) // => false + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const locked = (self: TxReentrantLock): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + return HashMap.size(state.readers) > 0 || Option.isSome(state.writer) + }) + +/** + * Checks whether any fiber holds a read lock. + * + * **Example** (Checking whether a read lock is held) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.readLocked(lock) + * }) + * + * await Effect.runPromise(program) // => false + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const readLocked = (self: TxReentrantLock): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + return HashMap.size(state.readers) > 0 + }) + +/** + * Checks whether any fiber holds a write lock. + * + * **Example** (Checking whether a write lock is held) + * + * ```ts import.meta.vitest + * import { Effect, TxReentrantLock } from "effect" + * + * const program = Effect.gen(function*() { + * const lock = yield* TxReentrantLock.make() + * return yield* TxReentrantLock.writeLocked(lock) + * }) + * + * await Effect.runPromise(program) // => false + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const writeLocked = (self: TxReentrantLock): Effect.Effect => + Effect.gen(function*() { + const state = yield* TxRef.get(self.stateRef) + return Option.isSome(state.writer) + }) + +// ============================================================================= +// Guards +// ============================================================================= + +/** + * Checks whether the given value is a TxReentrantLock. + * + * **Example** (Checking for TxReentrantLock values) + * + * ```ts import.meta.vitest + * import { TxReentrantLock } from "effect" + * + * const someValue: unknown = {} + * + * TxReentrantLock.isTxReentrantLock(someValue) // => false + * ``` + * + * @category guards + * @since 4.0.0 + */ +export const isTxReentrantLock = (u: unknown): u is TxReentrantLock => hasProperty(u, TypeId) diff --git a/.repos/effect/packages/effect/src/TxRef.ts b/.repos/effect/packages/effect/src/TxRef.ts new file mode 100644 index 000000000..8a2364fe9 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxRef.ts @@ -0,0 +1,291 @@ +/** + * Transactional references for coordinating mutable state with Effect + * transactions. A `TxRef` stores a current value, but reads and writes inside + * `Effect.tx` are recorded in a transaction journal and committed together only + * when the outermost transaction succeeds. + * + * This is the basic building block behind the other transactional collections + * in Effect. The module provides effectful and unsafe constructors plus the + * core operations for reading, setting, updating, and modifying a transactional + * value while returning a separate result. + * + * @since 4.0.0 + */ +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import { pipeArguments } from "./Pipeable.ts" +import type { Pipeable } from "./Pipeable.ts" +import type { NoInfer } from "./Types.ts" + +const TypeId = "~effect/transactions/TxRef" + +/** + * TxRef is a transactional value, it can be read and modified within the body of a transaction. + * + * **When to use** + * + * Use to store mutable state that must be read and modified inside Effect + * transactions. + * + * **Details** + * + * Accessed values are tracked by the transaction in order to detect conflicts and in order to + * track changes, a transaction will retry whenever a conflict is detected or whenever the + * transaction explicitely calls to `Effect.txRetry` and any of the accessed TxRef values + * change. + * + * **Example** (Using a transactional reference) + * + * ```ts import.meta.vitest + * import { Effect, TxRef } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a transactional reference + * const ref: TxRef.TxRef = yield* TxRef.make(0) + * + * // Use within a transaction + * yield* Effect.tx(Effect.gen(function*() { + * const current = yield* TxRef.get(ref) + * yield* TxRef.set(ref, current + 1) + * })) + * + * return yield* TxRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category models + * @since 4.0.0 + */ +export interface TxRef extends Pipeable { + readonly [TypeId]: typeof TypeId + + version: number + pending: Map void> + value: A +} + +/** + * Creates a new `TxRef` with the specified initial value. + * + * **When to use** + * + * Use to create a `TxRef` inside an `Effect` workflow. + * + * **Example** (Creating transactional references) + * + * ```ts import.meta.vitest + * import { Effect, TxRef } from "effect" + * + * const program = Effect.gen(function*() { + * // Create a transactional reference with initial value + * const counter = yield* TxRef.make(0) + * const name = yield* TxRef.make("Alice") + * + * // Use in transactions + * yield* Effect.tx(Effect.gen(function*() { + * yield* TxRef.set(counter, 42) + * yield* TxRef.set(name, "Bob") + * })) + * + * return [yield* TxRef.get(counter), yield* TxRef.get(name)] + * }) + * + * await Effect.runPromise(program) // => [42, "Bob"] + * ``` + * + * @category constructors + * @since 2.0.0 + */ +export const make = (initial: A) => Effect.sync(() => makeUnsafe(initial)) + +/** + * Creates a new `TxRef` synchronously with the specified initial value. + * + * **When to use** + * + * Use to construct a `TxRef` synchronously when it must be created outside an + * `Effect` workflow. + * + * **Example** (Creating transactional references unsafely) + * + * ```ts import.meta.vitest + * import { TxRef } from "effect" + * + * // Create a TxRef synchronously (unsafe - use make instead in Effect contexts) + * const counter = TxRef.makeUnsafe(0) + * const config = TxRef.makeUnsafe({ timeout: 5000, retries: 3 }) + * + * // These are now ready to use in transactions + * counter.value // => 0 + * config.value // => { timeout: 5000, retries: 3 } + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const makeUnsafe = (initial: A): TxRef => ({ + [TypeId]: TypeId, + pending: new Map(), + pipe() { + return pipeArguments(this, arguments) + }, + version: 0, + value: initial +}) + +/** + * Modifies the value of the `TxRef` using the provided function. + * + * **When to use** + * + * Use to update a `TxRef` and return a computed result from the same + * transaction step. + * + * **Example** (Modifying transactional references) + * + * ```ts import.meta.vitest + * import { Effect, TxRef } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* TxRef.make(0) + * + * // Modify and return both old and new value + * const result = yield* TxRef.modify(counter, (current) => [current * 2, current + 1]) + * + * return [result, yield* TxRef.get(counter)] + * }) + * + * await Effect.runPromise(program) // => [0, 1] + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const modify: { + (f: (current: NoInfer) => [returnValue: R, newValue: A]): (self: TxRef) => Effect.Effect + (self: TxRef, f: (current: A) => [returnValue: R, newValue: A]): Effect.Effect +} = dual(2, ( + self: TxRef, + f: (current: A) => [returnValue: R, newValue: A] +): Effect.Effect => + Effect.Transaction.pipe( + Effect.flatMap((state) => + Effect.sync(() => { + if (!state.journal.has(self)) { + state.journal.set(self, { version: self.version, value: self.value }) + } + const current = state.journal.get(self)! + const [returnValue, next] = f(current.value) + current.value = next + return returnValue + }) + ), + Effect.tx + )) + +/** + * Updates the value of the `TxRef` using the provided function. + * + * **When to use** + * + * Use to transform a `TxRef` when no result value is needed. + * + * **Example** (Updating transactional references) + * + * ```ts import.meta.vitest + * import { Effect, TxRef } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* TxRef.make(10) + * + * // Update the value using a function + * yield* Effect.tx( + * TxRef.update(counter, (current) => current * 2) + * ) + * + * return yield* TxRef.get(counter) + * }) + * + * await Effect.runPromise(program) // => 20 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const update: { + (f: (current: NoInfer) => A): (self: TxRef) => Effect.Effect + (self: TxRef, f: (current: A) => A): Effect.Effect +} = dual(2, ( + self: TxRef, + f: (current: A) => A +): Effect.Effect => modify(self, (current) => [void 0, f(current)])) + +/** + * Reads the current value of the `TxRef`. + * + * **When to use** + * + * Use to read the current value of a `TxRef`. + * + * **Example** (Reading transactional references) + * + * ```ts import.meta.vitest + * import { Effect, TxRef } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* TxRef.make(42) + * + * // Read the value within a transaction + * const value = yield* Effect.tx( + * TxRef.get(counter) + * ) + * + * return value + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const get = (self: TxRef): Effect.Effect => modify(self, (current) => [current, current]) + +/** + * Sets the value of the `TxRef`. + * + * **When to use** + * + * Use to replace the value of a `TxRef`. + * + * **Example** (Setting transactional references) + * + * ```ts import.meta.vitest + * import { Effect, TxRef } from "effect" + * + * const program = Effect.gen(function*() { + * const counter = yield* TxRef.make(0) + * + * // Set a new value within a transaction + * yield* Effect.tx( + * TxRef.set(counter, 100) + * ) + * + * return yield* TxRef.get(counter) + * }) + * + * await Effect.runPromise(program) // => 100 + * ``` + * + * @category combinators + * @since 2.0.0 + */ +export const set: { + (value: A): (self: TxRef) => Effect.Effect + (self: TxRef, value: A): Effect.Effect +} = dual(2, ( + self: TxRef, + value: A +): Effect.Effect => update(self, () => value)) diff --git a/.repos/effect/packages/effect/src/TxSemaphore.ts b/.repos/effect/packages/effect/src/TxSemaphore.ts new file mode 100644 index 000000000..65f3b4819 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxSemaphore.ts @@ -0,0 +1,725 @@ +/** + * Coordinates access to limited resources inside transactions. + * + * A `TxSemaphore` has a fixed capacity and stores its available permit count in + * a `TxRef`. Acquiring or releasing permits can therefore commit atomically + * with other transactional state changes. This module includes operations for + * creating semaphores, checking capacity and availability, acquiring or + * releasing permits, and running effects while permits are held. + * + * @since 4.0.0 + */ + +import * as Effect from "./Effect.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Scope from "./Scope.ts" +import * as TxRef from "./TxRef.ts" + +const TypeId = "~effect/transactions/TxSemaphore" + +/** + * A transactional semaphore that manages permits using Software Transactional + * Memory (STM) semantics, providing atomic permit acquisition and release + * operations within Effect transactions for concurrency control over limited + * resources. + * + * **When to use** + * + * Use to coordinate permit accounting atomically with other transactional + * state changes. + * + * **Example** (Managing permits transactionally) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * // Create a semaphore with 3 permits for managing concurrent database connections + * const program = Effect.gen(function*() { + * const dbSemaphore = yield* TxSemaphore.make(3) + * + * // Acquire a permit before accessing the database + * yield* TxSemaphore.acquire(dbSemaphore) + * const acquired = yield* TxSemaphore.available(dbSemaphore) + * + * // Perform database operations... + * + * // Release the permit when done + * yield* TxSemaphore.release(dbSemaphore) + * const released = yield* TxSemaphore.available(dbSemaphore) + * return [acquired, released] as const + * }) + * + * await Effect.runPromise(program) // => [2, 3] + * ``` + * + * @see {@link make} for creating a transactional semaphore + * @see {@link withPermit} for automatically acquiring and releasing one permit + * @see {@link acquire} for manually acquiring one permit transactionally + * + * @category models + * @since 4.0.0 + */ +export interface TxSemaphore extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + readonly permitsRef: TxRef.TxRef + readonly capacity: number +} + +const TxSemaphoreProto: Omit = { + [NodeInspectSymbol](this: TxSemaphore) { + return toJson(this) + }, + toJSON(this: TxSemaphore) { + return { + _id: "TxSemaphore", + capacity: this.capacity + } + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +const makeTxSemaphore = (permitsRef: TxRef.TxRef, capacity: number): TxSemaphore => { + const self = Object.create(TxSemaphoreProto) + self[TypeId] = TypeId + self.permitsRef = permitsRef + self.capacity = capacity + return self +} + +/** + * Creates a new TxSemaphore with the specified number of permits. + * + * **When to use** + * + * Use to create a transactional semaphore with a fixed permit capacity. + * + * **Example** (Creating a semaphore) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * // Create a semaphore for managing concurrent access to a resource pool + * const program = Effect.gen(function*() { + * // Create a semaphore with 3 permits for a connection pool + * const connectionSemaphore = yield* TxSemaphore.make(3) + * + * // Check initial state + * const available = yield* TxSemaphore.available(connectionSemaphore) + * const capacity = yield* TxSemaphore.capacity(connectionSemaphore) + * return [capacity, available] as const + * }) + * + * await Effect.runPromise(program) // => [3, 3] + * ``` + * + * @see {@link available} for reading the current available permit count + * @see {@link capacity} for reading the fixed total permit count + * + * @category constructors + * @since 2.0.0 + */ +export const make = (permits: number): Effect.Effect => + Effect.gen(function*() { + if (permits < 0) { + return yield* Effect.die(new Error("Permits must be non-negative")) + } + + const permitsRef = yield* TxRef.make(permits) + return makeTxSemaphore(permitsRef, permits) + }).pipe(Effect.tx) + +/** + * Gets the current number of available permits in the semaphore. + * + * **When to use** + * + * Use to inspect how many permits are currently available. + * + * **Example** (Checking available permits) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(5) + * + * // Check available permits before acquiring + * const before = yield* TxSemaphore.available(semaphore) + * + * // Acquire some permits + * yield* TxSemaphore.acquire(semaphore) + * yield* TxSemaphore.acquire(semaphore) + * + * // Check available permits after acquiring + * const after = yield* TxSemaphore.available(semaphore) + * return [before, after] as const + * }) + * + * await Effect.runPromise(program) // => [5, 3] + * ``` + * + * @see {@link capacity} for reading the fixed total permit count + * + * @category combinators + * @since 2.0.0 + */ +export const available = (self: TxSemaphore): Effect.Effect => TxRef.get(self.permitsRef) + +/** + * Gets the maximum capacity (total permits) of the semaphore. + * + * **When to use** + * + * Use to inspect the fixed total number of permits managed by the semaphore. + * + * **Example** (Checking semaphore capacity) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(10) + * + * const capacity = yield* TxSemaphore.capacity(semaphore) + * + * // Capacity remains constant regardless of current permits + * yield* TxSemaphore.acquire(semaphore) + * const stillSame = yield* TxSemaphore.capacity(semaphore) + * return [capacity, stillSame] as const + * }) + * + * await Effect.runPromise(program) // => [10, 10] + * ``` + * + * @see {@link available} for reading the current available permit count + * + * @category combinators + * @since 4.0.0 + */ +export const capacity = (self: TxSemaphore): Effect.Effect => Effect.succeed(self.capacity) + +/** + * Acquires a single permit from the semaphore. If no permits are available, + * the effect will block until one becomes available. + * + * **When to use** + * + * Use to manually acquire one permit transactionally, waiting until one is + * available. + * + * **Example** (Acquiring a permit) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(2) + * + * yield* TxSemaphore.acquire(semaphore) + * + * yield* TxSemaphore.acquire(semaphore) + * + * return yield* TxSemaphore.available(semaphore) + * }) + * + * await Effect.runPromise(program) // => 0 + * ``` + * + * @see {@link tryAcquire} for a non-blocking single-permit attempt + * @see {@link release} for returning one permit + * @see {@link withPermit} for automatic acquire and release around an effect + * + * @category combinators + * @since 2.0.0 + */ +export const acquire = (self: TxSemaphore): Effect.Effect => + Effect.gen(function*() { + const permits = yield* TxRef.get(self.permitsRef) + if (permits <= 0) { + return yield* Effect.txRetry + } + yield* TxRef.set(self.permitsRef, permits - 1) + }).pipe(Effect.tx) + +/** + * Acquires the specified number of permits from the semaphore. + * + * **When to use** + * + * Use to manually acquire multiple permits transactionally, waiting until all + * requested permits are available. + * + * **Details** + * + * If fewer than `n` permits are available, the transaction retries until enough + * permits are released. + * + * **Gotchas** + * + * Passing a non-positive `n` dies with a defect. Passing a value greater than + * the semaphore capacity can wait forever because the capacity is fixed. + * + * **Example** (Acquiring multiple permits) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(5) + * + * yield* TxSemaphore.acquireN(semaphore, 3) + * + * return yield* TxSemaphore.available(semaphore) + * }) + * + * await Effect.runPromise(program) // => 2 + * ``` + * + * @see {@link tryAcquireN} for a non-blocking multi-permit attempt + * @see {@link releaseN} for returning multiple permits + * @see {@link withPermits} for automatic acquire and release around an effect + * + * @category combinators + * @since 2.0.0 + */ +export const acquireN = (self: TxSemaphore, n: number): Effect.Effect => { + if (n <= 0) { + return Effect.die(new Error("Number of permits must be positive")) + } + return Effect.gen(function*() { + const permits = yield* TxRef.get(self.permitsRef) + if (permits < n) { + return yield* Effect.txRetry + } + yield* TxRef.set(self.permitsRef, permits - n) + }).pipe(Effect.tx) +} + +/** + * Tries to acquire a single permit from the semaphore without blocking, + * returning `true` if successful or `false` if no permits are available. + * + * **When to use** + * + * Use to attempt a single-permit acquisition without retrying when no permit is + * available. + * + * **Example** (Trying to acquire a permit) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(1) + * + * // First try should succeed + * const first = yield* TxSemaphore.tryAcquire(semaphore) + * + * // Second try should fail (no permits left) + * const second = yield* TxSemaphore.tryAcquire(semaphore) + * return [first, second] as const + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @see {@link acquire} for waiting until one permit is available + * @see {@link tryAcquireN} for attempting to acquire multiple permits without blocking + * + * @category combinators + * @since 4.0.0 + */ +export const tryAcquire = (self: TxSemaphore): Effect.Effect => + TxRef.modify(self.permitsRef, (permits: number) => { + if (permits > 0) { + return [true, permits - 1] + } + return [false, permits] + }) + +/** + * Tries to acquire the specified number of permits from the semaphore without + * blocking, returning `true` if successful or `false` if not enough permits are + * available. + * + * **When to use** + * + * Use to attempt a multi-permit acquisition without retrying when not enough + * permits are available. + * + * **Example** (Trying to acquire multiple permits) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(3) + * + * // Try to acquire 2 permits (should succeed) + * const first = yield* TxSemaphore.tryAcquireN(semaphore, 2) + * + * // Try to acquire 2 more permits (should fail, only 1 left) + * const second = yield* TxSemaphore.tryAcquireN(semaphore, 2) + * return [first, second] as const + * }) + * + * await Effect.runPromise(program) // => [true, false] + * ``` + * + * @see {@link acquireN} for waiting until all requested permits are available + * @see {@link tryAcquire} for attempting to acquire one permit without blocking + * + * @category combinators + * @since 4.0.0 + */ +export const tryAcquireN = (self: TxSemaphore, n: number): Effect.Effect => { + if (n <= 0) { + return Effect.die(new Error("Number of permits must be positive")) + } + return TxRef.modify(self.permitsRef, (permits: number) => { + if (permits >= n) { + return [true, permits - n] + } + return [false, permits] + }) +} + +/** + * Releases one permit back to the semaphore, making it available for + * acquisition. + * + * **When to use** + * + * Use to manually return one permit after a transactional acquire. + * + * **Details** + * + * If the semaphore is already at capacity, this operation leaves the permit + * count unchanged. + * + * **Example** (Releasing a permit) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(2) + * + * // Acquire a permit + * yield* TxSemaphore.acquire(semaphore) + * const afterAcquire = yield* TxSemaphore.available(semaphore) + * + * // Release the permit + * yield* TxSemaphore.release(semaphore) + * const afterRelease = yield* TxSemaphore.available(semaphore) + * return [afterAcquire, afterRelease] as const + * }) + * + * await Effect.runPromise(program) // => [1, 2] + * ``` + * + * @see {@link acquire} for manually acquiring one permit + * @see {@link releaseN} for returning multiple permits + * + * @category combinators + * @since 2.0.0 + */ +export const release = (self: TxSemaphore): Effect.Effect => + TxRef.update(self.permitsRef, (permits: number) => permits >= self.capacity ? permits : permits + 1) + +/** + * Releases the specified number of permits back to the semaphore. + * + * **When to use** + * + * Use to manually return multiple permits after a transactional acquire. + * + * **Details** + * + * The available permit count is capped at the semaphore capacity. + * + * **Gotchas** + * + * Passing a non-positive `n` dies with a defect. + * + * **Example** (Releasing multiple permits) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(5) + * + * // Acquire 3 permits + * yield* TxSemaphore.acquireN(semaphore, 3) + * const afterAcquire = yield* TxSemaphore.available(semaphore) + * + * // Release 2 permits + * yield* TxSemaphore.releaseN(semaphore, 2) + * const afterRelease = yield* TxSemaphore.available(semaphore) + * return [afterAcquire, afterRelease] as const + * }) + * + * await Effect.runPromise(program) // => [2, 4] + * ``` + * + * @see {@link acquireN} for manually acquiring multiple permits + * @see {@link release} for returning one permit + * + * @category combinators + * @since 2.0.0 + */ +export const releaseN = (self: TxSemaphore, n: number): Effect.Effect => { + if (n <= 0) { + return Effect.die(new Error("Number of permits must be positive")) + } + return TxRef.update(self.permitsRef, (permits: number) => { + const newPermits = permits + n + return newPermits > self.capacity ? self.capacity : newPermits + }) +} + +/** + * Executes an effect with a single permit from the semaphore. The permit is + * automatically acquired before execution and released afterwards, even if the + * effect fails or is interrupted. + * + * **When to use** + * + * Use to run an effect while automatically acquiring and releasing one + * transactional permit. + * + * **Details** + * + * The permit acquisition and release operations use atomic semantics to ensure + * proper resource management with Effect's scoped operations. + * + * **Example** (Running an effect with a permit) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(2) + * const events: Array = [] + * + * // Execute database operation with automatic permit management + * const result = yield* TxSemaphore.withPermit( + * semaphore, + * Effect.gen(function*() { + * events.push("permit acquired") + * yield* Effect.yieldNow + * events.push("operation complete") + * return "query result" + * }) + * ) + * + * // Permit is automatically released here + * const available = yield* TxSemaphore.available(semaphore) + * return [events, result, available] as const + * }) + * + * await Effect.runPromise(program) // => [["permit acquired", "operation complete"], "query result", 2] + * ``` + * + * @see {@link withPermits} for automatically acquiring and releasing multiple permits + * @see {@link withPermitScoped} for acquiring one permit for the current scope + * @see {@link acquire} for manual single-permit acquisition + * + * @category combinators + * @since 2.0.0 + */ +export const withPermit: { + (self: TxSemaphore): (effect: Effect.Effect) => Effect.Effect + (self: TxSemaphore, effect: Effect.Effect): Effect.Effect +} = ((...args: Array) => { + if (args.length === 1) { + const [self] = args + return (effect: Effect.Effect) => + Effect.acquireUseRelease( + acquire(self), + () => effect, + () => release(self) + ) + } + const [self, effect] = args + return Effect.acquireUseRelease( + acquire(self), + () => effect, + () => release(self) + ) +}) as any + +/** + * Runs an effect while holding the specified number of permits from the + * semaphore. + * + * **When to use** + * + * Use to run an effect while automatically acquiring and releasing multiple + * transactional permits. + * + * **Details** + * + * The permits are acquired before the effect starts and released after it + * completes, fails, or is interrupted. + * + * **Gotchas** + * + * Passing a non-positive `n` dies with a defect. Passing a value greater than + * the semaphore capacity can wait forever. + * + * **Example** (Running an effect with multiple permits) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(5) + * const events: Array = [] + * + * // Execute batch operation with 3 permits + * const results = yield* TxSemaphore.withPermits( + * semaphore, + * 3, + * Effect.gen(function*() { + * events.push("3 permits acquired") + * yield* Effect.yieldNow + * return ["result1", "result2", "result3"] + * }) + * ) + * + * // All 3 permits are automatically released here + * const available = yield* TxSemaphore.available(semaphore) + * return [events, results, available] as const + * }) + * + * await Effect.runPromise(program) // => [["3 permits acquired"], ["result1", "result2", "result3"], 5] + * ``` + * + * @see {@link withPermit} for automatically acquiring and releasing one permit + * @see {@link acquireN} for manual multi-permit acquisition + * + * @category combinators + * @since 2.0.0 + */ +export const withPermits: { + (self: TxSemaphore, n: number): (effect: Effect.Effect) => Effect.Effect + (self: TxSemaphore, n: number, effect: Effect.Effect): Effect.Effect +} = ((...args: Array) => { + if (args.length === 2) { + const [self, n] = args + return (effect: Effect.Effect) => + Effect.acquireUseRelease( + acquireN(self, n), + () => effect, + () => releaseN(self, n) + ) + } + const [self, n, effect] = args + return Effect.acquireUseRelease( + acquireN(self, n), + () => effect, + () => releaseN(self, n) + ) +}) as any + +/** + * Acquires a single permit from the semaphore in a scoped manner. The permit + * will be automatically released when the scope is closed, even if effects + * within the scope fail or are interrupted. + * + * **When to use** + * + * Use to acquire one transactional permit for the lifetime of the current + * scope. + * + * **Details** + * + * The permit acquisition and release operations use atomic semantics to ensure + * proper resource management with Effect's scoped operations. + * + * **Example** (Acquiring a scoped permit) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(3) + * const events: Array = [] + * + * yield* Effect.scoped( + * Effect.gen(function*() { + * // Acquire permit for the duration of this scope + * yield* TxSemaphore.withPermitScoped(semaphore) + * events.push("permit acquired for scope") + * + * // Do work within the scope + * yield* Effect.yieldNow + * events.push("work completed") + * + * // Permit will be automatically released when scope closes + * }) + * ) + * + * const available = yield* TxSemaphore.available(semaphore) + * return [events, available] as const + * }) + * + * await Effect.runPromise(program) // => [["permit acquired for scope", "work completed"], 3] + * ``` + * + * @see {@link withPermit} for acquiring one permit around a single effect + * @see {@link acquire} for manual single-permit acquisition + * + * @category combinators + * @since 2.0.0 + */ +export const withPermitScoped = (self: TxSemaphore): Effect.Effect => + Effect.acquireRelease( + acquire(self), + () => release(self) + ) + +/** + * Determines if the provided value is a TxSemaphore. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a `TxSemaphore`. + * + * **Example** (Checking semaphore values) + * + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" + * + * const program = Effect.gen(function*() { + * const semaphore = yield* TxSemaphore.make(5) + * const notSemaphore = { some: "object" } + * + * const semaphoreResult = TxSemaphore.isTxSemaphore(semaphore) + * const objectResult = TxSemaphore.isTxSemaphore(notSemaphore) + * + * // Useful for runtime type checking in generic functions + * if (TxSemaphore.isTxSemaphore(semaphore)) { + * const available = yield* TxSemaphore.available(semaphore) + * return [semaphoreResult, objectResult, available] as const + * } + * return [semaphoreResult, objectResult, 0] as const + * }) + * + * await Effect.runPromise(program) // => [true, false, 5] + * ``` + * + * @see {@link make} for creating a `TxSemaphore` + * + * @category guards + * @since 4.0.0 + */ +export const isTxSemaphore = (u: unknown): u is TxSemaphore => hasProperty(u, TypeId) diff --git a/.repos/effect/packages/effect/src/TxSubscriptionRef.ts b/.repos/effect/packages/effect/src/TxSubscriptionRef.ts new file mode 100644 index 000000000..668c0de89 --- /dev/null +++ b/.repos/effect/packages/effect/src/TxSubscriptionRef.ts @@ -0,0 +1,529 @@ +/** + * Stores transactional state and publishes committed changes. + * + * A `TxSubscriptionRef` combines a `TxRef` for the current value with a + * transactional pub/sub channel for updates. Subscribers first receive the + * current value and then every later value that is published by committed + * updates. This module includes constructors, reads, writes, update and modify + * helpers, transactional-queue subscriptions, stream subscriptions, and a guard. + * + * @since 4.0.0 + */ +import * as Effect from "./Effect.ts" +import { dual } from "./Function.ts" +import type { Inspectable } from "./Inspectable.ts" +import { NodeInspectSymbol, toJson } from "./Inspectable.ts" +import type { Pipeable } from "./Pipeable.ts" +import { pipeArguments } from "./Pipeable.ts" +import { hasProperty } from "./Predicate.ts" +import type * as Scope from "./Scope.ts" +import * as Stream from "./Stream.ts" +import * as TxPubSub from "./TxPubSub.ts" +import * as TxQueue from "./TxQueue.ts" +import * as TxRef from "./TxRef.ts" + +const TypeId = "~effect/transactions/TxSubscriptionRef" + +/** + * A TxSubscriptionRef is a transactional reference that allows subscribing to all + * committed changes. Subscribers receive the current value followed by every subsequent + * update via a transactional dequeue. + * + * **When to use** + * + * Use to store transactional state whose committed changes must be observable by + * subscribers. + * + * **Example** (Subscribing to transactional changes) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(0) + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxSubscriptionRef.changes(ref) + * const initial = yield* TxQueue.take(sub) + * + * yield* TxSubscriptionRef.set(ref, 1) + * const next = yield* TxQueue.take(sub) + * return [initial, next] + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => [0, 1] + * ``` + * + * @see {@link make} for creating a transactional subscription reference + * @see {@link changes} for subscribing through a transactional queue + * @see {@link changesStream} for subscribing through a `Stream` + * + * @category models + * @since 4.0.0 + */ +export interface TxSubscriptionRef extends Inspectable, Pipeable { + readonly [TypeId]: typeof TypeId + /** @internal */ + readonly ref: TxRef.TxRef + /** @internal */ + readonly pubsub: TxPubSub.TxPubSub +} + +const TxSubscriptionRefProto: Omit, typeof TypeId | "ref" | "pubsub"> = { + [NodeInspectSymbol](this: TxSubscriptionRef) { + return toJson(this) + }, + toJSON(this: TxSubscriptionRef) { + return { _id: "TxSubscriptionRef" } + }, + toString() { + return "TxSubscriptionRef" + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +// ============================================================================= +// Constructors +// ============================================================================= + +/** + * Creates a new TxSubscriptionRef with the specified initial value. + * + * **When to use** + * + * Use to create a `TxSubscriptionRef` that publishes every committed update to + * subscribers. + * + * **Example** (Creating a transactional subscription reference) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(42) + * return yield* TxSubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @see {@link changes} for subscribing to the created reference + * + * @category constructors + * @since 3.10.0 + */ +export const make = (value: A): Effect.Effect> => + Effect.gen(function*() { + const ref = yield* TxRef.make(value) + const pubsub = yield* TxPubSub.unbounded() + const self = Object.create(TxSubscriptionRefProto) + self[TypeId] = TypeId + self.ref = ref + self.pubsub = pubsub + return self + }).pipe(Effect.tx) + +// ============================================================================= +// Getters +// ============================================================================= + +/** + * Reads the current value of the TxSubscriptionRef. + * + * **When to use** + * + * Use to read the current `TxSubscriptionRef` value without subscribing to + * future changes. + * + * **Example** (Reading the current value) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make("hello") + * return yield* TxSubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => "hello" + * ``` + * + * @see {@link changes} for reading the current value and subsequent updates + * + * @category getters + * @since 3.10.0 + */ +export const get = (self: TxSubscriptionRef): Effect.Effect => TxRef.get(self.ref) + +// ============================================================================= +// Mutations +// ============================================================================= + +/** + * Modifies the value of the TxSubscriptionRef using a function that returns both a + * result and the new value. The new value is published to all subscribers atomically. + * + * **When to use** + * + * Use to compute a separate return value and next `TxSubscriptionRef` state in + * one transactional update. + * + * **Example** (Modifying and returning a value) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(10) + * const result = yield* TxSubscriptionRef.modify(ref, (n) => [`was ${n}`, n + 1]) + * return [result, yield* TxSubscriptionRef.get(ref)] + * }) + * + * await Effect.runPromise(program) // => ["was 10", 11] + * ``` + * + * @see {@link update} for deriving the next value without a separate return value + * @see {@link set} for replacing the value directly + * + * @category mutations + * @since 3.10.0 + */ +export const modify: { + ( + f: (current: A) => [returnValue: B, newValue: A] + ): (self: TxSubscriptionRef) => Effect.Effect + ( + self: TxSubscriptionRef, + f: (current: A) => [returnValue: B, newValue: A] + ): Effect.Effect +} = dual( + 2, + ( + self: TxSubscriptionRef, + f: (current: A) => [returnValue: B, newValue: A] + ): Effect.Effect => + Effect.gen(function*() { + const current = yield* TxRef.get(self.ref) + const [returnValue, newValue] = f(current) + yield* TxRef.set(self.ref, newValue) + yield* TxPubSub.publish(self.pubsub, newValue) + return returnValue + }).pipe(Effect.tx) +) + +/** + * Sets the value of the TxSubscriptionRef and publishes the new value to all subscribers. + * + * **When to use** + * + * Use to replace the current `TxSubscriptionRef` value with a known value and + * publish it. + * + * **Example** (Setting a new value) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(0) + * yield* TxSubscriptionRef.set(ref, 42) + * return yield* TxSubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 42 + * ``` + * + * @see {@link update} for deriving the new value from the current value + * @see {@link getAndSet} for setting while returning the previous value + * + * @category mutations + * @since 3.10.0 + */ +export const set: { + (value: A): (self: TxSubscriptionRef) => Effect.Effect + (self: TxSubscriptionRef, value: A): Effect.Effect +} = dual( + 2, + (self: TxSubscriptionRef, value: A): Effect.Effect => modify(self, () => [void 0, value]) +) + +/** + * Updates the value of the TxSubscriptionRef using a function and publishes the new + * value to all subscribers. + * + * **When to use** + * + * Use to derive the next `TxSubscriptionRef` value from the current value and + * publish it. + * + * **Example** (Updating a value) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(5) + * yield* TxSubscriptionRef.update(ref, (n) => n * 2) + * return yield* TxSubscriptionRef.get(ref) + * }) + * + * await Effect.runPromise(program) // => 10 + * ``` + * + * @see {@link set} for replacing the value directly + * @see {@link updateAndGet} for returning the new value after the update + * + * @category mutations + * @since 3.10.0 + */ +export const update: { + (f: (current: A) => A): (self: TxSubscriptionRef) => Effect.Effect + (self: TxSubscriptionRef, f: (current: A) => A): Effect.Effect +} = dual( + 2, + (self: TxSubscriptionRef, f: (current: A) => A): Effect.Effect => + modify(self, (current) => [void 0, f(current)]) +) + +/** + * Gets the current value and sets a new value atomically. Publishes the new value + * to all subscribers. + * + * **When to use** + * + * Use to replace a `TxSubscriptionRef` value while returning the previous value + * and publishing the update to subscribers. + * + * **Example** (Getting and setting atomically) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make("a") + * const old = yield* TxSubscriptionRef.getAndSet(ref, "b") + * return [old, yield* TxSubscriptionRef.get(ref)] + * }) + * + * await Effect.runPromise(program) // => ["a", "b"] + * ``` + * + * @see {@link set} for setting without returning the previous value + * @see {@link getAndUpdate} for deriving the new value from the previous value + * + * @category mutations + * @since 3.10.0 + */ +export const getAndSet: { + (value: A): (self: TxSubscriptionRef) => Effect.Effect + (self: TxSubscriptionRef, value: A): Effect.Effect +} = dual( + 2, + (self: TxSubscriptionRef, value: A): Effect.Effect => modify(self, (current) => [current, value]) +) + +/** + * Gets the current value and updates it using a function atomically. Publishes + * the new value to all subscribers. + * + * **When to use** + * + * Use to derive and publish a new `TxSubscriptionRef` value while returning the + * previous value. + * + * **Example** (Getting and updating atomically) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(1) + * const old = yield* TxSubscriptionRef.getAndUpdate(ref, (n) => n + 10) + * return [old, yield* TxSubscriptionRef.get(ref)] + * }) + * + * await Effect.runPromise(program) // => [1, 11] + * ``` + * + * @see {@link update} for updating without returning the previous value + * @see {@link updateAndGet} for returning the new value instead + * + * @category mutations + * @since 3.10.0 + */ +export const getAndUpdate: { + (f: (current: A) => A): (self: TxSubscriptionRef) => Effect.Effect + (self: TxSubscriptionRef, f: (current: A) => A): Effect.Effect +} = dual( + 2, + (self: TxSubscriptionRef, f: (current: A) => A): Effect.Effect => + modify(self, (current) => [current, f(current)]) +) + +/** + * Updates the value using a function and returns the new value. Publishes the + * new value to all subscribers. + * + * **When to use** + * + * Use to derive and publish a new `TxSubscriptionRef` value while returning + * that new value. + * + * **Example** (Updating and reading atomically) + * + * ```ts import.meta.vitest + * import { Effect, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(3) + * return yield* TxSubscriptionRef.updateAndGet(ref, (n) => n * 3) + * }) + * + * await Effect.runPromise(program) // => 9 + * ``` + * + * @see {@link update} for updating without returning the new value + * @see {@link getAndUpdate} for returning the previous value instead + * + * @category mutations + * @since 3.10.0 + */ +export const updateAndGet: { + (f: (current: A) => A): (self: TxSubscriptionRef) => Effect.Effect + (self: TxSubscriptionRef, f: (current: A) => A): Effect.Effect +} = dual( + 2, + (self: TxSubscriptionRef, f: (current: A) => A): Effect.Effect => + modify(self, (current) => { + const newValue = f(current) + return [newValue, newValue] + }) +) + +// ============================================================================= +// Subscriptions +// ============================================================================= + +/** + * Subscribes to all changes of the TxSubscriptionRef. Returns a scoped TxDequeue + * that first yields the current value, then every subsequent update. + * + * **When to use** + * + * Use to subscribe to `TxSubscriptionRef` committed changes through a scoped + * transactional queue. + * + * **Example** (Subscribing to changes) + * + * ```ts import.meta.vitest + * import { Effect, TxQueue, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(0) + * + * return yield* Effect.scoped( + * Effect.gen(function*() { + * const sub = yield* TxSubscriptionRef.changes(ref) + * const initial = yield* TxQueue.take(sub) + * + * yield* TxSubscriptionRef.set(ref, 1) + * const next = yield* TxQueue.take(sub) + * return [initial, next] + * }) + * ) + * }) + * + * await Effect.runPromise(program) // => [0, 1] + * ``` + * + * @see {@link changesStream} for subscribing through a `Stream` + * + * @category subscriptions + * @since 3.10.0 + */ +export const changes = ( + self: TxSubscriptionRef +): Effect.Effect, never, Scope.Scope> => + Effect.acquireRelease( + Effect.tx( + Effect.gen(function*() { + const sub = yield* TxPubSub.acquireSubscriber(self.pubsub) + const current = yield* TxRef.get(self.ref) + yield* TxQueue.offer(sub, current) + return sub + }) + ), + (queue) => Effect.tx(TxPubSub.releaseSubscriber(self.pubsub, queue)) + ) + +/** + * Returns a Stream of all changes to the TxSubscriptionRef, starting with the + * current value followed by every subsequent update. + * + * **When to use** + * + * Use to consume `TxSubscriptionRef` committed changes as a `Stream`. + * + * **Example** (Streaming changes) + * + * ```ts import.meta.vitest + * import { Effect, Stream, TxSubscriptionRef } from "effect" + * + * const program = Effect.gen(function*() { + * const ref = yield* TxSubscriptionRef.make(0) + * yield* TxSubscriptionRef.set(ref, 1) + * yield* TxSubscriptionRef.set(ref, 2) + * + * const values = yield* Stream.runCollect( + * TxSubscriptionRef.changesStream(ref).pipe(Stream.take(1)) + * ) + * return Array.from(values) + * }) + * + * await Effect.runPromise(program) // => [2] + * ``` + * + * @see {@link changes} for subscribing through a transactional queue + * + * @category subscriptions + * @since 3.10.0 + */ +export const changesStream = (self: TxSubscriptionRef): Stream.Stream => + Stream.unwrap( + Effect.map( + changes(self), + (sub) => Stream.fromEffectRepeat(Effect.tx(TxQueue.take(sub))) + ) + ) + +// ============================================================================= +// Guards +// ============================================================================= + +/** + * Checks whether the given value is a TxSubscriptionRef. + * + * **When to use** + * + * Use to narrow an unknown value before treating it as a `TxSubscriptionRef`. + * + * **Example** (Checking transactional subscription references) + * + * ```ts import.meta.vitest + * import { TxSubscriptionRef } from "effect" + * + * const someValue: unknown = {} + * TxSubscriptionRef.isTxSubscriptionRef(someValue) // => false + * ``` + * + * @see {@link make} for creating a `TxSubscriptionRef` + * + * @category guards + * @since 4.0.0 + */ +export const isTxSubscriptionRef = (u: unknown): u is TxSubscriptionRef => hasProperty(u, TypeId) diff --git a/.repos/effect/packages/effect/src/Types.ts b/.repos/effect/packages/effect/src/Types.ts new file mode 100644 index 000000000..a403f5c48 --- /dev/null +++ b/.repos/effect/packages/effect/src/Types.ts @@ -0,0 +1,1178 @@ +/** + * Provides compile-time utility types for TypeScript. + * + * Everything in this module is type-level only; it does not define runtime + * values. The types are used throughout Effect to work with tuple lengths, + * object shapes, tagged unions, reason-tagged errors, mutability, exactness, + * required keys, concurrency settings, and variance markers. + * + * @since 4.0.0 + */ + +/** + * @category utility types + * @since 2.0.0 + */ +type TupleOf_> = `${N}` extends `-${number}` ? never + : R["length"] extends N ? R + : TupleOf_ + +/** + * Constructs a tuple type with exactly `N` elements of type `T`. + * + * **When to use** + * + * Use when you need a fixed-length array type, especially instead of manually + * writing `[T, T, T, ...]` for longer tuples. + * + * **Details** + * + * - If `N` is a literal number, produces a tuple of that exact length. + * - If `N` is the general `number` type (non-literal), degrades to `Array`. + * - Negative numbers produce `never`. + * + * **Example** (Checking fixed-length tuples) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * // Exactly 3 numbers + * const triple: Types.TupleOf<3, number> = [1, 2, 3] + * + * // @ts-expect-error - too few elements + * const tooFew: Types.TupleOf<3, number> = [1, 2] + * + * // @ts-expect-error - too many elements + * const tooMany: Types.TupleOf<3, number> = [1, 2, 3, 4] + * ``` + * + * @see {@link TupleOfAtLeast} + * + * @category utility types + * @since 3.3.0 + */ +export type TupleOf = N extends N ? number extends N ? Array : TupleOf_ : never + +/** + * Constructs a tuple type with at least `N` elements of type `T`. + * + * **When to use** + * + * Use when you need a minimum-length array type that still allows additional + * elements. This is useful for variadic function signatures that require a + * minimum arity. + * + * **Details** + * + * Produces a tuple with `N` fixed positions followed by `...Array`. + * + * **Example** (Checking minimum-length tuples) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * // At least 2 strings + * const ok1: Types.TupleOfAtLeast<2, string> = ["a", "b"] + * const ok2: Types.TupleOfAtLeast<2, string> = ["a", "b", "c", "d"] + * + * // @ts-expect-error - too few elements + * const bad: Types.TupleOfAtLeast<2, string> = ["a"] + * ``` + * + * @see {@link TupleOf} + * + * @category utility types + * @since 3.3.0 + */ +export type TupleOfAtLeast = [...TupleOf, ...Array] + +/** + * Extracts the `_tag` string literal types from a union. + * + * **When to use** + * + * Use to get all discriminant values from a tagged union type. + * + * **Details** + * + * Members without a `_tag` field are ignored and produce `never`. + * + * **Example** (Extracting tags) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type MyError = + * | { readonly _tag: "NotFound"; readonly id: string } + * | { readonly _tag: "Timeout"; readonly ms: number } + * | string + * + * type Result = Types.Tags + * // "NotFound" | "Timeout" + * + * const witness: Result = "NotFound" + * ``` + * + * @see {@link ExtractTag} + * @see {@link ExcludeTag} + * + * @category utility types + * @since 2.0.0 + */ +export type Tags = E extends { readonly _tag: string } ? E["_tag"] : never + +/** + * Excludes members of a tagged union by their `_tag` value. + * + * **When to use** + * + * Use to remove tagged-union members whose `_tag` matches a specific value in + * type-level code. + * + * **Details** + * + * Non-tagged members of the union are preserved. + * + * **Example** (Removing a variant) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type MyError = + * | { readonly _tag: "NotFound"; readonly id: string } + * | { readonly _tag: "Timeout"; readonly ms: number } + * | string + * + * type WithoutTimeout = Types.ExcludeTag + * // { readonly _tag: "NotFound"; readonly id: string } | string + * + * const witness: WithoutTimeout = { _tag: "NotFound", id: "1" } + * ``` + * + * @see {@link ExtractTag} + * @see {@link Tags} + * + * @category utility types + * @since 2.0.0 + */ +export type ExcludeTag = Exclude + +/** + * Extracts a specific member of a tagged union by its `_tag` value. + * + * **When to use** + * + * Use to select tagged-union members whose `_tag` matches a specific value in + * type-level code. + * + * **Details** + * + * Returns `never` if no member matches the tag. + * + * **Example** (Extracting a variant) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type MyError = + * | { readonly _tag: "NotFound"; readonly id: string } + * | { readonly _tag: "Timeout"; readonly ms: number } + * + * type TimeoutError = Types.ExtractTag + * // { readonly _tag: "Timeout"; readonly ms: number } + * + * const witness: TimeoutError = { _tag: "Timeout", ms: 100 } + * ``` + * + * @see {@link ExcludeTag} + * @see {@link Tags} + * + * @category utility types + * @since 2.0.0 + */ +export type ExtractTag = E extends { readonly _tag: infer T } ? K extends T ? E : never : never + +/** + * Transforms a union type into an intersection type. + * + * **When to use** + * + * Use to combine all members of a union into a single type with all their + * properties. This is useful in advanced generic code where you need to merge + * union variants. + * + * **Details** + * + * - Uses distributive conditional types and contra-variant inference. + * - If the union members are incompatible (e.g. `string | number`), the + * result is `never`. + * + * **Example** (Converting a union to an intersection) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Union = { a: string } | { b: number } + * type Result = Types.UnionToIntersection + * // { a: string } & { b: number } + * + * const witness: Result = { a: "value", b: 1 } + * ``` + * + * @see {@link IsUnion} + * + * @category utility types + * @since 2.0.0 + */ +export type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R + : never + +/** + * Flattens an intersection type into a single object type for readability. + * + * **When to use** + * + * Use to clean up IDE tooltips that show `A & B & C` instead of a merged + * object. + * + * **Details** + * + * Does not change the type semantically, only its display. + * + * **Example** (Simplifying an intersection) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * // Without Simplify: IDE shows { a: number } & { b: string } + * // With Simplify: IDE shows { a: number; b: string } + * type Clean = Types.Simplify<{ a: number } & { b: string }> + * + * const witness: Clean = { a: 1, b: "value" } + * ``` + * + * @see {@link MergeLeft} + * @see {@link MergeRight} + * + * @category utility types + * @since 2.0.0 + */ +export type Simplify = { + [K in keyof A]: A[K] +} extends infer B ? B : never + +/** + * Determines if two types are exactly equal at the type level. + * + * **When to use** + * + * Use to assert type equality in conditional types or type-level tests. + * + * **Details** + * + * - Uses the `() => T extends X ? 1 : 2` trick for exact equality, + * distinguishing between `any`, `unknown`, `never`, and other types. + * - Resolves to `true` if `X` and `Y` are identical, `false` otherwise. + * + * **Example** (Checking type equality) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Yes = Types.Equals<{ a: number }, { a: number }> // true + * type No = Types.Equals<{ a: number }, { a: string }> // false + * type AnyCheck = Types.Equals // false + * ``` + * + * @see {@link EqualsWith} + * + * @category utility types + * @since 2.0.0 + */ +export type Equals = (() => T extends X ? 1 : 2) extends < + T +>() => T extends Y ? 1 : 2 ? true + : false + +/** + * Determines if two types are equal, returning custom types for each case. + * + * **When to use** + * + * Use when you need a type-level if/else based on type equality. + * + * **Details** + * + * Returns `Y` when `A` and `B` are equal, `N` otherwise. + * + * **Example** (Choosing a conditional type based on equality) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type R1 = Types.EqualsWith // "same" + * type R2 = Types.EqualsWith // "diff" + * ``` + * + * @see {@link Equals} + * + * @category utility types + * @since 3.15.0 + */ +export type EqualsWith = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? Y : N + +/** + * Checks whether an object type contains any of the specified keys. + * + * **When to use** + * + * Use to branch type-level logic when at least one key from a candidate key set + * exists on an object type. + * + * **Details** + * + * Returns `true` if at least one key from `Key` exists in `A`, `false` + * otherwise. + * + * **Example** (Checking key presence) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Yes = Types.Has<{ a: number; b: string }, "a" | "c"> // true + * type No = Types.Has<{ a: number }, "b" | "c"> // false + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export type Has = (Key extends infer K ? K extends keyof A ? true : never : never) extends never + ? false + : true + +/** + * Left-biased merge of two object types where keys from `Source` take + * precedence over `Target` on conflict. + * + * **When to use** + * + * Use when you want left-biased merging where the first argument wins. + * + * **Details** + * + * Implemented as `MergeRight`. + * + * **Example** (Merging with left bias) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Result = Types.MergeLeft< + * { a: number; b: number }, + * { a: string; c: boolean } + * > + * // { a: number; b: number; c: boolean } + * + * const witness: Result = { a: 1, b: 2, c: true } + * ``` + * + * @see {@link MergeRight} + * @see {@link Simplify} + * + * @category utility types + * @since 2.0.0 + */ +export type MergeLeft = MergeRight + +/** + * Right-biased merge of two object types where keys from `Source` take + * precedence over `Target` on conflict. + * + * **When to use** + * + * Use when you want right-biased merging where the second argument wins. + * + * **Details** + * + * The result is automatically simplified via {@link Simplify}. + * + * **Example** (Right-biased merge) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Result = Types.MergeRight< + * { a: number; b: number }, + * { a: string; c: boolean } + * > + * // { a: string; b: number; c: boolean } + * + * const witness: Result = { a: "value", b: 2, c: true } + * ``` + * + * @see {@link MergeLeft} + * @see {@link Simplify} + * + * @category utility types + * @since 2.0.0 + */ +export type MergeRight = Simplify< + & Source + & { + [Key in keyof Target as Key extends keyof Source ? never : Key]: Target[Key] + } +> + +/** + * Describes the concurrency level for Effect operations that run multiple + * effects. + * + * **When to use** + * + * Use to type options that control how many effects may run at the same time. + * + * **Details** + * + * - `number` — run at most N effects concurrently. + * - `"unbounded"` — run all effects concurrently with no limit. + * + * **Example** (Setting concurrency values) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * const sequential: Types.Concurrency = 1 + * const limited: Types.Concurrency = 5 + * const unbounded: Types.Concurrency = "unbounded" + * ``` + * + * @category models + * @since 2.0.0 + */ +export type Concurrency = number | "unbounded" + +/** + * Removes `readonly` from all properties of `T`. Supports arrays, tuples, + * and records. + * + * **When to use** + * + * Use when you need a mutable version of a readonly type. + * + * **Details** + * + * Only affects the top level; nested properties remain readonly. + * + * **Example** (Converting shallowly to mutable types) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Obj = Types.Mutable<{ + * readonly a: string + * readonly b: ReadonlyArray + * }> + * // { a: string; b: ReadonlyArray } + * // ^ mutable ^ still readonly inside + * + * type Arr = Types.Mutable> + * // string[] + * + * type Tup = Types.Mutable + * // [string, number] + * + * const tuple: Tup = ["value", 1] + * tuple[1] = 2 + * ``` + * + * @see {@link DeepMutable} + * + * @category utility types + * @since 2.0.0 + */ +export type Mutable = { + -readonly [P in keyof T]: T[P] +} + +/** + * Recursively removes `readonly` from all properties, including nested + * objects, arrays, `Map`, and `Set`. + * + * **When to use** + * + * Use when you need a fully mutable version of a deeply readonly type. + * + * **Details** + * + * Recursion stops at primitives (`string`, `number`, `boolean`, `bigint`, + * `symbol`) and functions. + * + * **Example** (Converting deeply to mutable types) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Deep = Types.DeepMutable<{ + * readonly a: string + * readonly b: ReadonlyArray<{ readonly c: number }> + * }> + * // { a: string; b: Array<{ c: number }> } + * + * const witness: Deep = { a: "value", b: [{ c: 1 }] } + * witness.b[0].c = 2 + * ``` + * + * @see {@link Mutable} + * + * @category utility types + * @since 3.1.0 + */ +export type DeepMutable = T extends ReadonlyMap ? Map, DeepMutable> + : T extends ReadonlySet ? Set> + : T extends string | number | boolean | bigint | symbol | Function ? T + : { -readonly [K in keyof T]: DeepMutable } + +/** + * Prevents TypeScript from inferring a type parameter from a specific + * position. + * + * **When to use** + * + * Use when a function parameter must match an inferred type without becoming + * an inference source. + * + * **Details** + * + * The parameter using `NoInfer` must still match the inferred type. + * + * **Example** (Controlling inference) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * function withDefault(value: T, _fallback: Types.NoInfer): T { + * return value + * } + * + * // T is inferred as "a" | "b" from the first argument only + * const result = withDefault<"a" | "b">("a", "b") + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export type NoInfer = [A][A extends any ? 0 : never] + +/** + * Function-type alias encoding invariant variance for a phantom type + * parameter. + * + * **When to use** + * + * Use as a phantom field type to make a type parameter invariant, neither + * covariant nor contravariant. + * + * **Details** + * + * A value of type `Invariant` cannot be assigned to `Invariant` unless + * `A` and `B` are the same type. + * + * **Example** (Defining an invariant phantom type) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * interface Container { + * readonly _phantom: Types.Invariant + * readonly value: T + * } + * + * const container: Container = { _phantom: (value) => value, value: 1 } + * ``` + * + * @see {@link Invariant.Type} + * @see {@link Covariant} + * @see {@link Contravariant} + * + * @category utility types + * @since 2.0.0 + */ +export type Invariant = (_: A) => A + +/** + * Namespace for {@link Invariant}-related utilities. + * + * **When to use** + * + * Use when referring to type-level helpers nested under `Invariant`. + * + * @since 3.9.0 + */ +export declare namespace Invariant { + /** + * Extracts the type parameter `A` from an `Invariant`. + * + * **When to use** + * + * Use to recover the carried type from an invariant phantom marker. + * + * **Example** (Extracting the inner type) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Inner = Types.Invariant.Type> + * // number + * + * const witness: Inner = 1 + * ``` + * + * @see {@link Invariant} + * + * @category utility types + * @since 3.9.0 + */ + export type Type = A extends Invariant ? U : never +} + +/** + * Function-type alias encoding covariant variance for a phantom type + * parameter. + * + * **When to use** + * + * Use as a phantom field type to make a type parameter covariant in output + * position. + * + * **Details** + * + * `Covariant` is assignable to `Covariant` when `A extends B`, following + * the subtype direction. + * + * **Example** (Defining a covariant phantom type) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * interface Producer { + * readonly _phantom: Types.Covariant + * readonly get: () => T + * } + * + * const producer: Producer = { _phantom: () => "value", get: () => "value" } + * ``` + * + * @see {@link Covariant.Type} + * @see {@link Contravariant} + * @see {@link Invariant} + * + * @category utility types + * @since 2.0.0 + */ +export type Covariant = (_: never) => A + +/** + * Namespace for {@link Covariant}-related utilities. + * + * **When to use** + * + * Use when referring to type-level helpers nested under `Covariant`. + * + * @since 3.9.0 + */ +export declare namespace Covariant { + /** + * Extracts the type parameter `A` from a `Covariant`. + * + * **When to use** + * + * Use to recover the carried type from a covariant phantom marker. + * + * **Example** (Extracting the inner type) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Inner = Types.Covariant.Type> + * // string + * + * const witness: Inner = "value" + * ``` + * + * @see {@link Covariant} + * + * @category utility types + * @since 3.9.0 + */ + export type Type = A extends Covariant ? U : never +} + +/** + * Function-type alias encoding contravariant variance for a phantom type + * parameter. + * + * **When to use** + * + * Use as a phantom field type to make a type parameter contravariant in input + * position. + * + * **Details** + * + * `Contravariant` is assignable to `Contravariant` when `B extends A`, + * following the supertype direction. + * + * **Example** (Defining a contravariant phantom type) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * interface Consumer { + * readonly _phantom: Types.Contravariant + * readonly accept: (value: T) => void + * } + * + * const consumer: Consumer = { + * _phantom: () => {}, + * accept: (_value) => {} + * } + * ``` + * + * @see {@link Contravariant.Type} + * @see {@link Covariant} + * @see {@link Invariant} + * + * @category utility types + * @since 2.0.0 + */ +export type Contravariant = (_: A) => void + +/** + * Namespace for {@link Contravariant}-related utilities. + * + * **When to use** + * + * Use when referring to type-level helpers nested under `Contravariant`. + * + * @since 3.9.0 + */ +export declare namespace Contravariant { + /** + * Extracts the type parameter `A` from a `Contravariant`. + * + * **When to use** + * + * Use to recover the carried type from a contravariant phantom marker. + * + * **Example** (Extracting the inner type) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Inner = Types.Contravariant.Type> + * // string + * + * const witness: Inner = "value" + * ``` + * + * @see {@link Contravariant} + * + * @category utility types + * @since 3.9.0 + */ + export type Type = A extends Contravariant ? U : never +} + +/** + * Conditional type that returns `void` if `S` is an empty object type, + * otherwise returns `S`. + * + * **When to use** + * + * Use to erase an empty object type from an API result or parameter position. + * + * @category utility types + * @since 3.19.20 + */ +export type VoidIfEmpty = keyof S extends never ? void : S + +/** + * Excludes function types from a union, keeping only non-function members. + * + * **When to use** + * + * Use to filter out callable types from a union. + * + * **Details** + * + * Returns `never` if the entire union consists of function types. + * + * **Example** (Filtering out functions) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Result = Types.NotFunction void) | number> + * // string | number + * + * const witness: Result = "value" + * ``` + * + * @category utility types + * @since 2.0.0 + */ +export type NotFunction = T extends Function ? never : T + +/** + * Constrains a type to prevent excess properties not present in `T`. + * + * **When to use** + * + * Use to catch accidental extra properties in generic functions at compile time. + * + * **Details** + * + * Extra keys from `U` that are not in `T` are mapped to `never`. + * + * **Example** (Preventing extra properties) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Expected = { a: number; b: string } + * type Input = { a: number; b: string; c: boolean } + * + * type Result = Types.NoExcessProperties + * // { a: number; b: string; readonly c: never } + * + * const accepted: Types.NoExcessProperties = { a: 1, b: "value" } + * ``` + * + * @category utility types + * @since 3.9.0 + */ +export type NoExcessProperties = T & Readonly, never>> + +/** + * Branded marker interface representing an unassigned type parameter. + * + * **When to use** + * + * Use when Effect's type-level machinery needs to represent a type parameter + * that has not been assigned yet. + * + * **Details** + * + * Used internally by the Effect type system to indicate that a type parameter + * has not been assigned a concrete type. + * + * @see {@link unhandled} + * + * @category utility types + * @since 4.0.0 + */ +export interface unassigned { + readonly _: unique symbol +} + +/** + * Branded marker interface representing an unhandled error type. + * + * **When to use** + * + * Use when Effect's type-level machinery needs to represent an error type that + * has not been handled yet. + * + * **Details** + * + * Used internally by the Effect type system to indicate that an error type + * has not been handled. + * + * @see {@link unassigned} + * + * @category utility types + * @since 4.0.0 + */ +export interface unhandled { + readonly _: unique symbol +} + +/** + * Checks whether a type `T` is a union type. + * + * **When to use** + * + * Use to branch type-level logic depending on whether a type is a union. + * + * **Details** + * + * - Compares `[T]` against `[UnionToIntersection]`. If they differ, `T` + * must be a union. + * - Returns `true` if `T` is a union of two or more members. + * - Returns `false` for single types, `never`, or `any`. + * + * **Example** (Detecting union types) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type Yes = Types.IsUnion<"a" | "b"> // true + * type No = Types.IsUnion // false + * ``` + * + * @see {@link UnionToIntersection} + * + * @category utility types + * @since 4.0.0 + */ +export type IsUnion = [T] extends [UnionToIntersection] ? false : true + +/** + * Extracts the `reason` type from an error that has a `reason` field. + * + * **When to use** + * + * Use when an error type stores nested sub-errors in a `reason` field and you + * need that field's full union type as a standalone type. + * + * **Details** + * + * Returns `never` if `E` has no `reason` field. + * + * **Example** (Extracting reason types) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } + * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number } + * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError } + * + * type Reasons = Types.ReasonOf + * // RateLimitError | QuotaError + * + * const witness: Reasons = { _tag: "QuotaError", limit: 10 } + * ``` + * + * @see {@link ReasonTags} + * @see {@link ExtractReason} + * @see {@link ExcludeReason} + * + * @category utility types + * @since 4.0.0 + */ +export type ReasonOf = E extends { readonly reason: infer R } ? R : never + +/** + * Extracts the `_tag` values from the `reason` type of an error. + * + * **When to use** + * + * Use to get the discriminant values available inside a nested `reason` + * error union. + * + * **Details** + * + * This is shorthand for `Tags>`. It returns `never` if `E` has no + * `reason` field or the reason has no `_tag`. + * + * **Example** (Getting reason tags) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } + * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number } + * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError } + * + * type Result = Types.ReasonTags + * // "RateLimitError" | "QuotaError" + * + * const witness: Result = "RateLimitError" + * ``` + * + * @see {@link ReasonOf} + * @see {@link ExtractReason} + * + * @category utility types + * @since 4.0.0 + */ +export type ReasonTags = E extends { readonly reason: { readonly _tag: string } } ? E["reason"]["_tag"] + : never + +/** + * Extracts a specific reason variant by its `_tag` from an error's `reason` + * field. + * + * **When to use** + * + * Use when you need the nested reason variant type itself, selected by `_tag`, + * rather than the enclosing error type. + * + * **Details** + * + * Returns `never` if `E` has no matching reason variant. + * + * **Example** (Extracting a reason variant) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } + * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number } + * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError } + * + * type Result = Types.ExtractReason + * // { readonly _tag: "RateLimitError"; readonly retryAfter: number } + * + * const witness: Result = { _tag: "RateLimitError", retryAfter: 30 } + * ``` + * + * @see {@link ExcludeReason} + * @see {@link ReasonOf} + * @see {@link ReasonTags} + * + * @category utility types + * @since 4.0.0 + */ +export type ExtractReason = E extends { readonly reason: infer R } + ? R extends { readonly _tag: infer T } ? K extends T ? R : never + : never + : never + +/** + * Narrows a specific reason variant by its `_tag` from an error's `reason` + * field. + * + * **When to use** + * + * Use to preserve the original error shape while narrowing its nested reason + * field to the matching variant. + * + * **Details** + * + * Returns `never` if `E` has no matching reason variant. + * + * **Example** (Narrowing a reason variant) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } + * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number } + * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError } + * + * type Result = Types.NarrowReason + * // ApiError & { readonly reason: { readonly _tag: "RateLimitError"; readonly retryAfter: number } } + * + * const witness: Result = { + * _tag: "ApiError", + * reason: { _tag: "RateLimitError", retryAfter: 30 } + * } + * ``` + * + * @see {@link ExcludeReason} + * @see {@link ReasonOf} + * @see {@link ReasonTags} + * + * @category utility types + * @since 4.0.0 + */ +export type NarrowReason = E extends { readonly reason: infer R } + ? R extends { readonly _tag: infer T } ? K extends T ? E & { readonly reason: R } : never + : never + : never + +/** + * Narrows an error's `reason` field to exclude a specific reason variant by + * its `_tag`. + * + * **When to use** + * + * Use to narrow the error to only the remaining reason variants after + * excluding the matched one. + * + * **Details** + * + * Returns `never` if `E` has no `reason` field or no remaining variants. + * + * **Example** (Omitting a reason variant) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } + * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number } + * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError } + * + * type Result = Types.OmitReason + * // ApiError & { readonly reason: { readonly _tag: "QuotaError"; readonly limit: number } } + * + * const witness: Result = { + * _tag: "ApiError", + * reason: { _tag: "QuotaError", limit: 10 } + * } + * ``` + * + * @see {@link NarrowReason} + * @see {@link ExcludeReason} + * @see {@link ReasonOf} + * @see {@link ReasonTags} + * + * @category utility types + * @since 4.0.0 + */ +export type OmitReason = E extends { readonly reason: infer R } + ? R extends { readonly _tag: infer T } ? K extends T ? never : E & { readonly reason: R } + : never + : never + +/** + * Excludes a specific reason variant by its `_tag` from an error's `reason` + * field. + * + * **When to use** + * + * Use when you need the remaining nested reason union type after removing + * variants handled by `_tag`, rather than the enclosing error type. + * + * **Details** + * + * Returns `never` if `E` has no `reason` field. + * + * **Example** (Excluding a reason variant) + * + * ```ts import.meta.vitest + * import type { Types } from "effect" + * + * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } + * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number } + * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError } + * + * type Result = Types.ExcludeReason + * // { readonly _tag: "QuotaError"; readonly limit: number } + * + * const witness: Result = { _tag: "QuotaError", limit: 10 } + * ``` + * + * @see {@link ExtractReason} + * @see {@link ReasonOf} + * @see {@link ReasonTags} + * + * @category utility types + * @since 4.0.0 + */ +export type ExcludeReason = E extends { readonly reason: infer R } + ? Exclude + : never + +/** + * Extracts the required keys from a type. + * + * **When to use** + * + * Use to derive the keys whose properties must be present on an object type. + * + * @category utility types + * @since 4.0.0 + */ +export type RequiredKeys = { [K in keyof T]-?: {} extends Pick ? never : K }[keyof T] diff --git a/.repos/effect/packages/effect/src/UndefinedOr.ts b/.repos/effect/packages/effect/src/UndefinedOr.ts new file mode 100644 index 000000000..eb9be8845 --- /dev/null +++ b/.repos/effect/packages/effect/src/UndefinedOr.ts @@ -0,0 +1,239 @@ +/** + * Works with values that may be `undefined`. + * + * Use this module for plain TypeScript values of type `A | undefined` when + * `undefined` is the only absence marker. It is a small alternative to wrapping + * values in `Option` when your data already uses `undefined` to mean "no + * value". The module includes helpers for mapping defined values, matching both + * cases, throwing when a value is missing, adapting throwing functions, and + * building reducers or combiners. + * + * @since 4.0.0 + */ +import * as Combiner from "./Combiner.ts" +import type { LazyArg } from "./Function.ts" +import { dual } from "./Function.ts" +import * as Reducer from "./Reducer.ts" + +/** + * Maps a defined value with `f`, or returns `undefined` unchanged. + * + * **When to use** + * + * Use to apply a pure transformation to an `A | undefined` value while + * preserving `undefined` as absence. + * + * @see {@link match} when you need to handle the `undefined` case explicitly + * + * @category mapping + * @since 4.0.0 + */ +export const map: { + (f: (a: A) => B): (self: A | undefined) => B | undefined + (self: A | undefined, f: (a: A) => B): B | undefined +} = dual(2, (self, f) => (self === undefined ? undefined : f(self))) + +/** + * Pattern matches on an `A | undefined` value, running `onDefined` when the + * value is present or evaluating `onUndefined` when the value is `undefined`. + * + * **When to use** + * + * Use when you need to turn an `A | undefined` into a non-optional result by + * handling both the defined and undefined branches in one expression. + * + * @see {@link map} for transforming defined values while preserving `undefined` + * @see {@link getOrThrowWith} for throwing when the value is `undefined` instead of returning a fallback branch + * + * @category pattern matching + * @since 4.0.0 + */ +export const match: { + (options: { + readonly onUndefined: LazyArg + readonly onDefined: (a: A) => C + }): (self: A | undefined) => B | C + (self: A | undefined, options: { + readonly onUndefined: LazyArg + readonly onDefined: (a: A) => C + }): B | C +} = dual( + 2, + (self: A | undefined, { onDefined, onUndefined }: { + readonly onUndefined: LazyArg + readonly onDefined: (a: A) => C + }): B | C => self === undefined ? onUndefined() : onDefined(self) +) + +/** + * Returns the defined value, or throws the value produced by `onUndefined` + * when the input is `undefined`. + * + * **When to use** + * + * Use when you need fail-fast unwrapping of an `A | undefined` value and want + * to provide the thrown error for the undefined case. + * + * **Details** + * + * Defined values are returned unchanged. When the input is `undefined`, + * `onUndefined` is called and its result is thrown. + * + * @see {@link getOrThrow} for the default-error sibling + * @see {@link match} for handling defined and undefined cases without throwing + * + * @category getters + * @since 4.0.0 + */ +export const getOrThrowWith: { + (onUndefined: () => unknown): (self: A | undefined) => A + (self: A | undefined, onUndefined: () => unknown): A +} = dual(2, (self: A | undefined, onUndefined: () => unknown): A => { + if (self !== undefined) { + return self + } + throw onUndefined() +}) + +/** + * Returns the defined value, or throws a default `Error` when the input is + * `undefined`. + * + * **When to use** + * + * Use when you need to unwrap a value that should already be defined and a + * generic missing-value `Error` is acceptable. + * + * **Details** + * + * Defined inputs are returned unchanged. `undefined` throws + * `new Error("getOrThrow called on a undefined")`. + * + * @see {@link getOrThrowWith} for the sibling that lets callers choose the thrown value + * @see {@link match} for handling defined and undefined cases without throwing + * + * @category getters + * @since 4.0.0 + */ +export const getOrThrow: (self: A | undefined) => A = getOrThrowWith(() => + new Error("getOrThrow called on a undefined") +) + +/** + * Converts a throwing function into one that returns successful results + * unchanged and returns `undefined` when the function throws. + * + * **When to use** + * + * Use to adapt exception-throwing functions when `undefined` is the absence + * value you want to return for failures. + * + * **Gotchas** + * + * Thrown values are discarded. If the wrapped function can successfully return + * `undefined`, that success is indistinguishable from a thrown failure. + * + * @category converting + * @since 4.0.0 + */ +export const liftThrowable = , B>( + f: (...a: A) => B +): (...a: A) => B | undefined => +(...a) => { + try { + return f(...a) + } catch { + return undefined + } +} + +/** + * Creates a `Reducer` for `UndefinedOr` that prioritizes the first non-`undefined` + * value and combines values when both operands are present. + * + * **When to use** + * + * Use when you need to reduce values that may be `undefined`, keeping the + * first defined value as a fallback and combining only when both operands are + * defined. + * + * **Details** + * + * Combining `undefined` with `undefined` returns `undefined`. Combining a + * defined value with `undefined` keeps the defined value, so the first defined + * value wins when only one side is present. When both values are defined, they + * are combined with `combiner.combine`. The reducer's initial value is + * `undefined`. + * + * @category constructors + * @since 4.0.0 + */ +export function makeReducer(combiner: Combiner.Combiner): Reducer.Reducer { + return Reducer.make((self, that) => { + if (self === undefined) return that + if (that === undefined) return self + return combiner.combine(self, that) + }, undefined as A | undefined) +} + +/** + * Creates a `Combiner` for `A | undefined` that combines values only when both + * operands are defined. + * + * **When to use** + * + * Use to lift a `Combiner` so any `undefined` operand makes the combined result + * `undefined`. + * + * **Details** + * + * - `undefined` combined with any value returns `undefined` + * - Any value combined with `undefined` returns `undefined` + * - `a` combined with `b` returns `combiner.combine(a, b)` + * + * @see {@link makeReducerFailFast} if you have a `Reducer` and want to lift it + * to `UndefinedOr` values. + * + * @category constructors + * @since 4.0.0 + */ +export function makeCombinerFailFast(combiner: Combiner.Combiner): Combiner.Combiner { + return Combiner.make((self, that) => { + if (self === undefined || that === undefined) return undefined + return combiner.combine(self, that) + }) +} + +/** + * Creates a `Reducer` for `A | undefined` by wrapping an existing reducer with + * fail-fast semantics. + * + * **When to use** + * + * Use to wrap an existing `Reducer` so any `undefined` value aborts the entire + * reduction result. + * + * **Details** + * + * - Initial value is the wrapped reducer's `initialValue` + * - Combining two defined values delegates to the wrapped reducer + * - If the accumulator or next value is `undefined`, the reduction returns `undefined` + * + * @see {@link makeCombinerFailFast} if you only have a `Combiner` and want to + * lift it to `UndefinedOr` values. + * + * @category constructors + * @since 4.0.0 + */ +export function makeReducerFailFast(reducer: Reducer.Reducer): Reducer.Reducer { + const combine = makeCombinerFailFast(reducer).combine + const initialValue = reducer.initialValue as A | undefined + return Reducer.make(combine, initialValue, (collection) => { + let out = initialValue + for (const value of collection) { + out = combine(out, value) + if (out === undefined) return out + } + return out + }) +} diff --git a/.repos/effect/packages/effect/src/Unify.ts b/.repos/effect/packages/effect/src/Unify.ts new file mode 100644 index 000000000..a2e280985 --- /dev/null +++ b/.repos/effect/packages/effect/src/Unify.ts @@ -0,0 +1,312 @@ +/** + * Defines Effect's type-level unification protocol. + * + * Unification collapses unions of protocol-enabled values into their public data + * types. It is mostly for maintainers of Effect data types and advanced library + * authors; application code usually benefits from it through APIs such as + * `Effect`, `Option`, `Result`, `Stream`, `Layer`, and `Match`. This module + * exports the protocol symbols, the `Unify` type that performs normalization, + * and `unify`, an identity function that changes only the inferred type. + * + * @since 2.0.0 + */ + +import { identity } from "./Function.ts" + +/** + * Defines the unique symbol used to identify unification behavior in Effect types. + * + * **When to use** + * + * Use to define the widened type produced by the `Unify` protocol for a custom + * protocol-enabled data type. + * + * **Details** + * + * This symbol is a type-level protocol key. It describes how a protocol-enabled + * type widens during unification and has no runtime behavior. + * + * @see {@link typeSymbol} for storing the source type information used during unification + * @see {@link ignoreSymbol} for excluding protocol entries from unification + * + * @category symbols + * @since 2.0.0 + */ +export declare const unifySymbol: unique symbol + +/** + * The type of the unifySymbol. + * + * **When to use** + * + * Use to reference the unification behavior property key in type-level + * protocol definitions. + * + * **Details** + * + * This type represents the unique symbol used for identifying unification + * behavior in Effect types. It's typically used in type-level operations + * to enable automatic type unification. + * + * @category symbols + * @since 2.0.0 + */ +export type unifySymbol = typeof unifySymbol + +/** + * Defines the unique symbol used to identify the type information for unification. + * + * **When to use** + * + * Use when you need a type-level protocol key that exposes the source type + * read by `Unify` from a protocol-enabled data type. + * + * **Details** + * + * This symbol is a type-level protocol key. It stores the source type that + * unification reads when widening protocol-enabled values. + * + * @see {@link unifySymbol} for defining how protocol entries widen + * + * @category symbols + * @since 2.0.0 + */ +export declare const typeSymbol: unique symbol + +/** + * The type of the typeSymbol. + * + * **When to use** + * + * Use to reference the type information property key in type-level protocol + * definitions. + * + * **Details** + * + * This type represents the unique symbol used for storing type information + * in types that support unification. It's used in type-level operations + * to access and manipulate type information. + * + * @category symbols + * @since 2.0.0 + */ +export type typeSymbol = typeof typeSymbol + +/** + * Defines the unique symbol used to specify types that should be ignored during unification. + * + * **When to use** + * + * Use to hide helper protocol entries from `Unify` when they should not + * contribute to the widened type. + * + * **Details** + * + * This symbol is a type-level protocol key. It lists protocol entries that + * unification should ignore when computing the widened type. + * + * @see {@link unifySymbol} for defining the protocol entries being filtered + * + * @category symbols + * @since 2.0.0 + */ +export declare const ignoreSymbol: unique symbol + +/** + * The type of the ignoreSymbol. + * + * **When to use** + * + * Use to reference the ignored-property key in type-level protocol + * definitions. + * + * **Details** + * + * This type represents the unique symbol used for marking types that should + * be ignored during unification operations. It's used in type-level operations + * to exclude specific types from the unification process. + * + * @category symbols + * @since 2.0.0 + */ +export type ignoreSymbol = typeof ignoreSymbol + +type MaybeReturn = F extends () => infer R ? R : NonNullable + +type Keys = X extends [infer A, infer Ignore] ? Exclude + : never + +type Values = X extends [infer A, infer Ignore] + ? Keys<[A, Ignore]> extends infer K ? K extends keyof A ? MaybeReturn : never : never + : never + +type Ignore = X extends { [ignoreSymbol]?: infer Obj } ? keyof NonNullable + : never + +type ExtractTypes< + X +> = X extends { + [typeSymbol]?: infer _Type + [unifySymbol]?: infer _Unify +} ? [NonNullable<_Unify>, Ignore] + : never + +type FilterIn = A extends any ? typeSymbol extends keyof A ? A : never : never + +type FilterInUnmatched = A extends any + ? typeSymbol extends keyof A + ? A extends { [unifySymbol]?: infer U } ? [Extract, K>] extends [never] ? A : never + : A + : never + : never + +type FilterOut = A extends any ? typeSymbol extends keyof A ? never : A : never + +/** + * Unifies types that implement the unification protocol. + * + * **When to use** + * + * Use to normalize unions of types that expose Effect's unification protocol. + * + * **Details** + * + * This type performs automatic type unification for types that contain + * the unification symbols (`unifySymbol`, `typeSymbol`, `ignoreSymbol`). + * It's primarily used internally by the Effect type system to handle + * complex type unions and provide better type inference. + * + * **Example** (Unifying protocol types) + * + * ```ts import.meta.vitest + * import type { Unify } from "effect" + * + * // Example of types that can be unified + * type UnifiableA = { + * value: string + * [Unify.typeSymbol]?: string + * [Unify.unifySymbol]?: { String: () => string } + * } + * + * type UnifiableB = { + * value: number + * [Unify.typeSymbol]?: number + * [Unify.unifySymbol]?: { Number: () => number } + * } + * + * // Unify automatically handles the union + * type Unified = Unify.Unify + * + * const witness: Unified = "value" + * ``` + * + * @see {@link unify} for applying this normalization to a value or function + * + * @category models + * @since 2.0.0 + */ +export type Unify = Values< + ExtractTypes< + ( + & FilterIn + & { [typeSymbol]: A } + ) + > +> extends infer Z ? + | Z + | FilterInUnmatched< + A, + Keys< + ExtractTypes< + ( + & FilterIn + & { [typeSymbol]: A } + ) + > + > + > + | FilterOut + : never + +/** + * Applies `Unify` to a value or function return type at compile time. + * + * **When to use** + * + * Use to keep a value or function unchanged at runtime while normalizing its + * inferred type with Effect's unification protocol. + * + * **Details** + * + * This is an identity function at runtime. For functions, the returned function + * has the same runtime behavior while its return type is normalized with the + * Effect unification protocol. + * + * **Example** (Unifying values and function results) + * + * ```ts import.meta.vitest + * import { Unify } from "effect" + * + * // Unify a simple value + * const unifiedValue = Unify.unify("hello") // => "hello" + * // Type: string + * + * // Unify a function result + * const createValue = () => ({ value: "test" }) + * + * const unifiedFunction = Unify.unify(createValue) + * unifiedFunction().value // => "test" + * + * // Unify with curried functions + * const curriedFunction = (a: string) => (b: number) => ({ result: a + b }) + * const unifiedCurried = Unify.unify(curriedFunction) + * // Type: (a: string) => (b: number) => Unify<{ result: string }> + * unifiedCurried("value-")(1).result // => "value-1" + * ``` + * + * @see {@link Unify} for the type-level normalization applied by this helper + * + * @category utility types + * @since 2.0.0 + */ +export const unify: { + < + Args extends Array, + Args2 extends Array, + Args3 extends Array, + Args4 extends Array, + Args5 extends Array, + T + >( + x: (...args: Args) => (...args: Args2) => (...args: Args3) => (...args: Args4) => (...args: Args5) => T + ): (...args: Args) => (...args: Args2) => (...args: Args3) => (...args: Args4) => (...args: Args5) => Unify + < + Args extends Array, + Args2 extends Array, + Args3 extends Array, + Args4 extends Array, + T + >( + x: (...args: Args) => (...args: Args2) => (...args: Args3) => (...args: Args4) => T + ): (...args: Args) => (...args: Args2) => (...args: Args3) => (...args: Args4) => Unify + < + Args extends Array, + Args2 extends Array, + Args3 extends Array, + T + >( + x: (...args: Args) => (...args: Args2) => (...args: Args3) => T + ): (...args: Args) => (...args: Args2) => (...args: Args3) => Unify + < + Args extends Array, + Args2 extends Array, + T + >( + x: (...args: Args) => (...args: Args2) => T + ): (...args: Args) => (...args: Args2) => Unify + < + Args extends Array, + T + >(x: (...args: Args) => T): (...args: Args) => Unify + (x: T): Unify +} = identity as any diff --git a/.repos/effect/packages/effect/src/Utils.ts b/.repos/effect/packages/effect/src/Utils.ts new file mode 100644 index 000000000..448c79b16 --- /dev/null +++ b/.repos/effect/packages/effect/src/Utils.ts @@ -0,0 +1,228 @@ +/** + * Internal and advanced utilities used by Effect's generator-based syntax and + * higher-kinded type support. This is not a general-purpose utility module for + * application code. + * + * `SingleShotGen` makes an Effect-style value work with `yield*` inside + * generator helpers. `Variance` and `Gen` provide the type-level signatures + * used by modules such as `Effect`, `Option`, and `Result` to type their + * `gen` APIs. + * + * @since 2.0.0 + */ +import type { Kind, TypeLambda } from "./HKT.ts" +import type * as Types from "./Types.ts" + +/** + * Yields its wrapped value exactly once through an `IterableIterator`. + * + * **When to use** + * + * Use to implement `[Symbol.iterator]()` on Effect-like types so they can be + * `yield*`-ed inside generator functions, such as `Effect.gen` and + * `Option.gen`. + * + * **Details** + * + * The first call to `next()` returns `{ value: self, done: false }`. Every + * subsequent call returns `{ value: a, done: true }` where `a` is the argument + * passed to `next()`. `[Symbol.iterator]()` returns a **new** `SingleShotGen` + * wrapping the same value, so the outer type can be iterated multiple times. + * + * **Example** (Yielding a wrapped value in a generator) + * + * ```ts import.meta.vitest + * import { Utils } from "effect" + * + * const gen = new Utils.SingleShotGen("hello") + * + * gen.next(0) // => { value: "hello", done: false } + * + * gen.next(42) // => { value: 42, done: true } + * ``` + * + * @see {@link Gen} for the type-level signature that relies on `SingleShotGen` + * @category constructors + * @since 2.0.0 + */ +export class SingleShotGen implements IterableIterator { + private called = false + readonly self: T + + constructor(self: T) { + this.self = self + } + + /** + * Yields the stored value once, then completes with the value sent back in. + * + * **When to use** + * + * Use to advance a `SingleShotGen` through its single yield and completion + * step. + * + * @since 2.0.0 + */ + next(a: A): IteratorResult { + return this.called ? + ({ + value: a, + done: true + }) : + (this.called = true, + ({ + value: this.self, + done: false + })) + } + + /** + * Creates a fresh single-shot iterator over the stored value. + * + * **When to use** + * + * Use to iterate the wrapped value again without reusing the consumed + * iterator state. + * + * @since 2.0.0 + */ + [Symbol.iterator](): IterableIterator { + return new SingleShotGen(this.self) + } +} + +/** + * Type-level marker encoding the variance of a `TypeLambda`'s type + * parameters. + * + * **When to use** + * + * Use to define variance constraints for a higher-kinded type so that + * {@link Gen} can correctly infer `R`, `O`, and `E` from yielded values. + * + * **Details** + * + * `F` is invariant and must match exactly. `R` is contravariant in the input + * or environment position. `O` and `E` are covariant in the output and error + * positions. This is a pure type-level construct with no runtime + * representation. + * + * **Example** (Declaring variance for a TypeLambda) + * + * ```ts import.meta.vitest + * import type { Option, Utils } from "effect" + * + * const variance: Utils.Variance< + * Option.OptionTypeLambda, + * unknown, + * string, + * string + * > = { + * _F: (value) => value, + * _R: () => {}, + * _O: () => "output", + * _E: () => "error" + * } + * Array.of(variance._O(undefined as never), variance._E(undefined as never)) // => ["output", "error"] + * ``` + * + * @see {@link Gen} for the type-level signature that uses `Variance` + * @category models + * @since 2.0.0 + */ +export interface Variance { + readonly _F: Types.Invariant + readonly _R: Types.Contravariant + readonly _O: Types.Covariant + readonly _E: Types.Covariant +} + +/** + * Type-level signature for generator-based monadic composition over any + * `TypeLambda`. + * + * **When to use** + * + * Use to type the `gen` function of a module that supports generator syntax, + * such as `Option.gen`, `Result.gen`, and `Effect.gen`. + * + * **Details** + * + * This is a pure type alias with no runtime behavior. It infers `R`, `O`, and + * `E` from the yielded values via {@link Variance} or `Kind` constraints. The + * generator's return type `A` becomes the output's `A` parameter. + * + * **Example** (Typing a gen function for Option) + * + * ```ts import.meta.vitest + * import { Option } from "effect" + * import type { Utils } from "effect" + * + * const gen: Utils.Gen = Option.gen + * const result = gen(function*() { + * return yield* Option.some(1) + * }) + * result // => Option.some(1) + * ``` + * + * @see {@link Variance} for encoding the variance used for inference + * @see {@link SingleShotGen} for the iterator protocol that makes yielding work + * @category models + * @since 2.0.0 + */ +export type Gen = < + Self, + K extends Variance | Kind, + A +>( + ...args: + | [ + self: Self, + body: (this: Self) => Generator + ] + | [ + body: () => Generator + ] +) => Kind< + F, + [K] extends [Variance] ? R + : [K] extends [Kind] ? R + : never, + [K] extends [Variance] ? O + : [K] extends [Kind] ? O + : never, + [K] extends [Variance] ? E + : [K] extends [Kind] ? E + : never, + A +> + +// the probe is wrapped in a single function call (rather than module-level +// statements) so the whole selection is pure-annotated by the build and +// tree-shakable when `internalCall` is unused. +const pickInternalCall = (): (body: () => A) => A => { + const InternalTypeId = "~effect/Utils/internal" + + const standard = { + [InternalTypeId]: (body: () => A) => { + return body() + } + } + + const forced = { + [InternalTypeId]: (body: () => A) => { + try { + return body() + } finally { + // + } + } + } + + const isNotOptimizedAway = standard[InternalTypeId](() => new Error().stack)?.includes(InternalTypeId) === true + + return isNotOptimizedAway ? standard[InternalTypeId] : forced[InternalTypeId] +} + +/** @internal */ +export const internalCall = pickInternalCall() diff --git a/.repos/effect/packages/effect/src/index.ts b/.repos/effect/packages/effect/src/index.ts new file mode 100644 index 000000000..67dd10684 --- /dev/null +++ b/.repos/effect/packages/effect/src/index.ts @@ -0,0 +1,717 @@ +/** + * @since 2.0.0 + */ + +export { + /** + * @since 2.0.0 + */ + absurd, + /** + * @since 2.0.0 + */ + cast, + /** + * @since 2.0.0 + */ + flow, + /** + * @since 2.0.0 + */ + hole, + /** + * @since 2.0.0 + */ + identity, + /** + * @since 2.0.0 + */ + pipe +} from "./Function.ts" + +// @barrel: Auto-generated exports. Do not edit manually. + +/** + * @since 2.0.0 + */ +export * as Array from "./Array.ts" + +/** + * @since 2.0.0 + */ +export * as BigDecimal from "./BigDecimal.ts" + +/** + * @since 2.0.0 + */ +export * as BigInt from "./BigInt.ts" + +/** + * @since 2.0.0 + */ +export * as Boolean from "./Boolean.ts" + +/** + * @since 2.0.0 + */ +export * as Brand from "./Brand.ts" + +/** + * @since 4.0.0 + */ +export * as Cache from "./Cache.ts" + +/** + * @since 2.0.0 + */ +export * as Cause from "./Cause.ts" + +/** + * @since 2.0.0 + */ +export * as Channel from "./Channel.ts" + +/** + * @since 4.0.0 + */ +export * as ChannelSchema from "./ChannelSchema.ts" + +/** + * @since 2.0.0 + */ +export * as Chunk from "./Chunk.ts" + +/** + * @since 2.0.0 + */ +export * as Clock from "./Clock.ts" + +/** + * @since 4.0.0 + */ +export * as Combiner from "./Combiner.ts" + +/** + * @since 4.0.0 + */ +export * as Config from "./Config.ts" + +/** + * @since 4.0.0 + */ +export * as ConfigProvider from "./ConfigProvider.ts" + +/** + * @since 2.0.0 + */ +export * as Console from "./Console.ts" + +/** + * @since 4.0.0 + */ +export * as Context from "./Context.ts" + +/** + * @since 2.0.0 + */ +export * as Cron from "./Cron.ts" + +/** + * @since 4.0.0 + */ +export * as Crypto from "./Crypto.ts" + +/** + * @since 2.0.0 + */ +export * as Data from "./Data.ts" + +/** + * @since 3.6.0 + */ +export * as DateTime from "./DateTime.ts" + +/** + * @since 2.0.0 + */ +export * as Deferred from "./Deferred.ts" + +/** + * @since 4.0.0 + */ +export * as Differ from "./Differ.ts" + +/** + * @since 2.0.0 + */ +export * as Duration from "./Duration.ts" + +/** + * @since 2.0.0 + */ +export * as Effect from "./Effect.ts" + +/** + * @since 4.0.0 + */ +export * as Effectable from "./Effectable.ts" + +/** + * @since 4.0.0 + */ +export * as Encoding from "./Encoding.ts" + +/** + * @since 2.0.0 + */ +export * as Equal from "./Equal.ts" + +/** + * @since 2.0.0 + */ +export * as Equivalence from "./Equivalence.ts" + +/** + * @since 4.0.0 + */ +export * as ErrorReporter from "./ErrorReporter.ts" + +/** + * @since 3.16.0 + */ +export * as ExecutionPlan from "./ExecutionPlan.ts" + +/** + * @since 2.0.0 + */ +export * as Exit from "./Exit.ts" + +/** + * @since 2.0.0 + */ +export * as Fiber from "./Fiber.ts" + +/** + * @since 2.0.0 + */ +export * as FiberHandle from "./FiberHandle.ts" + +/** + * @since 2.0.0 + */ +export * as FiberMap from "./FiberMap.ts" + +/** + * @since 2.0.0 + */ +export * as FiberSet from "./FiberSet.ts" + +/** + * @since 4.0.0 + */ +export * as FileSystem from "./FileSystem.ts" + +/** + * @since 4.0.0 + */ +export * as Filter from "./Filter.ts" + +/** + * @since 4.0.0 + */ +export * as Formatter from "./Formatter.ts" + +/** + * @since 2.0.0 + */ +export * as Function from "./Function.ts" + +/** + * @since 4.0.0 + */ +export * as Graph from "./Graph.ts" + +/** + * @since 2.0.0 + */ +export * as Hash from "./Hash.ts" + +/** + * @since 2.0.0 + */ +export * as HashMap from "./HashMap.ts" + +/** + * @since 4.0.0 + */ +export * as HashRing from "./HashRing.ts" + +/** + * @since 2.0.0 + */ +export * as HashSet from "./HashSet.ts" + +/** + * @since 2.0.0 + */ +export * as HKT from "./HKT.ts" + +/** + * @since 2.0.0 + */ +export * as Inspectable from "./Inspectable.ts" + +/** + * @since 2.0.0 + */ +export * as Iterable from "./Iterable.ts" + +/** + * @since 4.0.0 + */ +export * as JsonPatch from "./JsonPatch.ts" + +/** + * @since 4.0.0 + */ +export * as JsonPointer from "./JsonPointer.ts" + +/** + * @since 4.0.0 + */ +export * as JsonSchema from "./JsonSchema.ts" + +/** + * @since 4.0.0 + */ +export * as Latch from "./Latch.ts" + +/** + * @since 2.0.0 + */ +export * as Layer from "./Layer.ts" + +/** + * @since 3.14.0 + */ +export * as LayerMap from "./LayerMap.ts" + +/** + * @since 4.0.0 + */ +export * as LayerRef from "./LayerRef.ts" + +/** + * @since 2.0.0 + */ +export * as Logger from "./Logger.ts" + +/** + * @since 2.0.0 + */ +export * as LogLevel from "./LogLevel.ts" + +/** + * @since 2.0.0 + */ +export * as ManagedRuntime from "./ManagedRuntime.ts" + +/** + * @since 4.0.0 + */ +export * as Match from "./Match.ts" + +/** + * @since 2.0.0 + */ +export * as Metric from "./Metric.ts" + +/** + * @since 2.0.0 + */ +export * as MutableHashMap from "./MutableHashMap.ts" + +/** + * @since 2.0.0 + */ +export * as MutableHashSet from "./MutableHashSet.ts" + +/** + * @since 4.0.0 + */ +export * as MutableList from "./MutableList.ts" + +/** + * @since 2.0.0 + */ +export * as MutableRef from "./MutableRef.ts" + +/** + * @since 4.0.0 + */ +export * as Newtype from "./Newtype.ts" + +/** + * @since 2.0.0 + */ +export * as NonEmptyIterable from "./NonEmptyIterable.ts" + +/** + * @since 2.0.0 + */ +export * as Number from "./Number.ts" + +/** + * @since 4.0.0 + */ +export * as Optic from "./Optic.ts" + +/** + * @since 2.0.0 + */ +export * as Option from "./Option.ts" + +/** + * @since 2.0.0 + */ +export * as Order from "./Order.ts" + +/** + * @since 2.0.0 + */ +export * as Ordering from "./Ordering.ts" + +/** + * @since 4.0.0 + */ +export * as PartitionedSemaphore from "./PartitionedSemaphore.ts" + +/** + * @since 4.0.0 + */ +export * as Path from "./Path.ts" + +/** + * @since 2.0.0 + */ +export * as Pipeable from "./Pipeable.ts" + +/** + * @since 4.0.0 + */ +export * as PlatformError from "./PlatformError.ts" + +/** + * @since 2.0.0 + */ +export * as Pool from "./Pool.ts" + +/** + * @since 2.0.0 + */ +export * as Predicate from "./Predicate.ts" + +/** + * @since 2.0.0 + */ +export * as PrimaryKey from "./PrimaryKey.ts" + +/** + * @since 2.0.0 + */ +export * as PubSub from "./PubSub.ts" + +/** + * @since 4.0.0 + */ +export * as Pull from "./Pull.ts" + +/** + * @since 3.8.0 + */ +export * as Queue from "./Queue.ts" + +/** + * @since 4.0.0 + */ +export * as Random from "./Random.ts" + +/** + * @since 3.5.0 + */ +export * as RcMap from "./RcMap.ts" + +/** + * @since 3.5.0 + */ +export * as RcRef from "./RcRef.ts" + +/** + * @since 2.0.0 + */ +export * as Record from "./Record.ts" + +/** + * @since 4.0.0 + */ +export * as Redactable from "./Redactable.ts" + +/** + * @since 3.3.0 + */ +export * as Redacted from "./Redacted.ts" + +/** + * @since 4.0.0 + */ +export * as Reducer from "./Reducer.ts" + +/** + * @since 2.0.0 + */ +export * as Ref from "./Ref.ts" + +/** + * @since 4.0.0 + */ +export * as References from "./References.ts" + +/** + * @since 2.0.0 + */ +export * as RegExp from "./RegExp.ts" + +/** + * @since 2.0.0 + */ +export * as Request from "./Request.ts" + +/** + * @since 2.0.0 + */ +export * as RequestResolver from "./RequestResolver.ts" + +/** + * @since 2.0.0 + */ +export * as Resource from "./Resource.ts" + +/** + * @since 4.0.0 + */ +export * as Result from "./Result.ts" + +/** + * @since 4.0.0 + */ +export * as Runtime from "./Runtime.ts" + +/** + * @since 2.0.0 + */ +export * as Schedule from "./Schedule.ts" + +/** + * @since 2.0.0 + */ +export * as Scheduler from "./Scheduler.ts" + +/** + * @since 4.0.0 + */ +export * as Schema from "./Schema.ts" + +/** + * @since 4.0.0 + */ +export * as SchemaAST from "./SchemaAST.ts" + +/** + * @since 4.0.0 + */ +export * as SchemaGetter from "./SchemaGetter.ts" + +/** + * @since 4.0.0 + */ +export * as SchemaIssue from "./SchemaIssue.ts" + +/** + * @since 4.0.0 + */ +export * as SchemaParser from "./SchemaParser.ts" + +/** + * @since 4.0.0 + */ +export * as SchemaRepresentation from "./SchemaRepresentation.ts" + +/** + * @since 4.0.0 + */ +export * as SchemaTransformation from "./SchemaTransformation.ts" + +/** + * @since 2.0.0 + */ +export * as Scope from "./Scope.ts" + +/** + * @since 4.0.0 + */ +export * as ScopedCache from "./ScopedCache.ts" + +/** + * @since 2.0.0 + */ +export * as ScopedRef from "./ScopedRef.ts" + +/** + * @since 4.0.0 + */ +export * as Semaphore from "./Semaphore.ts" + +/** + * @since 2.0.0 + */ +export * as Sink from "./Sink.ts" + +/** + * @since 4.0.0 + */ +export * as StandardSchema from "./StandardSchema.ts" + +/** + * @since 4.0.0 + */ +export * as Stdio from "./Stdio.ts" + +/** + * @since 2.0.0 + */ +export * as Stream from "./Stream.ts" + +/** + * @since 2.0.0 + */ +export * as String from "./String.ts" + +/** + * @since 2.0.0 + */ +export * as Struct from "./Struct.ts" + +/** + * @since 2.0.0 + */ +export * as SubscriptionRef from "./SubscriptionRef.ts" + +/** + * @since 2.0.0 + */ +export * as Symbol from "./Symbol.ts" + +/** + * @since 2.0.0 + */ +export * as SynchronizedRef from "./SynchronizedRef.ts" + +/** + * @since 2.0.0 + */ +export * as Take from "./Take.ts" + +/** + * @since 4.0.0 + */ +export * as Terminal from "./Terminal.ts" + +/** + * @since 2.0.0 + */ +export * as Tracer from "./Tracer.ts" + +/** + * @since 2.0.0 + */ +export * as Trie from "./Trie.ts" + +/** + * @since 2.0.0 + */ +export * as Tuple from "./Tuple.ts" + +/** + * @since 4.0.0 + */ +export * as TxChunk from "./TxChunk.ts" + +/** + * @since 4.0.0 + */ +export * as TxDeferred from "./TxDeferred.ts" + +/** + * @since 2.0.0 + */ +export * as TxHashMap from "./TxHashMap.ts" + +/** + * @since 2.0.0 + */ +export * as TxHashSet from "./TxHashSet.ts" + +/** + * @since 4.0.0 + */ +export * as TxPriorityQueue from "./TxPriorityQueue.ts" + +/** + * @since 4.0.0 + */ +export * as TxPubSub from "./TxPubSub.ts" + +/** + * @since 4.0.0 + */ +export * as TxQueue from "./TxQueue.ts" + +/** + * @since 4.0.0 + */ +export * as TxReentrantLock from "./TxReentrantLock.ts" + +/** + * @since 4.0.0 + */ +export * as TxRef from "./TxRef.ts" + +/** + * @since 4.0.0 + */ +export * as TxSemaphore from "./TxSemaphore.ts" + +/** + * @since 4.0.0 + */ +export * as TxSubscriptionRef from "./TxSubscriptionRef.ts" + +/** + * @since 4.0.0 + */ +export * as Types from "./Types.ts" + +/** + * @since 4.0.0 + */ +export * as UndefinedOr from "./UndefinedOr.ts" + +/** + * @since 2.0.0 + */ +export * as Unify from "./Unify.ts" + +/** + * @since 2.0.0 + */ +export * as Utils from "./Utils.ts" diff --git a/.repos/effect/packages/effect/src/internal/array.ts b/.repos/effect/packages/effect/src/internal/array.ts new file mode 100644 index 000000000..9f5100e33 --- /dev/null +++ b/.repos/effect/packages/effect/src/internal/array.ts @@ -0,0 +1,8 @@ +/** + * @since 2.0.0 + */ + +import type { NonEmptyArray } from "../Array.ts" + +/** @internal */ +export const isArrayNonEmpty = (self: ReadonlyArray): self is NonEmptyArray => self.length > 0 diff --git a/.repos/effect/packages/effect/src/internal/core.ts b/.repos/effect/packages/effect/src/internal/core.ts new file mode 100644 index 000000000..021d72dd1 --- /dev/null +++ b/.repos/effect/packages/effect/src/internal/core.ts @@ -0,0 +1,667 @@ +import type * as Cause from "../Cause.ts" +import type * as Context from "../Context.ts" +import type * as Effect from "../Effect.ts" +import * as Equal from "../Equal.ts" +import type * as Exit from "../Exit.ts" +import { format } from "../Formatter.ts" +import { dual, identity } from "../Function.ts" +import * as Hash from "../Hash.ts" +import { NodeInspectSymbol } from "../Inspectable.ts" +import { pipeArguments } from "../Pipeable.ts" +import { hasProperty } from "../Predicate.ts" +import type { StackFrame } from "../References.ts" +import type * as Types from "../Types.ts" +import { SingleShotGen } from "../Utils.ts" +import type { FiberImpl } from "./effect.ts" +import * as InternalRecord from "./record.ts" + +/** @internal */ +export const EffectTypeId = `~effect/Effect` as const + +/** @internal */ +export const ExitTypeId = `~effect/Exit` as const + +const effectVariance = { + _A: identity, + _E: identity, + _R: identity +} + +/** @internal */ +export const identifier = `${EffectTypeId}/identifier` as const +/** @internal */ +export type identifier = typeof identifier + +/** @internal */ +export const args = `${EffectTypeId}/args` as const +/** @internal */ +export type args = typeof args + +/** @internal */ +export const evaluate = `${EffectTypeId}/evaluate` as const +/** @internal */ +export type evaluate = typeof evaluate + +/** @internal */ +export const contA = `${EffectTypeId}/successCont` as const +/** @internal */ +export type contA = typeof contA + +/** @internal */ +export const contE = `${EffectTypeId}/failureCont` as const +/** @internal */ +export type contE = typeof contE + +/** @internal */ +export const contAll = `${EffectTypeId}/ensureCont` as const +/** @internal */ +export type contAll = typeof contAll + +/** @internal */ +export const Yield = Symbol.for("effect/Effect/Yield") +/** @internal */ +export type Yield = typeof Yield + +/** @internal */ +export const PipeInspectableProto = { + pipe() { + return pipeArguments(this, arguments) + }, + toJSON(this: any) { + return { ...this } + }, + toString() { + return format(this.toJSON(), { ignoreToString: true, space: 2 }) + }, + [NodeInspectSymbol]() { + return this.toJSON() + } +} + +/** @internal */ +export const StructuralProto = { + [Hash.symbol](this: any): number { + return Hash.structureKeys(this, Object.keys(this)) + }, + [Equal.symbol](this: any, that: any): boolean { + const selfKeys = Object.keys(this) + const thatKeys = Object.keys(that) + if (selfKeys.length !== thatKeys.length) return false + for (let i = 0; i < selfKeys.length; i++) { + if (selfKeys[i] !== thatKeys[i] || !Equal.equals(this[selfKeys[i]], that[selfKeys[i]])) { + return false + } + } + return true + } +} + +/** @internal */ +export const EffectProto = { + [EffectTypeId]: effectVariance, + ...PipeInspectableProto, + [Symbol.iterator]() { + return new SingleShotGen(this) as any + }, + toJSON(this: Primitive) { + return { + _id: "Effect", + op: this[identifier], + ...(args in this ? { args: this[args] } : undefined) + } + } +} + +/** @internal */ +export const isEffect = (u: unknown): u is Effect.Effect => hasProperty(u, EffectTypeId) + +/** @internal */ +export const isExit = (u: unknown): u is Exit.Exit => hasProperty(u, ExitTypeId) + +// ---------------------------------------------------------------------------- +// Cause +// ---------------------------------------------------------------------------- + +/** @internal */ +export const CauseTypeId = "~effect/Cause" + +/** @internal */ +export const CauseReasonTypeId = "~effect/Cause/Reason" + +/** @internal */ +export const isCause = (self: unknown): self is Cause.Cause => hasProperty(self, CauseTypeId) + +/** @internal */ +export const isCauseReason = (self: unknown): self is Cause.Reason => hasProperty(self, CauseReasonTypeId) + +/** @internal */ +export class CauseImpl implements Cause.Cause { + readonly [CauseTypeId]: typeof CauseTypeId + readonly reasons: ReadonlyArray< + Cause.Fail | Cause.Die | Cause.Interrupt + > + constructor( + failures: ReadonlyArray< + Cause.Fail | Cause.Die | Cause.Interrupt + > + ) { + this[CauseTypeId] = CauseTypeId + this.reasons = failures + } + pipe() { + return pipeArguments(this, arguments) + } + toJSON(): unknown { + return { + _id: "Cause", + failures: this.reasons.map((f) => f.toJSON()) + } + } + toString() { + return `Cause(${format(this.reasons)})` + } + [NodeInspectSymbol]() { + return this.toJSON() + } + [Equal.symbol](that: any): boolean { + return ( + isCause(that) && + this.reasons.length === that.reasons.length && + this.reasons.every((e, i) => Equal.equals(e, that.reasons[i])) + ) + } + [Hash.symbol](): number { + return Hash.array(this.reasons) + } +} + +const annotationsMap = new WeakMap>() + +/** @internal */ +export abstract class ReasonBase implements Cause.Cause.ReasonProto { + readonly [CauseReasonTypeId]: typeof CauseReasonTypeId + readonly annotations: ReadonlyMap + readonly _tag: Tag + + constructor( + _tag: Tag, + annotations: ReadonlyMap, + originalError: unknown + ) { + this[CauseReasonTypeId] = CauseReasonTypeId + this._tag = _tag + if ( + annotations !== constEmptyAnnotations && typeof originalError === "object" && originalError !== null && + annotations.size > 0 + ) { + const prevAnnotations = annotationsMap.get(originalError) + if (prevAnnotations) { + annotations = new Map([ + ...prevAnnotations, + ...annotations + ]) + } + annotationsMap.set(originalError, annotations) + } + this.annotations = annotations + } + + annotate( + annotations: Context.Context, + options?: { readonly overwrite?: boolean | undefined } + ): this { + if (annotations.mapUnsafe.size === 0) return this + const newAnnotations = new Map(this.annotations) + annotations.mapUnsafe.forEach((value, key) => { + if (options?.overwrite !== true && newAnnotations.has(key)) return + newAnnotations.set(key, value) + }) + const self = Object.assign(Object.create(Object.getPrototypeOf(this)), this) + self.annotations = newAnnotations + return self + } + + pipe() { + return pipeArguments(this, arguments) + } + + abstract toJSON(): unknown + abstract [Equal.symbol](that: any): boolean + abstract [Hash.symbol](): number + + toString() { + return format(this) + } + + [NodeInspectSymbol]() { + return this.toString() + } +} + +/** @internal */ +export const constEmptyAnnotations: ReadonlyMap = new Map() + +/** @internal */ +export class Fail extends ReasonBase<"Fail"> implements Cause.Fail { + readonly error: E + constructor( + error: E, + annotations = constEmptyAnnotations + ) { + super("Fail", annotations, error) + this.error = error + } + override toString() { + return `Fail(${format(this.error)})` + } + toJSON(): unknown { + return { + _tag: "Fail", + error: this.error + } + } + [Equal.symbol](that: any): boolean { + return ( + isFailReason(that) && + Equal.equals(this.error, that.error) && + Equal.equals(this.annotations, that.annotations) + ) + } + [Hash.symbol](): number { + return Hash.combine(Hash.string(this._tag))( + Hash.combine(Hash.hash(this.error))(Hash.hash(this.annotations)) + ) + } +} + +/** @internal */ +export const causeFromReasons = ( + reasons: ReadonlyArray> +): Cause.Cause => new CauseImpl(reasons) + +/** @internal */ +export const causeEmpty: Cause.Cause = new CauseImpl([]) + +/** @internal */ +export const causeFail = (error: E): Cause.Cause => new CauseImpl([new Fail(error)]) + +/** @internal */ +export class Die extends ReasonBase<"Die"> implements Cause.Die { + readonly defect: unknown + constructor( + defect: unknown, + annotations = constEmptyAnnotations + ) { + super("Die", annotations, defect) + this.defect = defect + } + override toString() { + return `Die(${format(this.defect)})` + } + toJSON(): unknown { + return { + _tag: "Die", + defect: this.defect + } + } + [Equal.symbol](that: any): boolean { + return ( + isDieReason(that) && + Equal.equals(this.defect, that.defect) && + Equal.equals(this.annotations, that.annotations) + ) + } + [Hash.symbol](): number { + return Hash.combine(Hash.string(this._tag))( + Hash.combine(Hash.hash(this.defect))(Hash.hash(this.annotations)) + ) + } +} + +/** @internal */ +export const causeDie = (defect: unknown): Cause.Cause => new CauseImpl([new Die(defect)]) + +/** @internal */ +export const causeAnnotate: { + ( + annotations: Context.Context, + options?: { + readonly overwrite?: boolean | undefined + } + ): (self: Cause.Cause) => Cause.Cause + ( + self: Cause.Cause, + annotations: Context.Context, + options?: { + readonly overwrite?: boolean | undefined + } + ): Cause.Cause +} = dual( + (args) => isCause(args[0]), + ( + self: Cause.Cause, + annotations: Context.Context, + options?: { + readonly overwrite?: boolean | undefined + } + ): Cause.Cause => { + if (annotations.mapUnsafe.size === 0) return self + return new CauseImpl(self.reasons.map((f) => f.annotate(annotations, options))) + } +) + +/** @internal */ +export const isFailReason = ( + self: Cause.Reason +): self is Cause.Fail => self._tag === "Fail" + +/** @internal */ +export const isDieReason = (self: Cause.Reason): self is Cause.Die => self._tag === "Die" + +/** @internal */ +export const isInterruptReason = (self: Cause.Reason): self is Cause.Interrupt => self._tag === "Interrupt" + +/** @internal */ +export interface Primitive { + readonly [identifier]: string + readonly [contA]: + | ((value: unknown, fiber: FiberImpl, exit?: Exit.Exit) => Primitive | Yield) + | undefined + readonly [contE]: + | ((cause: Cause.Cause, fiber: FiberImpl, exit?: Exit.Exit) => Primitive | Yield) + | undefined + readonly [contAll]: + | (( + fiber: FiberImpl + ) => + | ((value: unknown, fiber: FiberImpl) => Primitive | Yield) + | undefined) + | undefined + [evaluate](fiber: FiberImpl): Primitive | Yield +} + +function defaultEvaluate(_fiber: FiberImpl): Primitive | Yield { + return exitDie(`Effect.evaluate: Not implemented`) as any +} + +/** @internal */ +export const makePrimitiveProto = (options: { + readonly op: Op + readonly [evaluate]?: ( + fiber: FiberImpl + ) => Primitive | Effect.Effect | Yield + readonly [contA]?: ( + this: Primitive, + value: any, + fiber: FiberImpl + ) => Primitive | Effect.Effect | Yield + readonly [contE]?: ( + this: Primitive, + cause: Cause.Cause, + fiber: FiberImpl + ) => Primitive | Effect.Effect | Yield + readonly [contAll]?: ( + this: Primitive, + fiber: FiberImpl + ) => void | ((value: any, fiber: FiberImpl) => void) +}): Primitive => + ({ + ...EffectProto, + [identifier]: options.op, + [evaluate]: options[evaluate] ?? defaultEvaluate, + [contA]: options[contA], + [contE]: options[contE], + [contAll]: options[contAll] + }) as any + +/** @internal */ +export const makePrimitive = < + Fn extends (...args: Array) => any, + Single extends boolean = true +>(options: { + readonly op: string + readonly single?: Single + readonly [evaluate]?: ( + this: Primitive & { + readonly [args]: Single extends true ? Parameters[0] : Parameters + }, + fiber: FiberImpl + ) => Primitive | Effect.Effect | Yield + readonly [contA]?: ( + this: Primitive & { + readonly [args]: Single extends true ? Parameters[0] : Parameters + }, + value: any, + fiber: FiberImpl, + exit?: Exit.Exit + ) => Primitive | Effect.Effect | Yield + readonly [contE]?: ( + this: Primitive & { + readonly [args]: Single extends true ? Parameters[0] : Parameters + }, + cause: Cause.Cause, + fiber: FiberImpl, + exit?: Exit.Exit + ) => Primitive | Effect.Effect | Yield + readonly [contAll]?: ( + this: Primitive & { + readonly [args]: Single extends true ? Parameters[0] : Parameters + }, + fiber: FiberImpl + ) => void | ((value: any, fiber: FiberImpl) => void) +}): Fn => { + const Proto = makePrimitiveProto(options as any) + return function() { + const self = Object.create(Proto) + self[args] = options.single === false ? arguments : arguments[0] + return self + } as Fn +} + +/** @internal */ +export const makeExit = < + Fn extends (...args: Array) => any, + Prop extends string +>(options: { + readonly op: "Success" | "Failure" + readonly prop: Prop + readonly [evaluate]: ( + this: Exit.Exit & { [args]: Parameters[0] }, + fiber: FiberImpl + ) => Primitive | Yield +}): Fn => { + const Proto = { + [ExitTypeId]: ExitTypeId, + _tag: options.op, + get [options.prop](): any { + return (this as any)[args] + }, + ...makePrimitiveProto(options), + toString(this: any) { + return `${options.op}(${format(this[args])})` + }, + toJSON(this: any) { + return { + _id: "Exit", + _tag: options.op, + [options.prop]: this[args] + } + }, + [Equal.symbol](this: any, that: any): boolean { + return ( + isExit(that) && + that._tag === this._tag && + Equal.equals(this[args], (that as any)[args]) + ) + }, + [Hash.symbol](this: any): number { + return Hash.combine(Hash.string(options.op), Hash.hash(this[args])) + } + } + return function(value: unknown) { + const self = Object.create(Proto) + self[args] = value + return self + } as Fn +} + +/** @internal */ +export const exitSucceed: (a: A) => Exit.Exit = makeExit({ + op: "Success", + prop: "value", + [evaluate](fiber) { + const cont = fiber.getCont(contA) + return cont ? cont[contA](this[args], fiber, this) : fiber.yieldWith(this) + } +}) + +/** @internal */ +export const StackTraceKey = { + key: "effect/Cause/StackTrace" satisfies typeof Cause.StackTrace.key +} as Context.Service + +/** @internal */ +export const InterruptorStackTrace = { + key: "effect/Cause/InterruptorStackTrace" satisfies typeof Cause.InterruptorStackTrace.key +} as Context.Service + +/** @internal */ +export const exitFailCause: (cause: Cause.Cause) => Exit.Exit = makeExit({ + op: "Failure", + prop: "cause", + [evaluate](fiber) { + let cause = this[args] + let annotated = false + if (fiber.currentStackFrame) { + cause = causeAnnotate(cause, { mapUnsafe: new Map([[StackTraceKey.key, fiber.currentStackFrame]]) } as any) + annotated = true + } + let cont = fiber.getCont(contE) + while (fiber.interruptible && fiber._interruptedCause && cont) { + cont = fiber.getCont(contE) + } + return cont + ? cont[contE](cause, fiber, annotated ? undefined : this) + : fiber.yieldWith(annotated ? exitFailCause(cause) : this) + } +}) + +/** @internal */ +export const exitFail = (e: E): Exit.Exit => exitFailCause(causeFail(e)) + +/** @internal */ +export const exitDie = (defect: unknown): Exit.Exit => exitFailCause(causeDie(defect)) + +/** @internal */ +export const withFiber: ( + evaluate: (fiber: FiberImpl) => Effect.Effect +) => Effect.Effect = makePrimitive({ + op: "WithFiber", + [evaluate](fiber) { + return this[args](fiber) + } +}) + +/** @internal */ +export const YieldableError: new( + message?: string, + options?: ErrorOptions +) => Cause.YieldableError = (function() { + class YieldableError extends globalThis.Error {} + const proto = makePrimitiveProto({ + op: "YieldableError", + [evaluate]() { + return exitFail(this) + } + }) + delete (proto as any).toString + Object.assign( + YieldableError.prototype, + proto + ) + return YieldableError as any +})() + +/** @internal */ +export const Error: new = {}>( + args: Types.VoidIfEmpty<{ readonly [P in keyof A]: A[P] }> +) => Cause.YieldableError & Readonly = (function() { + const plainArgsSymbol = Symbol.for("effect/Data/Error/plainArgs") + return class Base extends YieldableError { + constructor(args: Record | undefined) { + super(args?.message, args?.cause ? { cause: args.cause } : undefined) + if (args) { + InternalRecord.assignProperties(this, args) + // @effect-diagnostics-next-line floatingEffect:off + Object.defineProperty(this, plainArgsSymbol, { + value: args, + enumerable: false + }) + } + } + override toJSON() { + return { ...(this as any)[plainArgsSymbol], ...this } + } + } as any +})() + +/** @internal */ +export const TaggedError = ( + tag: Tag +): new = {}>( + args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }> +) => Cause.YieldableError & { readonly _tag: Tag } & Readonly => { + class Base extends Error<{}> { + readonly _tag = tag + } + ;(Base.prototype as any).name = tag + return Base as any +} + +/** @internal */ +export const NoSuchElementErrorTypeId = "~effect/Cause/NoSuchElementError" + +/** @internal */ +export const isNoSuchElementError = ( + u: unknown +): u is Cause.NoSuchElementError => hasProperty(u, NoSuchElementErrorTypeId) + +/** @internal */ +export class NoSuchElementError extends TaggedError("NoSuchElementError") { + readonly [NoSuchElementErrorTypeId] = NoSuchElementErrorTypeId + constructor(message?: string) { + super({ message } as any) + } +} + +/** @internal */ +export const DoneTypeId = "~effect/Cause/Done" + +/** @internal */ +export const isDone = ( + u: unknown +): u is Cause.Done => hasProperty(u, DoneTypeId) + +const DoneVoid: Cause.Done = { + [DoneTypeId]: DoneTypeId, + _tag: "Done", + value: undefined +} + +/** @internal */ +export const Done = (value?: A): Cause.Done => { + if (value === undefined) return DoneVoid as Cause.Done + return { + [DoneTypeId]: DoneTypeId, + _tag: "Done", + value + } +} + +const doneVoid = exitFail(DoneVoid) + +/** @internal */ +export const done = (value?: A): Effect.Effect> => { + if (value === undefined) return doneVoid as any + return exitFail(Done(value)) +} diff --git a/.repos/effect/packages/effect/src/internal/dateTime.ts b/.repos/effect/packages/effect/src/internal/dateTime.ts new file mode 100644 index 000000000..650b1230b --- /dev/null +++ b/.repos/effect/packages/effect/src/internal/dateTime.ts @@ -0,0 +1,1270 @@ +import { IllegalArgumentError } from "../Cause.ts" +import * as Clock from "../Clock.ts" +import type * as DateTime from "../DateTime.ts" +import * as Duration from "../Duration.ts" +import type * as Effect from "../Effect.ts" +import * as Equal from "../Equal.ts" +import * as Equ from "../Equivalence.ts" +import type { LazyArg } from "../Function.ts" +import { dual } from "../Function.ts" +import * as Hash from "../Hash.ts" +import * as Inspectable from "../Inspectable.ts" +import * as Option from "../Option.ts" +import * as order from "../Order.ts" +import { pipeArguments } from "../Pipeable.ts" +import * as Predicate from "../Predicate.ts" +import type { Mutable } from "../Types.ts" +import * as effect from "./effect.ts" + +/** @internal */ +export const TypeId = "~effect/time/DateTime" + +/** @internal */ +export const TimeZoneTypeId = "~effect/time/DateTime/TimeZone" + +const Proto = { + [TypeId]: TypeId, + pipe() { + return pipeArguments(this, arguments) + }, + [Inspectable.NodeInspectSymbol](this: DateTime.DateTime) { + return this.toString() + }, + toJSON(this: DateTime.DateTime) { + return toDateUtc(this).toJSON() + } +} + +const ProtoUtc = { + ...Proto, + _tag: "Utc", + [Hash.symbol](this: DateTime.Utc) { + return Hash.number(this.epochMilliseconds) + }, + [Equal.symbol](this: DateTime.Utc, that: unknown) { + return isDateTime(that) && that._tag === "Utc" && this.epochMilliseconds === that.epochMilliseconds + }, + toString(this: DateTime.Utc) { + return `DateTime.Utc(${toDateUtc(this).toJSON()})` + } +} + +const ProtoZoned = { + ...Proto, + _tag: "Zoned", + [Hash.symbol](this: DateTime.Zoned) { + return Hash.combine(Hash.number(this.epochMilliseconds))(Hash.hash(this.zone)) + }, + [Equal.symbol](this: DateTime.Zoned, that: unknown) { + return isDateTime(that) && that._tag === "Zoned" && this.epochMilliseconds === that.epochMilliseconds && + Equal.equals(this.zone, that.zone) + }, + toString(this: DateTime.Zoned) { + return `DateTime.Zoned(${formatIsoZoned(this)})` + } +} + +const ProtoTimeZone = { + [TimeZoneTypeId]: TimeZoneTypeId, + [Inspectable.NodeInspectSymbol](this: DateTime.TimeZone) { + return this.toString() + } +} + +const ProtoTimeZoneNamed = { + ...ProtoTimeZone, + _tag: "Named", + [Hash.symbol](this: DateTime.TimeZone.Named) { + return Hash.string(`Named:${this.id}`) + }, + [Equal.symbol](this: DateTime.TimeZone.Named, that: unknown) { + return isTimeZone(that) && that._tag === "Named" && this.id === that.id + }, + toString(this: DateTime.TimeZone.Named) { + return `TimeZone.Named(${this.id})` + }, + toJSON(this: DateTime.TimeZone.Named) { + return { + _id: "TimeZone", + _tag: "Named", + id: this.id + } + } +} + +const ProtoTimeZoneOffset = { + ...ProtoTimeZone, + _tag: "Offset", + [Hash.symbol](this: DateTime.TimeZone.Offset) { + return Hash.string(`Offset:${this.offset}`) + }, + [Equal.symbol](this: DateTime.TimeZone.Offset, that: unknown) { + return isTimeZone(that) && that._tag === "Offset" && this.offset === that.offset + }, + toString(this: DateTime.TimeZone.Offset) { + return `TimeZone.Offset(${offsetToString(this.offset)})` + }, + toJSON(this: DateTime.TimeZone.Offset) { + return { + _id: "TimeZone", + _tag: "Offset", + offset: this.offset + } + } +} + +/** @internal */ +export const makeZonedProto = ( + epochMillis: number, + zone: DateTime.TimeZone, + partsUtc?: DateTime.DateTime.PartsWithWeekday +): DateTime.Zoned => { + const self = Object.create(ProtoZoned) + self.epochMilliseconds = epochMillis + self.zone = zone + Object.defineProperty(self, "partsUtc", { + value: partsUtc, + enumerable: false, + writable: true + }) + Object.defineProperty(self, "adjustedEpochMillis", { + value: undefined, + enumerable: false, + writable: true + }) + Object.defineProperty(self, "partsAdjusted", { + value: undefined, + enumerable: false, + writable: true + }) + return self +} + +// ============================================================================= +// guards +// ============================================================================= + +/** @internal */ +export const isDateTime = (u: unknown): u is DateTime.DateTime => Predicate.hasProperty(u, TypeId) + +const isDateTimeArgs = (args: IArguments) => isDateTime(args[0]) + +/** @internal */ +export const isTimeZone = (u: unknown): u is DateTime.TimeZone => Predicate.hasProperty(u, TimeZoneTypeId) + +/** @internal */ +export const isTimeZoneOffset = (u: unknown): u is DateTime.TimeZone.Offset => isTimeZone(u) && u._tag === "Offset" + +/** @internal */ +export const isTimeZoneNamed = (u: unknown): u is DateTime.TimeZone.Named => isTimeZone(u) && u._tag === "Named" + +/** @internal */ +export const isUtc = (self: DateTime.DateTime): self is DateTime.Utc => self._tag === "Utc" + +/** @internal */ +export const isZoned = (self: DateTime.DateTime): self is DateTime.Zoned => self._tag === "Zoned" + +// ============================================================================= +// instances +// ============================================================================= + +/** @internal */ +export const Equivalence: Equ.Equivalence = Equ.make((a, b) => + a.epochMilliseconds === b.epochMilliseconds +) + +/** @internal */ +export const Order: order.Order = order.make((self, that) => + self.epochMilliseconds < that.epochMilliseconds ? -1 : self.epochMilliseconds > that.epochMilliseconds ? 1 : 0 +) + +/** @internal */ +export const clamp: { + ( + options: { readonly minimum: Min; readonly maximum: Max } + ): (self: A) => A | Min | Max + ( + self: A, + options: { readonly minimum: Min; readonly maximum: Max } + ): A | Min | Max +} = order.clamp(Order) + +// ============================================================================= +// constructors +// ============================================================================= + +const makeUtc = (epochMillis: number): DateTime.Utc => { + const self = Object.create(ProtoUtc) + self.epochMilliseconds = epochMillis + Object.defineProperty(self, "partsUtc", { + value: undefined, + enumerable: false, + writable: true + }) + return self +} + +/** @internal */ +export const fromDateUnsafe = (date: Date): DateTime.Utc => { + const epochMillis = date.getTime() + if (Number.isNaN(epochMillis)) { + throw new IllegalArgumentError("Invalid date") + } + return makeUtc(epochMillis) +} + +/** @internal */ +export const makeUnsafe = (input: A): DateTime.DateTime.PreserveZone => { + if (isDateTime(input)) { + return input as DateTime.DateTime.PreserveZone + } else if (input instanceof Date) { + return fromDateUnsafe(input) as DateTime.DateTime.PreserveZone + } else if (typeof input === "object") { + if ("epochMilliseconds" in input) { + return fromDateUnsafe(new Date(input.epochMilliseconds)) as DateTime.DateTime.PreserveZone + } + const date = new Date(0) + setPartsDate(date, input) + return fromDateUnsafe(date) as DateTime.DateTime.PreserveZone + } else if (typeof input === "string" && !hasZone(input)) { + return fromDateUnsafe(new Date(input + "Z")) as DateTime.DateTime.PreserveZone + } + return fromDateUnsafe(new Date(input)) as DateTime.DateTime.PreserveZone +} + +/** + * Detects whether a date string already contains timezone info. + * Without a zone, `new Date("2024-01-01T12:00:00")` is parsed as local time, + * so `makeUnsafe` appends "Z" to force UTC interpretation. + * This check prevents appending "Z" to strings that already have a zone + * (e.g. "2024-01-01T12:00:00Z", "...+05:30", "...GMT"), which would produce invalid dates. + */ +const hasZone = (input: string): boolean => /Z|GMT|[+-]\d{2}$|[+-]\d{2}:?\d{2}$|\]$/.test(input) + +const minEpochMillis = -8640000000000000 + (12 * 60 * 60 * 1000) +const maxEpochMillis = 8640000000000000 - (14 * 60 * 60 * 1000) + +/** @internal */ +export const makeZonedUnsafe = (input: DateTime.DateTime.Input, options?: { + readonly timeZone?: number | string | DateTime.TimeZone | undefined + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined +}): DateTime.Zoned => { + let timeZoneOption = options?.timeZone + if (timeZoneOption === undefined && isDateTime(input) && isZoned(input)) { + return input + } + const self = makeUnsafe(input) + if (self.epochMilliseconds < minEpochMillis || self.epochMilliseconds > maxEpochMillis) { + throw new RangeError(`Epoch millis out of range: ${self.epochMilliseconds}`) + } + if (timeZoneOption === undefined && typeof input === "object" && "timeZoneId" in input) { + timeZoneOption = input.timeZoneId + } + let zone: DateTime.TimeZone + if (timeZoneOption === undefined) { + const offset = new Date(self.epochMilliseconds).getTimezoneOffset() * -60 * 1000 + zone = zoneMakeOffset(offset) + } else if (isTimeZone(timeZoneOption)) { + zone = timeZoneOption + } else if (typeof timeZoneOption === "number") { + zone = zoneMakeOffset(timeZoneOption) + } else { + const parsedZone = zoneFromString(timeZoneOption) + if (Option.isNone(parsedZone)) { + throw new IllegalArgumentError(`Invalid time zone: ${timeZoneOption}`) + } + zone = parsedZone.value + } + if (options?.adjustForTimeZone !== true) { + return makeZonedProto(self.epochMilliseconds, zone, self.partsUtc) + } + return makeZonedFromAdjusted(self.epochMilliseconds, zone, options?.disambiguation ?? "compatible") +} + +/** @internal */ +export const makeZoned: ( + input: DateTime.DateTime.Input, + options?: { + readonly timeZone?: number | string | DateTime.TimeZone | undefined + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + } +) => Option.Option = Option.liftThrowable(makeZonedUnsafe) + +/** @internal */ +export const make: (input: A) => Option.Option> = + Option.liftThrowable(makeUnsafe) + +const zonedStringRegExp = /^(.{17,35})\[(.+)\]$/ + +/** @internal */ +export const makeZonedFromString = (input: string): Option.Option => { + const match = zonedStringRegExp.exec(input) + if (match === null) { + const offset = parseOffset(input) + return offset !== null ? makeZoned(input, { timeZone: offset }) : Option.none() + } + const [, isoString, timeZone] = match + return makeZoned(isoString, { timeZone }) +} + +/** @internal */ +export const now: Effect.Effect = effect.map(Clock.currentTimeMillis, makeUtc) + +/** @internal */ +export const nowAsDate: Effect.Effect = effect.map(Clock.currentTimeMillis, (millis) => new Date(millis)) + +/** @internal */ +export const nowUnsafe: LazyArg = () => makeUtc(Date.now()) + +// ============================================================================= +// time zones +// ============================================================================= + +/** @internal */ +export const toUtc = (self: DateTime.DateTime): DateTime.Utc => makeUtc(self.epochMilliseconds) + +/** @internal */ +export const setZone: { + (zone: DateTime.TimeZone, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): (self: DateTime.DateTime) => DateTime.Zoned + (self: DateTime.DateTime, zone: DateTime.TimeZone, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): DateTime.Zoned +} = dual(isDateTimeArgs, (self: DateTime.DateTime, zone: DateTime.TimeZone, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined +}): DateTime.Zoned => + options?.adjustForTimeZone === true + ? makeZonedFromAdjusted(self.epochMilliseconds, zone, options?.disambiguation ?? "compatible") + : makeZonedProto(self.epochMilliseconds, zone, self.partsUtc)) + +/** @internal */ +export const setZoneOffset: { + (offset: number, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): (self: DateTime.DateTime) => DateTime.Zoned + (self: DateTime.DateTime, offset: number, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): DateTime.Zoned +} = dual(isDateTimeArgs, (self: DateTime.DateTime, offset: number, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined +}): DateTime.Zoned => setZone(self, zoneMakeOffset(offset), options)) + +const validZoneCache = new Map() + +const formatOptions: Intl.DateTimeFormatOptions = { + day: "numeric", + month: "numeric", + year: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + timeZoneName: "longOffset", + fractionalSecondDigits: 3, + hourCycle: "h23" +} + +const zoneMakeIntl = (format: Intl.DateTimeFormat): DateTime.TimeZone.Named => { + const zoneId = format.resolvedOptions().timeZone + if (validZoneCache.has(zoneId)) { + return validZoneCache.get(zoneId)! + } + const zone = Object.create(ProtoTimeZoneNamed) + zone.id = zoneId + zone.format = format + validZoneCache.set(zoneId, zone) + return zone +} + +/** @internal */ +export const zoneMakeNamedUnsafe = (zoneId: string): DateTime.TimeZone.Named => { + if (validZoneCache.has(zoneId)) { + return validZoneCache.get(zoneId)! + } + try { + return zoneMakeIntl( + new Intl.DateTimeFormat("en-US", { + ...formatOptions, + timeZone: zoneId + }) + ) + } catch { + throw new IllegalArgumentError(`Invalid time zone: ${zoneId}`) + } +} + +/** @internal */ +export const zoneMakeOffset = (offset: number): DateTime.TimeZone.Offset => { + const zone = Object.create(ProtoTimeZoneOffset) + zone.offset = offset + return zone +} + +/** @internal */ +export const zoneMakeNamed: (zoneId: string) => Option.Option = Option.liftThrowable( + zoneMakeNamedUnsafe +) + +/** @internal */ +export const zoneMakeNamedEffect = (zoneId: string): Effect.Effect => + effect.try({ + try: () => zoneMakeNamedUnsafe(zoneId), + catch: (e) => e as IllegalArgumentError + }) + +/** @internal */ +export const zoneMakeLocal = (): DateTime.TimeZone.Named => + zoneMakeIntl(new Intl.DateTimeFormat("en-US", formatOptions)) + +const offsetZoneRegExp = /^(?:GMT|[+-])/ + +/** @internal */ +export const zoneFromString = (zone: string): Option.Option => { + if (offsetZoneRegExp.test(zone)) { + const offset = parseOffset(zone) + return offset === null ? Option.none() : Option.some(zoneMakeOffset(offset)) + } + return zoneMakeNamed(zone) +} + +/** @internal */ +export const zoneToString = (self: DateTime.TimeZone): string => { + if (self._tag === "Offset") { + return offsetToString(self.offset) + } + return self.id +} + +/** @internal */ +export const setZoneNamed: { + (zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): (self: DateTime.DateTime) => Option.Option + (self: DateTime.DateTime, zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): Option.Option +} = dual( + isDateTimeArgs, + (self: DateTime.DateTime, zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): Option.Option => Option.map(zoneMakeNamed(zoneId), (zone) => setZone(self, zone, options)) +) + +/** @internal */ +export const setZoneNamedUnsafe: { + (zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): (self: DateTime.DateTime) => DateTime.Zoned + (self: DateTime.DateTime, zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined + }): DateTime.Zoned +} = dual(isDateTimeArgs, (self: DateTime.DateTime, zoneId: string, options?: { + readonly adjustForTimeZone?: boolean | undefined + readonly disambiguation?: DateTime.Disambiguation | undefined +}): DateTime.Zoned => setZone(self, zoneMakeNamedUnsafe(zoneId), options)) + +// ============================================================================= +// comparisons +// ============================================================================= + +/** @internal */ +export const distance: { + (other: DateTime.DateTime): (self: DateTime.DateTime) => Duration.Duration + (self: DateTime.DateTime, other: DateTime.DateTime): Duration.Duration +} = dual( + 2, + (self: DateTime.DateTime, other: DateTime.DateTime): Duration.Duration => + Duration.millis(toEpochMillis(other) - toEpochMillis(self)) +) + +/** @internal */ +export const min: { + (that: That): (self: Self) => Self | That + (self: Self, that: That): Self | That +} = order.min(Order) + +/** @internal */ +export const max: { + (that: That): (self: Self) => Self | That + (self: Self, that: That): Self | That +} = order.max(Order) + +/** @internal */ +export const isGreaterThan: { + (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean + (self: DateTime.DateTime, that: DateTime.DateTime): boolean +} = order.isGreaterThan(Order) + +/** @internal */ +export const isGreaterThanOrEqualTo: { + (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean + (self: DateTime.DateTime, that: DateTime.DateTime): boolean +} = order.isGreaterThanOrEqualTo(Order) + +/** @internal */ +export const isLessThan: { + (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean + (self: DateTime.DateTime, that: DateTime.DateTime): boolean +} = order.isLessThan(Order) + +/** @internal */ +export const isLessThanOrEqualTo: { + (that: DateTime.DateTime): (self: DateTime.DateTime) => boolean + (self: DateTime.DateTime, that: DateTime.DateTime): boolean +} = order.isLessThanOrEqualTo(Order) + +/** @internal */ +export const between: { + (options: { minimum: DateTime.DateTime; maximum: DateTime.DateTime }): (self: DateTime.DateTime) => boolean + (self: DateTime.DateTime, options: { minimum: DateTime.DateTime; maximum: DateTime.DateTime }): boolean +} = order.isBetween(Order) + +/** @internal */ +export const isFuture = (self: DateTime.DateTime): Effect.Effect => effect.map(now, isLessThan(self)) + +/** @internal */ +export const isFutureUnsafe = (self: DateTime.DateTime): boolean => isLessThan(nowUnsafe(), self) + +/** @internal */ +export const isPast = (self: DateTime.DateTime): Effect.Effect => effect.map(now, isGreaterThan(self)) + +/** @internal */ +export const isPastUnsafe = (self: DateTime.DateTime): boolean => isGreaterThan(nowUnsafe(), self) + +// ============================================================================= +// conversions +// ============================================================================= + +/** @internal */ +export const toDateUtc = (self: DateTime.DateTime): Date => new Date(self.epochMilliseconds) + +/** @internal */ +export const toDate = (self: DateTime.DateTime): Date => { + if (self._tag === "Utc") { + return new Date(self.epochMilliseconds) + } else if (self.zone._tag === "Offset") { + return new Date(self.epochMilliseconds + self.zone.offset) + } else if (self.adjustedEpochMilliseconds !== undefined) { + return new Date(self.adjustedEpochMilliseconds) + } + const parts = self.zone.format.formatToParts(self.epochMilliseconds).filter((_) => _.type !== "literal") + const date = new Date(0) + date.setUTCFullYear( + Number(parts[2].value), + Number(parts[0].value) - 1, + Number(parts[1].value) + ) + date.setUTCHours( + Number(parts[3].value), + Number(parts[4].value), + Number(parts[5].value), + Number(parts[6].value) + ) + self.adjustedEpochMilliseconds = date.getTime() + return date +} + +/** @internal */ +export const zonedOffset = (self: DateTime.Zoned): number => { + const date = toDate(self) + return date.getTime() - toEpochMillis(self) +} + +const offsetToString = (offset: number): string => { + const abs = Math.abs(offset) + let hours = Math.floor(abs / (60 * 60 * 1000)) + let minutes = Math.round((abs % (60 * 60 * 1000)) / (60 * 1000)) + if (minutes === 60) { + hours += 1 + minutes = 0 + } + return `${offset < 0 ? "-" : "+"}${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}` +} + +/** @internal */ +export const zonedOffsetIso = (self: DateTime.Zoned): string => offsetToString(zonedOffset(self)) + +/** @internal */ +export const toEpochMillis = (self: DateTime.DateTime): number => self.epochMilliseconds + +/** @internal */ +export const toEpochSeconds = (self: DateTime.DateTime): number => Math.floor(self.epochMilliseconds / 1000) + +/** @internal */ +export const fromEpochSeconds = (seconds: number): DateTime.Utc => makeUtc(seconds * 1000) + +/** @internal */ +export const removeTime = (self: DateTime.DateTime): DateTime.Utc => + withDate(self, (date) => { + date.setUTCHours(0, 0, 0, 0) + return makeUtc(date.getTime()) + }) + +// ============================================================================= +// parts +// ============================================================================= + +const dateToParts = (date: Date): DateTime.DateTime.PartsWithWeekday => ({ + millisecond: date.getUTCMilliseconds(), + second: date.getUTCSeconds(), + minute: date.getUTCMinutes(), + hour: date.getUTCHours(), + day: date.getUTCDate(), + weekDay: date.getUTCDay(), + month: date.getUTCMonth() + 1, + year: date.getUTCFullYear() +}) + +/** @internal */ +export const toParts = (self: DateTime.DateTime): DateTime.DateTime.PartsWithWeekday => { + if (self._tag === "Utc") { + return toPartsUtc(self) + } else if (self.partsAdjusted !== undefined) { + return self.partsAdjusted + } + self.partsAdjusted = withDate(self, dateToParts) + return self.partsAdjusted +} + +/** @internal */ +export const toPartsUtc = (self: DateTime.DateTime): DateTime.DateTime.PartsWithWeekday => { + if (self.partsUtc !== undefined) { + return self.partsUtc + } + self.partsUtc = withDateUtc(self, dateToParts) + return self.partsUtc +} + +/** @internal */ +export const getPartUtc: { + (part: keyof DateTime.DateTime.PartsWithWeekday): (self: DateTime.DateTime) => number + (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number +} = dual(2, (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number => toPartsUtc(self)[part]) + +/** @internal */ +export const getPart: { + (part: keyof DateTime.DateTime.PartsWithWeekday): (self: DateTime.DateTime) => number + (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number +} = dual(2, (self: DateTime.DateTime, part: keyof DateTime.DateTime.PartsWithWeekday): number => toParts(self)[part]) + +const setPartsDate = (date: Date, parts: Partial): void => { + if (parts.year !== undefined) { + date.setUTCFullYear(parts.year) + } + if (parts.month !== undefined) { + date.setUTCMonth(parts.month - 1) + } + if (parts.day !== undefined) { + date.setUTCDate(parts.day) + } + if (parts.weekDay !== undefined) { + const diff = parts.weekDay - date.getUTCDay() + date.setUTCDate(date.getUTCDate() + diff) + } + if (parts.hour !== undefined) { + date.setUTCHours(parts.hour) + } + if (parts.minute !== undefined) { + date.setUTCMinutes(parts.minute) + } + if (parts.second !== undefined) { + date.setUTCSeconds(parts.second) + } + if (parts.millisecond !== undefined) { + date.setUTCMilliseconds(parts.millisecond) + } +} + +/** @internal */ +export const setParts: { + ( + parts: Partial + ): (self: A) => A + ( + self: A, + parts: Partial + ): A +} = dual( + 2, + (self: DateTime.DateTime, parts: Partial): DateTime.DateTime => + mutate(self, (date) => setPartsDate(date, parts)) +) + +/** @internal */ +export const setPartsUtc: { + ( + parts: Partial + ): (self: A) => A + ( + self: A, + parts: Partial + ): A +} = dual( + 2, + (self: DateTime.DateTime, parts: Partial): DateTime.DateTime => + mutateUtc(self, (date) => setPartsDate(date, parts)) +) + +// ============================================================================= +// mapping +// ============================================================================= + +const constDayMillis = 24 * 60 * 60 * 1000 + +const makeZonedFromAdjusted = ( + adjustedMillis: number, + zone: DateTime.TimeZone, + disambiguation: DateTime.Disambiguation +): DateTime.Zoned => { + if (zone._tag === "Offset") { + return makeZonedProto(adjustedMillis - zone.offset, zone) + } + const beforeOffset = calculateNamedOffset( + adjustedMillis - constDayMillis, + adjustedMillis, + zone + ) + const afterOffset = calculateNamedOffset( + adjustedMillis + constDayMillis, + adjustedMillis, + zone + ) + // If there is no transition, we can return early + if (beforeOffset === afterOffset) { + return makeZonedProto(adjustedMillis - beforeOffset, zone) + } + const isForwards = beforeOffset < afterOffset + const transitionMillis = beforeOffset - afterOffset + // If the transition is forwards, we only need to check if we should move the + // local wall clock time forward if it is inside the gap + if (isForwards) { + const currentAfterOffset = calculateNamedOffset( + adjustedMillis - afterOffset, + adjustedMillis, + zone + ) + if (currentAfterOffset === afterOffset) { + return makeZonedProto(adjustedMillis - afterOffset, zone) + } + const before = makeZonedProto(adjustedMillis - beforeOffset, zone) + const beforeAdjustedMillis = toDate(before).getTime() + // If the wall clock time has changed, we are inside the gap + if (adjustedMillis !== beforeAdjustedMillis) { + switch (disambiguation) { + case "reject": { + const formatted = new Date(adjustedMillis).toISOString() + throw new RangeError(`Gap time: ${formatted} does not exist in time zone ${zone.id}`) + } + case "earlier": + return makeZonedProto(adjustedMillis - afterOffset, zone) + + case "compatible": + case "later": + return before + } + } + // The wall clock time is in the earlier offset, so we use that + return before + } + + const currentBeforeOffset = calculateNamedOffset( + adjustedMillis - beforeOffset, + adjustedMillis, + zone + ) + // The wall clock time is in the earlier offset, so we use that + if (currentBeforeOffset === beforeOffset) { + if (disambiguation === "earlier" || disambiguation === "compatible") { + return makeZonedProto(adjustedMillis - beforeOffset, zone) + } + const laterOffset = calculateNamedOffset( + adjustedMillis - beforeOffset + transitionMillis, + adjustedMillis + transitionMillis, + zone + ) + if (laterOffset === beforeOffset) { + return makeZonedProto(adjustedMillis - beforeOffset, zone) + } + // If the offset changed in this period, then we are inside the period where + // the wall clock time occurs twice, once in the earlier offset and once in + // the later offset. + if (disambiguation === "reject") { + const formatted = new Date(adjustedMillis).toISOString() + throw new RangeError(`Ambiguous time: ${formatted} occurs twice in time zone ${zone.id}`) + } + // If the disambiguation is "later", we return the later offset below + } + return makeZonedProto(adjustedMillis - afterOffset, zone) +} + +const offsetRegExp = /([+-])(\d{2}):(\d{2})$/ +const parseOffset = (offset: string): number | null => { + const match = offsetRegExp.exec(offset) + if (match === null) { + return null + } + const [, sign, hours, minutes] = match + return (sign === "+" ? 1 : -1) * (Number(hours) * 60 + Number(minutes)) * 60 * 1000 +} + +const calculateNamedOffset = ( + utcMillis: number, + adjustedMillis: number, + zone: DateTime.TimeZone.Named +): number => { + const offset = zone.format.formatToParts(utcMillis).find((_) => _.type === "timeZoneName")?.value ?? "" + if (offset === "GMT") { + return 0 + } + const result = parseOffset(offset) + if (result === null) { + // fallback to using the adjusted date + return zonedOffset(makeZonedProto(adjustedMillis, zone)) + } + return result +} + +/** @internal */ +export const mutate: { + (f: (date: Date) => void, options?: { + readonly disambiguation?: DateTime.Disambiguation | undefined + }): (self: A) => A + (self: A, f: (date: Date) => void, options?: { + readonly disambiguation?: DateTime.Disambiguation | undefined + }): A +} = dual(isDateTimeArgs, (self: DateTime.DateTime, f: (date: Date) => void, options?: { + readonly disambiguation?: DateTime.Disambiguation | undefined +}): DateTime.DateTime => { + if (self._tag === "Utc") { + const date = toDateUtc(self) + f(date) + return makeUtc(date.getTime()) + } + const adjustedDate = toDate(self) + const newAdjustedDate = new Date(adjustedDate.getTime()) + f(newAdjustedDate) + return makeZonedFromAdjusted(newAdjustedDate.getTime(), self.zone, options?.disambiguation ?? "compatible") +}) + +/** @internal */ +export const mutateUtc: { + (f: (date: Date) => void): (self: A) => A + (self: A, f: (date: Date) => void): A +} = dual(2, (self: DateTime.DateTime, f: (date: Date) => void): DateTime.DateTime => + mapEpochMillis(self, (millis) => { + const date = new Date(millis) + f(date) + return date.getTime() + })) + +/** @internal */ +export const mapEpochMillis: { + (f: (millis: number) => number): (self: A) => A + (self: A, f: (millis: number) => number): A +} = dual(2, (self: DateTime.DateTime, f: (millis: number) => number): DateTime.DateTime => { + const millis = f(toEpochMillis(self)) + return self._tag === "Utc" ? makeUtc(millis) : makeZonedProto(millis, self.zone) +}) + +/** @internal */ +export const withDate: { + (f: (date: Date) => A): (self: DateTime.DateTime) => A + (self: DateTime.DateTime, f: (date: Date) => A): A +} = dual(2, (self: DateTime.DateTime, f: (date: Date) => A): A => f(toDate(self))) + +/** @internal */ +export const withDateUtc: { + (f: (date: Date) => A): (self: DateTime.DateTime) => A + (self: DateTime.DateTime, f: (date: Date) => A): A +} = dual(2, (self: DateTime.DateTime, f: (date: Date) => A): A => f(toDateUtc(self))) + +/** @internal */ +export const match: { + (options: { + readonly onUtc: (_: DateTime.Utc) => A + readonly onZoned: (_: DateTime.Zoned) => B + }): (self: DateTime.DateTime) => A | B + (self: DateTime.DateTime, options: { + readonly onUtc: (_: DateTime.Utc) => A + readonly onZoned: (_: DateTime.Zoned) => B + }): A | B +} = dual(2, (self: DateTime.DateTime, options: { + readonly onUtc: (_: DateTime.Utc) => A + readonly onZoned: (_: DateTime.Zoned) => B +}): A | B => self._tag === "Utc" ? options.onUtc(self) : options.onZoned(self)) + +// ============================================================================= +// math +// ============================================================================= + +/** @internal */ +export const addDuration: { + (duration: Duration.Input): (self: A) => A + (self: A, duration: Duration.Input): A +} = dual( + 2, + (self: DateTime.DateTime, duration: Duration.Input): DateTime.DateTime => + mapEpochMillis(self, (millis) => millis + Duration.toMillis(Duration.fromInputUnsafe(duration))) +) + +/** @internal */ +export const subtractDuration: { + (duration: Duration.Input): (self: A) => A + (self: A, duration: Duration.Input): A +} = dual( + 2, + (self: DateTime.DateTime, duration: Duration.Input): DateTime.DateTime => + mapEpochMillis(self, (millis) => millis - Duration.toMillis(Duration.fromInputUnsafe(duration))) +) + +const addMillis = (date: Date, amount: number): void => { + date.setTime(date.getTime() + amount) +} + +/** @internal */ +export const add: { + ( + parts: Partial + ): (self: A) => A + ( + self: A, + parts: Partial + ): A +} = dual( + 2, + (self: DateTime.DateTime, parts: Partial): DateTime.DateTime => + mutate(self, (date) => { + if (parts.milliseconds) { + addMillis(date, parts.milliseconds) + } + if (parts.seconds) { + addMillis(date, parts.seconds * 1000) + } + if (parts.minutes) { + addMillis(date, parts.minutes * 60 * 1000) + } + if (parts.hours) { + addMillis(date, parts.hours * 60 * 60 * 1000) + } + if (parts.days) { + date.setUTCDate(date.getUTCDate() + parts.days) + } + if (parts.weeks) { + date.setUTCDate(date.getUTCDate() + parts.weeks * 7) + } + if (parts.months) { + const day = date.getUTCDate() + date.setUTCMonth(date.getUTCMonth() + parts.months + 1, 0) + if (day < date.getUTCDate()) { + date.setUTCDate(day) + } + } + if (parts.years) { + const day = date.getUTCDate() + const month = date.getUTCMonth() + date.setUTCFullYear( + date.getUTCFullYear() + parts.years, + month + 1, + 0 + ) + if (day < date.getUTCDate()) { + date.setUTCDate(day) + } + } + }) +) + +/** @internal */ +export const subtract: { + ( + parts: Partial + ): (self: A) => A + ( + self: A, + parts: Partial + ): A +} = dual(2, (self: DateTime.DateTime, parts: Partial): DateTime.DateTime => { + const newParts = {} as Partial> + for (const key in parts) { + newParts[key as keyof DateTime.DateTime.PartsForMath] = -1 * parts[key as keyof DateTime.DateTime.PartsForMath]! + } + return add(self, newParts) +}) + +const startOfDate = (date: Date, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined +}) => { + switch (part) { + case "second": { + date.setUTCMilliseconds(0) + break + } + case "minute": { + date.setUTCSeconds(0, 0) + break + } + case "hour": { + date.setUTCMinutes(0, 0, 0) + break + } + case "day": { + date.setUTCHours(0, 0, 0, 0) + break + } + case "week": { + const weekStartsOn = options?.weekStartsOn ?? 0 + const day = date.getUTCDay() + const diff = (day - weekStartsOn + 7) % 7 + date.setUTCDate(date.getUTCDate() - diff) + date.setUTCHours(0, 0, 0, 0) + break + } + case "month": { + date.setUTCDate(1) + date.setUTCHours(0, 0, 0, 0) + break + } + case "year": { + date.setUTCMonth(0, 1) + date.setUTCHours(0, 0, 0, 0) + break + } + } +} + +/** @internal */ +export const startOf: { + (part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined + }): (self: A) => A + (self: A, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined + }): A +} = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined +}): DateTime.DateTime => mutate(self, (date) => startOfDate(date, part, options))) + +const endOfDate = (date: Date, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined +}) => { + switch (part) { + case "second": { + date.setUTCMilliseconds(999) + break + } + case "minute": { + date.setUTCSeconds(59, 999) + break + } + case "hour": { + date.setUTCMinutes(59, 59, 999) + break + } + case "day": { + date.setUTCHours(23, 59, 59, 999) + break + } + case "week": { + const weekStartsOn = options?.weekStartsOn ?? 0 + const day = date.getUTCDay() + const diff = (day - weekStartsOn + 7) % 7 + date.setUTCDate(date.getUTCDate() - diff + 6) + date.setUTCHours(23, 59, 59, 999) + break + } + case "month": { + date.setUTCMonth(date.getUTCMonth() + 1, 0) + date.setUTCHours(23, 59, 59, 999) + break + } + case "year": { + date.setUTCMonth(11, 31) + date.setUTCHours(23, 59, 59, 999) + break + } + } +} + +/** @internal */ +export const endOf: { + (part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined + }): (self: A) => A + (self: A, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined + }): A +} = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined +}): DateTime.DateTime => mutate(self, (date) => endOfDate(date, part, options))) + +/** @internal */ +export const nearest: { + (part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined + }): (self: A) => A + (self: A, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined + }): A +} = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: { + readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined +}): DateTime.DateTime => + mutate(self, (date) => { + if (part === "millisecond") return + const millis = date.getTime() + const start = new Date(millis) + startOfDate(start, part, options) + const startMillis = start.getTime() + const end = new Date(millis) + endOfDate(end, part, options) + const endMillis = end.getTime() + 1 + const diffStart = millis - startMillis + const diffEnd = endMillis - millis + if (diffStart < diffEnd) { + date.setTime(startMillis) + } else { + date.setTime(endMillis) + } + })) + +// ============================================================================= +// formatting +// ============================================================================= + +const intlTimeZone = (self: DateTime.TimeZone): string => { + if (self._tag === "Named") { + return self.id + } + return offsetToString(self.offset) +} + +/** @internal */ +export const format: { + ( + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined + ): (self: DateTime.DateTime) => string + ( + self: DateTime.DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined + ): string +} = dual(isDateTimeArgs, ( + self: DateTime.DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined +): string => { + try { + return new Intl.DateTimeFormat(options?.locale, { + timeZone: self._tag === "Utc" ? "UTC" : intlTimeZone(self.zone), + ...options + }).format(self.epochMilliseconds) + } catch { + return new Intl.DateTimeFormat(options?.locale, { + timeZone: "UTC", + ...options + }).format(toDate(self)) + } +}) + +/** @internal */ +export const formatLocal: { + ( + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined + ): (self: DateTime.DateTime) => string + ( + self: DateTime.DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined + ): string +} = dual(isDateTimeArgs, ( + self: DateTime.DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined +): string => new Intl.DateTimeFormat(options?.locale, options).format(self.epochMilliseconds)) + +/** @internal */ +export const formatUtc: { + ( + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined + ): (self: DateTime.DateTime) => string + ( + self: DateTime.DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined + ): string +} = dual(isDateTimeArgs, ( + self: DateTime.DateTime, + options?: + | Intl.DateTimeFormatOptions & { + readonly locale?: Intl.LocalesArgument + } + | undefined +): string => + new Intl.DateTimeFormat(options?.locale, { + ...options, + timeZone: "UTC" + }).format(self.epochMilliseconds)) + +/** @internal */ +export const formatIntl: { + (format: Intl.DateTimeFormat): (self: DateTime.DateTime) => string + (self: DateTime.DateTime, format: Intl.DateTimeFormat): string +} = dual(2, (self: DateTime.DateTime, format: Intl.DateTimeFormat): string => format.format(self.epochMilliseconds)) + +/** @internal */ +export const formatIso = (self: DateTime.DateTime): string => toDateUtc(self).toISOString() + +/** @internal */ +export const formatIsoDate = (self: DateTime.DateTime): string => toDate(self).toISOString().slice(0, 10) + +/** @internal */ +export const formatIsoDateUtc = (self: DateTime.DateTime): string => toDateUtc(self).toISOString().slice(0, 10) + +/** @internal */ +export const formatIsoOffset = (self: DateTime.DateTime): string => { + const date = toDate(self) + return self._tag === "Utc" ? date.toISOString() : `${date.toISOString().slice(0, -1)}${zonedOffsetIso(self)}` +} + +/** @internal */ +export const formatIsoZoned = (self: DateTime.Zoned): string => + self.zone._tag === "Offset" ? formatIsoOffset(self) : `${formatIsoOffset(self)}[${self.zone.id}]` diff --git a/.repos/effect/packages/effect/src/internal/doNotation.ts b/.repos/effect/packages/effect/src/internal/doNotation.ts new file mode 100644 index 000000000..7c6e3c919 --- /dev/null +++ b/.repos/effect/packages/effect/src/internal/doNotation.ts @@ -0,0 +1,117 @@ +import { dual } from "../Function.ts" +import type { Kind, TypeLambda } from "../HKT.ts" +import type { NoInfer } from "../Types.ts" + +interface Map { + ( + f: (a: A) => B + ): (self: Kind) => Kind + ( + self: Kind, + f: (a: A) => B + ): Kind +} + +interface FlatMap { + ( + f: (a: A) => Kind + ): ( + self: Kind + ) => Kind + ( + self: Kind, + f: (a: A) => Kind + ): Kind +} + +/** @internal */ +export const let_ = ( + map: Map +): { + ( + name: Exclude, + f: (a: NoInfer) => B + ): ( + self: Kind + ) => Kind + ( + self: Kind, + name: Exclude, + f: (a: NoInfer) => B + ): Kind +} => + dual( + 3, + ( + self: Kind, + name: Exclude, + f: (a: NoInfer) => B + ): Kind => + map(self, (a) => ({ ...a, [name]: f(a) }) as any) + ) + +/** @internal */ +export const bindTo = ( + map: Map +): { + ( + name: N + ): ( + self: Kind + ) => Kind> + ( + self: Kind, + name: N + ): Kind> +} => + dual( + 2, + ( + self: Kind, + name: N + ): Kind> => map(self, (a) => ({ [name]: a }) as Record) + ) + +/** @internal */ +export const bind = ( + map: Map, + flatMap: FlatMap +): { + ( + name: Exclude, + f: (a: NoInfer) => Kind + ): ( + self: Kind + ) => Kind< + F, + R1 & R2, + O1 | O2, + E1 | E2, + { [K in keyof A | N]: K extends keyof A ? A[K] : B } + > + ( + self: Kind, + name: Exclude, + f: (a: NoInfer) => Kind + ): Kind< + F, + R1 & R2, + O1 | O2, + E1 | E2, + { [K in keyof A | N]: K extends keyof A ? A[K] : B } + > +} => + dual( + 3, + ( + self: Kind, + name: Exclude, + f: (a: NoInfer) => Kind + ): Kind< + F, + R1 & R2, + O1 | O2, + E1 | E2, + { [K in keyof A | N]: K extends keyof A ? A[K] : B } + > => flatMap(self, (a) => map(f(a), (b) => ({ ...a, [name]: b }) as any)) + ) diff --git a/.repos/effect/packages/effect/src/internal/effect.ts b/.repos/effect/packages/effect/src/internal/effect.ts new file mode 100644 index 000000000..bb6e4bcaf --- /dev/null +++ b/.repos/effect/packages/effect/src/internal/effect.ts @@ -0,0 +1,6709 @@ +import * as Arr from "../Array.ts" +import type * as Cause from "../Cause.ts" +import type * as Clock from "../Clock.ts" +import type * as Console from "../Console.ts" +import * as Context from "../Context.ts" +import * as Duration from "../Duration.ts" +import type * as Effect from "../Effect.ts" +import * as Equal from "../Equal.ts" +import type * as Exit from "../Exit.ts" +import type * as Fiber from "../Fiber.ts" +import * as Filter from "../Filter.ts" +import { formatJson } from "../Formatter.ts" +import type { LazyArg } from "../Function.ts" +import { constant, constFalse, constTrue, constUndefined, constVoid, dual, identity } from "../Function.ts" +import * as Hash from "../Hash.ts" +import { toJson, toStringUnknown } from "../Inspectable.ts" +import * as Iterable from "../Iterable.ts" +import type * as _Latch from "../Latch.ts" +import type * as Logger from "../Logger.ts" +import type * as LogLevel from "../LogLevel.ts" +import type * as Metric from "../Metric.ts" +import * as Option from "../Option.ts" +import * as Order from "../Order.ts" +import { pipeArguments } from "../Pipeable.ts" +import type * as Predicate from "../Predicate.ts" +import { hasProperty, isIterable, isString, isTagged } from "../Predicate.ts" +import { currentFiberTypeId, redact } from "../Redactable.ts" +import type { StackFrame } from "../References.ts" +import * as Result from "../Result.ts" +import * as Scheduler from "../Scheduler.ts" +import type * as Scope from "../Scope.ts" +import * as Tracer from "../Tracer.ts" +import type { + Concurrency, + EqualsWith, + ExcludeReason, + ExcludeTag, + ExtractReason, + ExtractTag, + NarrowReason, + NoInfer, + OmitReason, + ReasonOf, + ReasonTags, + Simplify, + Tags, + unassigned +} from "../Types.ts" +import { internalCall } from "../Utils.ts" +import type { Primitive } from "./core.ts" +import { + args, + causeAnnotate, + causeEmpty, + causeFromReasons, + CauseImpl, + constEmptyAnnotations, + contA, + contAll, + contE, + evaluate, + exitDie, + exitFail, + exitFailCause, + exitSucceed, + ExitTypeId, + Fail, + InterruptorStackTrace, + isCause, + isDieReason, + isEffect, + isFailReason, + isInterruptReason, + isNoSuchElementError, + makePrimitive, + makePrimitiveProto, + NoSuchElementError, + ReasonBase, + StackTraceKey as CauseStackTrace, + TaggedError, + withFiber, + Yield +} from "./core.ts" +import * as doNotation from "./doNotation.ts" +import * as InternalMetric from "./metric.ts" +import * as InternalRecord from "./record.ts" +import { + CurrentErrorReporters, + CurrentLogAnnotations, + CurrentLogLevel, + CurrentLogSpans, + CurrentStackFrame, + MinimumLogLevel, + TracerEnabled, + TracerSpanAnnotations, + TracerSpanLinks, + TracerTimingEnabled +} from "./references.ts" +import { getStackTraceLimit, setStackTraceLimit } from "./stackTraceLimit.ts" +import { addSpanStackTrace, makeStackCleaner } from "./tracer.ts" + +// ---------------------------------------------------------------------------- +// Cause +// ---------------------------------------------------------------------------- + +/** @internal */ +export class Interrupt extends ReasonBase<"Interrupt"> implements Cause.Interrupt { + readonly fiberId: number | undefined + constructor( + fiberId: number | undefined, + annotations = constEmptyAnnotations + ) { + super("Interrupt", annotations, "Interrupted") + this.fiberId = fiberId + } + override toString() { + return `Interrupt(${this.fiberId})` + } + toJSON(): unknown { + return { + _tag: "Interrupt", + fiberId: this.fiberId + } + } + [Equal.symbol](that: any): boolean { + return ( + isInterruptReason(that) && + this.fiberId === that.fiberId && + this.annotations === that.annotations + ) + } + [Hash.symbol](): number { + return Hash.combine(Hash.string(`${this._tag}:${this.fiberId}`))( + Hash.random(this.annotations) + ) + } +} + +/** @internal */ +export const makeInterruptReason = (fiberId?: number | undefined): Cause.Interrupt => new Interrupt(fiberId) + +/** @internal */ +export const causeInterrupt = ( + fiberId?: number | undefined +): Cause.Cause => new CauseImpl([new Interrupt(fiberId)]) + +/** @internal */ +export const hasFails = (self: Cause.Cause): boolean => self.reasons.some(isFailReason) + +/** @internal */ +export const findFail = (self: Cause.Cause): Result.Result, Cause.Cause> => { + const reason = self.reasons.find(isFailReason) + return reason ? Result.succeed(reason) : Result.fail(self as Cause.Cause) +} + +/** @internal */ +export const findError = (self: Cause.Cause): Result.Result> => { + for (let i = 0; i < self.reasons.length; i++) { + const reason = self.reasons[i] + if (reason._tag === "Fail") { + return Result.succeed(reason.error) + } + } + return Result.fail(self as Cause.Cause) +} + +/** @internal */ +export const findErrorOption = Filter.toOption(findError) + +/** @internal */ +export const hasDies = (self: Cause.Cause): boolean => self.reasons.some(isDieReason) + +/** @internal */ +export const findDie = (self: Cause.Cause): Result.Result> => { + const reason = self.reasons.find(isDieReason) + return reason ? Result.succeed(reason) : Result.fail(self) +} + +/** @internal */ +export const findDefect = (self: Cause.Cause): Result.Result> => { + const reason = self.reasons.find(isDieReason) + return reason ? Result.succeed(reason.defect) : Result.fail(self) +} + +/** @internal */ +export const hasInterrupts = (self: Cause.Cause): boolean => self.reasons.some(isInterruptReason) + +/** @internal */ +export const findInterrupt = (self: Cause.Cause): Result.Result> => { + const reason = self.reasons.find(isInterruptReason) + return reason ? Result.succeed(reason) : Result.fail(self) +} + +/** @internal */ +export const causeFilterInterruptors = ( + self: Cause.Cause +): Result.Result, Cause.Cause> => { + let interruptors: Set | undefined + for (let i = 0; i < self.reasons.length; i++) { + const f = self.reasons[i] + if (f._tag !== "Interrupt") continue + interruptors ??= new Set() + if (f.fiberId !== undefined) { + interruptors.add(f.fiberId) + } + } + return interruptors ? Result.succeed(interruptors) : Result.fail(self) +} + +/** @internal */ +export const causeInterruptors = (self: Cause.Cause): ReadonlySet => { + const result = causeFilterInterruptors(self) + return Result.isFailure(result) ? emptySet : result.success +} +const emptySet = new Set() + +/** @internal */ +export const hasInterruptsOnly = (self: Cause.Cause): boolean => + self.reasons.length > 0 && self.reasons.every(isInterruptReason) + +/** @internal */ +export const reasonAnnotations = ( + self: Cause.Reason +): Context.Context => Context.makeUnsafe(self.annotations) + +/** @internal */ +export const causeAnnotations = ( + self: Cause.Cause +): Context.Context => { + const map = new Map() + for (const f of self.reasons) { + if (f.annotations.size > 0) { + for (const [key, value] of f.annotations) { + map.set(key, value) + } + } + } + return Context.makeUnsafe(map) +} + +/** @internal */ +export const causeCombine: { + (that: Cause.Cause): (self: Cause.Cause) => Cause.Cause + (self: Cause.Cause, that: Cause.Cause): Cause.Cause +} = dual( + 2, + (self: Cause.Cause, that: Cause.Cause): Cause.Cause => { + if (self.reasons.length === 0) { + return that as Cause.Cause + } else if (that.reasons.length === 0) { + return self as Cause.Cause + } + const newCause = new CauseImpl( + Arr.union(self.reasons, that.reasons) + ) + return Equal.equals(self, newCause) ? self : newCause + } +) + +/** @internal */ +export const causeMap: { + (f: (error: NoInfer) => E2): (self: Cause.Cause) => Cause.Cause + (self: Cause.Cause, f: (error: NoInfer) => E2): Cause.Cause +} = dual( + 2, + (self: Cause.Cause, f: (error: NoInfer) => E2): Cause.Cause => { + let hasFail = false + const failures = self.reasons.map((failure) => { + if (isFailReason(failure)) { + hasFail = true + return new Fail(f(failure.error), failure.annotations) + } + return failure + }) + return hasFail ? causeFromReasons(failures) : self as any + } +) + +/** @internal */ +export const causePartition = ( + self: Cause.Cause +): { + readonly Fail: ReadonlyArray> + readonly Die: ReadonlyArray + readonly Interrupt: ReadonlyArray +} => { + const obj = { + Fail: [] as Array>, + Die: [] as Array, + Interrupt: [] as Array + } + for (let i = 0; i < self.reasons.length; i++) { + obj[self.reasons[i]._tag].push(self.reasons[i] as any) + } + return obj +} + +/** @internal */ +export const causeSquash = (self: Cause.Cause): unknown => { + const partitioned = causePartition(self) + if (partitioned.Fail.length > 0) { + return partitioned.Fail[0].error + } else if (partitioned.Die.length > 0) { + return partitioned.Die[0].defect + } else if (partitioned.Interrupt.length > 0) { + return new globalThis.Error("All fibers interrupted without error") + } + return new globalThis.Error("Empty cause") +} + +/** @internal */ +export const causePrettyErrors = (self: Cause.Cause, options?: { + readonly includeCauseInStack?: boolean | undefined +}): Array => { + const errors: Array = [] + const interrupts: Array = [] + if (self.reasons.length === 0) return errors + + const prevStackLimit = getStackTraceLimit() + setStackTraceLimit(1) + + for (const failure of self.reasons) { + if (failure._tag === "Interrupt") { + interrupts.push(failure) + continue + } + errors.push( + causePrettyError( + failure._tag === "Die" ? failure.defect : failure.error as any, + failure.annotations, + options + ) + ) + } + if (errors.length === 0) { + const cause = new Error("The fiber was interrupted by:") + cause.name = "InterruptCause" + cause.stack = interruptCauseStack(cause, interrupts) + const error = new globalThis.Error("All fibers interrupted without error", { cause }) + error.name = "InterruptError" + error.stack = `${error.name}: ${error.message}` + errors.push(causePrettyError(error, interrupts[0].annotations, options)) + } + + setStackTraceLimit(prevStackLimit) + return errors +} + +/** @internal */ +export const causePrettyError = ( + original: Record | Error, + annotations?: ReadonlyMap, + options?: { + readonly includeCauseInStack?: boolean | undefined + } +): Error => { + const kind = typeof original + let error: Error + if (original && kind === "object") { + error = new globalThis.Error(causePrettyMessage(original), { + cause: original.cause ? causePrettyError(original.cause as any) : undefined + }) + if (typeof original.name === "string") { + error.name = original.name + } + if (typeof original.stack === "string") { + error.stack = cleanErrorStack(original.stack, error, annotations) + } else { + const stack = `${error.name}: ${error.message}` + error.stack = annotations ? addStackAnnotations(stack, annotations) : stack + } + if (options?.includeCauseInStack) { + error.stack = renderPrettyError(error)! + } + for (const key of Object.keys(original)) { + if (!(key in error)) { + ;(error as any)[key] = (original as any)[key] + } + } + } else { + error = new globalThis.Error( + !original ? `Unknown error: ${original}` : kind === "string" ? original as any : formatJson(original) + ) + } + return error +} + +const causePrettyMessage = (u: Record | Error): string => { + if (typeof u.message === "string") { + return u.message + } else if ( + typeof u.toString === "function" + && u.toString !== Object.prototype.toString + && u.toString !== Array.prototype.toString + ) { + try { + return u.toString() + } catch { + // something's off, rollback to json + } + } + return formatJson(u) +} + +const locationRegExp = /\((.*)\)/g + +const cleanErrorStack = ( + stack: string, + error: Error, + annotations: ReadonlyMap | undefined +): string => { + const message = `${error.name}: ${error.message}` + const lines = (stack.startsWith(message) ? stack.slice(message.length) : stack).split("\n") + const out: Array = [message] + for (let i = 1; i < lines.length; i++) { + if (/(?:Generator\.next|~effect\/Effect)/.test(lines[i])) { + break + } + out.push(lines[i]) + } + return annotations ? addStackAnnotations(out.join("\n"), annotations) : out.join("\n") +} + +const addStackAnnotations = (stack: string, annotations: ReadonlyMap) => { + const frame = annotations?.get(CauseStackTrace.key) as StackFrame | undefined + if (frame) { + stack = `${stack}\n${currentStackTrace(frame)}` + } + return stack +} + +const interruptCauseStack = (error: Error, interrupts: Array): string => { + const out: Array = [`${error.name}: ${error.message}`] + for (const current of interrupts) { + const fiberId = current.fiberId !== undefined ? `#${current.fiberId}` : "unknown" + const frame = current.annotations.get(InterruptorStackTrace.key) as StackFrame | undefined + out.push(` at fiber (${fiberId})`) + if (frame) out.push(currentStackTrace(frame)) + } + return out.join("\n") +} + +const currentStackTrace = (frame: StackFrame): string => { + const out: Array = [] + let current: StackFrame | undefined = frame + let i = 0 + while (current && i < 10) { + const stack = current.stack() + if (stack) { + const locationMatchAll = stack.matchAll(locationRegExp) + let match = false + for (const [, location] of locationMatchAll) { + match = true + out.push(` at ${current.name} (${location})`) + } + if (!match) { + out.push(` at ${current.name} (${stack.replace(/^at /, "")})`) + } + } else { + out.push(` at ${current.name}`) + } + current = current.parent + i++ + } + return out.join("\n") +} + +/** @internal */ +export const causePretty = (cause: Cause.Cause): string => + causePrettyErrors(cause).map(renderPrettyError).join("\n") + +const renderPrettyError = (e: Error): string | undefined => + e.cause ? `${e.stack} {\n${renderErrorCause(e.cause as Error, " ")}\n}` : e.stack + +const renderErrorCause = (cause: Error, prefix: string) => { + const lines = cause.stack!.split("\n") + let stack = `${prefix}[cause]: ${lines[0]}` + for (let i = 1, len = lines.length; i < len; i++) { + stack += `\n${prefix}${lines[i]}` + } + if (cause.cause) { + stack += ` {\n${renderErrorCause(cause.cause as Error, `${prefix} `)}\n${prefix}}` + } + return stack +} + +// ---------------------------------------------------------------------------- +// Fiber +// ---------------------------------------------------------------------------- + +/** @internal */ +export const FiberTypeId = "~effect/Fiber" as const + +const fiberVariance = { + _A: identity, + _E: identity +} + +const fiberIdStore = { id: 0 } + +/** @internal */ +export const getCurrentFiber = (): Fiber.Fiber | undefined => (globalThis as any)[currentFiberTypeId] + +/** @internal */ +export class FiberImpl implements Fiber.Fiber { + constructor( + context: Context.Context, + interruptible: boolean = true + ) { + this[FiberTypeId] = fiberVariance as any + this.setContext(context) + this.id = ++fiberIdStore.id + this.currentOpCount = 0 + this.interruptible = interruptible + this._stack = [] + this._observers = [] + this._exit = undefined + this._children = undefined + this._interruptedCause = undefined + this._yielded = undefined + this._running = false + this._deferredInterrupt = false + this.runtimeMetrics?.recordFiberStart(this.context) + } + + readonly [FiberTypeId]: Fiber.Fiber.Variance + + readonly id: number + interruptible: boolean + currentOpCount: number + readonly _stack: Array + readonly _observers: Array<(exit: Exit.Exit) => void> + _exit: Exit.Exit | undefined + _children: Set> | undefined + _interruptedCause: Cause.Cause | undefined + _yielded: Exit.Exit | (() => void) | undefined + _running: boolean + _deferredInterrupt: boolean + + // set in setContext + context!: Context.Context + currentScheduler!: Scheduler.Scheduler + currentTracerContext: Tracer.Tracer["context"] + currentSpan: Tracer.AnySpan | undefined + currentLogLevel!: LogLevel.LogLevel + minimumLogLevel!: LogLevel.LogLevel + currentStackFrame: StackFrame | undefined + runtimeMetrics: Metric.FiberRuntimeMetricsService | undefined + maxOpsBeforeYield!: number + currentPreventYield!: boolean + + _dispatcher: Scheduler.SchedulerDispatcher | undefined = undefined + get currentDispatcher(): Scheduler.SchedulerDispatcher { + return this._dispatcher ??= this.currentScheduler.makeDispatcher() + } + + getRef(ref: Context.Reference): X { + return Context.get(this.context, ref) + } + addObserver(cb: (exit: Exit.Exit) => void): () => void { + if (this._exit) { + cb(this._exit) + return constVoid + } + this._observers.push(cb) + return () => { + if (this._exit) return + const index = this._observers.indexOf(cb) + if (index >= 0) { + this._observers.splice(index, 1) + } + } + } + interruptUnsafe(fiberId?: number | undefined, annotations?: Context.Context | undefined): void { + if (this._exit) { + return + } + let cause = causeInterrupt(fiberId) + if (this.currentStackFrame) { + cause = causeAnnotate(cause, Context.make(CauseStackTrace, this.currentStackFrame)) + } + if (annotations) { + cause = causeAnnotate(cause, annotations) + } + this._interruptedCause = this._interruptedCause + ? causeCombine(this._interruptedCause, cause) + : cause + if (this.interruptible) { + if (this._running) { + this._deferredInterrupt = true + } else { + this.evaluate(failCause(this._interruptedCause) as any) + } + } + } + pollUnsafe(): Exit.Exit | undefined { + return this._exit + } + evaluate(effect: Primitive): void { + if (this._exit) { + return + } else if (this._yielded !== undefined) { + const yielded = this._yielded as () => void + this._yielded = undefined + yielded() + } + const exit = this.runLoop(effect) + if (exit === Yield) { + return + } + // the interruptChildren middleware is added in Effect.forkChild, so it can be + // tree-shaken if not used + const interruptChildren = fiberMiddleware.interruptChildren && + fiberMiddleware.interruptChildren(this) + if (interruptChildren !== undefined) { + return this.evaluate(flatMap(interruptChildren, () => exit) as any) + } + + this._exit = exit + this.runtimeMetrics?.recordFiberEnd(this.context, this._exit) + for (let i = 0; i < this._observers.length; i++) { + this._observers[i](exit) + } + this._observers.length = 0 + this._stack.length = 0 + this._children = undefined + this.context = Context.empty() + } + runLoop(effect: Primitive): Exit.Exit | Yield { + const prevFiber = (globalThis as any)[currentFiberTypeId] + ;(globalThis as any)[currentFiberTypeId] = this + const prevRunning = this._running + this._running = true + let yielding = false + let current: Primitive | Yield = effect + this.currentOpCount = 0 + try { + while (true) { + if (this._deferredInterrupt) { + this._deferredInterrupt = false + current = failCause(this._interruptedCause!) as any + } + this.currentOpCount++ + if ( + !yielding && + !this.currentPreventYield && + this.currentScheduler.shouldYield(this as any) + ) { + yielding = true + const prev = current + current = flatMap(yieldNow, () => prev as any) as any + } + current = this.currentTracerContext + ? this.currentTracerContext(current as any, this) + : (current as any)[evaluate](this) + if (current === Yield) { + const yielded = this._yielded! + if (ExitTypeId in yielded) { + this._deferredInterrupt = false + this._yielded = undefined + return yielded + } else if (this._deferredInterrupt) { + this._yielded = undefined + yielded() + continue + } + return Yield + } + } + } catch (error) { + if (!hasProperty(current, evaluate)) { + return exitDie(`Fiber.runLoop: Not a valid effect: ${String(current)}`) + } + return this.runLoop(exitDie(error) as any) + } finally { + this._running = prevRunning + ;(globalThis as any)[currentFiberTypeId] = prevFiber + } + } + getCont(symbol: S): + | (Primitive & Record Primitive>) + | undefined + { + if (this._deferredInterrupt) { + this._deferredInterrupt = false + return deferredInterruptCont + } + while (true) { + const op = this._stack.pop() + if (!op) return undefined + const cont = op[contAll] && op[contAll](this) + if (cont) { + ;(cont as any)[symbol] = cont + return cont as any + } + if (op[symbol]) return op as any + } + } + yieldWith(value: Exit.Exit | (() => void)): Yield { + this._yielded = value + return Yield + } + children(): Set> { + return (this._children ??= new Set()) + } + pipe() { + return pipeArguments(this, arguments) + } + setContext(context: Context.Context): void { + const previous = this.context + this.context = context + // Every key cached below opts in to Context caching, so contexts related + // only by non-caching adds cannot have changed any of them + if (previous !== undefined && Context.hasSameCache(previous, context)) return + const scheduler = this.getRef(Scheduler.Scheduler) + if (scheduler !== this.currentScheduler) { + this.currentScheduler = scheduler + this._dispatcher = undefined + } + // The string-keyed lookups keep the Tracer key values (and the native + // tracer behind Tracer.Tracer's default) out of every bundle + this.currentSpan = Context.getOrUndefinedUnsafe(context, Tracer.ParentSpanKey) + this.currentLogLevel = this.getRef(CurrentLogLevel) + this.minimumLogLevel = this.getRef(MinimumLogLevel) + this.currentStackFrame = this.getRef(CurrentStackFrame) + this.maxOpsBeforeYield = this.getRef(Scheduler.MaxOpsBeforeYield) + this.currentPreventYield = this.getRef(Scheduler.PreventSchedulerYield) + this.runtimeMetrics = Context.getOrUndefinedUnsafe(context, InternalMetric.FiberRuntimeMetricsKey) + const currentTracer = Context.getOrUndefinedUnsafe(context, Tracer.TracerKey) + this.currentTracerContext = currentTracer ? currentTracer["context"] : undefined + } + get currentSpanLocal(): Tracer.Span | undefined { + return this.currentSpan?._tag === "Span" ? this.currentSpan : undefined + } +} + +const deferredInterruptCont: any = { + [contA](_value: unknown, fiber: FiberImpl) { + return failCause(fiber._interruptedCause!) + }, + [contE](_cause: unknown, fiber: FiberImpl) { + return failCause(fiber._interruptedCause!) + } +} + +const fiberMiddleware = { + interruptChildren: undefined as + | ((fiber: FiberImpl) => Effect.Effect | undefined) + | undefined +} + +const fiberStackAnnotations = (fiber: Fiber.Fiber) => { + if (!fiber.currentStackFrame) return undefined + const annotations = new Map() + annotations.set(InterruptorStackTrace.key, fiber.currentStackFrame) + return Context.makeUnsafe(annotations) +} + +const fiberInterruptChildren = (fiber: FiberImpl) => { + if (fiber._children === undefined || fiber._children.size === 0) { + return undefined + } + return fiberInterruptAll(fiber._children) +} + +/** @internal */ +export const fiberAwait = ( + self: Fiber.Fiber +): Effect.Effect> => { + const impl = self as FiberImpl + if (impl._exit) return succeed(impl._exit) + return callback((resume) => { + if (impl._exit) return resume(succeed(impl._exit)) + return sync(self.addObserver((exit) => resume(succeed(exit)))) + }) +} + +/** @internal */ +export const fiberAwaitAll = >( + self: Iterable +): Effect.Effect< + Array< + Exit.Exit< + Fiber extends Fiber.Fiber ? _A : never, + Fiber extends Fiber.Fiber ? _E : never + > + > +> => + callback((resume) => { + const iter = self[Symbol.iterator]() as Iterator + const exits: Array> = [] + let cancel: (() => void) | undefined = undefined + function loop() { + let result = iter.next() + while (!result.done) { + if (result.value._exit) { + exits.push(result.value._exit) + result = iter.next() + continue + } + cancel = result.value.addObserver((exit) => { + exits.push(exit) + loop() + }) + return + } + resume(succeed(exits)) + } + loop() + return sync(() => cancel?.()) + }) + +/** @internal */ +export const fiberJoin = (self: Fiber.Fiber): Effect.Effect => { + const impl = self as FiberImpl + if (impl._exit) return impl._exit + return callback((resume) => { + if (impl._exit) return resume(impl._exit) + return sync(self.addObserver(resume)) + }) +} + +/** @internal */ +export const fiberJoinAll = >>(self: A): Effect.Effect< + Arr.ReadonlyArray.With> ? _A : never>, + A extends Iterable> ? _E : never +> => + callback((resume) => { + const fibers = Array.from(self) + if (fibers.length === 0) return resume(succeed(Arr.empty() as any)) + const out = new Array(fibers.length) as Arr.NonEmptyArray + const cancels = Arr.empty<() => void>() + let done = 0 + let failed = false + for (let i = 0; i < fibers.length; i++) { + if (failed) break + cancels.push(fibers[i].addObserver((exit) => { + done++ + if (exit._tag === "Failure") { + failed = true + cancels.forEach((cancel) => cancel()) + return resume(exit as any) + } + out[i] = exit.value + if (done === fibers.length) { + resume(succeed(out)) + } + })) + } + return sync(() => { + failed = true + cancels.forEach((cancel) => cancel()) + }) + }) + +/** @internal */ +export const fiberInterrupt = ( + self: Fiber.Fiber +): Effect.Effect => withFiber((fiber) => fiberInterruptAs(self, fiber.id)) + +/** @internal */ +export const fiberInterruptAs: { + ( + fiberId: number | undefined, + annotations?: Context.Context | undefined + ): (self: Fiber.Fiber) => Effect.Effect + ( + self: Fiber.Fiber, + fiberId: number | undefined, + annotations?: Context.Context | undefined + ): Effect.Effect +} = dual( + (args) => hasProperty(args[0], FiberTypeId), + ( + self: Fiber.Fiber, + fiberId: number | undefined, + annotations?: Context.Context | undefined + ): Effect.Effect => + withFiber((parent) => { + let ann = fiberStackAnnotations(parent) + ann = ann && annotations ? Context.merge(ann, annotations) : ann ?? annotations + self.interruptUnsafe(fiberId, ann) + return asVoid(fiberAwait(self)) + }) +) + +/** @internal */ +export const fiberInterruptAll = >>( + fibers: A +): Effect.Effect => + withFiber((parent) => { + const annotations = fiberStackAnnotations(parent) + let fiberArr = Arr.empty>() + for (const fiber of fibers) { + fiber.interruptUnsafe(parent.id, annotations) + fiberArr.push(fiber) + } + return asVoid(fiberAwaitAll(fiberArr)) + }) + +/** @internal */ +export const fiberInterruptAllAs: { + (fiberId: number): >>(fibers: A) => Effect.Effect + >>(fibers: A, fiberId: number): Effect.Effect +} = dual(2, >>( + fibers: A, + fiberId: number +): Effect.Effect => + withFiber((parent) => { + const annotations = fiberStackAnnotations(parent) + const fiberArr = Arr.empty>() + for (const fiber of fibers) { + fiber.interruptUnsafe(fiberId, annotations) + fiberArr.push(fiber) + } + return asVoid(fiberAwaitAll(fiberArr)) + })) + +/** @internal */ +export const succeed: (value: A) => Effect.Effect = exitSucceed + +/** @internal */ +export const failCause: (cause: Cause.Cause) => Effect.Effect = exitFailCause + +/** @internal */ +export const fail: (error: E) => Effect.Effect = exitFail + +/** @internal */ +export const sync: (thunk: LazyArg) => Effect.Effect = makePrimitive({ + op: "Sync", + [evaluate](fiber): Primitive | Yield { + const value = this[args]() + const cont = fiber.getCont(contA) + return cont ? cont[contA](value, fiber) : fiber.yieldWith(exitSucceed(value)) + } +}) + +/** @internal */ +export const suspend: ( + evaluate: LazyArg> +) => Effect.Effect = makePrimitive({ + op: "Suspend", + [evaluate](_fiber) { + return this[args]() + } +}) + +/** @internal */ +export const fromOption: < + Arg extends Option.Option | LazyArg, + Rest extends [] | [onNone: LazyArg | undefined] = [] +>( + arg: Arg & (Rest extends [] ? unknown : Option.Option), + ...rest: Rest +) => [Arg] extends [Option.Option] ? Effect.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.Option) => Effect.Effect + : never = dual( + (args) => args.length >= 2 || Option.isOption(args[0]), + (option: Option.Option, onNone?: LazyArg): Effect.Effect => + Option.isNone(option) + ? fail(onNone ? onNone() : new NoSuchElementError("Effect.fromOption: Option.none")) + : succeed(option.value) + ) + +/** @internal */ +export const fromResult: (result: Result.Result) => Effect.Effect = Result.match({ + onFailure: fail, + onSuccess: succeed +}) + +/** @internal */ +export const fromNullishOr = (value: A): Effect.Effect, Cause.NoSuchElementError> => + value == null ? fail(new NoSuchElementError()) : succeed(value) + +/** @internal */ +export const yieldNowWith: (priority?: number) => Effect.Effect = makePrimitive({ + op: "Yield", + [evaluate](fiber) { + let resumed = false + fiber.currentDispatcher.scheduleTask(() => { + if (resumed) return + fiber.evaluate(exitVoid as any) + }, this[args] ?? 0) + return fiber.yieldWith(() => { + resumed = true + }) + } +}) + +/** @internal */ +export const yieldNow: Effect.Effect = yieldNowWith(0) + +/** @internal */ +export const succeedSome = (a: A): Effect.Effect> => succeed(Option.some(a)) + +/** @internal */ +export const succeedNone: Effect.Effect> = succeed( + Option.none() +) + +/** @internal */ +export const transposeOption = ( + self: Option.Option> +): Effect.Effect, E, R> => Option.isNone(self) ? succeedNone : map(self.value, Option.some) + +/** @internal */ +export const failCauseSync = ( + evaluate: LazyArg> +): Effect.Effect => suspend(() => failCause(internalCall(evaluate))) + +/** @internal */ +export const die = (defect: unknown): Effect.Effect => exitDie(defect) + +/** @internal */ +export const failSync = (error: LazyArg): Effect.Effect => suspend(() => fail(internalCall(error))) + +/** @internal */ +const void_: Effect.Effect = succeed(void 0) +/** @internal */ +export { void_ as void } + +/** @internal */ +const try_ = ( + options: { + readonly try: LazyArg + readonly catch: (error: unknown) => E + } | LazyArg +): Effect.Effect => { + const evaluate = typeof options === "function" ? options : options.try + const catcher = typeof options === "function" + ? ((cause: unknown) => new UnknownError(cause, "An error occurred in Effect.try")) + : options.catch + return suspend(() => { + try { + return succeed(internalCall(evaluate)) + } catch (err) { + return fail(internalCall(() => catcher(err)) as E) + } + }) +} +/** @internal */ +export { try_ as try } + +/** @internal */ +export const promise = ( + evaluate: (signal: AbortSignal) => PromiseLike +): Effect.Effect => + callbackOptions(function(resume, signal) { + internalCall(() => evaluate(signal!)).then( + (a) => resume(succeed(a)), + (e) => resume(die(e)) + ) + }, evaluate.length !== 0) + +/** @internal */ +export const tryPromise = ( + options: { + readonly try: (signal: AbortSignal) => PromiseLike + readonly catch: (error: unknown) => E + } | ((signal: AbortSignal) => PromiseLike) +): Effect.Effect => { + const f = typeof options === "function" ? options : options.try + const catcher = typeof options === "function" + ? ((cause: unknown) => new UnknownError(cause, "An error occurred in Effect.tryPromise")) + : options.catch + return callbackOptions(function(resume, signal) { + const failWithCatch = (cause: unknown) => { + try { + resume(fail(internalCall(() => catcher(cause)) as E)) + } catch (err) { + resume(die(err)) + } + } + try { + internalCall(() => f(signal!)).then( + (a) => resume(succeed(a)), + failWithCatch + ) + } catch (err) { + failWithCatch(err) + } + }, f.length !== 0) +} + +/** @internal */ +export const withFiberId = ( + f: (fiberId: number) => Effect.Effect +): Effect.Effect => withFiber((fiber) => f(fiber.id)) + +/** @internal */ +export const fiber = withFiber(succeed) + +/** @internal */ +export const fiberId = withFiberId(succeed) + +const callbackOptions: ( + register: ( + this: Scheduler.Scheduler, + resume: (effect: Effect.Effect) => void, + signal?: AbortSignal + ) => void | Effect.Effect, + withSignal: boolean +) => Effect.Effect = makePrimitive({ + op: "Async", + single: false, + [evaluate](fiber) { + const register = internalCall(() => this[args][0].bind(fiber.currentScheduler)) + let resumed = false + let yielded: boolean | Primitive = false + const controller = this[args][1] ? new AbortController() : undefined + const onCancel = register((effect) => { + if (resumed) return + resumed = true + if (yielded) { + fiber.evaluate(effect as any) + } else { + yielded = effect as any + } + }, controller?.signal) + if (yielded !== false) return yielded + yielded = true + fiber._yielded = () => { + resumed = true + } + if (controller === undefined && onCancel === undefined) { + return Yield + } + fiber._stack.push( + asyncFinalizer(() => { + resumed = true + controller?.abort() + return onCancel ?? exitVoid + }) + ) + return Yield + } +}) + +const asyncFinalizer: ( + onInterrupt: () => Effect.Effect +) => Primitive = makePrimitive({ + op: "AsyncFinalizer", + [contAll](fiber) { + if (fiber.interruptible) { + fiber.interruptible = false + fiber._stack.push(setInterruptibleTrue) + } + }, + [contE](cause, _fiber) { + return hasInterrupts(cause) + ? flatMap(this[args](), () => failCause(cause)) + : failCause(cause) + } +}) + +/** @internal */ +export const callback = ( + register: ( + this: Scheduler.Scheduler, + resume: (effect: Effect.Effect) => void, + signal: AbortSignal + ) => void | Effect.Effect +): Effect.Effect => callbackOptions(register as any, register.length >= 2) + +/** @internal */ +export const never: Effect.Effect = callback(constVoid) + +/** @internal */ +export const gen = < + Self, + Eff extends Effect.Effect, + AEff +>( + ...args: + | [options: { readonly self: Self }, body: (this: Self) => Generator] + | [body: () => Generator] +): Effect.Effect< + AEff, + [Eff] extends [never] ? never + : [Eff] extends [Effect.Effect] ? E + : never, + [Eff] extends [never] ? never + : [Eff] extends [Effect.Effect] ? R + : never +> => + suspend(() => + fromIteratorUnsafe( + args.length === 1 ? args[0]() : (args[1].call(args[0].self) as any) + ) + ) + +/** @internal */ +export const fnUntraced: Effect.fn.Untraced = ( + body: Function, + ...pipeables: Array +) => { + const fn = pipeables.length === 0 + ? function(this: any) { + return suspend(() => fromIteratorUnsafe(body.apply(this, arguments))) + } + : function(this: any) { + let effect = suspend(() => fromIteratorUnsafe(body.apply(this, arguments))) + for (let i = 0; i < pipeables.length; i++) { + effect = pipeables[i](effect, ...arguments) + } + return effect + } + return defineFunctionLength(body.length, fn) +} + +const defineFunctionLength = (length: number, fn: F): F => + Object.defineProperty(fn, "length", { + value: length, + configurable: true + }) + +const fnStackCleaner = makeStackCleaner(2) + +/** @internal */ +export const fn: typeof Effect.fn = function() { + const nameFirst = typeof arguments[0] === "string" + const name = nameFirst ? arguments[0] : "Effect.fn" + const spanOptions = nameFirst ? arguments[1] : undefined + + const prevLimit = getStackTraceLimit() + setStackTraceLimit(2) + const defError = new globalThis.Error() + setStackTraceLimit(prevLimit) + + if (nameFirst) { + return (body: Function | { readonly self: any }, ...pipeables: Array) => + makeFn(name, body, defError, pipeables, nameFirst, spanOptions) + } + + return makeFn( + name, + arguments[0], + defError, + Array.prototype.slice.call(arguments, 1), + nameFirst, + spanOptions + ) +} as any + +const makeFn = ( + name: string, + bodyOrOptions: Function | { readonly self: any }, + defError: Error, + pipeables: Array, + addSpan: boolean, + spanOptions: Tracer.SpanOptionsNoTrace | undefined +) => { + const body = typeof bodyOrOptions === "function" + ? bodyOrOptions + : (pipeables.shift()!).bind(bodyOrOptions.self) + + return defineFunctionLength(body.length, function(this: any, ...args: Array) { + let result = suspend(() => { + const iter = body.apply(this, arguments) + return isEffect(iter) ? iter : fromIteratorUnsafe(iter) + }) + for (let i = 0; i < pipeables.length; i++) { + result = pipeables[i](result, ...args) + } + if (!isEffect(result)) { + return result + } + const prevLimit = getStackTraceLimit() + setStackTraceLimit(2) + const callError = new globalThis.Error() + setStackTraceLimit(prevLimit) + return updateService( + addSpan ? + useSpan(name, spanOptions!, (span) => provideParentSpan(result, span)) : + result, + CurrentStackFrame, + (prev) => ({ + name, + stack: fnStackCleaner(() => callError.stack), + parent: { + name: `${name} (definition)`, + stack: fnStackCleaner(() => defError.stack), + parent: prev + } + }) + ) + }) +} + +/** @internal */ +export const fnUntracedEager: Effect.fn.Untraced = ( + body: Function, + ...pipeables: Array +) => + defineFunctionLength( + body.length, + pipeables.length === 0 + ? function(this: any) { + return fromIteratorEagerUnsafe(() => body.apply(this, arguments)) + } + : function(this: any) { + let effect = fromIteratorEagerUnsafe(() => body.apply(this, arguments)) + for (const pipeable of pipeables) { + effect = pipeable(effect) + } + return effect + } + ) + +const fromIteratorEagerUnsafe = ( + evaluate: () => Iterator> +): Effect.Effect => { + try { + const iterator = evaluate() + let value: any = undefined + + // Try to resolve synchronously in a loop + while (true) { + const state = iterator.next(value) + + if (state.done) { + return succeed(state.value) + } + + const primitive = state.value as any + + if (primitive && primitive._tag === "Success") { + value = primitive.value + continue + } else if (primitive && primitive._tag === "Failure") { + return state.value + } else { + let isFirstExecution = true + + return suspend(() => { + if (isFirstExecution) { + isFirstExecution = false + return flatMap(state.value, (value) => fromIteratorUnsafe(iterator, value)) + } else { + return suspend(() => fromIteratorUnsafe(evaluate())) + } + }) + } + } + } catch (error) { + return die(error) + } +} + +const fromIteratorUnsafe: ( + iterator: Iterator>, + initial?: undefined +) => Effect.Effect = makePrimitive({ + op: "Iterator", + single: false, + [contA](value, fiber) { + const iter = this[args][0] + while (true) { + const state = iter.next(value) + if (state.done) return succeed(state.value) + if (!effectIsExit(state.value)) { + fiber._stack.push(this) + return state.value + } else if (state.value._tag === "Failure") { + return state.value + } + value = state.value.value + } + }, + [evaluate](this: any, fiber: FiberImpl) { + return this[contA](this[args][1], fiber) + } +}) + +// ---------------------------------------------------------------------------- +// mapping & sequencing +// ---------------------------------------------------------------------------- + +/** @internal */ +export const as: { + ( + value: B + ): (self: Effect.Effect) => Effect.Effect + (self: Effect.Effect, value: B): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + value: B + ): Effect.Effect => { + const b = succeed(value) + return flatMap(self, (_) => b) + } +) + +/** @internal */ +export const asSome = ( + self: Effect.Effect +): Effect.Effect, E, R> => map(self, Option.some) + +/** @internal */ +export const flip = ( + self: Effect.Effect +): Effect.Effect => + matchEffect(self, { + onFailure: succeed, + onSuccess: fail + }) + +/** @internal */ +export const andThen: { + ( + f: (a: A) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + f: Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (a: A) => Effect.Effect + ): Effect.Effect + ( + self: Effect.Effect, + f: Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: ((a: A) => Effect.Effect) | Effect.Effect + ): Effect.Effect => + flatMap(self, (a) => isEffect(f) ? f : internalCall(() => (f as (a: A) => Effect.Effect)(a))) +) + +/** @internal */ +export const tap: { + ( + f: (a: NoInfer) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + f: Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (a: NoInfer) => Effect.Effect + ): Effect.Effect + ( + self: Effect.Effect, + f: Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: ((a: A) => Effect.Effect) | Effect.Effect + ): Effect.Effect => + flatMap(self, (a) => as(isEffect(f) ? f : internalCall(() => (f as (a: A) => Effect.Effect)(a)), a)) +) + +/** @internal */ +export const asVoid = ( + self: Effect.Effect +): Effect.Effect => flatMap(self, (_) => exitVoid) + +/** @internal */ +export const sandbox = ( + self: Effect.Effect +): Effect.Effect, R> => catchCause(self, fail) + +/** @internal */ +export const raceAll = >( + all: Iterable, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } +): Effect.Effect< + Effect.Success, + Effect.Error, + Effect.Services +> => + withFiber((parent) => + callback((resume) => { + const effects = Arr.fromIterable(all) + const len = effects.length + let doneCount = 0 + let done = false + const fibers = new Set>() + const failures: Array> = [] + const onExit = (exit: Exit.Exit, fiber: Fiber.Fiber, i: number) => { + doneCount++ + if (exit._tag === "Failure") { + failures.push(...exit.cause.reasons) + if (doneCount >= len) { + resume(failCause(causeFromReasons(failures))) + } + return + } + const isWinner = !done + done = true + resume( + fibers.size === 0 + ? exit + : flatMap(uninterruptible(fiberInterruptAll(fibers)), () => exit) + ) + if (isWinner && options?.onWinner) { + options.onWinner({ fiber, index: i, parentFiber: parent }) + } + } + + for (let i = 0; i < len; i++) { + const fiber = forkUnsafe(parent, effects[i], true, true, false) + fibers.add(fiber) + fiber.addObserver((exit) => { + fibers.delete(fiber) + onExit(exit, fiber, i) + }) + if (done) break + } + + return fiberInterruptAll(fibers) + }) + ) + +/** @internal */ +export const raceAllFirst = >( + all: Iterable, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } +): Effect.Effect< + Effect.Success, + Effect.Error, + Effect.Services +> => + withFiber((parent) => + callback((resume) => { + let done = false + const fibers = new Set>() + const onExit = (exit: Exit.Exit) => { + done = true + resume( + fibers.size === 0 + ? exit + : flatMap(uninterruptible(fiberInterruptAll(fibers)), () => exit) + ) + } + + let i = 0 + for (const effect of all) { + if (done) break + const index = i++ + const fiber = forkUnsafe(parent, effect, true, true, false) + fibers.add(fiber) + fiber.addObserver((exit) => { + fibers.delete(fiber) + const isWinner = !done + onExit(exit) + if (isWinner && options?.onWinner) { + options.onWinner({ fiber, index, parentFiber: parent }) + } + }) + } + + return fiberInterruptAll(fibers) + }) + ) + +/** @internal */ +export const race: { + ( + that: Effect.Effect, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } + ): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + that: Effect.Effect, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } + ): Effect.Effect +} = dual( + (args) => isEffect(args[1]), + ( + self: Effect.Effect, + that: Effect.Effect, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } + ): Effect.Effect => raceAll([self, that], options) +) + +/** @internal */ +export const raceFirst: { + ( + that: Effect.Effect, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } + ): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + that: Effect.Effect, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } + ): Effect.Effect +} = dual( + (args) => isEffect(args[1]), + ( + self: Effect.Effect, + that: Effect.Effect, + options?: { + readonly onWinner?: (options: { + readonly fiber: Fiber.Fiber + readonly index: number + readonly parentFiber: Fiber.Fiber + }) => void + } + ): Effect.Effect => raceAllFirst([self, that], options) +) + +/** @internal */ +export const flatMap: { + ( + f: (a: A) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (a: A) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (a: A) => Effect.Effect + ): Effect.Effect => { + const onSuccess = Object.create(OnSuccessProto) + onSuccess[args] = self + onSuccess[contA] = f.length !== 1 ? (a: A) => f(a) : f + return onSuccess + } +) +const OnSuccessProto = makePrimitiveProto({ + op: "OnSuccess", + [evaluate](this: any, fiber: FiberImpl): Primitive { + fiber._stack.push(this) + return this[args] + } +}) + +/** @internal */ +export const matchCauseEffectEager: { + (options: { + readonly onFailure: (cause: Cause.Cause) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + }): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + } + ): Effect.Effect => { + if (effectIsExit(self)) { + return self._tag === "Success" + ? options.onSuccess(self.value) + : options.onFailure(self.cause) + } + return matchCauseEffect(self, options) + } +) + +/** @internal */ +export const effectIsExit = (effect: Effect.Effect): effect is Exit.Exit => ExitTypeId in effect + +/** @internal */ +export const flatMapEager: { + ( + f: (a: A) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (a: A) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (a: A) => Effect.Effect + ): Effect.Effect => { + if (effectIsExit(self)) { + return self._tag === "Success" ? f(self.value) : self as Exit.Exit + } + return flatMap(self, f) + } +) + +// ---------------------------------------------------------------------------- +// mapping & sequencing +// ---------------------------------------------------------------------------- + +/** @internal */ +export const flatten = ( + self: Effect.Effect, E2, R2> +): Effect.Effect => flatMap(self, identity) + +/** @internal */ +export const map: { + ( + f: (a: A) => B + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (a: A) => B + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (a: A) => B + ): Effect.Effect => flatMap(self, (a) => succeed(internalCall(() => f(a)))) +) + +/** @internal */ +export const mapEager: { + ( + f: (a: A) => B + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (a: A) => B + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (a: A) => B + ): Effect.Effect => effectIsExit(self) ? exitMap(self, f) : map(self, f) +) + +/** @internal */ +export const mapErrorEager: { + ( + f: (e: E) => E2 + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (e: E) => E2 + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (e: E) => E2 + ): Effect.Effect => effectIsExit(self) ? exitMapError(self, f) : mapError(self, f) +) + +/** @internal */ +export const mapBothEager: { + ( + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Effect.Effect => effectIsExit(self) ? exitMapBoth(self, options) : mapBoth(self, options) +) + +/** @internal */ +export const catchEager: { + ( + f: (e: NoInfer) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (e: NoInfer) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (e: NoInfer) => Effect.Effect + ): Effect.Effect => { + if (effectIsExit(self)) { + if (self._tag === "Success") return self as Exit.Exit + const error = findError(self.cause) + if (Result.isFailure(error)) return self as Exit.Exit + return f(error.success) + } + return catch_(self, f) + } +) + +// ---------------------------------------------------------------------------- +// Exit +// ---------------------------------------------------------------------------- + +/** @internal */ +export const exitInterrupt = (fiberId?: number | undefined): Exit.Exit => exitFailCause(causeInterrupt(fiberId)) + +/** @internal */ +export const exitIsSuccess = ( + self: Exit.Exit +): self is Exit.Success => self._tag === "Success" + +/** @internal */ +export const exitFilterSuccess = ( + self: Exit.Exit +): Result.Result, Exit.Failure> => + self._tag === "Success" ? Result.succeed(self as any) : Result.fail(self as any) + +/** @internal */ +export const exitFilterValue = ( + self: Exit.Exit +): Result.Result> => + self._tag === "Success" ? Result.succeed(self.value) : Result.fail(self as any) + +/** @internal */ +export const exitIsFailure = ( + self: Exit.Exit +): self is Exit.Failure => self._tag === "Failure" + +/** @internal */ +export const exitFilterFailure = ( + self: Exit.Exit +): Result.Result, Exit.Success> => + self._tag === "Failure" ? Result.succeed(self as any) : Result.fail(self as any) + +/** @internal */ +export const exitFilterCause = ( + self: Exit.Exit +): Result.Result, Exit.Success> => + self._tag === "Failure" ? Result.succeed(self.cause) : Result.fail(self as any) + +/** @internal */ +export const exitFindError = Filter.composePassthrough( + exitFilterCause, + findError +) + +/** @internal */ +export const exitFindDefect = Filter.composePassthrough( + exitFilterCause, + findDefect +) + +/** @internal */ +export const exitHasInterrupts = ( + self: Exit.Exit +): self is Exit.Failure => self._tag === "Failure" && hasInterrupts(self.cause) + +/** @internal */ +export const exitHasDies = ( + self: Exit.Exit +): self is Exit.Failure => self._tag === "Failure" && hasDies(self.cause) + +/** @internal */ +export const exitHasFails = ( + self: Exit.Exit +): self is Exit.Failure => self._tag === "Failure" && hasFails(self.cause) + +/** @internal */ +export const exitVoid: Exit.Exit = exitSucceed(void 0) + +/** @internal */ +export const exitMap: { + (f: (a: A) => B): (self: Exit.Exit) => Exit.Exit + (self: Exit.Exit, f: (a: A) => B): Exit.Exit +} = dual( + 2, + (self: Exit.Exit, f: (a: A) => B): Exit.Exit => + self._tag === "Success" ? exitSucceed(f(self.value)) : (self as any) +) + +/** @internal */ +export const exitMapError: { + (f: (a: NoInfer) => E2): (self: Exit.Exit) => Exit.Exit + (self: Exit.Exit, f: (a: NoInfer) => E2): Exit.Exit +} = dual( + 2, + (self: Exit.Exit, f: (a: NoInfer) => E2): Exit.Exit => { + if (self._tag === "Success") return self as Exit.Exit + const error = findError(self.cause) + if (Result.isFailure(error)) return self as Exit.Exit + return exitFail(f(error.success)) + } +) + +/** @internal */ +export const exitMapBoth: { + ( + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): (self: Exit.Exit) => Exit.Exit + ( + self: Exit.Exit, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Exit.Exit +} = dual( + 2, + ( + self: Exit.Exit, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Exit.Exit => { + if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value)) + const error = findError(self.cause) + if (Result.isFailure(error)) return self as Exit.Exit + return exitFail(options.onFailure(error.success)) + } +) + +/** @internal */ +export const exitAs: { + (b: B): (self: Exit.Exit) => Exit.Exit + (self: Exit.Exit, b: B): Exit.Exit +} = dual( + 2, + (self: Exit.Exit, b: B): Exit.Exit => exitIsSuccess(self) ? exitSucceed(b) : (self as any) +) + +/** @internal */ +export const exitZipRight: { + ( + that: Exit.Exit + ): (self: Exit.Exit) => Exit.Exit + ( + self: Exit.Exit, + that: Exit.Exit + ): Exit.Exit +} = dual( + 2, + ( + self: Exit.Exit, + that: Exit.Exit + ): Exit.Exit => (exitIsSuccess(self) ? that : (self as any)) +) + +/** @internal */ +export const exitMatch: { + (options: { + readonly onSuccess: (a: NoInfer) => X1 + readonly onFailure: (cause: Cause.Cause>) => X2 + }): (self: Exit.Exit) => X1 | X2 + ( + self: Exit.Exit, + options: { + readonly onSuccess: (a: A) => X1 + readonly onFailure: (cause: Cause.Cause) => X2 + } + ): X1 | X2 +} = dual( + 2, + ( + self: Exit.Exit, + options: { + readonly onSuccess: (a: A) => X1 + readonly onFailure: (cause: Cause.Cause) => X2 + } + ): X1 | X2 => + exitIsSuccess(self) + ? options.onSuccess(self.value) + : options.onFailure(self.cause) +) + +/** @internal */ +export const exitAsVoid: (self: Exit.Exit) => Exit.Exit = exitAs(void 0) + +/** @internal */ +export const exitAsVoidAll = >>( + exits: I +): Exit.Exit< + void, + I extends Iterable> ? _E : never +> => { + const failures: Array> = [] + for (const exit of exits) { + if (exit._tag === "Failure") { + failures.push(...exit.cause.reasons) + } + } + return failures.length === 0 ? exitVoid : exitFailCause(causeFromReasons(failures)) +} + +/** @internal */ +export const exitGetSuccess = (self: Exit.Exit): Option.Option => + exitIsSuccess(self) ? Option.some(self.value) : Option.none() + +/** @internal */ +export const exitGetCause = (self: Exit.Exit): Option.Option> => + exitIsFailure(self) ? Option.some(self.cause) : Option.none() + +/** @internal */ +export const exitFindErrorOption = (self: Exit.Exit): Option.Option => { + const error = exitFindError(self) + return Result.isFailure(error) ? Option.none() : Option.some(error.success) +} + +// ---------------------------------------------------------------------------- +// environment +// ---------------------------------------------------------------------------- + +/** @internal */ +export const service = (service: Context.Key): Effect.Effect => service + +/** @internal */ +export const serviceOption = ( + service: Context.Key +): Effect.Effect> => withFiber((fiber) => succeed(Context.getOption(fiber.context, service))) + +/** @internal */ +export const serviceOptional = ( + service: Context.Key +): Effect.Effect => + withFiber((fiber) => fromOption(Context.getOption(fiber.context, service))) + +/** @internal */ +export const updateContext: { + ( + f: (context: Context.Context) => Context.Context> + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (context: Context.Context) => Context.Context> + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (context: Context.Context) => Context.Context> + ): Effect.Effect => + withFiber((fiber) => { + const prevContext = fiber.context as Context.Context + const nextContext = f(prevContext) + if (prevContext === nextContext) return self as any + fiber.setContext(nextContext) + return onExitPrimitive(self, () => { + fiber.setContext(prevContext) + return undefined + }) + }) +) + +/** @internal */ +export const updateService: { + ( + service: Context.Key, + f: (value: A) => A + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + service: Context.Key, + f: (value: A) => A + ): Effect.Effect +} = dual( + 3, + ( + self: Effect.Effect, + service: Context.Key, + f: (value: A) => A + ): Effect.Effect => + updateContext(self, (s) => { + const prev = Context.getUnsafe(s, service) + const next = f(prev) + if (prev === next) return s + return Context.add(s, service, next) + }) +) + +/** @internal */ +export const updateServiceScoped = ( + service: Context.Key, + update: (value: A) => A, + options?: { + readonly reset?: ((original: A, updated: A, current: A) => A) | undefined + } | undefined +): Effect.Effect => + uninterruptible(withFiber((fiber) => { + const original = Context.getUnsafe(fiber.context, service) + const updated = update(original) + fiber.setContext(Context.add(fiber.context, service, updated)) + return scopeAddFinalizerExit(Context.getUnsafe(fiber.context, scopeTag), (_) => { + const current = Context.getUnsafe(fiber.context, service) + let next: A + if (options?.reset === undefined) { + if (current !== updated) return void_ + next = original + } else { + next = options.reset(original, updated, current) + } + fiber.setContext(Context.add(fiber.context, service, next)) + return void_ + }) + })) + +/** @internal */ +export const context = (): Effect.Effect> => getContext as any +const getContext = withFiber((fiber) => succeed(fiber.context)) + +/** @internal */ +export const contextWith = ( + f: (context: Context.Context) => Effect.Effect +): Effect.Effect => withFiber((fiber) => f(fiber.context as Context.Context)) + +/** @internal */ +export const setContext: { + ( + context: Context.Context + ): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + context: Context.Context + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + context: Context.Context + ): Effect.Effect => updateContext(self, constant(context)) +) + +/** @internal */ +export const provideContext: { + ( + context: Context.Context + ): ( + self: Effect.Effect + ) => Effect.Effect> + ( + self: Effect.Effect, + context: Context.Context + ): Effect.Effect> +} = dual( + 2, + ( + self: Effect.Effect, + context: Context.Context + ): Effect.Effect> => { + if (effectIsExit(self)) return self as any + return updateContext(self, Context.merge(context)) as any + } +) + +/** @internal */ +export const provideService: { + ( + service: Context.Key + ): { + (implementation: S): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, implementation: S): Effect.Effect> + } + ( + key: Context.Key, + implementation: S + ): ( + self: Effect.Effect + ) => Effect.Effect> + ( + self: Effect.Effect, + service: Context.Key, + implementation: S + ): Effect.Effect> +} = function(this: any) { + if (arguments.length === 1) { + return dual(2, (self, impl) => provideServiceImpl(self, arguments[0], impl)) as any + } + return dual(3, (self, service, impl) => provideServiceImpl(self, service, impl)) + .apply(this, arguments as any) as any +} + +const provideServiceImpl = ( + self: Effect.Effect, + service: Context.Key, + implementation: S +): Effect.Effect> => updateContext(self, Context.add(service, implementation)) as any + +/** @internal */ +export const provideServiceEffect: { + ( + service: Context.Key, + acquire: Effect.Effect + ): ( + self: Effect.Effect + ) => Effect.Effect | R2> + ( + self: Effect.Effect, + service: Context.Key, + acquire: Effect.Effect + ): Effect.Effect | R2> +} = dual( + 3, + ( + self: Effect.Effect, + service: Context.Key, + acquire: Effect.Effect + ): Effect.Effect | R2> => + flatMap(acquire, (implementation) => provideService(self, service, implementation)) +) + +// ---------------------------------------------------------------------------- +// zipping +// ---------------------------------------------------------------------------- + +/** @internal */ +export const zip: { + ( + that: Effect.Effect, + options?: { readonly concurrent?: boolean | undefined } | undefined + ): ( + self: Effect.Effect + ) => Effect.Effect<[A, A2], E2 | E, R2 | R> + ( + self: Effect.Effect, + that: Effect.Effect, + options?: { readonly concurrent?: boolean | undefined } + ): Effect.Effect<[A, A2], E | E2, R | R2> +} = dual( + (args) => isEffect(args[1]), + ( + self: Effect.Effect, + that: Effect.Effect, + options?: { readonly concurrent?: boolean | undefined } + ): Effect.Effect<[A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [a, a2], options) +) + +/** @internal */ +export const zipWith: { + ( + that: Effect.Effect, + f: (a: A, b: A2) => B, + options?: { readonly concurrent?: boolean | undefined } + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + that: Effect.Effect, + f: (a: A, b: A2) => B, + options?: { readonly concurrent?: boolean | undefined } + ): Effect.Effect +} = dual( + (args) => isEffect(args[1]), + ( + self: Effect.Effect, + that: Effect.Effect, + f: (a: A, b: A2) => B, + options?: { readonly concurrent?: boolean | undefined } + ): Effect.Effect => + options?.concurrent + // Use `all` exclusively for concurrent cases, as it introduces additional overhead due to the management of concurrency + ? map(all([self, that], { concurrency: 2 }), ([a, a2]) => internalCall(() => f(a, a2))) + : flatMap(self, (a) => map(that, (a2) => internalCall(() => f(a, a2)))) +) + +// ---------------------------------------------------------------------------- +// filtering & conditionals +// ---------------------------------------------------------------------------- + +/* @internal */ +export const filterOrFail: { + ( + refinement: Predicate.Refinement, B>, + orFailWith: (a: NoInfer) => E2 + ): (self: Effect.Effect) => Effect.Effect + ( + predicate: Predicate.Predicate>, + orFailWith: (a: NoInfer) => E2 + ): (self: Effect.Effect) => Effect.Effect + ( + refinement: Predicate.Refinement, B> + ): (self: Effect.Effect) => Effect.Effect + ( + predicate: Predicate.Predicate> + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + refinement: Predicate.Refinement, B>, + orFailWith: (a: NoInfer) => E2 + ): Effect.Effect + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + orFailWith: (a: NoInfer) => E2 + ): Effect.Effect + ( + self: Effect.Effect, + refinement: Predicate.Refinement, B> + ): Effect.Effect + ( + self: Effect.Effect, + predicate: Predicate.Predicate> + ): Effect.Effect +} = dual((args) => isEffect(args[0]), ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + orFailWith?: (a: any) => E2 +): Effect.Effect => + filterOrElse( + self, + predicate as any, + orFailWith ? (a: any) => fail(orFailWith(a)) : () => fail(new NoSuchElementError() as E2) + )) + +/** @internal */ +export const when: { + ( + condition: Effect.Effect + ): ( + self: Effect.Effect + ) => Effect.Effect, E | E2, R | R2> + ( + self: Effect.Effect, + condition: Effect.Effect + ): Effect.Effect, E | E2, R | R2> +} = dual( + 2, + ( + self: Effect.Effect, + condition: Effect.Effect + ): Effect.Effect, E | E2, R | R2> => flatMap(condition, (pass) => pass ? asSome(self) : succeedNone) +) + +// ---------------------------------------------------------------------------- +// repetition +// ---------------------------------------------------------------------------- + +/** @internal */ +export const replicate: { + ( + n: number + ): (self: Effect.Effect) => Array> + ( + self: Effect.Effect, + n: number + ): Array> +} = dual( + 2, + ( + self: Effect.Effect, + n: number + ): Array> => Array.from({ length: n }, () => self) +) + +/** @internal */ +export const replicateEffect: { + ( + n: number, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: false | undefined + } + ): (self: Effect.Effect) => Effect.Effect, E, R> + ( + n: number, + options: { + readonly concurrency?: Concurrency | undefined + readonly discard: true + } + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + n: number, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: false | undefined + } + ): Effect.Effect, E, R> + ( + self: Effect.Effect, + n: number, + options: { + readonly concurrency?: Concurrency | undefined + readonly discard: true + } + ): Effect.Effect +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect.Effect, + n: number, + options: { + readonly concurrency?: Concurrency | undefined + readonly discard: true + } + ): Effect.Effect => all(replicate(self, n), options) +) + +/** @internal */ +export const forever: { + < + Arg extends Effect.Effect | { + readonly disableYield?: boolean | undefined + } | undefined = { + readonly disableYield?: boolean | undefined + } + >( + effectOrOptions: Arg, + options?: { + readonly disableYield?: boolean | undefined + } | undefined + ): [Arg] extends [Effect.Effect] ? Effect.Effect + : (self: Effect.Effect) => Effect.Effect +} = dual((args) => isEffect(args[0]), ( + self: Effect.Effect, + options?: { + readonly disableYield?: boolean | undefined + } +): Effect.Effect => + whileLoop({ + while: constTrue, + body: constant(options?.disableYield ? self : flatMap(self, (_) => yieldNow)), + step: constVoid + }) as any) + +// ---------------------------------------------------------------------------- +// error handling +// ---------------------------------------------------------------------------- + +/** @internal */ +export const catchCause: { + ( + f: (cause: NoInfer>) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (cause: NoInfer>) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (cause: NoInfer>) => Effect.Effect + ): Effect.Effect => { + const onFailure = Object.create(OnFailureProto) + onFailure[args] = self + onFailure[contE] = f.length !== 1 ? (cause: Cause.Cause) => f(cause) : f + return onFailure + } +) +const OnFailureProto = makePrimitiveProto({ + op: "OnFailure", + [evaluate](this: any, fiber: FiberImpl): Primitive { + fiber._stack.push(this as any) + return this[args] + } +}) + +/** @internal */ +export const catchCauseIf: { + ( + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): Effect.Effect => + catchCause(self, (cause): Effect.Effect => { + if (!predicate(cause)) { + return failCause(cause) as any + } + return internalCall(() => f(cause)) + }) +) + +/** @internal */ +export const catchCauseFilter: { + >( + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect.Effect + ): ( + self: Effect.Effect + ) => Effect.Effect | E2, R | R2> + >( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect.Effect + ): Effect.Effect | E2, R | R2> +} = dual( + 3, + >( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect.Effect + ): Effect.Effect | E2, R | R2> => + catchCause(self, (cause): Effect.Effect | E2, R2> => { + const eb = filter(cause) + return Result.isFailure(eb) ? failCause(eb.failure) : internalCall(() => f(eb.success, cause)) + }) +) + +/** @internal */ +export const catch_: { + ( + f: (e: NoInfer) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (e: NoInfer) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (a: NoInfer) => Effect.Effect + ): Effect.Effect => catchCauseFilter(self, findError as any, (e: any) => f(e)) as any +) + +/** @internal */ +export const catchNoSuchElement = ( + self: Effect.Effect +): Effect.Effect, Exclude, R> => + matchEffect(self, { + onFailure: (error) => + isNoSuchElementError(error) + ? succeedNone + : fail(error as Exclude), + onSuccess: succeedSome + }) + +/** @internal */ +export const catchDefect: { + ( + f: (defect: unknown) => Effect.Effect + ): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + f: (defect: unknown) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (defect: unknown) => Effect.Effect + ): Effect.Effect => catchCauseFilter(self, findDefect as any, f as any) as any +) + +/** @internal */ +export const tapCause: { + ( + f: (cause: NoInfer>) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (cause: NoInfer>) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (cause: NoInfer>) => Effect.Effect + ): Effect.Effect => + catchCause(self, (cause) => andThen(internalCall(() => f(cause)), failCause(cause))) +) + +/** @internal */ +export const tapCauseIf: { + ( + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): Effect.Effect => + catchCauseIf( + self, + predicate, + (cause) => andThen(internalCall(() => f(cause)), failCause(cause)) + ) +) + +/** @internal */ +export const tapCauseFilter: { + >( + filter: Filter.Filter, EB, X>, + f: (a: EB, cause: Cause.Cause) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + >( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (a: EB, cause: Cause.Cause) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + >( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (a: EB, cause: Cause.Cause) => Effect.Effect + ): Effect.Effect => + catchCause(self, (cause) => { + const result = filter(cause) + if (Result.isFailure(result)) { + return failCause(cause) + } + return andThen(internalCall(() => f(result.success, cause)), failCause(cause)) + }) +) + +/** @internal */ +export const tapError: { + ( + f: (e: NoInfer) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (e: NoInfer) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (e: NoInfer) => Effect.Effect + ): Effect.Effect => tapCauseFilter(self, findError as any, (e: any) => f(e)) as any +) + +/** @internal */ +export const tapErrorTag: { + | Arr.NonEmptyReadonlyArray>, E, A1, E1, R1>( + k: K, + f: ( + e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1 + >( + self: Effect.Effect, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1 + >( + self: Effect.Effect, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Effect.Effect + ): Effect.Effect => { + const predicate = Array.isArray(k) + ? ((e: E): e is ExtractTag ? K[number] : K> => + hasProperty(e, "_tag") && k.includes(e._tag)) + : isTagged(k as string) + return tapError( + self, + (error) => + predicate(error) + ? f(error as ExtractTag ? K[number] : K>) + : void_ + ) + } +) + +/** @internal */ +export const tapDefect: { + ( + f: (defect: unknown) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (defect: unknown) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (defect: unknown) => Effect.Effect + ): Effect.Effect => tapCauseFilter(self, findDefect as any, (_: any) => f(_)) as any +) + +/** @internal */ +export const catchIf: { + ( + refinement: Predicate.Refinement, EB>, + f: (e: EB) => Effect.Effect, + orElse?: ((e: Exclude) => Effect.Effect) | undefined + ): ( + self: Effect.Effect + ) => Effect.Effect< + A | A2 | Exclude, + E2 | E3 | (A3 extends unassigned ? Exclude : never), + R | R2 | R3 + > + ( + predicate: Predicate.Predicate>, + f: (e: NoInfer) => Effect.Effect, + orElse?: ((e: NoInfer) => Effect.Effect) | undefined + ): ( + self: Effect.Effect + ) => Effect.Effect, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> + ( + self: Effect.Effect, + refinement: Predicate.Refinement, + f: (e: EB) => Effect.Effect, + orElse?: ((e: Exclude) => Effect.Effect) | undefined + ): Effect.Effect< + A | A2 | Exclude, + E2 | E3 | (A3 extends unassigned ? Exclude : never), + R | R2 | R3 + > + ( + self: Effect.Effect, + predicate: Predicate.Predicate, + f: (e: E) => Effect.Effect, + orElse?: ((e: E) => Effect.Effect) | undefined + ): Effect.Effect, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect.Effect, + predicate: Predicate.Predicate, + f: (e: E) => Effect.Effect, + orElse?: ((e: E) => Effect.Effect) | undefined + ): Effect.Effect => + catchCause(self, (cause): Effect.Effect => { + const error = findError(cause) + if (Result.isFailure(error)) return failCause(error.failure) + if (!predicate(error.success)) { + return orElse ? internalCall(() => orElse(error.success as any)) : failCause(cause as any as Cause.Cause) + } + return internalCall(() => f(error.success as any)) + }) +) + +/** @internal */ +export const catchFilter: { + ( + filter: Filter.Filter, EB, X>, + f: (e: EB) => Effect.Effect, + orElse?: ((e: X) => Effect.Effect) | undefined + ): ( + self: Effect.Effect + ) => Effect.Effect, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> + ( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (e: EB) => Effect.Effect, + orElse?: ((e: X) => Effect.Effect) | undefined + ): Effect.Effect, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (e: EB) => Effect.Effect, + orElse?: ((e: X) => Effect.Effect) | undefined + ): Effect.Effect => + catchCause(self, (cause): Effect.Effect => { + const error = findError(cause) + if (Result.isFailure(error)) return failCause(error.failure) + const result = filter(error.success) + if (Result.isFailure(result)) { + return orElse ? internalCall(() => orElse(result.failure as any)) : failCause(cause as any as Cause.Cause) + } + return internalCall(() => f(result.success)) + }) +) + +/** @internal */ +export const catchTag: { + < + const K extends Tags | Arr.NonEmptyReadonlyArray>, + E, + A1, + E1, + R1, + A2 = unassigned, + E2 = never, + R2 = never + >( + k: K, + f: ( + e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> + ) => Effect.Effect, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Effect.Effect) + | undefined + ): ( + self: Effect.Effect + ) => Effect.Effect< + A | A1 | Exclude, + | E1 + | E2 + | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), + R | R1 | R2 + > + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1, + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Effect.Effect, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Effect.Effect, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Effect.Effect) + | undefined + ): Effect.Effect< + A | A1 | Exclude, + | E1 + | E2 + | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), + R | R1 | R2 + > +} = dual( + (args) => isEffect(args[0]), + < + A, + E, + R, + const K extends Tags | Arr.NonEmptyReadonlyArray>, + R1, + E1, + A1, + A2 = never, + E2 = ExcludeTag ? K[number] : K>, + R2 = never + >( + self: Effect.Effect, + k: K, + f: (e: ExtractTag ? K[number] : K>) => Effect.Effect, + orElse?: + | ((e: ExcludeTag ? K[number] : K>) => Effect.Effect) + | undefined + ): Effect.Effect => { + const pred = Array.isArray(k) + ? ((e: E): e is any => hasProperty(e, "_tag") && k.includes(e._tag)) + : isTagged(k as string) + return catchIf(self, pred, f, orElse as any) as any + } +) + +/** @internal */ +export const catchTags: { + < + E, + Cases extends (E extends { _tag: string } ? { + [K in E["_tag"]]+?: (error: Extract) => Effect.Effect + } : + {}), + A2 = unassigned, + E2 = never, + R2 = never + >( + cases: Cases, + orElse?: ((e: Exclude) => Effect.Effect) | undefined + ): (self: Effect.Effect) => Effect.Effect< + | A + | Exclude + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? A : never + }[keyof Cases], + | E2 + | (A2 extends unassigned ? Exclude : never) + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? E : never + }[keyof Cases], + | R + | R2 + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? R : never + }[keyof Cases] + > + < + R, + E, + A, + Cases extends (E extends { _tag: string } ? { + [K in E["_tag"]]+?: (error: Extract) => Effect.Effect + } : + {}), + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Effect.Effect, + cases: Cases, + orElse?: ((e: Exclude) => Effect.Effect) | undefined + ): Effect.Effect< + | A + | Exclude + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? A : never + }[keyof Cases], + | E2 + | (A2 extends unassigned ? Exclude : never) + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? E : never + }[keyof Cases], + | R + | R2 + | { + [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? R : never + }[keyof Cases] + > +} = dual((args) => isEffect(args[0]), (self: Effect.Effect, cases: Record, orElse: any) => { + let keys: Array + return catchFilter( + self, + (e) => { + keys ??= Object.keys(cases) + return hasProperty(e, "_tag") && isString(e["_tag"]) && keys.includes(e["_tag"]) + ? Result.succeed(e) + : Result.fail(e) + }, + (e: any) => internalCall(() => cases[e["_tag"] as string](e)), + orElse + ) as any +}) + +/** @internal */ +export const catchReason: { + < + K extends Tags, + E, + RK extends ReasonTags, K>>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + errorTag: K, + reasonTag: RK, + f: ( + reason: ExtractReason, K>, RK>, + error: NarrowReason, K>, RK> + ) => Effect.Effect, + orElse?: + | (( + reasons: ExcludeReason, K>, RK>, + error: OmitReason, K>, RK> + ) => Effect.Effect) + | undefined + ): ( + self: Effect.Effect + ) => Effect.Effect< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > + < + A, + E, + R, + K extends Tags, + RK extends ReasonTags>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + self: Effect.Effect, + errorTag: K, + reasonTag: RK, + f: ( + reason: ExtractReason, RK>, + error: NarrowReason, RK> + ) => Effect.Effect, + orElse?: + | (( + reasons: ExcludeReason, RK>, + error: OmitReason, RK> + ) => Effect.Effect) + | undefined + ): Effect.Effect< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > +} = dual( + (args) => isEffect(args[0]), + < + A, + E, + R, + K extends Tags, + RK extends ReasonTags>, + A2, + E2, + R2, + A3 = unassigned, + E3 = never, + R3 = never + >( + self: Effect.Effect, + errorTag: K, + reasonTag: RK, + f: (reason: ExtractReason, RK>, error: ExtractTag) => Effect.Effect, + orElse?: + | (( + reasons: ExcludeReason, RK>, + error: OmitReason, RK> + ) => Effect.Effect) + | undefined + ): Effect.Effect< + A | A2 | Exclude, + ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), + R | R2 | R3 + > => + catchIf( + self, + ((e: any) => isTagged(e, errorTag) && hasProperty(e, "reason")) as any, + (e: any): Effect.Effect => { + const reason = e.reason as any + if (isTagged(reason, reasonTag)) return f(reason as any, e) + return orElse ? internalCall(() => orElse(reason, e)) : fail(e) + } + ) as any +) + +/** @internal */ +export const catchReasons: { + < + K extends Tags, + E, + Cases extends { + [RK in ReasonTags, K>>]+?: ( + reason: ExtractReason, K>, RK>, + error: NarrowReason, K>, RK> + ) => Effect.Effect + }, + A2 = unassigned, + E2 = never, + R2 = never + >( + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: ExcludeReason, K>, Extract>, + error: OmitReason, K>, Extract> + ) => Effect.Effect) + | undefined + ): (self: Effect.Effect) => Effect.Effect< + | A + | Exclude + | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? A : never }[ + keyof Cases + ], + | ExcludeTag + | E2 + | (A2 extends unassigned ? ExtractTag : never) + | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? E : never }[ + keyof Cases + ], + | R + | R2 + | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? R : never }[ + keyof Cases + ] + > + < + A, + E, + R, + K extends Tags, + Cases extends { + [RK in ReasonTags>]+?: ( + reason: ExtractReason, RK>, + error: NarrowReason, RK> + ) => Effect.Effect + }, + A2 = unassigned, + E2 = never, + R2 = never + >( + self: Effect.Effect, + errorTag: K, + cases: Cases, + orElse?: + | (( + reason: ExcludeReason, K>, Extract>, + error: OmitReason, K>, Extract> + ) => Effect.Effect) + | undefined + ): Effect.Effect< + | A + | Exclude + | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? A : never }[ + keyof Cases + ], + | ExcludeTag + | E2 + | (A2 extends unassigned ? ExtractTag : never) + | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? E : never }[ + keyof Cases + ], + | R + | R2 + | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? R : never }[ + keyof Cases + ] + > +} = dual((args) => isEffect(args[0]), (self, errorTag, cases, orElse) => { + let keys: Array + return catchIf( + self, + ((e: any) => + isTagged(e, errorTag) && + hasProperty(e, "reason") && + hasProperty(e.reason, "_tag") && + isString(e.reason._tag)) as any, + (e: any) => { + const reason = e.reason + keys ??= Object.keys(cases) + if (keys.includes(reason._tag)) { + return internalCall(() => (cases as any)[reason._tag](reason, e)) + } + return orElse ? internalCall(() => orElse(reason, e)) : fail(e) + } + ) +}) + +/** @internal */ +export const unwrapReason: { + < + K extends Effect.TagsWithReason, + E + >( + errorTag: K + ): (self: Effect.Effect) => Effect.Effect | ReasonOf>, R> + < + A, + E, + R, + K extends Effect.TagsWithReason + >( + self: Effect.Effect, + errorTag: K + ): Effect.Effect | ReasonOf>, R> +} = dual( + 2, + < + A, + E, + R, + K extends Effect.TagsWithReason + >( + self: Effect.Effect, + errorTag: K + ): Effect.Effect | ReasonOf>, R> => + catchFilter( + self, + (e: any) => { + if (isTagged(e, errorTag) && hasProperty(e, "reason")) { + return Result.succeed(e.reason) + } + return Result.fail(e) + }, + fail as any + ) as any +) + +/** @internal */ +export const mapError: { + ( + f: (e: E) => E2 + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (e: E) => E2 + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (e: E) => E2 + ): Effect.Effect => catch_(self, (error) => failSync(() => f(error))) +) + +/* @internal */ +export const mapBoth: { + ( + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } + ): Effect.Effect +} = dual(2, ( + self: Effect.Effect, + options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } +): Effect.Effect => + matchEffect(self, { + onFailure: (e) => failSync(() => options.onFailure(e)), + onSuccess: (a) => sync(() => options.onSuccess(a)) + })) + +/** @internal */ +export const orDie = ( + self: Effect.Effect +): Effect.Effect => catch_(self, die) + +/** @internal */ +export const orElseSucceed: { + ( + f: LazyArg + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: LazyArg + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: LazyArg + ): Effect.Effect => catch_(self, (_) => sync(f)) +) + +/** @internal */ +export const firstSuccessOf = >( + effects: Iterable +): Effect.Effect, Effect.Error, Effect.Services> => + suspend(() => { + const iterator = effects[Symbol.iterator]() + let state = iterator.next() + if (state.done) { + return die(new Error("Received an empty collection of effects")) + } + function loop(current: IteratorYieldResult): Eff { + const next = iterator.next() + if (next.done) return current.value + return catch_(current.value, (_) => loop(next)) as any + } + return loop(state) + }) + +/** @internal */ +export const eventually = (self: Effect.Effect): Effect.Effect => + catch_(self, (_) => flatMap(yieldNow, () => eventually(self))) + +/** @internal */ +export const ignore: < + Arg extends Effect.Effect | { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } | undefined = { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } +>( + effectOrOptions: Arg, + options?: { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } | undefined +) => [Arg] extends [Effect.Effect] ? Effect.Effect + : (self: Effect.Effect) => Effect.Effect = dual( + (args) => isEffect(args[0]), + ( + self: Effect.Effect, + options?: { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } | undefined + ): Effect.Effect => { + if (!options?.log) { + return matchEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ }) + } + const logEffect = logWithLevel(options.log === true ? undefined : options.log) + return matchCauseEffect(self, { + onFailure(cause) { + const failure = findFail(cause) + return Result.isFailure(failure) + ? failCause(failure.failure) + : options.message === undefined + ? logEffect(cause) + : logEffect(options.message, cause) + }, + onSuccess: (_) => void_ + }) + } + ) + +/** @internal */ +export const ignoreCause: < + Arg extends Effect.Effect | { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } | undefined = { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } +>( + effectOrOptions: Arg, + options?: { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } | undefined +) => [Arg] extends [Effect.Effect] ? Effect.Effect + : (self: Effect.Effect) => Effect.Effect = dual( + (args) => isEffect(args[0]), + ( + self: Effect.Effect, + options?: { + readonly log?: boolean | LogLevel.Severity | undefined + readonly message?: string | undefined + } | undefined + ): Effect.Effect => { + if (!options?.log) { + return matchCauseEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ }) + } + const logEffect = logWithLevel(options.log === true ? undefined : options.log) + return matchCauseEffect(self, { + onFailure: (cause) => options.message === undefined ? logEffect(cause) : logEffect(options.message, cause), + onSuccess: (_) => void_ + }) + } + ) + +/** @internal */ +export const option = ( + self: Effect.Effect +): Effect.Effect, never, R> => match(self, { onFailure: Option.none, onSuccess: Option.some }) + +/** @internal */ +export const result = ( + self: Effect.Effect +): Effect.Effect, never, R> => + matchEager(self, { onFailure: Result.fail, onSuccess: Result.succeed }) + +// ---------------------------------------------------------------------------- +// pattern matching +// ---------------------------------------------------------------------------- + +/** @internal */ +export const matchCauseEffect: { + (options: { + readonly onFailure: (cause: Cause.Cause) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + }): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + } + ): Effect.Effect => { + const primitive = Object.create(OnSuccessAndFailureProto) + primitive[args] = self + primitive[contA] = options.onSuccess.length !== 1 ? (a: A) => options.onSuccess(a) : options.onSuccess + primitive[contE] = options.onFailure.length !== 1 + ? (cause: Cause.Cause) => options.onFailure(cause) + : options.onFailure + return primitive + } +) +const OnSuccessAndFailureProto = makePrimitiveProto({ + op: "OnSuccessAndFailure", + [evaluate](this: any, fiber: FiberImpl): Primitive { + fiber._stack.push(this) + return this[args] + } +}) + +/** @internal */ +export const matchCause: { + (options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (a: A) => A3 + }): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (a: A) => A3 + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (a: A) => A3 + } + ): Effect.Effect => + matchCauseEffect(self, { + onFailure: (cause) => sync(() => options.onFailure(cause)), + onSuccess: (value) => sync(() => options.onSuccess(value)) + }) +) + +/** @internal */ +export const matchEffect: { + (options: { + readonly onFailure: (e: E) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + }): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly onFailure: (e: E) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly onFailure: (e: E) => Effect.Effect + readonly onSuccess: (a: A) => Effect.Effect + } + ): Effect.Effect => + matchCauseEffect(self, { + onFailure: (cause) => { + const fail = cause.reasons.find(isFailReason) + return fail + ? internalCall(() => options.onFailure(fail.error)) + : failCause(cause as Cause.Cause) + }, + onSuccess: options.onSuccess + }) +) + +/** @internal */ +export const match: { + (options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + }): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect.Effect => + matchEffect(self, { + onFailure: (error) => sync(() => options.onFailure(error)), + onSuccess: (value) => sync(() => options.onSuccess(value)) + }) +) + +/** @internal */ +export const matchEager: { + (options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + }): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly onFailure: (error: E) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect.Effect => { + if (effectIsExit(self)) { + if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value)) + const error = findError(self.cause) + if (Result.isFailure(error)) return self as Exit.Exit + return exitSucceed(options.onFailure(error.success)) + } + return match(self, options) + } +) + +/** @internal */ +export const matchCauseEager: { + (options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (value: A) => A3 + }): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly onFailure: (cause: Cause.Cause) => A2 + readonly onSuccess: (value: A) => A3 + } + ): Effect.Effect => { + if (effectIsExit(self)) { + if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value)) + return exitSucceed(options.onFailure(self.cause)) + } + return matchCause(self, options) + } +) + +/** @internal */ +export const exit = (self: Effect.Effect): Effect.Effect, never, R> => + effectIsExit(self) ? exitSucceed(self) : exitPrimitive(self) + +const exitPrimitive: (self: Effect.Effect) => Effect.Effect, never, R> = + makePrimitive({ + op: "Exit", + [evaluate](fiber): Primitive { + fiber._stack.push(this) + return this[args] as any + }, + [contA](value, _, exit) { + return succeed(exit ?? exitSucceed(value)) + }, + [contE](cause, _, exit) { + return succeed(exit ?? exitFailCause(cause)) + } + }) + +// ---------------------------------------------------------------------------- +// Condition checking +// ---------------------------------------------------------------------------- + +/** @internal */ +export const isFailure: (self: Effect.Effect) => Effect.Effect = matchEager({ + onFailure: () => true, + onSuccess: () => false +}) + +/** @internal */ +export const isSuccess: (self: Effect.Effect) => Effect.Effect = matchEager({ + onFailure: () => false, + onSuccess: () => true +}) + +// ---------------------------------------------------------------------------- +// delays & timeouts +// ---------------------------------------------------------------------------- + +/** @internal */ +export const delay: { + ( + duration: Duration.Input + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + duration: Duration.Input + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + duration: Duration.Input + ): Effect.Effect => andThen(sleep(duration), self) +) + +/** @internal */ +export const timeoutOrElse: { + (options: { + readonly duration: Duration.Input + readonly orElse: LazyArg> + }): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + options: { + readonly duration: Duration.Input + readonly orElse: LazyArg> + } + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + options: { + readonly duration: Duration.Input + readonly orElse: LazyArg> + } + ): Effect.Effect => + raceFirst( + self, + flatMap(sleep(options.duration), options.orElse) + ) +) + +/** @internal */ +export const timeout: { + ( + duration: Duration.Input + ): ( + self: Effect.Effect + ) => Effect.Effect + ( + self: Effect.Effect, + duration: Duration.Input + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + duration: Duration.Input + ): Effect.Effect => + timeoutOrElse(self, { + duration, + orElse: () => fail(new TimeoutError()) + }) +) + +/** @internal */ +export const timeoutOption: { + ( + duration: Duration.Input + ): ( + self: Effect.Effect + ) => Effect.Effect, E, R> + ( + self: Effect.Effect, + duration: Duration.Input + ): Effect.Effect, E, R> +} = dual( + 2, + ( + self: Effect.Effect, + duration: Duration.Input + ): Effect.Effect, E, R> => + raceFirst( + asSome(self), + as(sleep(duration), Option.none()) + ) +) + +/** @internal */ +export const timed = ( + self: Effect.Effect +): Effect.Effect<[duration: Duration.Duration, result: A], E, R> => + clockWith((clock) => { + const start = clock.monotonicTimeNanosUnsafe() + return map(self, (a) => [Duration.nanos(clock.monotonicTimeNanosUnsafe() - start), a]) + }) + +// ---------------------------------------------------------------------------- +// resources & finalization +// ---------------------------------------------------------------------------- + +/** @internal */ +export const ScopeTypeId = "~effect/Scope" + +/** @internal */ +export const ScopeCloseableTypeId = "~effect/Scope/Closeable" + +/** @internal */ +export const scopeTag: Context.Service = Context.Service("effect/Scope") + +/** @internal */ +export const scopeClose = (self: Scope.Scope, exit_: Exit.Exit) => + suspend(() => scopeCloseUnsafe(self, exit_) ?? void_) + +/** @internal */ +export const scopeCloseUnsafe = (self: Scope.Scope, exit_: Exit.Exit) => { + if (self.state._tag === "Closed") return + const closed: Scope.State.Closed = { _tag: "Closed", exit: exit_ } + if (self.state._tag === "Empty") { + self.state = closed + return + } + const state = self.state + self.state = closed + if (state.finalizer !== undefined) { + return state.finalizer(exit_) + } + const finalizers = state.finalizers + if (finalizers === undefined || finalizers.size === 0) { + return + } else if (finalizers.size === 1) { + return finalizers.values().next().value!(exit_) + } + return scopeCloseFinalizers(self, finalizers, exit_) +} + +const combineFinalizerCause = ( + exit_: Exit.Exit, + finalizer: Effect.Effect +): Effect.Effect => + exitIsSuccess(exit_) ? finalizer : catchCause(finalizer, (cause) => failCause(causeCombine(exit_.cause, cause))) + +const scopeCloseFinalizers = fnUntraced(function*( + self: Scope.Scope, + finalizers: NonNullable, + exit_: Exit.Exit +) { + let exits: Array> = [] + const fibers: Array> = [] + const arr = Array.from(finalizers.values()) + const parent = getCurrentFiber()! + for (let i = arr.length - 1; i >= 0; i--) { + const finalizer = arr[i] + if (self.strategy === "sequential") { + exits.push(yield* exit(finalizer(exit_))) + } else { + fibers.push(forkUnsafe(parent, finalizer(exit_), true, true, "inherit")) + } + } + if (fibers.length > 0) { + exits = yield* fiberAwaitAll(fibers) + } + return yield* exitAsVoidAll(exits) +}) + +/** @internal */ +export const scopeFork = (scope: Scope.Scope, finalizerStrategy?: "sequential" | "parallel") => + sync(() => scopeForkUnsafe(scope, finalizerStrategy)) + +/** @internal */ +export const scopeForkUnsafe = (scope: Scope.Scope, finalizerStrategy?: "sequential" | "parallel") => { + const newScope = scopeMakeUnsafe(finalizerStrategy) + if (scope.state._tag === "Closed") { + newScope.state = scope.state + return newScope + } + const key = {} + scopeAddFinalizerUnsafe(scope, key, (exit) => scopeClose(newScope, exit)) + scopeAddFinalizerUnsafe(newScope, key, (_) => sync(() => scopeRemoveFinalizerUnsafe(scope, key))) + return newScope +} + +/** @internal */ +export const scopeAddFinalizerExit = ( + scope: Scope.Scope, + finalizer: (exit: Exit.Exit) => Effect.Effect +): Effect.Effect => { + return suspend(() => { + if (scope.state._tag === "Closed") { + return finalizer(scope.state.exit) + } + scopeAddFinalizerUnsafe(scope, {}, finalizer) + return void_ + }) +} + +/** @internal */ +export const scopeAddFinalizer = ( + scope: Scope.Scope, + finalizer: Effect.Effect +): Effect.Effect => scopeAddFinalizerExit(scope, constant(finalizer)) + +/** @internal */ +export const scopeAddFinalizerUnsafe = ( + scope: Scope.Scope, + key: {}, + finalizer: (exit: Exit.Exit) => Effect.Effect +): void => { + if (scope.state._tag === "Empty") { + scope.state = { _tag: "Open", finalizerKey: key, finalizer, finalizers: undefined } + } else if (scope.state._tag === "Open") { + const state = scope.state + if (state.finalizer !== undefined) { + state.finalizers = new Map([[state.finalizerKey!, state.finalizer]]) + state.finalizerKey = undefined + state.finalizer = undefined + state.finalizers.set(key, finalizer) + } else if (state.finalizers === undefined) { + state.finalizerKey = key + state.finalizer = finalizer + } else { + state.finalizers.set(key, finalizer) + } + } +} + +/** @internal */ +export const scopeRemoveFinalizerUnsafe = ( + scope: Scope.Scope, + key: {} +): void => { + if (scope.state._tag === "Open") { + const state = scope.state + if (state.finalizerKey === key) { + state.finalizerKey = undefined + state.finalizer = undefined + } else if (state.finalizers !== undefined) { + state.finalizers.delete(key) + } + } +} + +/** @internal */ +export const scopeFinalizerCountUnsafe = (scope: Scope.Scope): number => + scope.state._tag !== "Open" + ? 0 + : scope.state.finalizer !== undefined + ? 1 + : (scope.state.finalizers?.size ?? 0) + +/** @internal */ +export const scopeMakeUnsafe = (finalizerStrategy: "sequential" | "parallel" = "sequential"): Scope.Closeable => ({ + [ScopeCloseableTypeId]: ScopeCloseableTypeId, + [ScopeTypeId]: ScopeTypeId, + strategy: finalizerStrategy, + state: constScopeEmpty +}) + +const constScopeEmpty = { _tag: "Empty" } as const + +/** @internal */ +export const scopeMake = (finalizerStrategy?: "sequential" | "parallel"): Effect.Effect => + sync(() => scopeMakeUnsafe(finalizerStrategy)) + +/** @internal */ +export const scope: Effect.Effect = scopeTag + +/** @internal */ +export const provideScope: { + (value: Scope.Scope): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, value: Scope.Scope): Effect.Effect> +} = provideService(scopeTag) + +/** @internal */ +export const scoped = (self: Effect.Effect): Effect.Effect> => + withFiber((fiber) => { + const prev = fiber.context + const scope = scopeMakeUnsafe() + fiber.setContext(Context.add(fiber.context, scopeTag, scope)) + return onExitPrimitive(self, (exit) => { + fiber.setContext(prev) + return scopeCloseUnsafe(scope, exit) + }) + }) as any + +/** @internal */ +export const scopeUse: { + ( + scope: Scope.Closeable + ): (self: Effect.Effect) => Effect.Effect> + (self: Effect.Effect, scope: Scope.Closeable): Effect.Effect> +} = dual( + 2, + (self: Effect.Effect, scope: Scope.Closeable): Effect.Effect> => + onExit(provideScope(self, scope), (exit) => suspend(() => scopeCloseUnsafe(scope, exit) ?? void_)) +) + +/** @internal */ +export const scopedWith = ( + f: (scope: Scope.Scope) => Effect.Effect +): Effect.Effect => + suspend(() => { + const scope = scopeMakeUnsafe() + return onExit(f(scope), (exit) => suspend(() => scopeCloseUnsafe(scope, exit) ?? void_)) + }) + +/** @internal */ +export const acquireRelease = ( + acquire: Effect.Effect, + release: (a: A, exit: Exit.Exit) => Effect.Effect, + options?: { readonly interruptible?: boolean } +): Effect.Effect => + contextWith((context: Context.Context) => + uninterruptibleMask((restore) => + flatMap( + scope, + (scope) => + tap( + options?.interruptible ? restore(acquire) : acquire, + (a) => scopeAddFinalizerExit(scope, (exit) => provideContext(release(a, exit), context)) + ) + ) + ) + ) + +/** @internal */ +export const addFinalizer = ( + finalizer: (exit: Exit.Exit) => Effect.Effect +): Effect.Effect => + flatMap( + scope, + (scope) => + contextWith((context: Context.Context) => + scopeAddFinalizerExit(scope, (exit) => provideContext(finalizer(exit), context)) + ) + ) + +/** @internal */ +export const onExitPrimitive: ( + self: Effect.Effect, + f: (exit: Exit.Exit) => Effect.Effect | undefined, + interruptible?: boolean +) => Effect.Effect = makePrimitive({ + op: "OnExit", + single: false, + [evaluate](fiber: FiberImpl) { + fiber._stack.push(this) + return this[args][0] + }, + [contAll](fiber) { + if (fiber.interruptible && this[args][2] !== true) { + fiber._stack.push(setInterruptibleTrue) + fiber.interruptible = false + } + }, + [contA](value, _, exit) { + exit ??= exitSucceed(value) + const eff = this[args][1](exit) + return eff ? flatMap(eff, (_) => exit) : exit + }, + [contE](cause, _, exit) { + exit ??= exitFailCause(cause) + const eff = this[args][1](exit) + return eff ? flatMap(combineFinalizerCause(exit, eff), (_) => exit) : exit + } +}) + +/** @internal */ +export const onExit: { + ( + f: (exit: Exit.Exit) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (exit: Exit.Exit) => Effect.Effect + ): Effect.Effect +} = dual(2, onExitPrimitive) + +/** @internal */ +export const ensuring: { + ( + finalizer: Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + finalizer: Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + finalizer: Effect.Effect + ): Effect.Effect => onExit(self, (_) => finalizer) +) + +/** @internal */ +export const onExitIf: { + ( + predicate: Predicate.Predicate, NoInfer>>, + f: (exit: Exit.Exit, NoInfer>) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + predicate: Predicate.Predicate, NoInfer>>, + f: (exit: Exit.Exit, NoInfer>) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + ( + self: Effect.Effect, + predicate: Predicate.Predicate, NoInfer>>, + f: (exit: Exit.Exit, NoInfer>) => Effect.Effect + ): Effect.Effect => + onExit(self, (exit) => { + if (!predicate(exit)) { + return void_ + } + return f(exit) + }) +) + +/** @internal */ +export const onExitFilter: { + ( + filter: Filter.Filter, NoInfer>, B, X>, + f: (b: B, exit: Exit.Exit, NoInfer>) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + filter: Filter.Filter, NoInfer>, B, X>, + f: (b: B, exit: Exit.Exit, NoInfer>) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + ( + self: Effect.Effect, + filter: Filter.Filter, NoInfer>, B, X>, + f: (b: B, exit: Exit.Exit, NoInfer>) => Effect.Effect + ): Effect.Effect => + onExit(self, (exit) => { + const b = filter(exit) + return Result.isFailure(b) ? void_ : f(b.success, exit) + }) +) + +/** @internal */ +export const onError: { + ( + f: (cause: Cause.Cause>) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + f: (cause: Cause.Cause>) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + f: (cause: Cause.Cause>) => Effect.Effect + ): Effect.Effect => onExitFilter(self, exitFilterCause as any, f as any) as any +) + +/** @internal */ +export const onErrorIf: { + ( + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + f: (cause: Cause.Cause) => Effect.Effect + ): Effect.Effect => + onExitIf( + self, + (exit): exit is Exit.Failure => { + if (exit._tag !== "Failure") { + return false + } + return predicate(exit.cause) + }, + (exit) => f((exit as Exit.Failure).cause) + ) as any +) + +/** @internal */ +export const onErrorFilter: { + ( + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + ( + self: Effect.Effect, + filter: Filter.Filter, EB, X>, + f: (failure: EB, cause: Cause.Cause) => Effect.Effect + ): Effect.Effect => + onExit(self, (exit) => { + if (exit._tag !== "Failure") { + return void_ + } + const result = filter(exit.cause) + return Result.isFailure(result) ? void_ : f(result.success, exit.cause) + }) +) + +/** @internal */ +export const onInterrupt: { + ( + finalizer: (interruptors: ReadonlySet) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + finalizer: (interruptors: ReadonlySet) => Effect.Effect + ): Effect.Effect +} = dual( + 2, + ( + self: Effect.Effect, + finalizer: (interruptors: ReadonlySet) => Effect.Effect + ): Effect.Effect => onErrorFilter(causeFilterInterruptors as any, finalizer)(self) as any +) + +/** @internal */ +export const acquireUseRelease = ( + acquire: Effect.Effect, + use: (a: Resource) => Effect.Effect, + release: (a: Resource, exit: Exit.Exit) => Effect.Effect +): Effect.Effect => + uninterruptibleMask((restore) => + flatMap(acquire, (a) => + onExitPrimitive( + restore(use(a)), + (exit) => release(a, exit), + true + )) + ) + +/** @internal */ +export const acquireDisposable = ( + acquire: Effect.Effect +): Effect.Effect => + acquireRelease(acquire, (resource) => + hasProperty(resource, Symbol.asyncDispose) + ? promise(() => resource[Symbol.asyncDispose]()) + : sync(() => resource[Symbol.dispose]())) + +// ---------------------------------------------------------------------------- +// Caching +// ---------------------------------------------------------------------------- + +/** @internal */ +export const cachedInvalidateWithTTL: { + (timeToLive: Duration.Input): ( + self: Effect.Effect + ) => Effect.Effect<[Effect.Effect, Effect.Effect]> + ( + self: Effect.Effect, + timeToLive: Duration.Input + ): Effect.Effect<[Effect.Effect, Effect.Effect]> +} = dual(2, ( + self: Effect.Effect, + ttl: Duration.Input +): Effect.Effect<[Effect.Effect, Effect.Effect]> => + sync(() => { + const ttlMillis = Duration.toMillis(Duration.fromInputUnsafe(ttl)) + const isFinite = Number.isFinite(ttlMillis) + const latch = makeLatchUnsafe(false) + let expiresAt = 0 + let running = false + let exit: Exit.Exit | undefined + const wait = flatMap(latch.await, () => exit!) + return [ + withFiber((fiber) => { + const clock = fiber.getRef(ClockRef) + const now = isFinite ? clock.currentTimeMillisUnsafe() : 0 + if (running || now < expiresAt) return exit ?? wait + running = true + latch.closeUnsafe() + exit = undefined + return onExit(self, (exit_) => + sync(() => { + running = false + expiresAt = clock.currentTimeMillisUnsafe() + ttlMillis + exit = exit_ + latch.openUnsafe() + })) + }), + sync(() => { + expiresAt = 0 + latch.closeUnsafe() + exit = undefined + }) + ] + })) + +/** @internal */ +export const cachedWithTTL: { + ( + timeToLive: Duration.Input + ): (self: Effect.Effect) => Effect.Effect> + ( + self: Effect.Effect, + timeToLive: Duration.Input + ): Effect.Effect> +} = dual( + 2, + ( + self: Effect.Effect, + timeToLive: Duration.Input + ): Effect.Effect> => map(cachedInvalidateWithTTL(self, timeToLive), (tuple) => tuple[0]) +) + +/** @internal */ +export const cached = (self: Effect.Effect): Effect.Effect> => + cachedWithTTL(self, Duration.infinity) + +// ---------------------------------------------------------------------------- +// interruption +// ---------------------------------------------------------------------------- + +/** @internal */ +export const interrupt: Effect.Effect = withFiber((fiber) => failCause(causeInterrupt(fiber.id))) + +/** @internal */ +export const uninterruptible = ( + self: Effect.Effect +): Effect.Effect => + withFiber((fiber) => { + if (!fiber.interruptible) return self + fiber.interruptible = false + fiber._stack.push(setInterruptibleTrue) + return self + }) + +const setInterruptible: (interruptible: boolean) => Primitive = makePrimitive({ + op: "SetInterruptible", + [contAll](fiber) { + fiber.interruptible = this[args] + if (fiber._interruptedCause && fiber.interruptible) { + return () => failCause(fiber._interruptedCause!) + } + } +}) +const setInterruptibleTrue = setInterruptible(true) +const setInterruptibleFalse = setInterruptible(false) + +const setFiberInterruptible = (fiber: FiberImpl): Effect.Effect | undefined => { + fiber.interruptible = true + fiber._stack.push(setInterruptibleFalse) + if (fiber._interruptedCause) return failCause(fiber._interruptedCause) +} + +/** @internal */ +export const interruptible = ( + self: Effect.Effect +): Effect.Effect => + withFiber((fiber) => { + if (fiber.interruptible) return self + return setFiberInterruptible(fiber) ?? self + }) + +/** @internal */ +export const uninterruptibleMask = ( + f: ( + restore: ( + effect: Effect.Effect + ) => Effect.Effect + ) => Effect.Effect +): Effect.Effect => + withFiber((fiber) => { + if (!fiber.interruptible) return f(identity) + fiber.interruptible = false + fiber._stack.push(setInterruptibleTrue) + return f(interruptible) + }) + +/** @internal */ +export const interruptibleMask = ( + f: ( + restore: ( + effect: Effect.Effect + ) => Effect.Effect + ) => Effect.Effect +): Effect.Effect => + withFiber((fiber) => { + if (fiber.interruptible) return f(identity) + const interrupted = setFiberInterruptible(fiber) + const effect = f(uninterruptible) + return interrupted ?? effect + }) + +/** @internal */ +export const abortSignal: Effect.Effect = map( + acquireRelease( + sync(() => new AbortController()), + (controller) => sync(() => controller.abort()) + ), + (_) => _.signal +) + +// ======================================================================== +// collecting & elements +// ======================================================================== + +/** @internal */ +export const all = < + const Arg extends + | Iterable> + | Record>, + O extends { + readonly concurrency?: Concurrency | undefined + readonly discard?: boolean | undefined + readonly mode?: "default" | "result" | undefined + } +>( + arg: Arg, + options?: O +): Effect.All.Return => { + if (isIterable(arg)) { + return options?.mode === "result" + ? (forEach as any)(arg, result, options) + : (forEach as any)(arg, identity, options) + } else if (options?.discard) { + return options.mode === "result" + ? (forEach as any)(Object.values(arg), result, options) + : (forEach as any)(Object.values(arg), identity, options) + } + return suspend(() => { + const out: Record = {} + return as( + forEach( + Object.entries(arg), + ([key, effect]) => + map(options?.mode === "result" ? result(effect) : effect, (value) => { + InternalRecord.assignProperty(out, key, value) + }), + { + discard: true, + concurrency: options?.concurrency + } + ), + out + ) + }) as any +} + +/** @internal */ +export const partition: { + ( + f: (a: A, i: number) => Effect.Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): (elements: Iterable) => Effect.Effect<[excluded: Array, satisfying: Array], never, R> + ( + elements: Iterable, + f: (a: A, i: number) => Effect.Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect.Effect<[excluded: Array, satisfying: Array], never, R> +} = dual( + (args) => isIterable(args[0]) && !isEffect(args[0]), + ( + elements: Iterable, + f: (a: A, i: number) => Effect.Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect.Effect<[excluded: Array, satisfying: Array], never, R> => + map( + forEach(elements, (a, i) => result(f(a, i)), options), + (results) => Arr.partition(results, identity) + ) +) + +/** @internal */ +export const reduce: { + ( + zero: LazyArg, + f: (z: Z, a: A, i: number) => Effect.Effect + ): (elements: Iterable) => Effect.Effect + ( + elements: Iterable, + zero: LazyArg, + f: (z: Z, a: A, i: number) => Effect.Effect + ): Effect.Effect +} = dual( + 3, + ( + elements: Iterable, + zero: LazyArg, + f: (z: Z, a: A, i: number) => Effect.Effect + ) => { + const arr = Arr.fromIterable(elements) + if (arr.length === 0) return sync(zero) + return suspend(() => { + let index = 0 + let state = zero() + return map( + whileLoop({ + while: () => index < arr.length, + body: () => f(state, arr[index], index), + step(next) { + state = next + index++ + } + }), + () => state + ) + }) + } +) + +/** @internal */ +export const validate: { + ( + f: (a: A, i: number) => Effect.Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: false | undefined + } | undefined + ): (elements: Iterable) => Effect.Effect, Arr.NonEmptyArray, R> + ( + f: (a: A, i: number) => Effect.Effect, + options: { + readonly concurrency?: Concurrency | undefined + readonly discard: true + } + ): (elements: Iterable) => Effect.Effect, R> + ( + elements: Iterable, + f: (a: A, i: number) => Effect.Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: false | undefined + } | undefined + ): Effect.Effect, Arr.NonEmptyArray, R> + ( + elements: Iterable, + f: (a: A, i: number) => Effect.Effect, + options: { + readonly concurrency?: Concurrency | undefined + readonly discard: true + } + ): Effect.Effect, R> +} = dual( + (args) => isIterable(args[0]) && !isEffect(args[0]), + ( + elements: Iterable, + f: (a: A, i: number) => Effect.Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: boolean | undefined + } | undefined + ): Effect.Effect | void, Arr.NonEmptyArray, R> => + flatMap( + partition(elements, f, { concurrency: options?.concurrency }), + ([excluded, satisfying]) => { + if (Arr.isArrayNonEmpty(excluded)) { + return fail(excluded) + } + return options?.discard ? void_ : succeed(satisfying) + } + ) +) + +/** @internal */ +export const findFirst: { + ( + predicate: (a: NoInfer, i: number) => Effect.Effect + ): (elements: Iterable) => Effect.Effect, E, R> + ( + elements: Iterable, + predicate: (a: NoInfer, i: number) => Effect.Effect + ): Effect.Effect, E, R> +} = dual( + (args) => isIterable(args[0]) && !isEffect(args[0]), + ( + elements: Iterable, + predicate: (a: A, i: number) => Effect.Effect + ): Effect.Effect, E, R> => + suspend(() => { + const iterator = elements[Symbol.iterator]() + const next = iterator.next() + if (!next.done) { + return findFirstLoop(iterator, 0, predicate, next.value) + } + return succeed(Option.none()) + }) +) + +const findFirstLoop = ( + iterator: Iterator, + index: number, + predicate: (a: A, i: number) => Effect.Effect, + value: A +): Effect.Effect, E, R> => + flatMap(predicate(value, index), (keep) => { + if (keep) { + return succeed(Option.some(value)) + } + const next = iterator.next() + if (!next.done) { + return findFirstLoop(iterator, index + 1, predicate, next.value) + } + return succeed(Option.none()) + }) + +/** @internal */ +export const findFirstFilter: { + ( + filter: (input: NoInfer, i: number) => Effect.Effect, E, R> + ): (elements: Iterable) => Effect.Effect, E, R> + ( + elements: Iterable, + filter: (input: NoInfer, i: number) => Effect.Effect, E, R> + ): Effect.Effect, E, R> +} = dual( + (args) => isIterable(args[0]) && !isEffect(args[0]), + ( + elements: Iterable, + filter: (input: A, i: number) => Effect.Effect, E, R> + ): Effect.Effect, E, R> => + suspend(() => { + const iterator = elements[Symbol.iterator]() + const next = iterator.next() + if (!next.done) { + return findFirstFilterLoop(iterator, 0, filter, next.value) + } + return succeed(Option.none()) + }) +) + +const findFirstFilterLoop = ( + iterator: Iterator, + index: number, + filter: (input: A, i: number) => Effect.Effect, E, R>, + value: A +): Effect.Effect, E, R> => + flatMap(filter(value, index), (result) => { + if (Result.isSuccess(result)) { + return succeed(Option.some(result.success)) + } + const next = iterator.next() + if (!next.done) { + return findFirstFilterLoop(iterator, index + 1, filter, next.value) + } + return succeed(Option.none()) + }) + +/** @internal */ +export const whileLoop: (options: { + readonly while: LazyArg + readonly body: LazyArg> + readonly step: (a: A) => void +}) => Effect.Effect = makePrimitive({ + op: "While", + [contA](value, fiber) { + this[args].step(value) + if (this[args].while()) { + fiber._stack.push(this) + return this[args].body() + } + return exitVoid + }, + [evaluate](fiber) { + if (this[args].while()) { + fiber._stack.push(this) + return this[args].body() + } + return exitVoid + } +}) + +/** @internal */ +export const forEach: { + = Iterable, const Discard extends boolean = false>( + f: (a: A, i: number) => Effect.Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: Discard | undefined + } | undefined + ): ( + self: [S] extends [never] ? Iterable : S + ) => Effect.Effect : void, E, R> + , const Discard extends boolean = false>( + self: S, + f: (a: Arr.ReadonlyArray.Infer, i: number) => Effect.Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: Discard | undefined + } | undefined + ): Effect.Effect : void, E, R> +} = dual((args) => typeof args[1] === "function", ( + iterable: Iterable, + f: (a: A, index: number) => Effect.Effect, + options?: { + readonly concurrency?: Concurrency | undefined + readonly discard?: boolean | undefined + } +): Effect.Effect => + suspend(() => { + const concurrencyOption = options?.concurrency ?? 1 + const concurrency = concurrencyOption === "unbounded" + ? Number.POSITIVE_INFINITY + : Math.max(1, concurrencyOption) + + if (concurrency === 1) { + return forEachSequential(iterable, f, options) + } + + const items = Arr.fromIterable(iterable) + let length = items.length + if (length === 0) { + return options?.discard ? void_ : succeed([]) + } + + const out: Array | undefined = options?.discard + ? undefined + : new Array(length) + const eff = forEachConcurrent({ f, out }, items, { concurrency }) + return eff ? as(eff, out as any) : succeed(out as any) + })) + +/** @internal */ +export const head = ( + self: Effect.Effect, E, R> +): Effect.Effect => + flatMap(self, (elements) => { + const result = elements[Symbol.iterator]().next() + return result.done ? fail(new NoSuchElementError()) : succeed(result.value) + }) + +const forEachSequential = ( + iterable: Iterable, + f: (a: A, index: number) => Effect.Effect, + options?: { + readonly discard?: boolean | undefined + } +) => + suspend(() => { + const out: Array | undefined = options?.discard ? undefined : [] + const iterator = iterable[Symbol.iterator]() + let state = iterator.next() + let index = 0 + return as( + whileLoop({ + while: () => !state.done, + body: () => f(state.value!, index++), + step: (b) => { + if (out) out.push(b) + state = iterator.next() + } + }), + out + ) + }) + +type IterateEagerOptions = { + readonly concurrency?: number | undefined + readonly end?: number | undefined + readonly orderedStep?: boolean | undefined +} + +const iterateEagerImpl = (options: { + readonly onItem: (state: S, item: A, index: number) => Effect.Effect + readonly step: (state: NoInfer, item: A, exit: Exit.Exit, index: number) => Exit.Exit | void +}): ( + initialState: S, + items: ReadonlyArray, + options?: IterateEagerOptions +) => Effect.Effect | undefined => { + const onItem = options.onItem + const step = options.step + + const runSequential = ( + state: S, + items: ReadonlyArray, + index: number, + end: number + ): Effect.Effect | undefined => { + for (; index < end; index++) { + const item = items[index] + const effect = onItem(state, item, index) + if (!effectIsExit(effect)) { + return flatMap( + exit(effect), + (itemExit) => step(state, item, itemExit, index) ?? runSequential(state, items, index + 1, end) ?? void_ + ) + } + const terminal = step(state, item, effect, index) + if (terminal) return terminal._tag === "Failure" ? terminal : undefined + } + } + + return ( + state: S, + items: ReadonlyArray, + opts: IterateEagerOptions | undefined + ): Effect.Effect | undefined => { + let index = 0 + const end = opts?.end ?? items.length + const concurrency = opts?.concurrency ?? 1 + if (concurrency === 1) { + return runSequential(state, items, 0, end) + } + const orderedStep = opts?.orderedStep === true + let done = false + let parentFiber: Fiber.Fiber | undefined + let fibers: Set> | undefined + let resume: ((effect: Effect.Effect) => void) | undefined + let interrupted = false + let terminal: Exit.Exit | void + let effect: Effect.Effect | undefined + let nextIndex = index + const exits: Array | undefined> | undefined = orderedStep ? new Array(end) : undefined + + const failDefect = (error: unknown): Effect.Effect => { + const defect = exitDie(error) + terminal = defect + done = true + interrupted = true + return fibers && fibers.size > 0 + ? flatMap(uninterruptible(fiberInterruptAll(Array.from(fibers))), () => defect) + : defect + } + + const runStep = (item: A, exit: Exit.Exit, currentIndex: number): Exit.Exit | void => { + if (!orderedStep) return step(state, item, exit, currentIndex) + if (terminal) return terminal + exits![currentIndex] = exit + while (nextIndex < end) { + const nextExit = exits![nextIndex] + if (nextExit === undefined) return + exits![nextIndex] = undefined + const index = nextIndex++ + const result = step(state, items[index], nextExit, index) + if (result) return result + } + } + + const go = (): Effect.Effect | undefined => { + let paused = false + for (; !terminal && index < end; index++) { + const item = items[index] + const eff = effect ?? onItem(state, item, index) + + // fast case (already an exit) + if (effectIsExit(eff)) { + terminal = runStep(item, eff, index) + if (terminal) break + + // We have an effect, so enter "async" mode + } else if (!parentFiber) { + return callback((cb) => { + parentFiber = getCurrentFiber()! + fibers = new Set() + effect = eff + resume = cb + let result: Effect.Effect | undefined + try { + result = go() + } catch (error) { + return cb(failDefect(error)) + } + if (result) return cb(result) + return suspend(() => { + terminal = exitVoid + interrupted = true + return fibers ? fiberInterruptAll(fibers) : void_ + }) + }) + + // Fork the effect with concurrency > 1 + } else { + // Clear the temporary effect from capturing the parentFiber + effect = undefined + + const fiber = forkUnsafe(parentFiber, eff, true, true, "inherit") + if (fiber._exit) { + terminal = runStep(item, fiber._exit, index) + if (terminal) break + continue + } + + // Add the fiber to the Set + fibers!.add(fiber) + + const currentIndex = index + fiber.addObserver((exit) => { + fibers!.delete(fiber) + try { + if (terminal) { + if (!interrupted && exit._tag === "Failure") { + for (const reason of exit.cause.reasons) { + if (reason._tag === "Interrupt") continue + else if (terminal._tag === "Failure") { + ;(terminal.cause.reasons as Array).push(reason) + } else { + terminal = exitFailCause(causeFromReasons([reason])) + } + } + } + } else { + const result = runStep(item, exit, currentIndex) + if (result) { + terminal = result._tag === "Failure" + ? exitFailCause(causeFromReasons(result.cause.reasons.slice())) + : result + go() + } + } + + if (paused) { + const eff = go() + if (eff) resume!(eff) + } else if (done && fibers!.size === 0) { + resume!(terminal ?? void_) + } + } catch (error) { + resume!(failDefect(error)) + } + }) + + // Check if we have reached the concurrency limit + if (fibers!.size < concurrency) continue + paused = true + index++ + return + } + } + + done = true + + if (terminal) { + if (fibers && fibers.size > 0) { + const annotations = fiberStackAnnotations(parentFiber!) + fibers.forEach((f) => f.interruptUnsafe(parentFiber!.id, annotations)) + return + } + if (resume || terminal._tag === "Failure") { + return terminal + } + } else if (resume) { + if (!fibers) { + return exitVoid + } else if (fibers.size === 0) { + resume(void_) + } + } + } + + return go() + } +} + +/** @internal */ +export const iterateEager = (): (options: { + readonly onItem: (state: S, item: A, index: number) => Effect.Effect + readonly step: (state: NoInfer, item: A, exit: Exit.Exit, index: number) => Exit.Exit | void +}) => ( + initialState: S, + items: ReadonlyArray, + options?: IterateEagerOptions +) => Effect.Effect | undefined => iterateEagerImpl + +const forEachConcurrent = iterateEagerImpl({ + onItem( + state: { + readonly f: (a: any, i: number) => Effect.Effect + readonly out: Array | undefined + }, + item, + index + ) { + return state.f(item, index) + }, + step(state, _, exit, index) { + if (exit._tag === "Failure") return exit + else if (state.out) { + state.out[index] = exit.value + } + } +}) + +/* @internal */ +export const filterOrElse: { + ( + refinement: Predicate.Refinement, B>, + orElse: (a: EqualsWith, Exclude, B>>) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + predicate: Predicate.Predicate>, + orElse: (a: NoInfer) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + refinement: Predicate.Refinement, + orElse: (a: EqualsWith>) => Effect.Effect + ): Effect.Effect + ( + self: Effect.Effect, + predicate: Predicate.Predicate>, + orElse: (a: NoInfer) => Effect.Effect + ): Effect.Effect +} = dual(3, ( + self: Effect.Effect, + predicate: Predicate.Predicate, + orElse: (a: A) => Effect.Effect +): Effect.Effect => + flatMap( + self, + (a) => predicate(a) ? succeed(a) : orElse(a) + )) + +/** @internal */ +export const filterMapOrElse: { + ( + filter: Filter.Filter, B, X>, + orElse: (x: X) => Effect.Effect + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + filter: Filter.Filter, B, X>, + orElse: (x: X) => Effect.Effect + ): Effect.Effect +} = dual(3, ( + self: Effect.Effect, + filter: Filter.Filter, B, X>, + orElse: (x: X) => Effect.Effect +): Effect.Effect => + flatMap( + self, + (a) => { + const result = filter(a) + return (Result.isFailure(result) + ? orElse(result.failure) + : succeed(result.success)) as Effect.Effect + } + )) + +/* @internal */ +export const filterMapOrFail: { + ( + filter: Filter.Filter, B, X>, + orFailWith: (x: X) => E2 + ): (self: Effect.Effect) => Effect.Effect + ( + filter: Filter.Filter, B, X> + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + filter: Filter.Filter, B, X>, + orFailWith: (x: X) => E2 + ): Effect.Effect + ( + self: Effect.Effect, + filter: Filter.Filter, B, X> + ): Effect.Effect +} = dual((args) => isEffect(args[0]), ( + self: Effect.Effect, + filter: Filter.Filter, B, X>, + orFailWith?: (x: X) => E2 +): Effect.Effect => + filterMapOrElse( + self, + filter, + orFailWith ? (x: X) => fail(orFailWith(x)) : () => fail(new NoSuchElementError() as E2) + )) + +/** @internal */ +export const filter: { + ( + refinement: Predicate.Refinement, B> + ): (elements: Iterable) => Effect.Effect> + ( + predicate: Predicate.Predicate> + ): (elements: Iterable) => Effect.Effect> + ( + predicate: (a: NoInfer, i: number) => Effect.Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): (iterable: Iterable) => Effect.Effect, E, R> + ( + elements: Iterable, + refinement: Predicate.Refinement + ): Effect.Effect> + ( + elements: Iterable, + predicate: Predicate.Predicate + ): Effect.Effect> + ( + iterable: Iterable, + predicate: (a: NoInfer, i: number) => Effect.Effect, + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect.Effect, E, R> +} = dual( + (args) => isIterable(args[0]) && !isEffect(args[0]), + ( + elements: Iterable, + predicate: + | Predicate.Predicate + | ((a: A, i: number) => Effect.Effect), + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect.Effect, E, R> => + suspend(() => { + const out: Array = [] + return as( + forEach( + elements, + (a, i) => { + const result = (predicate as Function)(a, i) + if (typeof result === "boolean") { + if (result) out.push(a) + return void_ as any + } + return map(result, (keep) => { + if (keep) { + out.push(a) + } + }) + }, + { + discard: true, + concurrency: options?.concurrency + } + ), + out + ) + }) +) + +/** @internal */ +export const filterMap: { + ( + filter: Filter.Filter, B, X> + ): (elements: Iterable) => Effect.Effect> + ( + elements: Iterable, + filter: Filter.Filter, B, X> + ): Effect.Effect> +} = dual( + (args) => isIterable(args[0]) && !isEffect(args[0]), + ( + elements: Iterable, + filter: Filter.Filter + ): Effect.Effect> => + suspend(() => { + const out: Array = [] + for (const a of elements) { + const result = filter(a) + if (Result.isSuccess(result)) { + out.push(result.success) + } + } + return succeed(out) + }) +) + +/** @internal */ +export const filterMapEffect: { + ( + filter: Filter.FilterEffect, B, X, E, R>, + options?: { readonly concurrency?: Concurrency | undefined } + ): (elements: Iterable) => Effect.Effect, E, R> + ( + elements: Iterable, + filter: Filter.FilterEffect, B, X, E, R>, + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect.Effect, E, R> +} = dual( + (args) => isIterable(args[0]) && !isEffect(args[0]), + ( + elements: Iterable, + filter: Filter.FilterEffect, + options?: { readonly concurrency?: Concurrency | undefined } + ): Effect.Effect, E, R> => + suspend(() => { + const out: Array = [] + return as( + forEach( + elements, + (a) => + map(filter(a), (result) => { + if (Result.isSuccess(result)) { + out.push(result.success) + } + }), + { + discard: true, + concurrency: options?.concurrency + } + ), + out + ) + }) +) + +// ---------------------------------------------------------------------------- +// do notation +// ---------------------------------------------------------------------------- + +/** @internal */ +export const Do: Effect.Effect<{}> = succeed({}) + +/** @internal */ +export const bindTo: { + ( + name: N + ): ( + self: Effect.Effect + ) => Effect.Effect, E, R> + ( + self: Effect.Effect, + name: N + ): Effect.Effect, E, R> +} = doNotation.bindTo(map) + +/** @internal */ +export const bind: { + , B, E2, R2>( + name: N, + f: (a: NoInfer) => Effect.Effect + ): ( + self: Effect.Effect + ) => Effect.Effect & Record>, E | E2, R | R2> + , E, R, B, E2, R2, N extends string>( + self: Effect.Effect, + name: N, + f: (a: NoInfer) => Effect.Effect + ): Effect.Effect & Record>, E | E2, R | R2> +} = doNotation.bind(map, flatMap) + +/** @internal */ +const let_: { + , B>( + name: N, + f: (a: NoInfer) => B + ): ( + self: Effect.Effect + ) => Effect.Effect & Record>, E, R> + , E, R, B, N extends string>( + self: Effect.Effect, + name: N, + f: (a: NoInfer) => B + ): Effect.Effect & Record>, E, R> +} = doNotation.let_(map) + +/** @internal */ +export { let_ as let } + +// ---------------------------------------------------------------------------- +// fibers & forking +// ---------------------------------------------------------------------------- + +/** @internal */ +export const forkChild: { + < + Arg extends Effect.Effect | { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined = { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + >( + effectOrOptions: Arg, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined + ): [Arg] extends [Effect.Effect] ? Effect.Effect, never, _R> + : (self: Effect.Effect) => Effect.Effect, never, R> +} = dual((args) => isEffect(args[0]), ( + self: Effect.Effect, + options?: { + readonly startImmediately?: boolean + readonly uninterruptible?: boolean | "inherit" + } +): Effect.Effect, never, R> => + withFiber((fiber) => { + interruptChildrenPatch() + return succeed(forkUnsafe( + fiber, + self, + options?.startImmediately, + false, + options?.uninterruptible ?? false + )) + })) + +/** @internal */ +export const forkUnsafe = ( + parent: Fiber.Fiber, + effect: Effect.Effect, + immediate = false, + daemon = false, + uninterruptible: boolean | "inherit" = false +): FiberImpl => { + const parentRuntime = parent as FiberImpl + const interruptible = uninterruptible === "inherit" ? parentRuntime.interruptible : !uninterruptible + const child = new FiberImpl(parentRuntime.context, interruptible) + if (immediate) { + child.evaluate(effect as any) + } else { + parentRuntime.currentDispatcher.scheduleTask(() => child.evaluate(effect as any), 0) + } + if (!daemon && !child._exit) { + parentRuntime.children().add(child) + child.addObserver(() => parentRuntime._children!.delete(child)) + } + return child +} + +/** @internal */ +export const forkDetach: { + < + Arg extends Effect.Effect | { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined = { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + >( + effectOrOptions: Arg, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined + ): [Arg] extends [Effect.Effect] ? Effect.Effect, never, _R> + : (self: Effect.Effect) => Effect.Effect, never, R> +} = dual((args) => isEffect(args[0]), ( + self: Effect.Effect, + options?: { + readonly startImmediately?: boolean + readonly uninterruptible?: boolean | "inherit" | undefined + } +): Effect.Effect, never, R> => + withFiber((fiber) => succeed(forkUnsafe(fiber, self, options?.startImmediately, true, options?.uninterruptible)))) + +/** @internal */ +export const awaitAllChildren = ( + self: Effect.Effect +): Effect.Effect => + withFiber((fiber) => { + const initialChildren = fiber._children && new Set(fiber._children) + return onExit( + self, + (_) => { + let children = fiber._children + if (children === undefined || children.size === 0) { + return void_ + } else if (initialChildren) { + children = Iterable.filter( + children, + (child: FiberImpl) => !initialChildren.has(child) + ) as Set> + } + return asVoid(fiberAwaitAll(children)) + } + ) + }) + +/** @internal */ +export const forkIn: { + ( + scope: Scope.Scope, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + ): ( + self: Effect.Effect + ) => Effect.Effect, never, R> + ( + self: Effect.Effect, + scope: Scope.Scope, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + ): Effect.Effect, never, R> +} = dual( + (args) => isEffect(args[0]), + ( + self: Effect.Effect, + scope: Scope.Scope, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + ): Effect.Effect, never, R> => + withFiber((parent) => { + const fiber = forkUnsafe(parent, self, options?.startImmediately, true, options?.uninterruptible) + if (!(fiber as FiberImpl)._exit) { + if (scope.state._tag !== "Closed") { + const key = {} + const finalizer = () => withFiberId((interruptor) => interruptor === fiber.id ? void_ : fiberInterrupt(fiber)) + scopeAddFinalizerUnsafe(scope, key, finalizer) + fiber.addObserver(() => scopeRemoveFinalizerUnsafe(scope, key)) + } else { + fiber.interruptUnsafe(parent.id, fiberStackAnnotations(parent)) + } + } + return succeed(fiber) + }) +) + +/** @internal */ +export const forkScoped: { + < + Arg extends Effect.Effect | { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined = { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } + >( + effectOrOptions: Arg, + options?: { + readonly startImmediately?: boolean | undefined + readonly uninterruptible?: boolean | "inherit" | undefined + } | undefined + ): [Arg] extends [Effect.Effect] ? + Effect.Effect, never, _R | Scope.Scope> + : (self: Effect.Effect) => Effect.Effect, never, R | Scope.Scope> +} = dual((args) => isEffect(args[0]), ( + self: Effect.Effect, + options?: { + readonly startImmediately?: boolean + readonly uninterruptible?: boolean | "inherit" + } +): Effect.Effect, never, R | Scope.Scope> => flatMap(scope, (scope) => forkIn(self, scope, options))) + +// ---------------------------------------------------------------------------- +// execution +// ---------------------------------------------------------------------------- + +/** @internal */ +export const runForkWith = (context: Context.Context) => +( + effect: Effect.Effect, + options?: Effect.RunOptions | undefined +): Fiber.Fiber => { + const fiber = new FiberImpl( + options?.scheduler ? Context.add(context, Scheduler.Scheduler, options.scheduler) : context, + options?.uninterruptible !== true + ) + fiber.evaluate(effect as any) + if (fiber._exit) return fiber + + if (options?.signal) { + if (options.signal.aborted) { + fiber.interruptUnsafe() + } else { + const abort = () => fiber.interruptUnsafe() + options.signal.addEventListener("abort", abort, { once: true }) + fiber.addObserver(() => options.signal!.removeEventListener("abort", abort)) + } + } + if (options?.onFiberStart) { + options.onFiberStart(fiber) + } + return fiber +} + +/** @internal */ +export const fiberRunIn: { + (scope: Scope.Scope): (self: Fiber.Fiber) => Fiber.Fiber + ( + self: Fiber.Fiber, + scope: Scope.Scope + ): Fiber.Fiber +} = dual(2, ( + self: FiberImpl, + scope: Scope.Scope +): Fiber.Fiber => { + if (self._exit) { + return self + } else if (scope.state._tag === "Closed") { + self.interruptUnsafe(self.id) + return self + } + const key = {} + scopeAddFinalizerUnsafe(scope, key, () => fiberInterrupt(self)) + self.addObserver(() => scopeRemoveFinalizerUnsafe(scope, key)) + return self +}) + +/** @internal */ +export const runFork: ( + effect: Effect.Effect, + options?: Effect.RunOptions | undefined +) => Fiber.Fiber = runForkWith(Context.empty()) + +/** @internal */ +export const runCallbackWith = (context: Context.Context) => { + const runFork = runForkWith(context) + return ( + effect: Effect.Effect, + options?: + | Effect.RunOptions & { + readonly onExit: (exit: Exit.Exit) => void + } + | undefined + ): (interruptor?: number | undefined) => void => { + const fiber = runFork(effect, options) + if (options?.onExit) { + fiber.addObserver(options.onExit) + } + return (interruptor) => { + return fiber.interruptUnsafe(interruptor) + } + } +} + +/** @internal */ +export const runCallback = runCallbackWith(Context.empty()) + +/** @internal */ +export const runPromiseExitWith = (context: Context.Context) => { + const runFork = runForkWith(context) + return ( + effect: Effect.Effect, + options?: Effect.RunOptions | undefined + ): Promise> => { + const fiber = runFork(effect, options) + return new Promise((resolve) => { + fiber.addObserver((exit) => resolve(exit)) + }) + } +} + +/** @internal */ +export const runPromiseExit = runPromiseExitWith(Context.empty()) + +/** @internal */ +export const runPromiseWith = (context: Context.Context) => { + const runPromiseExit = runPromiseExitWith(context) + return ( + effect: Effect.Effect, + options?: + | Effect.RunOptions + | undefined + ): Promise => + runPromiseExit(effect, options).then((exit) => { + if (exit._tag === "Failure") { + throw causeSquash(exit.cause) + } + return exit.value + }) +} + +/** @internal */ +export const runPromise: ( + effect: Effect.Effect, + options?: + | Effect.RunOptions + | undefined +) => Promise = runPromiseWith(Context.empty()) + +/** @internal */ +export const runSyncExitWith = (context: Context.Context) => { + const runFork = runForkWith(context) + return (effect: Effect.Effect): Exit.Exit => { + if (effectIsExit(effect)) return effect + const scheduler = new Scheduler.MixedScheduler("sync") + const fiber = runFork(effect, { scheduler }) as FiberImpl + fiber._dispatcher?.flush() + return fiber._exit ?? exitDie(new AsyncFiberError(fiber)) + } +} + +/** @internal */ +export const runSyncExit: (effect: Effect.Effect) => Exit.Exit = runSyncExitWith( + Context.empty() +) + +/** @internal */ +export const runSyncWith = (context: Context.Context) => { + const runSyncExit = runSyncExitWith(context) + return (effect: Effect.Effect): A => { + const exit = runSyncExit(effect) + if (exit._tag === "Failure") throw causeSquash(exit.cause) + return exit.value + } +} + +/** @internal */ +export const runSync: (effect: Effect.Effect) => A = runSyncWith(Context.empty()) + +const succeedTrue = succeed(true) +const succeedFalse = succeed(false) + +class Latch implements _Latch.Latch { + waiters: Array<(_: Effect.Effect) => void> = [] + scheduled: Array<(_: Effect.Effect) => void> | undefined = undefined + private _isOpen: boolean + + constructor(isOpen: boolean) { + this._isOpen = isOpen + } + + private scheduleUnsafe(fiber: Fiber.Fiber) { + if (this.waiters.length === 0) { + return succeedTrue + } + if (this.scheduled === undefined) { + this.scheduled = this.waiters + fiber.currentDispatcher.scheduleTask(this.flushScheduled, 0) + } else { + for (let i = 0; i < this.waiters.length; i++) { + this.scheduled.push(this.waiters[i]) + } + } + this.waiters = [] + return succeedTrue + } + private flushScheduled = () => { + if (this.scheduled === undefined) return + const waiters = this.scheduled + this.scheduled = undefined + for (let i = 0; i < waiters.length; i++) { + waiters[i](exitVoid) + } + } + private flushWaiters() { + // swap both arrays out before any resume runs: a resumed waiter can + // reentrantly close the latch and register new waiters, which must not + // be drained by this flush + const waiters = this.waiters + this.waiters = [] + this.flushScheduled() + for (let i = 0; i < waiters.length; i++) { + waiters[i](exitVoid) + } + } + + open = withFiber((fiber) => { + if (this._isOpen) return succeedFalse + this._isOpen = true + return this.scheduleUnsafe(fiber) + }) + release = withFiber((fiber) => this._isOpen ? succeedFalse : this.scheduleUnsafe(fiber)) + openUnsafe() { + if (this._isOpen) return false + this._isOpen = true + this.flushWaiters() + return true + } + await = callback((resume) => { + if (this._isOpen) { + return resume(void_) + } + this.waiters.push(resume) + return sync(() => { + let index = this.waiters.indexOf(resume) + if (index !== -1) { + this.waiters.splice(index, 1) + } else if (this.scheduled !== undefined) { + index = this.scheduled.indexOf(resume) + if (index !== -1) { + this.scheduled.splice(index, 1) + } + } + }) + }) + closeUnsafe() { + if (!this._isOpen) return false + this._isOpen = false + return true + } + close = sync(() => this.closeUnsafe()) + whenOpen = (self: Effect.Effect): Effect.Effect => flatMap(this.await, () => self) + isOpen() { + return this._isOpen + } +} + +/** @internal */ +export const makeLatchUnsafe = (open?: boolean | undefined): _Latch.Latch => new Latch(open ?? false) + +/** @internal */ +export const makeLatch = (open?: boolean | undefined) => sync(() => makeLatchUnsafe(open)) + +// ---------------------------------------------------------------------------- +// Tracer +// ---------------------------------------------------------------------------- + +/** @internal */ +export const tracer: Effect.Effect = withFiber((fiber) => succeed(fiber.getRef(Tracer.Tracer))) + +/** @internal */ +export const withTracer: { + (tracer: Tracer.Tracer): (effect: Effect.Effect) => Effect.Effect + (effect: Effect.Effect, tracer: Tracer.Tracer): Effect.Effect +} = dual( + 2, + (effect: Effect.Effect, tracer: Tracer.Tracer): Effect.Effect => + provideService(effect, Tracer.Tracer, tracer) +) + +/** @internal */ +export const withTracerEnabled: { + (enabled: boolean): (effect: Effect.Effect) => Effect.Effect + (effect: Effect.Effect, enabled: boolean): Effect.Effect +} = provideService(TracerEnabled) + +/** @internal */ +export const withTracerTiming: { + (enabled: boolean): (effect: Effect.Effect) => Effect.Effect + (effect: Effect.Effect, enabled: boolean): Effect.Effect +} = provideService(TracerTimingEnabled) + +const bigint0 = BigInt(0) + +const NoopSpanProto: Omit = { + _tag: "Span", + spanId: "noop", + traceId: "noop", + sampled: false, + status: { + _tag: "Ended", + startTime: bigint0, + endTime: bigint0, + exit: exitVoid + }, + attributes: new Map(), + links: [], + kind: "internal", + attribute() {}, + event() {}, + end() {}, + addLinks() {} +} + +/** @internal */ +export const noopSpan = (options: { + readonly name: string + readonly parent: Option.Option + readonly annotations: Context.Context +}): Tracer.Span => Object.assign(Object.create(NoopSpanProto), options) + +const filterDisablePropagation = (span: Tracer.AnySpan | undefined): Option.Option => { + if (!span) return Option.none() + return Context.get(span.annotations, Tracer.DisablePropagation) + ? span._tag === "Span" ? filterDisablePropagation(Option.getOrUndefined(span.parent)) : Option.none() + : Option.some(span) +} + +/** @internal */ +export const makeSpanUnsafe = ( + fiber: Fiber.Fiber, + name: string, + options: Tracer.SpanOptionsNoTrace | undefined +) => { + const disablePropagation = !fiber.getRef(TracerEnabled) || + (options?.annotations && Context.get(options.annotations, Tracer.DisablePropagation)) + const parent = options?.parent !== undefined + ? Option.some(options.parent) + : options?.root + ? Option.none() + : filterDisablePropagation(fiber.currentSpan) + + let span: Tracer.Span + + if (disablePropagation) { + span = noopSpan({ + name, + parent, + annotations: Context.add( + options?.annotations ?? Context.empty(), + Tracer.DisablePropagation, + true + ) + }) + } else { + const tracer = fiber.getRef(Tracer.Tracer) + const clock = fiber.getRef(ClockRef) + const timingEnabled = fiber.getRef(TracerTimingEnabled) + const annotationsFromEnv = fiber.getRef(TracerSpanAnnotations) + const linksFromEnv = fiber.getRef(TracerSpanLinks) + const level = options?.level ?? fiber.getRef(Tracer.CurrentTraceLevel) + + const links = options?.links !== undefined ? + [...linksFromEnv, ...options.links] : + linksFromEnv.length === 0 + ? [] + : linksFromEnv.slice() + + span = tracer.span({ + name, + parent, + annotations: options?.annotations ?? Context.empty(), + links, + startTime: timingEnabled ? clock.currentTimeNanosUnsafe() : BigInt(0), + kind: options?.kind ?? "internal", + root: options?.root ?? Option.isNone(parent), + sampled: options?.sampled ?? + (Option.isSome(parent) && parent.value.sampled === false + ? false + : !isLogLevelGreaterThan(fiber.getRef(Tracer.MinimumTraceLevel), level)) + }) + + for (const key in annotationsFromEnv) { + span.attribute(key, annotationsFromEnv[key]) + } + if (options?.attributes !== undefined) { + for (const key in options.attributes) { + span.attribute(key, options.attributes[key]) + } + } + } + + return span +} + +/** @internal */ +export const makeSpan = ( + name: string, + options?: Tracer.SpanOptions +): Effect.Effect => withFiber((fiber) => succeed(makeSpanUnsafe(fiber, name, options))) + +/** @internal */ +export const makeSpanScoped = ( + name: string, + options?: Tracer.SpanOptionsNoTrace | undefined +): Effect.Effect => + uninterruptible( + withFiber((fiber) => { + const scope = Context.getUnsafe(fiber.context, scopeTag) + const span = makeSpanUnsafe(fiber, name, options ?? {}) + const clock = fiber.getRef(ClockRef) + const timingEnabled = fiber.getRef(TracerTimingEnabled) + return as( + scopeAddFinalizerExit(scope, (exit) => endSpan(span, exit, clock, timingEnabled)), + span + ) + }) + ) + +/** @internal */ +export const withSpanScoped: { + ( + name: string, + options?: Tracer.SpanOptions + ): (self: Effect.Effect) => Effect.Effect | Scope.Scope> + ( + self: Effect.Effect, + name: string, + options?: Tracer.SpanOptions + ): Effect.Effect | Scope.Scope> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const options = addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) + if (dataFirst) { + const self = arguments[0] + return flatMap( + makeSpanScoped(name, options), + (span) => withParentSpan(self, span, options) + ) + } + return (self: Effect.Effect) => + flatMap( + makeSpanScoped(name, options), + (span) => withParentSpan(self, span, options) + ) +} as any + +const provideSpanStackFrame = (name: string, stack: (() => string | undefined) | undefined) => { + stack = typeof stack === "function" ? stack : constUndefined + return updateService(CurrentStackFrame, (parent) => ({ + name, + stack, + parent + })) +} + +/** @internal */ +export const spanAnnotations: Effect.Effect>> = TracerSpanAnnotations + +/** @internal */ +export const spanLinks: Effect.Effect> = TracerSpanLinks + +/** @internal */ +export const linkSpans: { + ( + span: Tracer.AnySpan | ReadonlyArray, + attributes?: Record + ): (self: Effect.Effect) => Effect.Effect + ( + self: Effect.Effect, + span: Tracer.AnySpan | ReadonlyArray, + attributes?: Record + ): Effect.Effect +} = dual((args) => isEffect(args[0]), ( + self: Effect.Effect, + span: Tracer.AnySpan | ReadonlyArray, + attributes: Record = {} +): Effect.Effect => { + const spans: Array = Array.isArray(span) ? span : [span] + const links = spans.map((span): Tracer.SpanLink => ({ span, attributes })) + return updateService(self, TracerSpanLinks, (current) => [...current, ...links]) +}) + +/** @internal */ +export const endSpan = ( + span: Tracer.Span, + exit: Exit.Exit, + clock: Clock.Clock, + timingEnabled: boolean +) => + sync(() => { + if (span.status._tag === "Ended") return + span.end(timingEnabled ? clock.currentTimeNanosUnsafe() : bigint0, exit) + }) + +/** @internal */ +export const useSpan: { + (name: string, evaluate: (span: Tracer.Span) => Effect.Effect): Effect.Effect + ( + name: string, + options: Tracer.SpanOptionsNoTrace, + evaluate: (span: Tracer.Span) => Effect.Effect + ): Effect.Effect +} = ( + name: string, + ...args: [evaluate: (span: Tracer.Span) => Effect.Effect] | [ + options: any, + evaluate: (span: Tracer.Span) => Effect.Effect + ] +): Effect.Effect => { + const options = args.length === 1 ? undefined : args[0] + const evaluate: (span: Tracer.Span) => Effect.Effect = args[args.length - 1] + return withFiber((fiber) => { + const span = makeSpanUnsafe(fiber, name, options) + const clock = fiber.getRef(ClockRef) + const timingEnabled = fiber.getRef(TracerTimingEnabled) + return onExit(internalCall(() => evaluate(span)), (exit) => endSpan(span, exit, clock, timingEnabled)) + }) +} + +const provideParentSpan = provideService(Tracer.ParentSpan) + +/** @internal */ +export const withParentSpan: { + ( + value: Tracer.AnySpan, + options?: Tracer.TraceOptions + ): (self: Effect.Effect) => Effect.Effect> + ( + self: Effect.Effect, + value: Tracer.AnySpan, + options?: Tracer.TraceOptions + ): Effect.Effect> +} = function() { + const dataFirst = isEffect(arguments[0]) + const span: Tracer.AnySpan = dataFirst ? arguments[1] : arguments[0] + let options = dataFirst ? arguments[2] : arguments[1] + let provideStackFrame: (self: Effect.Effect) => Effect.Effect = identity + if (span._tag === "Span") { + options = addSpanStackTrace(options) + provideStackFrame = provideSpanStackFrame(span.name, options?.captureStackTrace) + } + if (dataFirst) { + return provideParentSpan(provideStackFrame(arguments[0]), span) + } + return (self: Effect.Effect) => provideParentSpan(provideStackFrame(self), span) +} as any + +/** @internal */ +export const withSpan: { + >( + name: string, + options?: Tracer.SpanOptionsNoTrace | ((...args: NoInfer) => Tracer.SpanOptionsNoTrace) | undefined, + traceOptions?: Tracer.TraceOptions | undefined + ): (self: Effect.Effect, ...args: Args) => Effect.Effect> + ( + self: Effect.Effect, + name: string, + options?: Tracer.SpanOptions | undefined + ): Effect.Effect> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const traceOptions = addSpanStackTrace(arguments[2]) + if (dataFirst) { + const self = arguments[0] + return useSpan(name, arguments[2], (span) => withParentSpan(self, span, traceOptions)) + } + const fnArg = typeof arguments[1] === "function" ? arguments[1] : undefined + const options = fnArg ? undefined : arguments[1] + return (self: Effect.Effect, ...args: any) => + useSpan( + name, + fnArg ? fnArg(...args) : options, + (span) => withParentSpan(self, span, traceOptions) + ) +} as any + +/** @internal */ +export const annotateSpans: { + (key: string, value: unknown): (effect: Effect.Effect) => Effect.Effect + (values: Record): (effect: Effect.Effect) => Effect.Effect + (effect: Effect.Effect, key: string, value: unknown): Effect.Effect + (effect: Effect.Effect, values: Record): Effect.Effect +} = dual( + (args) => isEffect(args[0]), + ( + effect: Effect.Effect, + ...args: [Record] | [key: string, value: unknown] + ): Effect.Effect => + updateService(effect, TracerSpanAnnotations, (annotations) => { + const newAnnotations = args.length === 1 ? { ...annotations, ...args[0] } : { ...annotations } + if (args.length === 1) { + return newAnnotations + } else { + InternalRecord.assignProperty(newAnnotations, args[0], args[1]) + } + return newAnnotations + }) +) + +/** @internal */ +export const annotateCurrentSpan: { + (key: string, value: unknown): Effect.Effect + (values: Record): Effect.Effect +} = (...args: [Record] | [key: string, value: unknown]) => + withFiber((fiber) => { + const span = fiber.currentSpanLocal + if (span) { + if (args.length === 1) { + for (const [key, value] of Object.entries(args[0])) { + span.attribute(key, value) + } + } else { + span.attribute(args[0], args[1]) + } + } + return void_ + }) + +/** @internal */ +export const currentSpan: Effect.Effect = withFiber((fiber) => { + const span = fiber.currentSpanLocal + return span ? succeed(span) : fail(new NoSuchElementError()) +}) + +/** @internal */ +export const currentParentSpan: Effect.Effect = serviceOptional( + Tracer.ParentSpan +) + +// ---------------------------------------------------------------------------- +// Clock +// ---------------------------------------------------------------------------- + +/** @internal */ +export const ClockRef = Context.Reference("effect/Clock", { + defaultValue: (): Clock.Clock => new ClockImpl() +}) + +const MAX_TIMER_MILLIS = 2 ** 31 - 1 + +class ClockImpl implements Clock.Clock { + currentTimeMillisUnsafe(): number { + return Date.now() + } + readonly currentTimeMillis: Effect.Effect = sync(() => this.currentTimeMillisUnsafe()) + currentTimeNanosUnsafe(): bigint { + return wallTimeNanos() + } + readonly currentTimeNanos: Effect.Effect = sync(() => this.currentTimeNanosUnsafe()) + monotonicTimeNanosUnsafe(): bigint { + return monotonicNowNanos() + } + readonly monotonicTimeNanos: Effect.Effect = sync(() => this.monotonicTimeNanosUnsafe()) + sleep(duration: Duration.Duration): Effect.Effect { + return this.sleepMillis(Duration.toMillis(duration)) + } + private sleepMillis(millis: number): Effect.Effect { + if (millis <= 0) return yieldNow + else if (!Number.isFinite(millis)) return never + return callback((resume) => { + const continuation = millis > MAX_TIMER_MILLIS + ? this.sleepMillis(millis - MAX_TIMER_MILLIS) + : void_ + const handle = setTimeout(() => resume(continuation), Math.min(millis, MAX_TIMER_MILLIS)) + return sync(() => clearTimeout(handle)) + }) + } +} + +const nanosPerMilli = BigInt(1_000_000) + +const monotonicNowNanos = (function() { + const processHrtime = (globalThis as { + readonly process?: { readonly hrtime?: { readonly bigint?: () => bigint } } + }).process?.hrtime + if (typeof processHrtime?.bigint === "function") { + return () => processHrtime.bigint!() + } + if (typeof performance !== "undefined" && typeof performance.now === "function") { + return () => BigInt(Math.round(performance.now() * 1_000_000)) + } + let previous = BigInt(0) + return () => { + const current = BigInt(Date.now()) * nanosPerMilli + if (current > previous) { + previous = current + } + return previous + } +})() + +const wallTimeNanos = (function() { + const reanchorThresholdNanos = BigInt(1_000_000_000) + let origin: bigint | undefined + return () => { + const monotonic = monotonicNowNanos() + const wall = BigInt(Date.now()) * nanosPerMilli + if (origin === undefined) { + origin = wall - monotonic + } else { + const projected = origin + monotonic + const skew = wall > projected ? wall - projected : projected - wall + if (skew > reanchorThresholdNanos) { + origin = wall - monotonic + } + } + return origin + monotonic + } +})() + +/** @internal */ +export const clockWith = (f: (clock: Clock.Clock) => Effect.Effect): Effect.Effect => + withFiber((fiber) => f(fiber.getRef(ClockRef))) + +/** @internal */ +export const sleep = (duration: Duration.Input): Effect.Effect => + clockWith((clock) => clock.sleep(Duration.fromInputUnsafe(duration))) + +/** @internal */ +export const currentTimeMillis: Effect.Effect = clockWith((clock) => clock.currentTimeMillis) + +/** @internal */ +export const currentTimeNanos: Effect.Effect = clockWith((clock) => clock.currentTimeNanos) + +/** @internal */ +export const monotonicTimeNanos: Effect.Effect = clockWith((clock) => clock.monotonicTimeNanos) + +// ---------------------------------------------------------------------------- +// Errors +// ---------------------------------------------------------------------------- + +/** @internal */ +export const TimeoutErrorTypeId = "~effect/Cause/TimeoutError" + +/** @internal */ +export const isTimeoutError = (u: unknown): u is Cause.TimeoutError => hasProperty(u, TimeoutErrorTypeId) + +/** @internal */ +export class TimeoutError extends TaggedError("TimeoutError") { + readonly [TimeoutErrorTypeId] = TimeoutErrorTypeId + constructor(message?: string) { + super({ message } as any) + } +} + +/** @internal */ +export const IllegalArgumentErrorTypeId = "~effect/Cause/IllegalArgumentError" + +/** @internal */ +export const isIllegalArgumentError = ( + u: unknown +): u is Cause.IllegalArgumentError => hasProperty(u, IllegalArgumentErrorTypeId) + +/** @internal */ +export class IllegalArgumentError extends TaggedError("IllegalArgumentError") { + readonly [IllegalArgumentErrorTypeId] = IllegalArgumentErrorTypeId + constructor(message?: string) { + super({ message } as any) + } +} + +/** @internal */ +export const ExceededCapacityErrorTypeId = "~effect/Cause/ExceededCapacityError" + +/** @internal */ +export const isExceededCapacityError = ( + u: unknown +): u is Cause.ExceededCapacityError => hasProperty(u, ExceededCapacityErrorTypeId) + +/** @internal */ +export class ExceededCapacityError extends TaggedError("ExceededCapacityError") { + readonly [ExceededCapacityErrorTypeId] = ExceededCapacityErrorTypeId + constructor(message?: string) { + super({ message } as any) + } +} + +/** @internal */ +export const AsyncFiberErrorTypeId = "~effect/Cause/AsyncFiberError" + +/** @internal */ +export const isAsyncFiberError = ( + u: unknown +): u is Cause.AsyncFiberError => hasProperty(u, AsyncFiberErrorTypeId) + +/** @internal */ +export class AsyncFiberError extends TaggedError("AsyncFiberError")<{ + fiber: Fiber.Fiber + message: string +}> { + readonly [AsyncFiberErrorTypeId] = AsyncFiberErrorTypeId + constructor(fiber: Fiber.Fiber) { + super({ + message: "An asynchronous Effect was executed with Effect.runSync", + fiber + }) + } +} + +/** @internal */ +export const UnknownErrorTypeId = "~effect/Cause/UnknownError" + +/** @internal */ +export const isUnknownError = ( + u: unknown +): u is Cause.UnknownError => hasProperty(u, UnknownErrorTypeId) + +/** @internal */ +export class UnknownError extends TaggedError("UnknownError")<{ + cause: unknown + message?: string | undefined +}> { + readonly [UnknownErrorTypeId] = UnknownErrorTypeId + constructor(cause: unknown, message?: string) { + super({ message, cause } as any) + } +} + +// ---------------------------------------------------------------------------- +// Console +// ---------------------------------------------------------------------------- + +/** @internal */ +export const ConsoleRef = Context.Reference( + "effect/Console/CurrentConsole", + { defaultValue: (): Console.Console => globalThis.console } +) + +// ---------------------------------------------------------------------------- +// LogLevel +// ---------------------------------------------------------------------------- + +/** @internal */ +export const logLevelToOrder = (level: LogLevel.LogLevel) => { + switch (level) { + case "All": + return Number.MIN_SAFE_INTEGER + case "Fatal": + return 50_000 + case "Error": + return 40_000 + case "Warn": + return 30_000 + case "Info": + return 20_000 + case "Debug": + return 10_000 + case "Trace": + return 0 + case "None": + return Number.MAX_SAFE_INTEGER + } +} + +/** @internal */ +export const LogLevelOrder = Order.mapInput(Order.Number, logLevelToOrder) + +/** @internal */ +export const isLogLevelGreaterThan = Order.isGreaterThan(LogLevelOrder) + +// ---------------------------------------------------------------------------- +// Logger +// ---------------------------------------------------------------------------- + +/** @internal */ +export const CurrentLoggers = Context.Reference< + ReadonlySet> +>("effect/Loggers/CurrentLoggers", { + defaultValue: () => new Set([defaultLogger, tracerLogger]) +}) + +/** @internal */ +export const LogToStderr = Context.Reference("effect/Logger/LogToStderr", { + defaultValue: constFalse +}) + +/** @internal */ +export const annotateLogsScoped: { + (key: string, value: unknown): Effect.Effect + (values: Record): Effect.Effect +} = function() { + const entries = typeof arguments[0] === "string" ? + [[arguments[0], arguments[1]]] : + Object.entries(arguments[0]) + return uninterruptible(withFiber((fiber) => { + const prev = fiber.getRef(CurrentLogAnnotations) + const next = { ...prev } + for (let i = 0; i < entries.length; i++) { + const [key, value] = entries[i] + InternalRecord.assignProperty(next, key, value) + } + fiber.setContext(Context.add(fiber.context, CurrentLogAnnotations, next)) + return scopeAddFinalizerExit(Context.getUnsafe(fiber.context, scopeTag), (_) => { + const current = fiber.getRef(CurrentLogAnnotations) + const next = { ...current } + for (let i = 0; i < entries.length; i++) { + const [key, value] = entries[i] + if (current[key] !== value) continue + if (Object.hasOwn(prev, key)) { + InternalRecord.assignProperty(next, key, prev[key]) + } else { + delete next[key] + } + } + fiber.setContext(Context.add(fiber.context, CurrentLogAnnotations, next)) + return void_ + }) + })) +} + +/** @internal */ +export const LoggerTypeId = "~effect/Logger" + +const LoggerProto = { + [LoggerTypeId]: { + _Message: identity, + _Output: identity + }, + pipe() { + return pipeArguments(this, arguments) + } +} + +/** @internal */ +export const loggerMake = ( + log: (options: Logger.Options) => Output +): Logger.Logger => { + const self = Object.create(LoggerProto) + self.log = log + return self +} + +/** + * Sanitize a given string by replacing spaces, equal signs, and double quotes + * with underscores. + * + * @internal + */ +export const formatLabel = (key: string) => key.replace(/[\s="]/g, "_") + +/** + * Formats a log span into a `

, f: Fn] +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>>, + Types.ApplyFilters>>>, + A | ReturnType, + Pr, + Ret +> = internal.discriminator + +/** + * Matches values where a specified field starts with a given prefix. + * + * **When to use** + * + * Use to match string discriminator values by prefix instead of exact value. + * + * **Details** + * + * Instead of checking for exact matches, this helper matches values that share + * a common prefix. For example, if the discriminant field contains hierarchical + * names like `"A"`, `"A.A"`, and `"B"`, a single `"A"` rule can match both + * `"A"` and `"A.A"`. + * + * **Example** (Matching discriminator prefixes) + * + * ```ts import.meta.vitest + * import { Match, pipe } from "effect" + * + * const match = pipe( + * Match.type<{ type: "A" } | { type: "B" } | { type: "A.A" } | {}>(), + * Match.discriminatorStartsWith("type")("A", (_) => 1 as const), + * Match.discriminatorStartsWith("type")("B", (_) => 2 as const), + * Match.orElse((_) => 3 as const) + * ) + * + * match({ type: "A" }) // => 1 + * match({ type: "B" }) // => 2 + * match({ type: "A.A" }) // => 1 + * ``` + * + * @see {@link discriminator} for matching exact discriminator values + * + * @category defining patterns + * @since 4.0.0 + */ +export const discriminatorStartsWith: ( + field: D +) => >) => Ret>( + pattern: P, + f: Fn +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>>, + Types.ApplyFilters>>>, + A | ReturnType, + Pr, + Ret +> = internal.discriminatorStartsWith + +/** + * Matches values based on a field that serves as a discriminator, mapping each + * possible value to a corresponding handler. + * + * **When to use** + * + * Use to define several discriminator handlers at once without finalizing the + * matcher. + * + * **Details** + * + * This function simplifies working with discriminated unions by letting you + * define a set of handlers for each possible value of a given field. Instead of + * chaining multiple calls to {@link discriminator}, this function allows + * defining all possible cases at once using an object where the keys are the + * possible values of the field, and the values are the corresponding handler + * functions. + * + * **Example** (Mapping discriminator handlers) + * + * ```ts import.meta.vitest + * import { Match, pipe } from "effect" + * + * const match = pipe( + * Match.type< + * { type: "A"; a: string } | { type: "B"; b: number } | { + * type: "C" + * c: boolean + * } + * >(), + * Match.discriminators("type")({ + * A: (a) => a.a, + * B: (b) => b.b, + * C: (c) => c.c + * }), + * 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 + * @since 4.0.0 + */ +export const discriminators: ( + field: D +) => < + R, + Ret, + P extends + & { readonly [Tag in Types.Tags & string]?: ((_: Extract>) => Ret) | undefined } + & { readonly [Tag in Exclude>]: never } +>( + fields: P +) => ( + self: Matcher +) => Matcher< + I, + Types.AddWithout>>, + Types.ApplyFilters>>>, + A | ReturnType, + Pr, + Ret +> = internal.discriminators + +/** + * Matches values by a discriminator field and requires every possible case to + * be handled. + * + * **When to use** + * + * Use to define an exhaustive discriminator handler map that finalizes the + * matcher. + * + * **Details** + * + * This is the exhaustive variant of {@link discriminators}. Each possible + * discriminator value must have a corresponding handler, so the matcher is + * finalized directly and does not require `Match.exhaustive` at the end of the + * pipeline. + * + * **Example** (Handling all discriminator cases) + * + * ```ts import.meta.vitest + * import { Match, pipe } from "effect" + * + * const match = pipe( + * Match.type< + * { type: "A"; a: string } | { type: "B"; b: number } | { + * type: "C" + * c: boolean + * } + * >(), + * Match.discriminatorsExhaustive("type")({ + * A: (a) => a.a, + * B: (b) => b.b, + * C: (c) => c.c + * }) + * ) + * match({ type: "C", c: true }) // => true + * ``` + * + * @see {@link discriminators} for defining discriminator handlers without finalizing the matcher + * + * @category defining patterns + * @since 4.0.0 + */ +export const discriminatorsExhaustive: ( + field: D +) => < + R, + Ret, + P extends + & { readonly [Tag in Types.Tags & string]: (_: Extract>) => Ret } + & { readonly [Tag in Exclude>]: never } +>( + fields: P +) => ( + self: Matcher +) => [Pr] extends [never] ? (u: I) => Unify> : Unify> = + internal.discriminatorsExhaustive + +/** + * Matches discriminated union members by their `_tag` field. + * + * **When to use** + * + * Use to handle one or more `_tag` cases with the same matcher branch. + * + * **Details** + * + * This helper follows the Effect convention that discriminated unions use + * `"_tag"` as their discriminator field. Use {@link discriminator} for a + * different discriminator field. + * + * **Example** (Matching a discriminated union by tag) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * type Event = + * | { readonly _tag: "fetch" } + * | { readonly _tag: "success"; readonly data: string } + * | { readonly _tag: "error"; readonly error: Error } + * | { readonly _tag: "cancel" } + * + * const match = Match.type().pipe( + * // Match either "fetch" or "success" + * Match.tag("fetch", "success", () => `Ok!`), + * // Match "error" and extract the error message + * Match.tag("error", (event) => `Error: ${event.error.message}`), + * // Match "cancel" + * Match.tag("cancel", () => "Cancelled"), + * Match.exhaustive + * ) + * + * match({ _tag: "success", data: "Hello" }) // => "Ok!" + * + * match({ _tag: "error", error: new Error("Oops!") }) // => "Error: Oops!" + * ``` + * + * @category defining patterns + * @since 4.0.0 + */ +export const tag: < + R, + P extends Types.Tags<"_tag", R> & string, + Ret, + Args extends Array, + Fn extends (_: Extract>, ...args: Args) => Ret +>( + ...pattern: [first: P, ...values: Array